IT's Jenna

9. 이력 데이터와 친해지기 본문

Study/관계형 데이터 베이스 실전 입문

9. 이력 데이터와 친해지기

developer Jenna 2021. 6. 28. 14:02
이력 데이터란?
이력 데이터의 문제점
해결방법
미래 가격 설정

 

이전장까지 우리는 관계형 모델에 대해서 알아보았습니다. 이제 관계형 모델을 벗어나서 데이터를 취급하는 방법을 알아보도록 하겠습니다. 그 첫 번째는 바로 이력 데이터를 이해하는 것입니다.

이력 데이터란?

이력 데이터는 과거부터 현재까지의 데이터가 저장된 것을 의미합니다. 다시 말해 데이터가 변화할 때마다 그 변화 내역을 모아둔 데이터라고 할 수 있습니다. 주로 구매이력, 가격 이력, 프로그램 조작 이력 등이 이력 데이터에 속합니다.

이력 데이터의 문제점

그런데 관계형 모델에서 이력 데이터를 다루기란 상당히 어렵습니다.

 

우선 이력 데이터는 릴레이션의 요건을 만족하지 않습니다. 릴레이션이란 집합이라고 설명했습니다. 그리고 집합의 요소들 간에는 순서가 존재하지 않습니다. 그런데 이력 데이터상에서는 과거부터 현재까지의 순서가 명확히 존재합니다.

 

또한, 이력 데이터의 테이블은 쉽게 커지는 경향이 있습니다. 데이터가 적을때는 빠르게 처리되던 쿼리문도 데이터 양이 방대해질수록 점점 성능이 저하됩니다.

 

이력데이터의 구체적인 예

이력데이터의 대표적인 예로 쇼핑몰의 가격표를 들 수 있습니다. 제품의 가격은 변동 사항이 많으며 과거와 현재, 그리고 미래의 가격이 다릅니다. 아래 테이블에서 턱걸이 기계의 현재 가격을 확인하는 쿼리를 작성해 봅시다.

SELECT price FROM sys.price_list
WHERE item = '턱걸이 기계'
AND now() Between start_date and end_date;

오늘 날짜인 2021.06.28의 턱걸이 기계의 가격은 19000원이 나옵니다.

 

그렇다면 이 쿼리는 제대로 된 쿼리일까요? 아닙니다! 데이터는 시간에 상관없이 같은 질의에 같은 응답을 반환해야 합니다. 하지만 위 쿼리는 시간에 따라서 다른 응답을 합니다. 이렇게 시간에 따라서 결과가 달라지는 것을 보고 이력 데이터는 시간축과 릴레이션이 직교하지 않는다고 합니다. 다시 말해 시간에 따라 쿼리의 실행 결과가 변한다는 것이죠!

 

릴레이션은 사실의 집합입니다. 하지만 이렇게 시간이 다른 것만으로 결과가 달라지는 것은 릴레이션의 요건을 만족한다고 할 수 없고 관계형 모델에서 벗어납니다.

 

그렇다면 다른 예시를 하나 봅시다. 이번엔 end_date 없이 start_date만 테이블에 존재합니다. 이런 경우에 현재 턱걸이 기계의 가격을 구하는 쿼리문은 다음과 같습니다.

SELECT price FROM sys.price_list_2
WHERE item = '턱걸이기계'
AND start_date = (SELECT max(start_date)
FROM sys.price_list_2 WHERE item='턱걸이기계')

종료 시점이 정의되어 있지 않기 때문에 시작 날짜의 최댓값의 가격 정보를 가져오는 쿼리입니다. 그렇다면 이 쿼리는 제대로 된 쿼리일까요? 역시 아닙니다! 예시 1과 2의 쿼리는 릴레이션을 받아서 릴레이션을 반환하지 않습니다. 또한 공통적으로 <x는 현재 유효한 가격이다>라는 명제에 대한 참, 거짓이 튜플에 따라 달라집니다.

 

다시 말해 튜플에 따라 <턱걸이 기계의 가격은 180000이다>라는 명제가 참인 경우도 있고 거짓인 경우도 있다는 뜻입니다. 이것은 릴레이션의 조건을 벗어납니다. 또한 어떤 2개 이상의 릴레이션의 합집합일 수도 있습니다.

해결방법

그렇다면 우리는 이 이력 데이터를 어떻게 다뤄야 할까요? 우선 이력데이터의 문제에 대해서 절대적인 해답은 없습니다. 이력데이터 자체가 관계형 모델에 맞지 않는 것이기 때문에 모든 해결방법은 그에 대한 차선책일 뿐입니다. 각각의 해결방법 역시 장단점이 있기 때문에 용도에 따라 구분하여 사용해야 합니다.

 

방법 1.

가장 간단한 방법으로, 현재의 가격과 과거의 가격을 두 개의 릴레이션으로 나눌 수 있습니다.

이렇게 분해한 릴레이션에서 현재 턱걸이 기계의 가격을 검색하는 쿼리는 굉장히 간단해집니다. 

SELECT * FROM sys.current_price_list
WHERE item = '턱걸이기계' ;

하지만 이 경우에도 단점은 있습니다. 과거부터 현재까지에 이르는 가격들에 대한 질의가 필요한 경우에는 두 테이블을 합쳐서 확인해야 하기 때문에 더욱 복잡한 쿼리가 필요할 수 있습니다.

 

이때 발생할 수 있는 문제는 price_list테이블은 외부 키로 사용될 수 없다는 것입니다. 테이블이 두개로 나눠졌기 때문에 current_price_list 또는 price_list_history중에서 하나의 행을 선택해서 외부키로 사용할 수 없습니다.

 

방법 2.

데이터의 특성상 price정보가 외부 키로 꼭 사용되어야 하는 경우엔 다음과 같이 현재의 가격 테이블과 과거부터 현재까지의 가격 테이블 두 개로 나타낼 수도 있습니다.

이때 과거부터 현재까지의 전체 가격이 나와있는 price_list_history테이블을 외부 키로 사용할 수 있습니다. 하지만 이 방법의 문제는 바로 데이터의 중복입니다. 따라서 price_list_history테이블의 행을 갱신하지 않아야 하고 가격 정보가 변경될 때는 INSERT로 행을 추가해야 합니다. 또한 현재 가격이 나와있는 current_price_list테이블에 같은 아이템의 가격이 존재하면 오래된 쪽의 행이 삭제되어야 합니다.

 

방법 3.

외부 키제약을 사용하고 싶고, 데이터 중복도 피하고 싶은 경우엔 대리키를  사용하여 설계할 수 있습니다. 다음과같이 price_id_master테이블을 만들고, price_list 테이블과 price_list_history 테이블에 idprice_id_master를 외부키로 사용합니다.

이 방법은 데이터 중복도 없을뿐더러 외부키 사용도 가능합니다. 하지만 해당 방법은 쿼리를 작성할 때 JOIN 등의 조작 횟수가 증가하고 디스크 공간이 추가적으로 사용된다는 문제점이 있습니다.

미래 가격 설정

사실 가격에 대한 개정은 갑자기 결정되는 것이 아니라 이미 정해져 있는 경우가 대부분입니다. 그렇다면 미래의 가격에 대한 설정은 어떻게 할까요? 이때는 테이블을 하나 추가해주어야 합니다.

미래의 가격이 적용되는 날짜에 이것을 현재의 가격으로 적용하는 것은 사실상 쿼리로는 불가합니다. 쿼리는 어디까지나 테이블의 데이터를 참조할 수 있을 뿐입니다. 따라서 미래 가격 데이터를 현재 가격으로 적용하는 로직은 응용프로그램에서 구현하고 실행해야 합니다. 이때 테이블 재배치에 따른 지연이 발생할 수 있다는 점에 주의합시다!


지금까지 이력 데이터에 대하여 알아보았습니다. 이 장에서는 날짜를 예시로 들어 설명하였지만 이력 데이터가 날짜에만 국한되어 있지는 않습니다. 이력데이터가 DB에 있으면 잠재적인 문제점을 안고 있는 것임을 유의하시기 바랍니다.

다음장에서는 그래프에 대해서 더욱 깊이 알아보도록 하겠습니다!

'Study > 관계형 데이터 베이스 실전 입문' 카테고리의 다른 글

10. 그래프에 맞서다  (0) 2021.06.30
8. SELECT를 공략하자  (0) 2021.06.02
7. NULL과의 싸움  (0) 2021.06.01
6. 도메인 설계 전략  (0) 2021.05.28
5. 릴레이션의 직교성  (0) 2021.05.27
Comments