출처: 노찬형의 제로에서 시작하는 데이터 모델링 시즌II (1회) : 헷갈리는 릴레이션십 개념, 제대로 이해하기 – DATA ON-AIR (dataonair.or.kr)
▶ 1회: 헷갈리는 릴레이션십 개념, 제대로 이해하기
▷ 2회: 데이터 모델링에서 릴레이션십과 상속의 원리
▷ 3회: 엔터티 계층과 릴레이션십 구성
▷ 4회: 릴레이션십 구성과 관계 유형별 모델
▷ 5회: M:M 논리 모델을 1:M의 모델로 바꾸기
▷ 6회: 릴레이션십 응용으로 프로그램의 유연성 확보
▷ 7회: 구체적이고 명확하게 이해하는 ‘속성’
▷ 8회: 데이터 모델링에서 속성명 도출의 원리
▷ 9회: 식별자로 인스턴스의 유일성 확보
▷ 최종회: 모델링을 잘할 수 있는 조건 ‘정규화’
필자: 노찬형
빅터플랫폼 CIO. 대학에서 소프트웨어공학을 전공했으며 개발자로 사회 생활을 시작했다. 사회 생활 10년을 넘기고 시작했던 DB 공부가 프로그래머로서 자신을 분명하게 되돌아볼 수 있는 기회를 주었다. 사회 초년생 또는 대학생에게 도움이 되는 데이터 모델링 글을 쓰고 싶은 게 그의 작은 바람이다.
pemaker@gmail.com
주경야독하는 이들을 위해
우연한 일이 계기가 돼 필자는 DB와 데이터 모델링을 글로 정리할 수밖에 없는 상황에 맞닥뜨렸다. 필자는 2012년부터 2013년까지 한 대학에서 DB 강의를 했다. 강의를 요청받았을 때, 어떻게 해야 할지 난감했다. 필자가 맡은 반은 낮에는 일하고 저녁에 공부하는 학생들로 구성돼 있었다. 일반 대학생들처럼 많은 시간을 공부에 쓸 수 없는 학생들에 DB를 알려줘야 했다. 어떻게 하면 그들에게 작으나마 도움이 될까 하고 고민하던중 시중 교재 대신, 필자가 직접 강의 자료를 만들어 보면 좋겠다는 생각을 하기에 이르렀다.
물론 시중의 책이 부족해서 그런 것은 아니다. 필자가 자료를 직접 만들어 쓰면, 일반 책으로 했을 때보다 더 쉽게 소개할 수 있을 것 같아서 그랬다. 누가 보더라도 이해하기 쉽게 전달하겠다는 목표로 강의 자료를 만들기 시작했다. 2년 넘게 강의 자료를 준비하다 보니, DB의 기초와 데이터 모델링의 기초에 대한 내용을 어느 정도 만들어 낼 수 있었다.
학생들이 강의자료를 요청하면 줬다. 하지만 설명이 없는 프레젠테이션 문서라서 아쉬웠다. 설명이 추가되면 학생들이 예습/복습을 할 때도 훨씬 좋을 텐데…. 배웠거나 배울 학생들을 위해 프레젠테이션 문서를 글로 정리하기 시작했다. 말보다 글로 정리하는 게 더 어렵다는 걸 실감하는 순간의 연속이었다.
‘하늘 아래 새로운 건 없다’는 말처럼 필자의 강의 자료 역시 인식하든 못하든 수많은 자료와 가르침을 받았던 결과물들이다. 물론 보고 들었던 이론을 개발 현장에서 적용확인하는 과정을 거친, 경험의 산물이다. 앞으로 몇 회에 걸쳐 ‘제로에서 시작하는 데이터 모델링’ 연재를 하겠다고 용기를 내보았다. 독자 여러분과 함께 쓴다는 생각으로 수많은 의견이나 접근 방법을 댓글 또는 이메일로 받을 수 있었으면 좋겠다.
‘제로에서 시작하는 데이터 모델링’ 시즌II는 릴레이션십을 알아보는 것으로부터 시작한다. 엔터티 개념을 이해하기도 어려운데 다시 릴레이션이 등장하니 부담스러울 수 있을 것이다. 하지만 데이터 모델링 분야로 진입을 위해서는 꼭 알고 넘어가야 할 존재이다. 개발 현장에서 릴레이션십을 잘못 이해하고 접근하는 경우를 종종 보곤한다. 릴레이션 방향을 반대로 하거나 간접적인 관계에도 릴레이션을 설정하는 경우가 있다. 개발 시스템의 문제와 성능 저하야기 지점이다.
릴레이션십이란 무엇인가
데이터 모델은 비즈니스를 만족하기 위해 많은 엔터티와 엔터티 간 관계로 구성된다. 상품을 구매한 사람이라는 비즈니스 모델은 ‘구매+사람’이라는 집합으로 표현된다. 이렇듯 독립된 엔터티만으로는 비즈니스를 만족하는 전체 집합을 완성할 수 없으므로 엔터티들 간에 관계(relation)를 맺어 관리한다.
릴레이션십(relationship)이란 엔터티와 엔터티의 비즈니적 관계를 나타내며, 엔터티 혼자 표현할 수 없는 집합을 다룬다. 따라서 릴레이션십은 집합이며, 합집합·교집합·차집합 등의 특징을 그대로 갖는다. 엔터티와 마찬가지로 통합과 분리가 가능하다.
릴레이션십은 두 엔터티 사이의 직접적인 관계만 정의한다. 따라서 릴레이션은 두 엔터티 간 비즈니스 연관성을 표현하며 name, cardinality, optionality(selectivity)라는 3가지 요소를 이용해 표현된다. 릴레이션십 없이 데이터 모델에서 분리된 엔터티 내의 데이터들은 서로 고립돼 정보로서의 가치를 잃게 된다.
릴레이션은 개발자들이 어떻게 SQL을 작성할지를 결정해 주는 요소이기도 하다. 릴레이션이 잘못되면 SQL 작성이 잘못되며, 그로 인해 시스템 성능 저하와 원하는 데이터 추출이 힘들어질 수 있다.
데이터 모델은 데이터를 중복없이 관리하고 필요할 때 연결해서 보는 것이 핵심이다. 데이터 모델은 비즈니스를 집합으로 보고 엔터티로 표현한 것으로서 엔터티만으로는 전체 집합을 완성할 수 없다.
연결해서 본다는 말은 SQL의 JOIN을 의미하는 것으로, 연결하기 위해서는 엔터티 간의 관계가 있어야 한다. 엔터티가 도출되고 엔터티 간 관계가 도출돼야만 전체 집합을 완성할 수 있다. 릴레이션은 엔터티와 엔터티 간의 비즈니스 관계를 나타내며, 엔터티가 표현할 수 없는 집합을 다룬다.
릴레이션 설정은 엔터티와 엔터티 사이에 줄을 그어 연결해 주면 된다. 이때 부모자식 관계인지, 상위하위 관계인지, 식별관계인지, 비식별관계인지에 따라 릴레이션의 방향과 모양이 달라진다.
[그림 1] ER 다이어그램으로 본 엔터티와 속성, 릴레이션
[표 1] 엔터티·속성·릴레이션의 관계
앞서 릴레이션은 엔터티와 마찬가지로 집합적 특성을 그대로 갖고 있으며 통합과 분리가 가능하다고 했다. 고등학교때 배웠던 집합의 핵심은 A합집합B, A교집합B, A차집합B 등을 보면 A합집합B는 A집합과 B집합이 합쳐져서 또 다른 집합이 되고, A교집합B는 A집합과 B집합에 공통적으로 있는 요소들이 또 다른 집합이 되고, A차집합 B는 A집합에서 B집합을 빼서 또 다른 집합이 된다는 것이었다. 이와 같이 릴레이션 관계를 갖고 있는 두 엔터티에서 필요한 데이터를 추출할 때 합집합, 교집합, 차집합 개념을 이용해 데이터를 추출함으로써 다른 집합(엔터티)를 만들 수 있게 된다.
릴레이션의 방향은 상위에서 하위로, 부모에서 자식으로 설정하는 것이 일반적이며, 직접적인 관계만 설정한다. 그런데 데이터 모델링에 익숙하지 않을 경우, 릴레이션 방향을 반대로 하거나 간접적인 관계에도 릴레이션을 설정하는 경우가 있다. 이렇게 하는 이유는 아마도 엔터티에 대한 명확한 정의 부족으로 두 엔터티가 어떤 관계인지를 명확하게 구분하지 못함에 따라 발생한다. 또한 간접관계에 릴레이션을 설정하는 경우는 왠지 관계가 있을 것 같다는 생각과 프로그램 개발 시에 더 편리하게 할 수 있을 것이라는 기대 때문으로 보인다.
이제 릴레이션십의 종류와 어떻게 하면 릴레이션을 잘 설정할 수 있는지 좀 더 자세히 알아보자.
기본 형태의 릴레이션십
1) 전형적인 1:M 관계/Cardinality
[그림 2] 1:M 관계
온라인 서점에서 책 주문의 예로서 알아보자. 옷이나 화장품 쇼핑몰을 머리 속에 떠올려도 좋다.
온라인 서점에서 책을 고르고 나면 바로 구매하거나 여러 책을 장바구니에 담았다가 한꺼번에 주문을 한다. 한 권만 주문하면 위 모델에서 ‘주문내역’에 데이터 1건이 쌓이고 ‘주문상품내역’에 책 1권에 대한 정보가 쌓이게 된다. 주문내역과 주문상품내역은 1:1 관계가 형성된다. 여러 책을 한꺼번에 주문하면 주문내역에는 1건의 데이터가 쌓이고 주문상품내역에는 여러 책에 대한 정보가 쌓이게 돼 1:M 관계가 형성된다.
여기서 주문상품내역이 있는데 주문내역이 없는 것은 말이 안 된다는 것을 금방 알 것이다. 따라서 주문내역이 부모가 되고 주문상품내역이 자식이 된다. 부모자식 관계가 된다는 것은 부모 없이는 자식이 존재할 수 없듯이 주문내역 없이는 주문상품내역이 없다는 말이 된다. 바꿔서 말하면 부모 엔터티인 주문내역에 데이터가 쌓이고 자식 엔터티인 주문상품내역에 데이터가 쌓인다는 것을 말한다.
이때 주문내역에서 -> 주문상품내역으로 릴레이션을 설정해 주면 되고, 부모자식 관계이므로 식별관계와
으로 설정해 주면 된다.
식별관계란 부모 식별자를 자식이 자신의 식별자로 물려받는 것을 말한다. 자녀가 아버지의 성을 물려받는 것과 비슷하다고 생각하면 된다(ER-Win이나 DA# 등 케이스툴을 이용하면 쉽게 릴레이션을 설정할 수 있다).
이렇게 설정하면, 자식이 부모 식별자를 자신의 식별자로 물려받았으므로 부모 식별자로 연결된다. 이 부모 식별자를 통해 JOIN할 수 있게 된다. SQL에서 쓴다면 다음과 같다. 이때 주무내역과 주문상품내역에 각각 있는 데이터를 JOIN해 함께 조회할 수 있다.
데이터 모델을 설계해 보면 대다수의 릴레이션이 1:M 관계를 가지며, 1:M 릴레이션십은 반복 그룹을 제거하는 역할을 하기도 한다. 반복 그룹을 제거하는 역할을 한다는 의미는 뒤에 이야기하겠지만, 정규화와도 관련이 있으며 모델의 유연성을 향상시켜 준다.
2) 반복 그룹을 제거하는 1:M 릴레이션십
주문에 대하여 [그림 3] 모델처럼 설계하지 않고 주문내역에 주문상품내역을 함께 넣는 형태로 접근했다면 개념적으로 [그림 3]과 같을 것이다.
[그림 3] 주문내역에 주문상품을 함께 관리하는 경우
이런 모델은 주문 상품이 3개 이하인 경우는 아무런 문제가 없지만, 3개가 넘는 경우는 주문상품을 담을 수 없다. 3개 이상의 상품을 담으려면 상품4라는 속성을 늘려줘야 한다. 5개를 주문하고 싶다면 다시 상품 5를 추가하면 된다. 그러나 이렇게 속성을 추가하는 방법은 데이터 모델을 수정해야하기 때문에 좋지 않다.
이것을 해결하는 방법은 주문내역과 주문상품내역으로 엔터티를 분리해 1:M 관계를 만들어 줌으로써 반복을 제거하면서 확장성를 높일 수 있다([그림 4]와 같이 모델을 설계하면 정규화 위반이다. 어느 정규화 위반인지 한 번 생각해 보기 바란다. 정규화는 뒤에 자세히 소개하겠다).
[그림 4] 주문상품을 분리해 관리하는 경우
직접적인 관계만 설정
직접적인 관계, 다른 말로 하면 직접적으로 관련 있는 2개 엔터티 사이에만 관계를 설정해주어야 하고, 직접 관계를 모두 정의하면 데이터 모델 전체 관계가 완성된다.
[그림 5] 부자관계를 통한 직접적인 관계
나와 아버지, 할아버지를 예를 들어 직접 관계를 알아보자. 나와 아버지는 직접관계, 아버지와 할아버지는 직접관계, 나와 할아버지는 간접관계다. 따라서 위와 같이 나와 아버지, 나와 할아버지 간 릴레이션을 설정할 때는 직접관계만 설정하면 된다.
[그림 6] 직접관계로 구성된 주문 모델
[그림 6]에서 고객은 주문내역과 직접적인 관계를 갖고, 주문내역은 주문상품내역과 직접적인 관계를 갖는다. 고객과 주문상품내역은 직접적인 관계는 없지만 주문상품내역(상품)을 주문한 고객이 누군지 JOIN을 통해 알 수 있다. 혹 노파심에 말하지만, JOIN은 두 엔터티만 걸 수 있는 것은 아니다. 2개, 3개….n개 엔터티를 JOIN할 수 있다. 이해를 돕기 위해 위 3개의 엔터티를 JOIN하는 SQL을 살펴보면 다음과 같다.
이렇게 하면 고객번호 1234이 주문한 모든 주문내역과 주문상품내역을 알 수 있다. [그림 7]의 예가 위 모델에서 간접관계를 설정한 것이다. 이렇게 간접관계를 설정하는 이유는 무엇일까 고객이 주문한 주문상품내역을 JOIN으로 알 수 있는데, 고객과 주문상품내역을 연결해야 알 수 있다고 생각하기 때문이거나, 개발자들이 SQL을 좀 더 쉽게 구현하도록 하기 위해서인 듯하다.
[그림 7] 간접관계 릴레이션십
[그림 7]과 같이 간접관계를 설정하면 주문상품내역의 고객번호가 상속돼 중복이 발생한다. 이득도 거의 없고 불필요한 부분이 추가되면서 데이터 정합성이 훼손되고, 엔터티 간 계층 관계를 불필요하게 복잡해지게 한다.
릴레이션십과 상속의 원리
릴레이션십을 거론할 때 △부모자식간 관계 △상위하위 관계 △식별/비식별관계 등으로 소개하는 것을 볼 수 있다.
식별일 때는 부모자식간 관계이고, 비식별일 때는 상위하위 관계라고 소개하기도 한다. 필자는 부모자식간 관계보다는 상위하위 관계를 다시 식별/비식별로 구분하는 것이 적절하다고 본다. 하지만 이 글에서는 상위=부모, 하위=자식이라고 가정하고 좀 더 자세히 알아보겠다.
1) (상위)자식(하위) 관계
릴레이션십은 직접 관계인 2개의 엔터티 사이에서 설정하기 때문에 두 엔터티 중에 하나는 부모(상위) 엔터티가 되고 나머지 하나는 자식(하위) 엔터티가 된다. 1:M 관계에서 1쪽이 부모(상위) 엔터티가 되고, M쪽이 자식(하위) 엔터티가 된다. 1:1이나 M:M 관계에서도 어느 한 쪽이 부모(상위) 엔터티가 되고 나머지가 자식(하위) 엔터티가 된다.
2) 렐레이션십 구현
릴레이션십은 부모(상위)로부터 자식(하위)으로 관계선을 연결해 표현한다. 릴레이션십이 형성되지만 자식(하위) 엔터티는 부모(상위) 엔터티에 영향을 미치지 않는다. 하지만 자식(하위) 엔터티는 부모(상위) 엔터티의 식별자를 속성으로 갖게 된다. 자식(하위) 엔터티에 생성된 속성은 Foreign Key가 되며, 이는 참조무결성(reference integrity)을 지원하는 역할을 한다.
[그림 1] 릴레이션십의 예
[그림 1]은 회사와 부서 관계다. 회사 엔터티와 부서 엔터티, 부서 엔터티와 사원 엔터티 릴레이션의 차이는 식별관계냐 비식별관계냐이다.
- 회사 엔터티와 부서 엔터티 관계
회사가 부모(상위), 부서가 자식(하위) 엔터티다. 부서는 회사 엔터티의 식별자인 회사 ID를 상속받아 부서 엔터티의 일반 속성으로 구현됐다. 이와 같이 자식(하위) 엔터티가 부모(상위) 엔터티의 키를 자신의 일반 속성으로 상속받는 것을 비식별관계라고 한다.
- 부서 엔터티와 사원 엔터티 관계
부서가 부모(상위), 사원이 자식(하위) 엔터티다. 사원은 부서 엔터티의 식별자인 부서 ID를 상속받아 사원 엔터티의 식별자 속성으로 구현됐다 이와 같이 자식(하위) 엔터티가 부모(상위) 엔터티의 키를 자신의 식별자 속성으로 상속받는 것을 식별관계라고 한다.
3) 릴레이션십으로 상속이 이루어진다
[그림 2]에는 ‘계좌’와 ‘계좌입출금내역’이라는 2개의 엔터티가 있다. 두 개의 엔터티 가운데 어느 것이 부모(상위)이고, 어느 것이 자식(하위)인지 결정하는 방법을 알아보자.
필자는 어떤 것이 먼저냐, 먼저 생성되는지 또는 되어야 하는지를 따져본다. 자식보다 부모가 먼저 있어야 하듯이, 계좌입출금내역이 있으려면 계좌가 먼저 있어야 하기 때문에 계좌가 부모(상위) 엔터티가 되고, 계좌입출금내역이 자식(하위) 엔터티가 된다.
[그림 2] 릴레이션십에 의한 상속
부모(상위) 엔터티와 자식(하위) 엔터티가 정해지면 계좌에서 계좌입출금내역 쪽으로 관계선을 그어주면 릴레이션십이 설정된다. 부모(상위) 엔터티의 식별자인 계좌번호가 자식(하위) 엔터티인 계좌입출금내역의 속성으로 생성된다.
이렇게 되면 자식은 부모(상위) 개체 이름(식별자)을 갖고 있으므로 부모(상위)를 유일하게 식별할 수 있어, 부모(상위)의 모든 속성을 사용할 수 있게 된다. 이것이 엔터티가 식별자를 가져야하는 이유이자 엔터티와 릴레이션십(E-R)의 기본 원리다.
이로써 릴레이션을 설정하는 방법에 대해 알아보았다. 이번에는 식별과 비식별에 대해 알아보자. 이 부분은 설명하기는 쉽지만, 실제 논리 모델링에서 완벽하게 지켜지지는 않는 것 같다. 그 이유는 식별관계인데도 여러 가지 이유에 따라 비식별관계로 변할 때도 있기 때문이다. 그렇더라도 원론에 입각해 식별인지 비식별인지를 구별하고, 릴레이션십을 설정할 수 있어야 한다.
식별/비식별관계 여부는 상속된 속성의 위치에 따라 결정된다. 부모(상위) 식별자 전체가 자식(하위) 식별자에 포함되면 식별관계, 부모(상위)의 식별자 전체가 자식(하위)의 일반속성이 되면 비식별관계가 된다.
식별/비식별관계
1) 식별관계
상위(부모) 식별자 전체가 하위(자식)의 식별자에 포함되면 식별관계이며, 이를 부모-자식간 관계라고 한다.
[그림 3] 릴레이션십에 의한 상속 및 식별관계
식별관계로 릴레이션 선을 설정하면 위와 같이
모양의 선이 그어진다. 부모 ‘계좌’ 엔터티의 식별자인 계좌번호가 자식 ‘계좌입출금내역’ 엔터티의 식별자로 포함ㆍ생성된다(DA#의 Barker 표기법에서 #은 식별자를 나타낸다).
식별관계의 특징은 부모 엔터티는 자식의 탄생에 직접적인 역할을 해 자식은 부모 없이 생성할 수 없다는 점이다. 따라서 식별관계는 릴레이션십 중 부모-자식 사이가 강하고, 중요한 관계로서 일반적으로 데이터 모델의 골격 역할을 한다.
2) 비식별관계
비식별관계는 부모(상위) 엔터티의 식별자가 자식(하위) 엔터티의 일반속성에 포함되는 것을 말한다.
[그림 4] 릴레이션십에 의한 상속/비식별관계
비식별관계로 릴레이션을 설정하면 [그림 4]와 같이
모양의 선이 그어진다. ‘직업’ 엔터티의 식별자 직업코드가 ‘고객’ 엔터티의 일반속성으로 포함된다.
위 모델의 ‘직업’ 엔터티와 ‘고객’ 엔터티를 보면 서로 관계를 갖고 있고, 비식별관계로 설정돼 있음을 알 수 있다. 비식별관계는 그림과 같이 직업 엔터티의 식별자 ‘직업코드’가 고객 엔터티의 일반속성으로 생성된다. 이 관계를 비식별관계라고 하며, ‘직업’ 엔터티는 ‘고객’ 엔터티 생성에 직접 관여하지 않는다. 즉 직업이 없어도 고객은 존재할 수 있다는 말이다.
[그림 5] 비식별관계 예와 Join
[그림 5]에서 보는 바와 같이 ‘직업’ 엔터티와 ‘고객’ 엔터티는 비식별관계로 릴레이션이 설정돼 있다. 서로 관계를 갖고 있고, 고객 엔터티는 직업 엔터티로부터 식별자를 상속받았기 때문에 SQL에서 Join할 수 있으며, 고객 개체가 가진 직업코드에 해당하는 직업은 반드시 1개여야 한다.
여기서 유의해야 할 점이 하나 있다. ‘고객’ 엔터티의 (직업 엔터티로부터) 상속받은 직업코드 식별자가 널(null)이냐 널이 아니냐는 다른 이야기이다. 즉 비식별 릴레이션십이 설정됐다고 해서 상속받은 식별자가 꼭 있어야 하는 것은 아니다. 없을 수도 있다. 직업이 없는 고객이 존재할 수 있으므로, 그런 고객은 ‘고객’ 엔터티의 직업 코드가 널(null)일 수도 있다.
[그림 5] 릴레이션에서는 널(null)이 없다고 돼 있다(고객 엔터티의 직업코드 속성 앞 * 표시가 있으면 널(null)이 없음(not null)을 의미한다). 이는 현실 세계에서는 고객의 직업이 없을 수도 있지만, 비즈니스적으로나 데이터적으로는 고객의 직업은 꼭 존재해야 한다는 의미라고 볼 수 있다.
위에서 소개한 식별과 비식별의 표기법을 정리해 보면 다음과 같다.
[그림 6] 식별/비식별관계 표기법
식별관계인 경우 자식 엔터티의 관계선에 바(I)가 표시되지만, 비식별관계인 경우에는 바(I)가 없다. 모델링 툴인 DA#, ER-WIN으로 실습해 보면 금방 알 수 있으니 꼭 해보기를 바란다.
관계(relation)에서 참조 무결성(referential integrity), 종속과 참조 관계, 식별과 비식별관계, 카디널리티(cardinality), 관계 디그리(relationship degree), 관계명, 순환 관계, 추출 관계(derived relationship) 등에 대한 것을 알아야 좋은 모델을 할 수 있다.
엔터티 계층
1) 엔터티 계층
엔터티를 도출하고 릴레이션을 설정하면 상속을 통해 전체적으로 자연스럽게 계층화가 이뤄진다. 부모로부터 자식으로 상속되고 그 자식으로부터 자식에게 상속돼 현실 세계의 가족 구조와 비슷하게 된다.
다음의 예를 같이 보자.
[그림 1] 계층이 없는 모델
[그림 1] 모델을 보면 모든 엔터티가 ‘회원’ 엔터티와 직접 관계를 맺고 있다. 즉 최상위 엔터티가 모든 엔터티와 직접 관계인데 이는 엔터티 직접 관계도출에 실패했다고 볼 수 있다. [그림 1] 계층이 없는 모델을 독자 나름대로 좋은 모델로 바꿔 보는 연습도 필요하지 싶다.
다음 [그림 2]는 비교적 잘 도출한 모델이다. 그림이 작아서 자세한 내용을 볼 수 없겠지만, 모델의 전체적인 모습을 보고 그 모양을 느껴보기 바란다.
[그림 2] 계층이 있는 모델
엔터티 간 상속 관계를 통해 계층이 발생하는 것은 지극히 정상적이다. 실제로 상위 엔터티와 직접 연결되는 엔터티는 그리 많지 않다. 따라서 [그림 1]과 같이 모든 엔터티가 최상위 엔터티와 직접 관계를 맺고 있는 모델이 나온다면, 모델이 잘 설계됐는지 다시 고민해 볼 필요가 있다.
2) 관계와 SQL
엔터티 간 관계를 통해 자연스럽게 엔터티의 계층화가 이뤄지면, 차상위의 엔터티 정보 찾기가 어렵다고 생각하는 경우가 있다. 그런데 아버지 엔터티가 할아버지 엔터티의 식별자를 물려주었다면, 할아버지의 정보를 직접 찾을 수 있으므로 SQL이 복합하지 않을 수 있다. 즉 관계는 반드시 지나가야 하는 경로(path)가 아니다.
[그림 3] 엔터티의 계층과 SQL Join
[그림 3]은 ‘시험-시험접수내역-시험접수별선택과목’으로 식별관계로 설정돼 있다. ‘시험접수별선택과목’ 엔터티는 시험과 시험접수내역의 식별자를 자신의 속성으로 다 갖고 있다.
관계는 시험-시험접수내역-시험접수별선택과목 순이지만, SQL을 작성할 때는 시험과 시험접수별선택과목을 직접 조인해 사용할 수 있다. 과목코드 ‘10’인 고객목록을 조회하는 SQL은 다음과 같이 간단하게 작성할 수 있다.
릴레이션십의 구성
릴레이션십은 name, cardinality, optionality(selectivity)라는 3가지 요소를 이용해 표현된다.
1) Name
릴레이션십이 설정되면 해당 릴레이션십의 이름, 즉 관계명을 기술하는 것이 좋다. 해당 릴레이션을 단순히 관계선만 보고 추정 가능한 때도 있지만, 그렇지 않은 경우도 있다. 따라서 본인을 위해서나 추후 모델을 관리하고 참조해야 하는 담당자를 위해서도 기술하는 것이 좋다.
기억력을 자만해서는 안된다. 다들 알겠지만 순간의 기억이 장기 기억으로 가기 위해서는 많은 노력과 반복이 필요하다. 그러므로 자신의 머리와 기억력을 믿지 말고 가능하면 기록을 습관화해야 한다.
관계에 명칭을 정의하는 것은 엔터티와 마찬가지로 해당 릴레이션이 어떤 집합인지를 보여준다.
[그림 4] 관계 명칭
[그림 4] 모델의 ‘부서’와 ‘사원’ 관계에 대해 알아보자. 사원 엔터티는 부서 엔터티로부터 비식별관계로 설정됐다. 그렇다면 사원에 포함된 부서는 어떤 부서일까 사원이 소속된 소속부서일까 사원을 관리하는 관리부서일까 아니면 사원이 희망하는 근무부서일까 릴레이션만 보고는 알 수가 없다. 관계명이 없으면 일반적으로 사람들은 소속부서일 것이라고 추정하는 오류를 범하게 된다. 이런 오류를 줄이고 명확히 하기 위해 관계명이 필요하다.
관계명은 바라보는 관점에 따라 달라진다. 예를 들어 부부에서 남자는 여자의 남편이 되고, 반대일 경우는 아내가 되듯>
[그림 5] 관계명 관점
부모 또는 상위가 하위를 바라볼 때는 Include, 자식 또는 하위가 바라볼 때는 belong to 관점으로 정의하나, 실제 업무에서는 중간 입장의 관계 명칭을 사용하는 것이 일반적이다.
[그림 6] 관계 명칭
관계 명칭을 써야 하는 또 하나의 이유는 엔터티 사이에 렐레이션이 하나 이상일 수 있기 때문이다. 데이터 모델링에 대해 배우기 시작하는 사람들이 엔터티 사이의 관계는 하나라고 생각하는 경우가 있다. [그림 6]에서 보는 바와 같이 부서 엔터티와 사원 엔터티 사이에는 3개의 릴레이션(관계선)이 존재한다. 그것도 무려 양방향으로 말이다. 이렇게 여러 개의 릴레이션이 있을 때 관계 명칭이 없으면, 그 릴레이션이 어떤 의미인지를 알 수 없으므로 관계 명칭은 가능하면 꼭 써줘야 한다.
관계명칭을 써야 하는 경우는 롤(Role) 이름이 필요할 때, 순환 관계일 때, 추출 관계일 때, 양방향 관계일 때, 다대다 관계일 때다. 반면에 부모(상위) 엔터티에 일대다(1:M)로 종속되는 관계는 관계명을 굳이 쓰지 않아도 되나, 처음에는 쓰는 것을 습관화하면 좋다.
릴레션이션십 구성
1) ELECTIVITY
SELECTIVITY란 선택도를 의미한다. 하나의 데이터에 대해 선택될 수 있는가 없는가를 의미한다. 즉 SELECTIVIY는 상속받은 속성(자식ㆍ하위 엔터티의 속성)이 null일 수 있는지를 말한다.
Null은 대응하는 데이터가 없고, Not NULL은 대응하는 데이터가 있다는 뜻이다. 상속받았다고 해서 꼭 Not NULL은 아니다. 비즈니스에 따라서 null일 수도 있으므로 릴레이션을 설정할 때 null 여부에 따라서 알맞은 릴레이션을 설정해 주어야 한다.
단 식별관계일 경우는 부모ㆍ상위 엔터티의 속성이 자식ㆍ하위 엔터티의 식별자가 되기 때문에 Not NULL이 돼야 한다.
여기서 같이 알아야 할 점은 SELETIVITY에는 null 여부에 따라서 필수(mandatory)와 선택(optional)이 있다는 것이다. Mandatory는 Not Null이라는 뜻이고, Optional은 NULL이 가능하다는 말이다. Mandatory와 Optional은 앞서 소개했듯이 비즈니스에 따라서 결정하는 것이고, 모델링툴에서 지원하는 관계선을 잘 선택설정해 주면 된다.
[그림 1] SELECTIVITY/Mandatory, Optional
[그림 1] 모델을 보면서 좀 더 자세히 알아보자. [그림 1] 모델은 사원의 소속부서와 근무부서가 있는 경우다. 소속부서는 꼭 존재하지만, 근무부서는 존재하지 않을 수 있음을 모델에서 알려주고 있다. 즉 사원의 ‘소속부서’ 속성 앞을 보면 별표(*)가 돼 있다. 이것은 Not Null, 즉 Mandatory를 의미한다. 근무부서 속성 앞에는 동그라미(o)가 돼 있다. 이것은 null 가능, 즉 Optional을 의미한다. 정리하면 위 모델은 사원의 소속부서는 꼭 있어야 하며, 사원의 근무부서는 없을 수도 있음을 보여준다.
SELECTIVITY를 구분하는 또 하나의 방법은 관계선이다. 관계선의 점선과 실선으로 필수 여부를 표기한다.
다음 [그림 2]와 [그림 3] 모델을 보면서 관계선을 해석해보자.
[그림 2] SELECTIVITY 관계선 해석 1
[그림 2] 모델은 ‘기업’에서 ‘발생주식’으로 릴레이션이 설정돼 있다. 기업 쪽이 점선, 발생주식 쪽이 실선으로 돼 있다. 이와 같이 각 엔터티 측의 관계선이 점선 또는 실선으로 입장이 결정된다.
위 모델을 해석해 보면 ‘발생주식’은 부모ㆍ상위 엔터티인 ‘기업’이 필요하다. 하지만 기업 엔터티는 자식ㆍ하위 엔터티인 발생주식 엔터티가 없어도 무방하다. 일반적으로 부모ㆍ상위 측이 점선, 자식ㆍ하위 측이 실선이다.
이는 식별/비식별관계와는 독립적이다. 즉 식별/비식별과 SELECTIVITY는 다르게 또는 독립적인 관점에서 생각해야 한다.
[그림 3] SELECTIVITY 관계선 해석 2
관계선에서 실선은 필수, 점선은 선택 관계를 의미한다.
위 모델에서 ‘고객접속내역’은 실전으로 ‘회원’과 ‘접속방식’이 반드시 필요함을 나타내고 있다. 이에 비해 ‘지역’은 점선으로 처리됐으므로 선택적이라는 뜻이다. 회원과 접속방식 입장에서는 고객접속내역과 점선으로 피현했으므로 선택적 관계다. 회원은 고객접속내역이 없더라도 문제가 없음을 의미한다.
2) CARDINALITY
CARDINALITY는 관계가 있는 두 엔터티 사이에 데이터 대응 수를 의미한다. 사람마다 취미를 하나 또는 하나 이상을 갖고 있는 것처럼, 사람에 대응하는 취미의 수를 표기하는 것이다.
데이터 모델에서 CADINALITY는 1:M, M:M, 1:1이라는 3가지가 있다. 모델의 관계는 대부분 1:M의 CADINALITY로 표현된다. 3 가지 CADINALITY에 대해 알아보자.
(1) 1:M 관계
[그림 4] 1:M 관계
[그림 4]는 우리가 흔히 사용하는 신용카드 사용내역을 모델링한 것이다. 매달 받아보는 카드 내역서를 떠올려 보면, 하나의 카드에 여r>
[그림 4] 모델은 하나의 카드번호 데이터에 여러 개의 거래내역 데이터가 생성되며, 하나의 거래내역 데이터는 하나의 카드번호 데이터에 의해 발생함을 보여준다. 카드 엔터티가 1, 거래내역 엔터티가 M인 1:M 관계다.
이런 관계는 가정 정상적인 릴리이션십이다. 논리모델에서 거론할 바는 아니지만 일반적으로 빈번하게 조인이 일어나며, 빈번한 조인이 발생하면 성능 저하가 발생할 가능성이 있으므로 조인에 대한 조인 방식과 조인 순서 최적화가 필요하다.
1:M 관계에서 SELECTIVITY에 따라 다음과 같이 3 가지 릴레이션이 발생할 수 있다.
① 1:M이면서 식별관계
[그림 5] 1:M 식별관계
식별이란 자식(하위) 엔터티가 부모(상위) 엔터티의 식별자를 (자신의 식별자로) 상속받는 경우를 말한다. 자식(하위)은 부모(상위) 없이 생성이 안 되며(그림 5의 2와 3), 부모(상위)는 자식(하위)을 갖지 않을 수 있다(그림 5의 1). 즉 부모 없는 자식이 있을 수 없듯이 거래내역은 카드 없이 생성될 수 없으며, 카드는 거래내역이 없을 수 있다는 뜻이다.
② 1:M이면서 비식별관계
[그림 6] 1:M 비식별관계
비식별이란 자식(하위) 엔터티가 부모(상위) 엔터티의 식별자를 본인의 일반속성으로 상속받는 경우를 말한다. [그림 6]의 1과 2가 비식별관계선이다. 이는 참조할 필요가 있는 데이터만 연결하며, 일반적인 참조의 형태에서 발생한다. 이때 [그림 6]의 1은 Mandatory Not Null이며, 2는 Optional NULL 허용이 된다.
③ 부모가 반드시 하나 이상의 자식이 있어야 하는 관계
[그림 7] 1:M 부모가 반드시 하나 이상의 자식을 가져야 하는 관계
자식에 의해 부모가 발생하는 관계다. 이 관계는 현실 세계에서는 발생할 수 없으나, 데이터 관계에서는 드물지만 발생할 수 있다.
(2) 1:1 관계
[그림 8] 1:1 관계
하나의 민원요청 데이터에 대해 반드시 한 민원인 데이터가 존재하거나 한 명의 민원인은 하나의 민원을 요청한다는 모델이다(물론 모델로 적합하지는 않지만 설명을 위한 것이니 모델의 좋고 나쁨은 다른 문제다.).
모델에서 1:1 관계가 많이 나오면, 하나의 엔터티로 구성해도 무방할 가능성이 높으므로 모델을 재검토해 볼 필요가 있다. 하나의 엔터티로 구성해도 되는 것을 1:1 관계로 두 개의 엔터티로 구성하면, 빈번한 조인 발생으로 성능 저하 가능성이 높고, 데이터 정합성 위험도 따른다. 하지만 1:1 관계를 발견하면 기계적으로 통합 또는 분할하는 경우가 있다. 실제로 1:1 관계는 다른 관계들보다 사용 빈도가 매우 낮고 통합/분할 여부 결정이 어렵다.
통합하면 일부에서는 조인 감소로 유리하지만, 그렇지 않은 경우도 있으므로 업무 요소를 따져서 통합 여부를 결정해야 한다. 결국 모델링 초기에 1:1 관계가 나오면 서둘러 통합할 필요는 없다. 서둘러 통합하면 분리할 기회를 놓칠 수 있다.
최종 모델에서 1:1 관계는 수직분할 때 나타날 수 있다. 1:1 관계에서 SELECTIVITY에 따라 다음과 같이 3가지 릴레이션이 발생할 수 있다. 다음 모델은 1:1 관계이면서 식별관계다. 식별관계 외의 식별관계가 있을 수 있다.
① 1:1이면서 식별관계
[그림 9] 1:1 식별관계
부모(상위) 엔터티와 자식(하위) 엔터티가 1:1 관계이면서 자식(하위) 엔터티가 부모(자식)의 식별자를 자신의 식별자로 상속받는 경우다.
② 1:1이면서 비식별관계/Mandatory
[그림 10] 1:1 비식별관계/Mandatory
부모(상위) 엔터티와 자식(하위) 엔터티가 1:1 관계이면서 자식(하위) 엔터티가 부모(자식)의 식별자를 자신의 일반식별자로 상속받으면서 Not Null이 되는 경우다.
③1:1 비식별관계/Optional
[그림 11] 1:1 비식별관계/Optional
부모(상위) 엔터티와 자식(하위) 엔터티가 1:1 관계이면서 자식(하위) 엔터티가 부모(자식)의 식별자를 자신의 일반식별자로 상속받으면서 NULL이 허용되는 경우다.
(3) M:M 관계
M:M 관계는 논리적으로 존재한다. 현재 존재하는 DBMS에서는 지원하지 않기 때문에 물리로 변환할 때는 M:M 관계를 해소해 줘야 한다. 이 때문에 논리 모델링 시에 M:M이 나오는 경우, 물리변환을 고려해 M:M 관계를 해소하는 경우가 많다.
M:M 관계는 요리와 식재료의 예를 들 수 있다.
[그림 12] M:M 관계인 요리와 식재료
닭도리탕을 예로써 알아보자. 닭도리탕에 들어가는 주요 식재료는 닭(고기), 감자, 당근 등이다. 그러면 닭도리탕과 식재료인 닭고기, 감자, 당근은 1:M 관계가 된다.
그런데 닭은 닭도리탕에만 들어가는 것이 아니라 치킨에도 들어간다. 이 때문에 반대로 M:1 관계가 발생하므로 결국 M:M 관계가 발생하게 된다.
이를 모델로 표현하면 [그림 13]과 같다.
[그림 13] M:M 모델
[그림 13]과 같은 모델은 조인 발생시 Group By를 사용(M:M 조인에 의해 증가된 데이터를 감소시키기 위해 사용)하게 된다. 이때 조인 횟수의 증가로 성능 저하가 발생할 수 있으며, 더 나가가 요리나 식재료의 자식(하위) 엔터티가 있을 시 조인이 일어나 성능 저하가 발생할 수도 있다.
M:M 관계는 이해에서는 M:M 관계 도출도 M:M 관계를 풀어내는 것도 힘들다. 분명하게 이해하기 위해 위의 요리, 식재료 데이터를 예를 들어 조금 더 알아보자.
[표 1] 요리별 식재료 표
요리별 식재료를 [표 1]처럼 도출했다고 하자. 닭도리탕에는 닭·감자·당근이 들어가고, 삼계탕에는 닭·찹쌀·생강·마늘이 들어간다고 정리했다. 생각한 것을 익숙한 표로 작성하는 것은 쉽다. [표 1]의 요리와 식재료 표를 모델로 표현한다면 어떻게 할 수 있을까
표를 자세히 보면 요리와 식재료가 연결됐음을 알 수 있다. 요리를 기준으로 정리했으므로 요리1에 식재료1, 2, 3이 들어간다. 즉 연결됐다고 할 수 있다. 이렇게 연결하기 위해서는 M:M 관계를 해소해야 한다. 관계를 해소하는 방법으로 요리와 식재료 사이에 연결, 릴레이션(교차) 엔터티를 만들어 관계를 설정해 주는 것이 있다.
물리적 세계에서 존재하지 않는 M:M 논리 모델을 1:M의 관계로 설정하는 방법에 대해 알아본다.
M:M 관계의 해소
앞 연재에서 소개했듯이 데이터 모델의 M:M 상태는 현재의 DBMS들에서는 직접 지원하지 않아 논리적으로만 존재한다. 따라서 물리적 구현을 위한 모델링에서는 반드시 물리적 변환이 필요하다.
M:M 관계를 해소하는 방법으로 릴레이션(교차) 엔터티를 이용하는 방법 이외에 식별자(unique identifier)를 이용하는 방법, 속성(attribute)을 이용하는 방법이 있다. 필자는 주로 릴레이션(교차) 엔터티를 이용하는 방법을 사용한다. 각각의 방법에 대해 알아보자.
1) 릴레이션 엔터티를 이용하는 방법
M:M 관계는 중간에 릴레이션 엔터티를 배치해 각각을 1:M 관계로 설정해 해소할 수 있다. 릴레이션 엔터티는 각 부모(상위) 엔터티와 식별관계로 상속받아 생성된다. 이때 릴레이션 엔터티는 속성이 적고 변경이 적어야 유리하다. 따라서 릴레이션 엔터티에 불필요거나 변경될 가능성이 있는 속성을 넣지 않도록 해야 한다.
[그림 1] M:M 관계 해소: 릴레이션(교차) 엔터티 이용 모델
위 모델을 표로 만들어 보면 [그림 2]와 같다.
[그림 2] 릴레이션 엔터티를 이용한 M:M 관계 해소 모델의 데이터 표
[그림 1]과 [그림 2]를 보면 표기하는 방법만 다를 뿐 같은 말을 하고 있다. 이것을 잘 이해하면 M:M 관계 모델을 잘 처리할 할 수 있다.
2) 식별자로 M:M 관계 해소
이 방법은 부모(상위) 엔터티의 식별자(Unique Identifier)를 자식(하위) 엔터티의 식별자로 상속받아 자식 엔터티 식별자를 ‘잘 설정해주는’ 것이다. 잘 설정한다는 것은 식별자의 정의에 맞게 유일하게 구분할 수 있도록 해주는 것을 말한다.
[그림 3] 식별자로 M:M 관계 해소
식별자로 M:M 관계를 해소하기 위해서는 ‘식재료’의 식별자를 유일하게 만들어 줘야 한다. 일반적으로 부모(상위) 엔터티의 키를 식별관계로 상속받아 해결한다. M:M 관계를 식별자를 변경해 1:M 관계로 바꿈으로써 데이터 모델을 단순화할 수 있고 조인의 복잡도를 줄일 수 있다. 하지만 최근 데이터 추출 방법에 대한 고민이 필요하다.
위 모델을 정리해 보면 [그림 4]와 같다.
[그림 4] 식별자로 M:M 관계 해소 모델 데이터 표
3) 속성으로 M:M 관계 해소
이 방법은 비즈니스적으로 명확한 제약사항이 있을 때 사용할 수 있다. 하지만 유연성이 떨어지는 모델이므로 이런 방법도 있다는 정도만 알고 넘어가자.
[그림 5] 속성을 이용한 M:M 해소 모델
위 모델은 사원은 3번까지만 부서 이동이 가능하다는 전체 아래 속성을 이용해 M:M 관계를 해소한 예다. 이 모델은 유연성이 낮고 조회의 어려움이 있어서 필자는 사용을 지양하는 편이다.
M:M 관계를 해소하는 방법 몇 가지를 알아보았다. 논리모델의 마지막 단계나 논리모델 중 M:M이 자식 엔터티를 갖는 경우에 해소하는 것이 바람직하다. 일반적으로 필자는 논리모델을 하면서 M:M이 나오면 바로 해소하는 편이다.
M:M 관계에 대해 생각해 보기
고객과 상품(보험) 모델로 M:M에 관계에 대해 조금 더 알아보자. 고객이라는 주체와 상품이라는 대상이 만나면, 고객가입계약이라는 행위가 발생한다. 즉 고객이 상품(보험)을 구매(계약)한다가 된다. 이런 대부분의 행위 엔터티는 M:M 형태로 일어난다.
[그림 6] 고객:상품의 M:M 관계
[그림 7] 고객:상품의 M:M 관계 해소
[그림 6] M:M 관계를 [그림 7]처럼 해소할 수 있다. 여기서 주의 깊게 봐야 할 점은 릴레이션 엔터티는 각 부모 엔터티 식별자를 식별관계로 상속받고 있다는 것이다. 다음으로 부서와 사원에 대해 알아보자.
[그림 8] 부서와 사원 모델
위 모델은 사원이 부서에 소속됐음을 의미한다. 사원이 어떤 부서에 소속돼 있는지 명확하다. 위 모델의 데이터는 [그림 9]와 같다.
[그림 9] 사원 데이터
사원이 부서에 배정되면 사원에 데이터 하나가 생성된다. 부서가 바뀌면 다시 데이터가 하나 생성된다. 이렇게 되면 사원이 현재 어느 부서 소속인지를 알 수 없게 된다([그림 9]의 순서를 데이터 생성 순으로 생각하면 안 된다. 데이터를 조회했을 때 나오는 순서와 데이터 생성 순서는 무관하다).
현 소속 부서를 표현하기 위해 사원 쪽에 데이터 생성 순번을 넣어 해결하는 사례도 봤다. 이 방법은 데이터 생성 순서로 현재의 부서를 알 수 있으나 더 많은 정보, 예를 들어 해당 부서 발령시점 등을 알고 싶다면 또 다른 속성을 추가해야 한다. 이는 사원이라는 엔터티의 성격정의를 애매하게 한다. 순번을 넣든 안 넣든 위 모델은 정규화 위배가 일어나므로 그리 좋은 방법이 아니다.
위 모델의 문제점은 다음과 같이 모델링함으로써 해결할 수 있다.
[그림 10] 부서와 사원 이력을 고려한 모델
사원과 부서는 각 주체다. 주체와 주체가 만나 행위를 탄생시키는 경우로, 이 경우는이 력성 릴레이션(관계) 엔터티가 된다. 위와 같이 모델링함으로써 사원과 부서의 M:M 관계를 만족하고 이력까지 관리할 수 있다. [그림 5-9] 모델이 틀린 모델은 아니지만 [그림 5-10]과 같이 모델링한다면 확장성과 유연성을 가질 수 있어서 좀 더 좋은 모델이라고 할 수 있다.
상호 배타적 관계
배타 관계는 두 개 이상의 부모(상위) 엔터티와 관계이면서 그 관계가 상호 배타적일 때의 관계(Mutually Exclusive Arc Relationship)를 말한다. 딱 맞는 예는 아니지만, 아들에게 엄마와 아빠가 동시에 심부름을 시킨다고 하자. 이때 아들은 심부름을 동시에 할 수 없으므로 우선 순위를 둬야 한다.
[그림 11] 배타관계 모델
[그림 11] 모델 ‘상품구매내역’의 한 인스턴스(데이터 한 건)는 ‘개인고객’ 또는 ‘법인고객’과 관계를 갖게 된다. ‘상품구매내역’의 한 데이터가 개인고객과 법인고객 둘 다와 관계를 가질 수 없다.
배타관계가 발생한 엔터티에서는 일반적으로 해당 데이터가 어느 부모(상위) 엔터티와 관계를 갖는지를 보여주는 구분자 속성을 둬 관리한다. 위 모델의 구분자는 고객구분 속성이다. 이 구분자를 기준으로 상품을 구매한 고객(부모상위)이 누구인지 알 수 있다.
이때 구분자는 널(null)이면 안 된다. 부모(상위)가 누구인지 알기 위해 구분자를 썼는데, 이는 SQL 작성과도 연관된다. ‘상품구매내역’을 기준으로 데이터를 추출할 때, 부모가 누구인지를 모르면 부모 정보를 가져올 수 없기 때문이다. 배타관계는 [그림 11]에서 보듯이 서브타입과 관련돼 있다. 구분자인 고객구분이 서브타입이다.
상호 배타적 관계는 동시에 하나의 부모만 갖는 경우에만 사용해야 한다. 두 개 이상의 엔터티가 상호배타 관계의 부모로 계속 등장하면, 이 엔터티들은 원래 하나의 집합이 적합한 것일 수 있으므로 통합을 고려해야 한다. 그렇다고 상호 배타의 부모를 무조건 통합하는 것은 아니다.
자기참조관계
자기참조관계(self referencing)는 순환(recursive) 관계라고도 한다. 자기참조관계는 하나의 엔티티타입 내에서 엔티티와 엔티티가 관계를 맺고 있는 형태다. 즉 하나의 엔터티가 자기 자신과 관계를 맺는 것이다. 부서, 부품, 메뉴 등과 같이 계층 구조 형태를 표현할 때 유용한 관계 형식이기도 하다.
[그림 1] 학부모와 학생
학부모와 학생의 예로써 자기참조관계를 알아보자. 학생의 학부모는 1건만 있다고 가정해 보면, [그림 1]과 같이 모델링할 수 있다. 이 모델은 당장은 문제가 없다. 하지만 학생이 자라서 학부모가 됐을 상황을 수용할 수 없는 모델이다. 이 문제는 어떻게 해결할 수 있을까
[그림 2] 학부모, 학생, 학생_1
[그림 2]와 같이 학생 밑에 새로운 엔터티를 추가하면 된다. 하지만 이 모델 또한 학생_1이 커서 학부모가 되면 그 상황을 수용할 수 없다. 또 다시 하위에 엔터티를 추가해야 할까
좋은 모델이란 엔터티의 변경 없이 또는 최소 변경으로 변화에 대응할 수 있어야 하는데 위와 같이 엔터티와 관계를 계속 추가하는 것은 좋은 모델이라고 할 수 없다. 이럴 때 적용할 만한 방법이 자기참조관계 활용이다. 자기참조관계를 활용하면 학부모와 학생은 다음 [그림 3]과 같은 모델이 된다.
[그림 3] 자기참조관계
학부모와 학생은 모두 사람이므로 학부모 엔터티와 학생 엔터티를 통합한다. 이어서 자기참조관계선을 추가해 학생의 학부모가 누구인지만 관리하면 된다. 자기참조관계는 앞서 제시했듯이 트리구조, 조직, 분류 등의 데이터를 관리할 때나 자기참조를 위해 사용할 수 있다. 이때 주의할 점은 필수적인 관계로 만들면 최상위 부모 개체가 만들어 질 수 없기 때문에 널(null)값을 허용한 관계선을 설정해야 한다. 그리고 SQL 구현 시에는 connect by 절을 사용한다.
제품의 부품 항목(BOM) 모델은 어떻게 해야 할까
BOM(Bill Of Material)은 특정 제품이 어떤 부품으로 구성되는지를 표나 그림으로 표현한 것이다. 여기서 제품과 부품은 같을 수도 있고, 부품의 조합이 제품일 수도 있다. 즉 BOM은 제품과 부품 간 관계(relationship)를 정의하는 것이다.
대부분의 제품은 모두 여러 부품의 조합결과라고 볼 수 있다. 예를 들어 노트북, 청소기, 키보드, 마우스, TV, 시계 등은 모두 여러 부품을 조립해 만든다. 전자제품보다 더 친숙한 요리와 식재료를 갖고 좀 더 자세히 알아보자.
[그림 4] 요리와 식재료 모델
하나의 요리는 여러 식재료를 필요로 하고, 한 식재료는 여러 가지 요리에 사용될 수 있다. 따라서 M:M 관계가 되었고, 이 관계를 릴레이션(교차) 엔터티로 풀어냈다.
식재료 중 일부는 또 다른 식재료와 조합해 하나의 요리가 된다고 하자. 만두처럼 식재료 중 일부는 요리이고, 요리 중 일부는 다시 떡만두국 같은 다른 요리의 식재료가 될 수 있다. 이는 식재료와 요리는 근본적으로 같다는 말이므로 요리 엔터티와 식재료 엔터티의 통합을 생각해볼 수 있다. 요리 엔터티와 식재료 엔터티를 통합하면 [그림 4]와 같이 모델링할 수 있다.
[그림 5] BOM 모델
BOM 모델은 ‘부품_통합’ 엔터티에 전체 데이터, 즉 요리와 식재료 데이터를 한꺼번에 관리하면서 요리에 들어가는 식재료가 무엇인지를 ‘조립재료’ 엔터티에서 관리하는 형태다.
[그림 6] 요리 BOM 모델
엔터티명이나 속성명이 적합하지 않지만, 요리와 식재료를 BOM 모델로 표현하면 [그림 6]과 같다. [그림 7]은 요리 모델의 데이터를 표현한 것이다.
[그림 7] 요리 BOM 모델의 데이터
BOM 모델은 하위에 아무리 많은 부품이 있더라도 모델 변경 없이 수용할 수 있다. 자기참조관계는 하나의 부모만 지원하지만, BOM은 여러 개의 부모를 가질 수 있어서 주로 제조생산 부문에서 사용된다. 하지만 SQL 난이도가 그만큼 올라간다(connect by).
일반화와 특수화
엔터티의 일반화와 특수화에 대해 알아보자. 일반화를 하면 유연성이 올라가고 특수화를 하면 비즈니스 룰을 정확하게 표현하고 강제할 수 있다고 했다. 일반화와 특수화 중 어느 것이 좋은지는 비즈니스 요건에 따라서 정의돼야 한다.
하지만 현재의 특수성을 고려할 것인지 향후 확장성을 고려할 것인지는 모델러의 역량에 따라서 결정되기도 한다. 필자는 주로 확정성과 유연성을 위해 일반화를 하는 편이다. 어떤 것이 더 좋고 나쁘다를 논하기보다는 어떤 것이 더 유리한지에 집중할 필요가 있다.
1) 다중릴레이션십
IT 프로젝트를 관리하는 모델을 갖고 이야기해보자. 원래는 문제를 내고 직접해보고 서로가 한 것을 보면서 부족한 부분을 채우는 방법이 좋으나 지면상 그것은 불가능하니 계속 이야기를 진행하겠다. 그렇더라도 가능하면 이쯤에서 잠시 멈추고 IT 프로젝트를 관리하는 모델을 직접 한번 해보길 바란다. IT 프로젝트를 관리하는 모델의 조건은 다음과 같다.
① 우리가 관리하는 프로젝트에는 PM, 사업관리, 모델러, 품질관리, DBA가 배정된다.
② 필요한 인력은 정직원외부직원인턴사원 중에서 배정할 수 있다.
위 조건을 살펴보면, 엔터티로 나올 수 있는 것이 일단 프로젝트, 사원 정도가 될 것 같다. 사원에는 정직원·외부직원·인턴사원이 서브타입으로 구분될 수 있을 것이다. 이를 토대로 모델링을 해보면 [그림 8]과 같이 된다.
[그림 8] 프로젝트 관리 모델 1
[그림 8]에서 보는 바와 같이 두 개의 엔터티 간의 관계는 하나 이상일 수 있다. 프로젝트 계획에 따라서 필요한 인력은 각각 릴레이션을 가질 수 있으며, 관계의 방향이 서로 다른 릴레이션십이 공존할 수도 있다.
위 모델은 비즈니스 요건을 만족하기 때문에 별다른 문제는 없어 보인다. 그런데 필요한 인력의 종류가 늘어나면 릴레이션을 추가해 줘야 한다. 릴레이션을 추가한다는 것은 단순히 렐레이션만 설정하는 끝날 일이 아니다. 프로젝트 엔터티에 속성이 추가되는 것이며 이는 곧 물리적으로 테이블 변경이 필요한 일이다. 결국 이와 관련된 프로그램도 변경돼야 한다. 매번 늘어날 때마다 이런 식으로 관리하는 것은 물론 문제가 있다. 이런 모델이 바로 좋지 않은 모델이다.
이런 경우는 다음과 같이 여러 개의 릴레이션을 하나로 통합해 엔터티화하면 논리모델이나 물리 테이블의 변경없이 확장성을 확보할 수 있다.
[그림 9] 프로젝트 관리 모델 2
[그림 9] 모델의 1:M 릴레이션십은 통합할 수 있고, 양방향(M:M)의 릴레이션십이 되기 때문에 릴레이션(교차) 엔터티를 도출한 모델이다. 이 모델은 프로젝트에 배치되는 사원의 역할이 늘어나도 모델변경 없이 ‘사원역할’만 추가하면 된다. 또한 식별자를 조정해 이력으로 관리하거나 역할별 참여 수를 제한할 수도 있다.
2) 릴레이션십의 일반화
렐레이션십의 통합과 일반화는 엔터티와 마찬가지로 통합을 통해 일반화가 가능하며, 일반화가 될수록 더 유연하다. 경우에 따라서 구현이 어려운 경우도 존재한다. 릴레이션십이 확장될 가능성이 없다면 불리할 수 있으므로 잘 판단해야 한다. 즉 프로젝트에 투입되는 인력의 역할의 종류가 고정이라면 [그림 8]과 같은 모델이라도 문제가 없다. 하지만 조금이라고 확장될 가능성이 있다면 [그림 9]가 더 나은 나은 모델이라고 할 수 있다.
속성은 엔터티와 릴레이션십의 세부사항
엔터티(entity)와 릴레이션십이 데이터 모델의 골격이라면, 속성은 세부적인 부품 또는 구성요소라 할 수 있다. 속성이란 엔터티나 릴레이션십의 특성, 식별, 분류, 수량, 상태를 표현하기 위한 모든 세부사항이다(Any detail that serves to qualify, identify, classify, Quantify or express the state of a relation or an entity.).
사람을 놓고 생각해 보자. 아무개라는 사람을 특성, 식별, 분류, 수량, 상태로 나타낸다고 하자. 보통 키, 몸무게, 여성/남성, 두 귀, 두 손 등이 먼저 떠오를 것이다. 이것을 일종의 속성이라 할 수 있다. 엔터티에도 이와 같은 속성들이 존재한다. 이는 곧 속성들이 모여 엔터티를 구성한다는 말이 된다.
속성을 모두 도출하면, 해당 엔터티에서 관리할 데이터가 무엇인지 알게 된다. 속성은 데이터를 저장하는 가장 작은 저장 단위이기 때문이다.
하나하나의 속성이 모여 엔터티를 구성한다고 했는데, 이 말은 곧 ‘속성은 엔터디 개체의 세부 내용을 표현한다’는 뜻이 된다. 아무개의 속성이 아무개를 표현해준다면 그 뜻을 이해할 수 있을 것이다.
[그림 1] 고객 엔터티 개체와 속성
[그림 1]의 왼쪽은 고객이라는 엔터티이며, 오른쪽은 고객 엔터티에 들어 있는 데이터다. 고객 엔터티는 고객번호, 고객명, 직업, 자택주소라는 속성으로 구성됐음을 알 수 있다. 각 속성에 들어 있는 값을 볼 수 있다.
데이터 모델에서 엔터티와 릴레이션십이 중요하지만, 실제 데이터 값은 속성에서 관리된다.
속성의 구성 요소
논리모델에서 속성은 속성명, 데이터타입, 식별속성 여부, 필수속성 여부, 제약조건, 도메인, 속성의 정의요소로 구성된다(바커 표기법 기준).
[그림 2] 속성의 구성 요소
1) 속성명
속성명은 속성의 명칭을 말하므로 속성을 가장 잘 표현할 수 있도록 다는 것이 좋다. 예를 들어, 속성명을 ‘1’이나 ‘2’로 달았다면 1이 도대체 무엇인지 알 수 없으므로 고객번호 또는 고객명과 같이 명확한 명칭을 사용해야 한다.
2) 데이터 타입
속성에 들어가는 데이터의 종류와 그 크기를 말한다. 고객명 데이터 타입은 문자형을 주로 사용하며, 돈이나 계산과 관련된 속성은 숫자형을 사용한다.
오라클 DBMS에서 제공하는 데이터 타입은 다음과 같다. 오라클 DBMS도 버전에 따라서 제공하는 데이터 타입이 다를 수 있다.
① 문자형
② 숫자형
③ 날짜형
④ 바이너리
데이터 타입은 DBMS별로 조금씩 다를 수 있으므로 물리모델 변환 시에는 DBMS에서 제공하는 데이터 타입을 확인한 후 적용해야 한다. 모델링 케이스툴(ER-WIN, DA#)에서 새로운 모델을 만들 때, 대상 DBMS을 설정하면 해당 DBMS에서 제공하는 데이터 타입을 제공하므로 편리하다.
3) 식별속성
속성은 크게 식별속성과 비식별속성으로 구분할 수 있다. 식별자 역할을 하는 속성이 식별속성이며, 나머지가 비식별속성(일반속성)이 된다.
식별속성, 식별자는 엔터티에 존재하는 인스턴스를 유일하게 구분해 주는 속성이나 속성들을 말한다. 여기서 ‘속성들’이라는 식별자는 한 개의 속성으로 구성될 수 있 개의 속성이 모여서 식별자가 되는 경우를 복합 식별자(composite key attribute 또는 composite identifier)라고 한다.
식별속성은 바커 표기에서는 속성명 앞에 #을 붙여 구분한다. 식별자를 부르는 용어는 여러 가지가 있는데, 이는 식별자 부분에서 소개하겠다.
4) 필수속성
필수속성은 널(null)값을 허용하지 않는 Not Null 성격을 갖는다. 엔터티의 속성들 중에는 처음 인스턴스 생성 시 꼭 있어야 하는 속성이 있고, 그렇지 않은 속성이 있을 수 있다. 이때 꼭 데이터가 있어야 하는 속성을 필수속성이라고 하고, 그렇지 않은 속성을 옵션속성이라고 한다.
필수속성은 바커 표기에서는 속성명 앞에 *이 붙으며, 비식별속성은 속성명 앞에 o가 붙는다. 식별자는 당연히 필수속성이다.
데이터 모델링의 속성을 추출해 정의할 때, 필수속성과 옵션속성을 잘 구분한 후 정의해야 한다. 정확하게 구분해 정의하지 않으면, 프로그램 개발 후 꼭 들어와야 하는 데이터가 들어오지 않을 수 있다. 데이터 모델에서 필수속성으로 정의해 놓으면, 개발자가 프로그램 개발 시에 필수속성에 데이터가 꼭 들어 올 수 있도록 개발할 수밖에 없으므로 데이터의 품질을 올릴 수 있다. 반대로 필수속성을 옵션속성으로 정의하면 복잡한 상황이 발생할 수 있다.
5) 제약조건
속성의 제약조건이란 속성에 저장되는 값의 제약조건 또는 값의 범위나 가능 값(value)을 말한다. 예를 들어 금액 속성에는 숫자만 들어가야 한다. 남녀구분에서는 M 또는 F 이외에는 들어갈 수 없다.
6) 도메인
이 부분은 표준화와 관련된 부분이다. 도메인을 쉽게 이야기하면 그룹핑한 것이다. 예를 들어 성명은 문자, 주소는 문자, 성별은 문자라고 하면 성명주소성별은 문자형 속성이 된다. 이때 문자를 도메인이라고 할 수 있다. 도메인은 데이터 표준에서 정의하며, 속성은 정의된 도메인 중에 하나여야 한다. 혹 새로운 도메인이 필요하면, 데이터 표준에 정의한 후에 사용해야 한다. 표준에 없는 도메인을 임의로 사용하면 표준에 어긋나므로 지양해야 한다.
7) 속성정의
속성정의를 하는 이유는 자기 자신뿐 아니라 후임자를 위한 배려이기 때문이다. 처음에 속성명을 정할 때는 분명 어떤 데이터를 담을 것이라고 생각했을 텐데, 시간이 지나서 보면 그 의미를 만든 자기 자신도 헷갈릴 경우가 있다.
예를 들어서 고객 엔터티에 주소라는 속성이 있다고 가정하자. 주소 속성에 들어가는 것은 고객의 집 주소인지, 회사 주소인지, 우편물 수령지인지 알 수 없다. 이런 경우는 속성명도 좋지 않지만, 속성정의를 해놓지 않았기 때문에 정확성과 명확성이 떨어진다.
따라서 속성명을 잘 기술하는 것도 중요하지만, 속성정의를 잘 해놓는 것도 중요하다. 속성정의를 하는 방법과 기능은 ER-WIN이나 DA#같은 케이스툴에서 제공한다.
8) 속성과 데이터 모델
논리모델에서는 ‘하나의 속성은 반드시 단 하나만 존재해야 한다’를 이상적인 목표로 한다. 하나의 속성이란 단순히 같은 이름의 속성이 아닌, 그 의미가 같음을 말한다. 논리모델은 또한 속성의 중복을 배제한 No Redundancy한 상태를 목표로 한다. 그런 논리모델의 이상적 목표가 실용적이냐는 다른 문제다.
속성 도출
모델러는 인터뷰, 관련 문서, 리포트, 장표, 정보 시스템의 DB 등을 통해 데이터 모델을 설계하고 속성을 도출한다. 모델러가 해당 도메인 업무 전문가라면 비즈니스를 완벽하게 이해하고 모든 속성을 도출할 수 있다면 좋겠지만, 하지만 현실은 그렇지 않은 경우가 많다. 따라서 책임은 모델러에게 있더라도 개발자나 협업 실무자들의 도움을 받아 도출해야 한다.
[그림 1] 모델 단계별 주요 업무
개념 모델링 단계에서는 업무적 의미 전달을 위해 엔터티와 식별자, 주요 속성을 도출한다. 논리 모델링 단계에서는 개념 모델을 기반으로 모든 속성을 원자성을 고려해 정의한다. 이때 논리적인 데이터 타입과 길이, 식별자 확정, 필수 여부 확정, 값의 범위나 지정값 정의, 속성의 도메인 지정 등의 작업을 한다.
물리 모델링에서는 논리모델을 기반으로 변환·반정규화를 한다.
속성 도출 가이드
속성을 도출할 때는 다음 부분에 유의해야 한다.
· 속성의 의미를 명확히 한다.
· 속성이 하나의 사실만 갖는지 확인한다.
· 원시속성인지 확인한다.
· 속성에 적절한 명칭을 부여한다.
1) 속성의 의미 명확화
속성은 속성이 무엇을 저장할 것인지와 저장 값은 어떻게 해석해야 할까에 대한 질문을 통해 그 의미를 명확히 할 수 있다.
[그림 2] 주문내역의 속성
[그림 2]는 고객이 주문한 주문내역을 관리하는 엔터티다. 주문내역에는 주문번호, 고객번호, 주문금액, 주문상태, 우편번호, 주소 속성이 있다. 이 속성 중 주문금액은 어떤 데이터를 관리하는 속성일까
단순히 생각하면 말 그대로 ‘고객이 주문한 주문금액’이라고 할 수 있다. 조금 더 생각해 보면 주문 상품들의 총금액인지, 단일 상품에 대한 주문금액인지, 할인이 적용된 금액인지, 할인이 적용되기 전 금액인지 명확하지 않다.
속성을 도출할 때는 속성명도 중요하고 속성이 갖는 의미와 무엇을 저장하고 그 값을 어떻게 해석해야 하는지도 중요하다. 정확한 의미로 정의되지 않는다면, 잘못된 이해와 사용으로 집합의 의미가 변질되거나 최종결과 값이 안 맞는 등의 오류로 연결될 수 있다.
[그림 3] 주문내역의 누락, 중복 속성
속성에 정확하고 구체적인 의미를 부여해야 정확하게 의미를 전달할 수 있다. 속성정의가 명확하면 누락된 속성과 중복 속성도 발견할 수 있다.
[그림 3]을 보면, 주문금액은 주문 상품들 가격의 총합이며 할인 전 금액임을 알 수 있다. 할인금액은 다른 엔터티에서 관리하고 있으므로, 주문내역 엔터티에서 누락된 것은 결제금액, 즉 주문금액에서 할인금액을 제외한 고객이 최종 결제한 금액이다.
2) 속성이 하나의 사실만 갖는지 확인
속성은 관계형 데이터 모델에서 가장 작은 단위로서 하나의 사실만을 가져야 한다. 이것을 속성의 원자성이라고 한다. 하나의 속성이 한 개 이상의 사실을 가지면 안 되며, 이는 제1정규화 위반이다(제1정규화 도메인의 원자성). 또한 하나의 속성에 하나의 사실, 즉 팩트만 관리한다. 이때 팩트는 비즈니스 관점에서 봐야 한다.
[그림 4] 최소 단위의 예
우리가 잘 알고 있는 전화번호로 알아보자. 전화번호는 [그림 4]와 같이 최소 단위로 분해해 관리할 수 있고, 전화번호 전체를 하나의 속성으로 관리할 수도 있다. 어떤 것이 맞는 것일까 둘 다 맞다고 할 수 있다.
맞고 틀리고는 비즈니스 관점에서 봐야 한다. 전화사업자는 지역번호, 국번호, 개별번호 각각이 의미가 있으므로 최소 단위로 분리하여 관리할 것이다. 일반 기업에서 고객 전화번호를 관리하는 경우라면 하나의 속성으로 통합 관리하는 것이 일반적이다.
즉 비즈니스 요건에 따라 최소 단위로 분리해 관리해야 한다면, 최소 단위로 분리하는 것이 맞다. 무조건적인 물리적인 최소 분해는 잘못된 속성 도출로 연결될 수 있으므로 유의해야 한다.
속성의 원자성을 확인할 때는 다음과 같은 생각을 해보면 도움이 된다. 비즈니스 관점에서 하나 이상의 사실(fact)이 존재하는지, 속성의 일부만 변경되는 경우가 있는지, 속성의 일부 값에만 종속이 존재하는지, 속성의 일부만으로 정렬하는 경우가 있는지, 수식 연산 과정에서 복잡한 과정이 발생하는지를 살펴서 분리할지 합칠지를 결정하면 좋다.
3) 원시속성인지 확인
원시속성을 거론할 때 추출속성을 제시하면 쉽게 이해할 수 있다. 원시속성이란 다른 속성에 독립적으로 존재할 수 있는 원천적인 속성을 말한다. 추출속성이란 다른 속성을 기반으로 중복가공유도되는 속성을 말한다.
다음 예를 통해 알아보자.
[그림 5] 원시속성, 추출속성
[그림 5]는 고객과 고객의 결혼여부, 고객 중 회원의 접속내역을 관리하는 모델이다. 고객 엔터티의 결혼여부 속성을 보면 별 문제가 없어 보이지만 이는 추출속성이다. 고객이 결혼을 했는지 안 했는지는 고객결혼이력 엔터티의 데이터를 조회하면 알 수 있는데도 고객 엔터티에 결혼여부 속성을 중복으로 도출한 것이다.
두 엔터티의 데이터 발생에 따라서 속성 값의 변화를 생각해 보자. 고객이 처음 가입했을 때는 결혼을 하지 않아서 고객결혼이력에 데이터가 존재하지 않을 수 있다. 이때는 고객의 결혼여부는 아마도 N(미혼)으로 저장할 것이다. 이후에 고객이 결혼하게 되면 고객결혼이력에 데이터가 생성되므로 결혼여부가 Y(결혼)로 바뀌어야 한다. 즉 고객결혼이력에 의해 결혼여부가 종속적으로 바뀌는 것이다. 또 다른 측면으로 이야기해 보면, 고객결혼이력에 데이터가 생성되지 않고서도 결혼여부 속성의 데이터를 바꿀 수 있다.
이와 같은 예를 봤을 때, 추출속성은 데이터 정합성 훼손의 원인이 될 수 있다. 따라서 속성은 하나의 사실이 변경될 때, 하나의 원시속성이 변경되는 것이 이상적이다. 또한 추출속성을 사용하면 제2정규화(부분함수종속제거) 위반이 일어날 수 있다.
그런데 추출속성을 무조건 사용하지 않는 것은 아니다. 논리모델링을 할 때 성능 이슈 등으로 인해 추출속성을 도출해 사용하는 경우가 있다. 이때 추출속성의 유형은 여러 속성 중에서 하나를 선택(최초, 현재, 최종 등)하거나 여러 속성을 모아 그룹 연산(SUM, COUNT, AVG)하는 경우가 있다.
[그림 6] 추출속성
[그림 6]은 온라인 카페를 관리하는 모델이다. 온라인 카페 엔터티에 보면 카페회원수, 총게시물수, 금일방문자수, 최근7일방문자수가 추출속성이다. 이런 속성들은 다른 엔터티에서 조회하면 파악할 수 있다. 그렇더라도 화면을 열 때마다 총게시물수를 조회해 온다는 것은 매우 비효율적이고, 성능에 영향을 줄 수 있기에 추출속성을 사용하기도 한다. 따라서 추출속성은 실제로는 중복(redundancy)인 것은 맞지만 현실적으로 배제할 수 없기 때문에 꼭 필요한 곳에 적절히 사용해야 한다. 이때 언뜻 보기에는 추출속성 같은데 원시속성인 경우가 있으므로 주의해서 잘 살펴봐야 한다.
원시속성에 대해 소개한다 해놓고 추출속성에 대한 얘기만 했다. 추출속성에 대해 이해하면 원시속성은 자연스럽게 알 수 있다. 추출속성이 아니면 원시속성이니까 말이다.
논리 모델링을 할 때, 논리 단계에서 원시속성에 집중한다. 논리 단계라도 비즈니스 상 중요한 추출속성은 먼저 도출한다. 도출된 추출속성은 반듯이 원시속성과 관계를 정의해야 한다.
4) 속성에 적절한 명칭 부여
명칭에 대해 계속해서 거론했다. 사람에게 이름이 중요하 듯이 엔터티명이든 속성명이든 적절하고 명확하게 부여해야 한다.
[그림 7] 이상한 속성명
[그림 7] 고객 엔터티에 있는 속성 명칭을 보자. ID는 식별자이다. 그럼 고객구분과 고객유형은 무엇일까 SEQ는 sequence(일련번호)인 듯 한데 무엇을 의미하는 것일까 MAX구매액은 실제 구매액 중에서 가장 큰 값일까 아니면 최대로 구매 가능한 금액일까 등 많은 의문이 들 수 있다. 속성명에 속성정의가 있다면 그것을 보고 이해할 수 있을 것이다. 속성정의도 중요하지만 속성명도 명확하게 부여해야 한다.
속성에 명확한 이름을 붙이는 것은 곧 속성정의가 명확함을 의미한다. 따라서 꼭 속성정의와 일치되는 이름을 부여해야 한다.
속성에 이름을 부여할 때는 일반적으로 다음과 같은 기준을 따른다(이것은 모델러에 따라 데이터 표준에 다라 달라질 수 있다. 단지 필자가 주로 사용하는 방법 가운데 하나다).
[그림 8] 속성명 작성 가이드
속성명은 일반적으로 수식어+수식어+도메인으로 구성한다. 앞서 수식어는 WHAT으로 속성이 무엇이냐를 나타내며, 도메인은 분류를 나타낸다.
예를 들어 구매금액이라는 속성명은 구매라는 수식어와 금액이라는 도메인을 붙여서 만든 경우다(구매금액= 구매(수식어) + 금액(도메인)). 이때 수식어는 하나 이상일 수 있으며, 도메인은 속성이 어떤 분류유형인지를 표현한다.
다음의 속성명을 보고 부족한 부분이 있으면 좀 더 좋은 속성명으로 바꿔보자.
[그림 9] 속성명 바꾸기 예제 1
[그림 9]를 보면, 고객·상품·수량·가격·할인·일자라는 속성명이 적합하게 부여된 것 같지는 않다. 오해의 소지가 있다. 할인은 할인금액인지 할인율인지 애매하다. 따라서 다음과 같이 바꿔보면 좀 더 명확한 속성명이 될 것이다.
[그림 10] 속성명 바꾸기 예제 2
[그림 10]은 상품은 상품NO, 수량은 주문수량, 가격은 주문가격, 할인은 할인율, 일자는 구입일자로 속성명을 바꾼 것이다. 바꾼 속성명과 데이터를 보면 [그림 9]보다 명확하게 이해할 수 있다. 이와 같이 속성명은 해당 데이터를 명확하게 표현할 수 있는 명칭으로 부여해야 한다. 속성명 부여 시, 내가 정의한 명칭을 다른 사람이 나와 똑같이 이해할 것이라는 생각을 늘 염두에 둘 필요가 있다.
속성부여 사례
1) 원자분해 실패
원자분해 실패는 하나의 속성에 2개 이상의 사실(fact)이 결합된 경우를 말한다. [그림 11]의 주문수량 속성의 수량이 일반적으로 짐작할 수 있는 개수를 말하는 것인지, 아니면 다른 의미를 갖고 있는지 정확한 의미를 살펴봐야 한다.
[그림 11] 단위가 생략돼 원자분해에 실패한 사례
[그림 10]과 [그림 11]의 주문수량은 단위가 생략돼 있고, 2개 이상의 사실(fact)이 결합된 하나의 속성이다. 속성을 볼 때는 비즈니스 의미를 갖는 가장 작은 조각으로 분리됐는지, 속성의 일부(수량이나 단위)만 변경될 수 있는지, 속성의 일부만으로 정렬이 필요한지, 속성 값으로 수식 연산이나 비교가 용이한지 등을 잘 살펴봐서 [그림 11]의 주문수량과 같이 2개의 사실이 결합되지 않도록 해야 한다.
2) 다중 의미 코드 세트
다중 의미 코드 세트란 하나 이상의 코드(code)가 결합된 하나의 속성을 말한다.
[그림 12] 다중 의미 코드 속성 예
[그림 12] 사원구분코드에 S, M, T의 값이 들어가 있다. S는 우리사원이면서 기술직, M은 우리사원이면서 매니저, T는 협력사원이면서 기술직을 의미한다.
이렇게 속성 값은 우리사원 + 기술직 등 다중 의미 코드가 결합된 값을 쓰는 것을 지양해야 한다. 이렇게 사용하면 우리 사원만 조회하거나, 기술직만 조회하는 경우 SQL이 복잡해진다. 협력사원매니저라는 속성이 추가되면 새로운 값을 만들어 내야 하지만, 코드 인스턴스가 추가될 때마다 SQL 수정이 필요할 수 있다. 따라서 속성은 하나의 의미를 가져야 하기 때문에 [그림 12]의 경우는 속성을 2개로 나눠주는 것이 바람직하다.
인스턴스를 구분할 수 있는 속성의 조합
식별자란 여러 개의 데이터(인스턴스)에서 각각의 데이터(인스턴스)를 구분할 수 있는 속성의 조합을 말한다. 식별자는 엔터티 내의 개체들을 식별하는 중요한 역할을 수행한다. 릴레이션십 관점에서는 두 엔터티 간의 연결에 큰 영향을 미친다. 잘못된 식별자는 개체에 대한 접근(access)를 어렵게 하며, 데이터 정합성을 훼손시킬 수 있다. 또한 식별자의 영향은 자신의 엔터티에서만 끝나는 것이 아니다. 자신보다 하위에 있는 모든 자식(하위) 엔터티에 영향을 미치므로 잘 정의해야 한다.
쉬운 말로 식별자를 표현하면 엔터티의 개체(데이터인스턴스)를 부르는 이름이다. 이 이름에 해당하는 개체는 반드시 1개만 존재해야 한다. 대한민국 국민의 주민등록번호에 대응하는 사람은 1명이므로 주민등록번호가 대한민국 국민을 식별하는 식별자라고 할 수 있다.
주민등록번호가 앞 생년월일 6자리와 뒤 7자리로 구성돼 있듯이, 식별자는 하나 또는 하나 이상의 속성 조합으로 이뤄질 수 있다. 하나이든 하나 이상의 조합이든 조합된 결과는 유일(unique)해야 한다. 이때 식별자는 엔터티 개체(데이터인스턴스)에 대해 유일(unique) 하지만 유일하다고 해서 꼭 식별자가 되는 것은 아니다.
식별자는 개체(데이터인스턴스)의 이름이고, 정의할 때는 △나는 누구인가 △나를 무엇이라고 부르는가 이 두 가지 근본적인 질문에 답할 수 있어야 한다.
식별자는 엔터티 개체의 본질을 정의하며, 서비스 타입과 함께 엔터티를 구체적으로 규명하는 역할을 한다. 따라서 식별자는 엔터티 내에서 다른 개체들과 구별되는 ‘나’를 정의할 수 있는 속성을 선택해야 한다.
독자들의 식별자를 생각해 보자. 한국의 국민이면 위에서 소개한 주민등록번호가 될 것이다. 한국을 넘어 전 세계 속에서 누군가를 식별할 수 있는 식별자는 무엇이 될까 어떻게 식별자를 정해야 여러 국민들 중의 특정인을 유일하게 구분할 수 있을까 이에 대한 답은 독자 스스로 찾아보기 바란다.
[그림 1] 고객 엔터티
[그림 1]에서 고객 엔터티의 식별자는 고객번호다. 고객번호가 다름은 데이터 관점에서 다른 고객을 의미한다. 예를 들어 어떤 온라인 쇼핑몰에 내가 두 번 이상 등록돼 2개 이상의 고객번호를 갖고 있다고 하자. 내 입장에서는 고객번호를 여러 개 갖고 있어도 한 사람이지만, 데이터 관점에서는 고객번호가 다르므로 각각을 별도의 고객으로 인식할 수밖에 없다.
[그림 2] 고객 엔터티에서 주민등록번호가 식별자인 경우
이전에는 [그림 2]와 같이 고객의 식별자로 주민등록번호를 사용했다. 이렇게 모델링할 때는 고객의 주민등록번호를 사용하더라도 문제가 없다. 주민등록번호는 유일하며 바뀌지 않는다는 전제가 있었기 때문이다.
하지만 주민등록번호가 중복되는 경우가 나타났으며, 이로 인해 주민등록번호가 변경될 수 있다. 또한 최근에 개인정보보호 관련 법령으로 주민등록번호 등 개인을 구분할 수 있는 정보 사용이 제한되면서 주민등록번호 대신에 [그림 1]과 같이 별도 식별자를 만들어 사용하게 됐다.
이렇게 되면서 실제로는 동일인지만 데이터적으로 한 명 이상인 경우가 발생할 수 있다. 따라서 데이터의 정합성을 위해 데이터 모델링과 프로그램 측면에서 추가 노력을 해야만 한다. 이런 노력의 대표적인 사례가 회원가입 시 휴대폰 번호인증, I-PIN 인증을 통해 기존에 가입된 가입자인지를 확인하는 것이다.
식별자의 이름
식별자는 당연히 변하지 않는 속성으로 하는 것이 맞다. 또한 자주 사용하거나 부르기 쉬운 것을 선택하는 것이 좋다. 대한민국 국민의 식별자 후보는 여러 가지가 될 수 있다. 주민등록번호, 지문, DNA 등 유일하게 구분할 수 있는 속성이 여러 개 있다. 이중에 지문이나 DNA를 식별자로 쓰면 유일하게 구분할 수 있으므로 식별자가 될 수도 있지만 쉬게 부르기 어렵다. 사람을 유일하게 구분할 수 있는 식별자 후보 중에서 가능하면 자주 사용하거나 쉽게 부를 수 있는 주민등록번호 등의 속성을 선택하는 것이 좋다.
식별자는 SQL 개발시에 JOIN 등의 구문에서 자주 사용하므로 부르기 쉽고 사용하기 쉬운 이름을 사용하는 것이 유리하다.
주식별자와 부(보조)식별자
사람의 식별자를 주민등록번호로 하면, 주민등록번호를 주식별자라고 한다. 지문이나 DNA는 부(보조)식별자라고 한다.
주적으로 구현되면 PK(Primary Key)가 된다. 또한 엔터티를 대표하는 개체이름이 되며, Not Null이 된다.
부(보조)식별자는 주식별자와 마찬가지로 개체를 식별하는 용도로 사용할 수 있다. 주식별자로 커버할 수 없는 영역을 담당하기도 하며, 하나의 엔터티에 하나 이상이 존재할 수 있다. 물리적으로 구현되면 unique Key가 된다.
[그림 3] 주식별자와 부식별자 사용
[그림 3] 모델처럼 사원근태내역은 사원의 주식별자인 사원번호를 상속받았으며, 연금납부내역은 사원의 부식별자인 주민등록번호를 상속받았다. 이와 같이 부식별자는 다른 엔터티와의 관계에서 사용될 수 있으며, 부식별자도 식별자의 모든 역할이 가능하다.
본질식별자와 인조식별자
1)본질식별자
식별자는 본질식별자와 인조식별자로 구분하기도 한다. 본질식별자는 개체마다 고유하며 본질적으로 구분할 수 있는 것을 말한다. 사람의 지문 같은 것이다. 우리가 갖고 있는 주민등록번호는 태어난 후에 발급되므로 엄격하게는 본질식별자라기보다는 인조식별자로 봐야 한다. 그런데 한국 국민이면 누구나 주민등록번호를 출생 신고 시에 부여받으므로 본질식별자로 여기고 있을 뿐이다.
엔터티의 식별자를 정의함에 있어서 이 본질식별자를 찾아내고 그것을 식별자로 사용하는 것이 바람직하다. 본질식별자를 찾는 것은 엔터티를 정의하는 과정의 일부이며, 메인엔터티나 액션(action)엔터티인 경우는 더더욱 중요하다. 본질식별자를 찾으려는 노력을 하지 않고 무분별한 인조식별자의 사용은 지양해야 한다.
엔터티의 식별자로서 꼭 본질식별자를 사용하는 것은 아니다. 최종적으로 엔터티의 식별자를 정의할 때에 따라 본질식별자를 사용하는 경우도 있고, 인조식별자를 사용하는 경우도 있다. 어느 것이 더 효율적이냐는 관점에서 결정하게 된다. 따라서 최종 사용된 식별자는 업무적으로 효율적인 식별자이므로 업무식별자로 부르는 것도 타당하다고 볼 수 있다.
최종적으로 본질식별자를 사용하든 인조식별자를 사용하든 중요한 것은 본질식별자를 찾아냄으로써 엔터티의 본질적인 의미를 정의해야 한다는 점이다. 본질식별자는 엔터티의 정체성을 나타내는 것으로 이를 정의하는 것은 엔터티를 정의하는 과정의 일부다.
[그림 4] 본질식별자의 예
[그림 4] 모델의 통화내역 엔터티의 식별자를 보면 서비스계약의 서비스계약번호, 통화종류의 통화종류코드를 상속받고 본인의 통화시작일시(통합종류코드+서비스계약번호+통화시작일시)로 구성돼 있다. 이렇게 정의된 식별자는 엔터티의 정체성을 결정하고, 정의 과정에서 자연스럽게 부모 엔터티와 릴레이션십이 이뤄진다. 또한 하위(자식) 엔터티의 본질식별자는 내가 어디서 왔는가 즉 부모가 누구인지가 포함되며, 주로 누가무엇을언제라는 의미를 가진다.
2) 인조식별자
인조식별자는 가명 또는 별명이라고 할 수 있다. 인조식별자도 식별자이므로 식별자의 기본적인 원칙은 지켜져야 한다. 인조식별자를 쓰는 이유는 부르기 쉽고 관리하기 쉽게 하기 위해서다. 인조식별자는 식별속성 중에 인조 속성이 하나라도 포함된 것을 말하며, 자연(업무)적 의미가 없는 속성이다.
주민등록번호 앞 6자리 생년월일은 사람에 대한 본질적인 속성이라고 한다면, 뒤 숫자 7자리는 자연(업무)적 의미가 없는 속성이므로 인조식별자라고 할 수 있다. 뒤 7자리는 특별한 규칙에 의해 만들어졌으므로 본질적이라고 할 수 있겠지만, 숫자만 나열해 놓고 보면 특별한 본질적 의미를 갖지 않는다.
[그림 5] 본질식별자가 사용된 모델
[그림 5] 모델의 배송접수내역은 고객번호, 접수일시라는 본질식별자로 정의한 모델이다. 위와 같이 모델링하는 경우도 있겠지만, 시스템(애플리케이션)에서 일자로 구분하거나 그룹하는 경우가 많다면 접수일시를 잘라서 사용해야 한다. 이는 성능상의 문제가 발생할 수 있어서 다음처럼 하기도 한다.
[그림 6] 본질식별자와 인조식별자를 함께 사용한 모델
[그림 6] 모델은 고객번호, 접수일자와 접수일련번호라는 인조식별자로 정의해 위에서 소개한 일자별로 구분하거나 관리할 때 발생할 수 있는 성능 이슈를 해결했다. 이 모델에서 식별자 접수일련번호에는 자연(업무)적으로 의미가 없는 인조식별자가 포함됐으므로 배송접수내역의 식별자는 인조식별자라고 할 수 있다.
[그림 7] 인조식별자만 사용한 모델
인조식별자만으로 식별자의 역할을 충분히 할 수 있다. 따라서 [그림 7-6]과 본질식별자와 인조식별자를 합쳐서 식별자를 구성하는 것은 식별자로서 적절하지 않다.
[표 1] 본질식별자와 인조식별자 비교
[그림 7]과 같이 본질식별자인 접수일자와 고객번호는 일반속성으로 하고, 배송접수번호라는 인조속성 하나만으로 인조식별자를 정의하는 것이 더 좋은 접근이다.
인조식별자를 사용해야 할 때는 다음과 같다. △식별자를 정의할 때 본질식별자로 정의했으나 업무적 요건에 의해 인조식별자가 들어갈 때 △상속받은 본질식별자가 많아서 본인의 식별자가 너무 길어서 부르기 쉽고 편하게 관리해야 할 때 등이다.
인조식별자를 사용하는 것이 문제가 되지 않는다. 단 본질식별자를 모르는 상태에서 무조건 인조식별자를 사용하는 것은 지양해야 한다.
정규화
정규화(normalization)는 데이터 모델링의 핵심이자 꽃이다. 정규화의 목적은 관계형 DB 기반의 모든 프로젝트에서 중복 없는(no redundancy) 상태의 좋은 모델을 만들기 위해서다.
식별자와의 종속성, 즉 함수종속을 기반으로 유사한 속성들을 모으고 분리하는 형태로 정규화한다. 정규화를 잘하면 모델링도 잘할 수 있다는 말처럼, 정규화는 모델링에서 꼭 해야 하는 부분이다. ‘중복이 없는 상태’는 앞선 연재에서 여러 번 소개했으니 어느 정도 감을 잡았을 것이다.
함수종속(functional dependency)은 특별히 소개한 적도 없어서 다소 생소하게 여겨질 것이다. 함수종속이 나오는 이유는 코드(E.F.Codd) 박사가 수학의 집합 개념을 기반으로 데이터 모델링의 개념을 정립했기 때문이다.
함수종속이란 데이터의 종속성(data dependency)에 관한 것이다. 집합에는 집합을 대표하는 속성이 존재한다. 종속성이란 바로 이 대표 속성과 나머지 속성상의 연관 관계를 말한다.
한국 사람이라면 이름, 주민등록번호, 키, 몸무게, 성별, 핸드폰번호 등 많은 속성을 갖고 있다. 이 가운데 이름이나 주민등록번호가 한국인임을 구분해주는 어느 한 사람의 속성이다. 주민등록번호가 대표적인 속성으로, 특정인을 다른 사람과 구별해주는 역할을 한다. 나머지 속성들은 이 주민등록번호에 종속돼 있다.
이것을 다른 말로 바꿔보면, A 속성 값이 B 속성 값을 유일하게 식별할 수 있는 기준이라면 B 속성은 A 속성에 함수적으로 종속됐다고 한다.
본 연재에서 정규화의 기본 개념을 이해하고 자세한 사항은 김기창의 [관계형 데이터 모데링 프리미디어 가이드](위즈덤마인드 간) 등을 참고하여 심화학습하기 바란다.
정규화의 종류
정규화는 제1정규화, 제2정규화, 제3정규화, 보이스-코드정규화(BCNF), 제4정규화, 제5정규화 등이 있다. 여기서는 1정규화에서 3정규화까지는 쉽게 습득할 수 있으므로 제1정규화부터 제3정규화까지 알아본다. 본 연재에서 제5정규화 심지어 제6정규화까지도 소개할 수 있지만, 기본을 먼저 다지면 나머지를 학습하는 힘이 생길 것이다.
앞 연재에서 좋은 모델은 완전성(누락된 업무가 없음), 유일성(데이터 중복이 없음), 업무 적용성(모든 업무와 규약을 담고 있어야 함), 유연성(비즈니스 모델이 변경돼도 가능하면 모델이 변경되지 않아야 함)과 함께 단순하면서도 엘레강스해야 한다고 소개했다.
좋은 모델링을 위해 정규화를 해야 하며, 이를 통해 비즈니스와 데이터 성격에 맞는 엔터티가 도출된다. 비즈니스와 데이터 성격에 맞는 엔터티가 도출되면 확장성이 좋은 모델이 된다.
또한 정규화는 이상(anomaly) 현상을 최소화하는 작업이기도 하다. 데이터 중복은 이상현상을 발생시키는 데 이상현상이 없어야 데이터 품질을 높일 수 있다. 일부 전문가는 ‘정규화 단계를 포기하면 모델링을 수행했다고 할 수 없다. 모델러라면 반드시 정규화를 해야 한다’고 강조한다. 그만큼 정규화는 모델링에 있어서 중요한 부분이다.
그렇다면 1에서 3까지의 정규화, BCNF, 4에서 5까지의 정규화를 전부 해야 할까 3정규화까지만 하면 된다는 의견도 있고 안 해도 된다는 의견도 있다. 정규화는 업무 요건에 따라 함수종속성을 따져 데이터 중복을 제거하는 작업이므로 몇 정규화까지만 수행해야 하는지 논쟁은 의미가 없다. 필요하다면 5정규화가 아닌 그 이상이라도 해야 한다.
일반적으로 1에서 3정규화가 정규화의 대부분을 차지하므로 3정규화까지만 하면 된다는 의견도 있다. 하지만 BC정규화, 4정규화, 5정규화를 위반하는 경우도 종종 있으므로 나머지도 알고 있어야 한다.
이제부터 1부터 3정규화까지 알아보자. ‘정규화’라는 말과 ‘정규형’이라는 말이 있다. 정규화한 결과를 정규형(normal form)이라 한다.
정규화는 1, 2, 3 순서대로 하지 않는다. 설명하기 쉽게 제1정규화부터 이야기하는 것이지, 모델러들이 정규화할 때는 제1에서 제3, 심지어는 제5정규화까지 동시에 한다. 운전할 때와 비슷하다. 운전할 때, 브레이크에서 발을 떼는 순간 엑셀러레이터를 밟으면서 핸들을 조작하면서 앞뒤와 옆을 보듯이 모델링의 정규화도 정해진 순서에 따라 진행하지 않는다.
결정자와 종속자
정규화에 대한 이야기를 하기 전에 결정자(determinant)와 종속자(dependant) 대해 알아보고 가자. 이 이야기를 하는 이유는 혹 대학생 때 DB 관련 강의를 수강했거나 정보처리기사 시험을 봤던 사람이라면 ‘두부이겨다줘’라고 외웠던 기억을 갖고 있을 것이다.
각 정규화의 정의를 앞 글자만 따서 외울 때 쓰던 용어다. 제일 앞의 ‘두’는 원래 ‘도(도메인의 원행함수종속제거가 나온다.
함수와 종속제거라는 말이 나오는데, 이는 함수와 종속 개념을 정확히 알아야 정규화할 수 있다는 말이다. 우리가 흔히 쓰는 것이 아니므로 어렵게 느껴지겠지만, 너무 걱정하지 말고 하나씩 이해하고 넘어가면 된다. 결정자는 속성 간 종속성을 분석할 때 기준이 되는 값을 말한다. 이 결정자에 의해 정해질 수 있는 값을 종속자라고 한다.
속성 Y가 속성 X에 의해 함수적으로 종속됐다는 말은, 속성 X값을 이용해 속성 Y값을 유일하게 식별할 수 있다는 뜻이다. 앞서 소개한 여러분의 키와 주민등록번호를 보면, 키는 종속자, 주민등록번호는 결정자다. 다시 말하면 결정자는 식별자, 종속자는 속성을 의미한다.
이를 X→Y 또는 y=f(x)라고 표현한다.
[그림 1] 결정자와 종속자
결정자는 종속자의 값을 결정한다. [그림 1]의 주문금액 엔터티는 무엇으로 결정되는가를 보면 고객번호와 주문일시에 의해 결정되는 것을 알 수 있다. 성명은 고객번호에 의해 결정되는 것을 알 수 있다.
고객 엔터티에서 보면 고객번호가 결정자가 되며, 나머지 속성이 종속자가 된다. 이것을 반대로 말하면 식별자(결정자)로 데이터를 찾으면 딱 한 건이 나와야 한다고 할 수 있다. 만약에 2건이 나온다면 결정자가 아니다. 우리의 이름이 결정자가 될 수 없는 이유는 이 때문이다.
결국 결정자와 종속자는 앞서 소개한 식별자를 정의하는 것과 같다. 결정자는 식별자, 종속자는 속성이라고 생각하면 함수종속을 쉽게 이해할 수 있을 것이다.
정규화 실무
1) 제1정규화
제1정규화는 엔터티 안의 모든 속성은 반드시 1개 값을 가져야 한다는 뜻이다. 이를 도메인의 원자성이라고도 하는데, 속성이 하나의 값을 가져야 한다는 뜻이다. 원자성 또는 하나의 값을 가져야 한다는 뜻을 조금 더 알아보자.
제1정규화를 하려면 다가속성(multivalued attributes)과 복합속성(composite attribute)을 알아둘 필요가 있다.
[그림 2] 고객정보 정규형을 위반한 릴레이션: 다가속성
다가속성은 한 속성에 여러 값이 들어 있는 것을 말한다. [그림 2]를 보면 고객의 ‘취미’라는 하나의 속성에서 1개 이상의 값을 관리하는 것을 볼 수 있다. 현실에서는 이렇게 많이 관리하더라도 데이터 영역에서 좋지 않다. 이렇게 하나의 속성에 여러 값을 넣어서 관리하는 것이 제1정규화 위배의 다가속성에 해당한다.
데이터 모델링 관점에서 다가속성을 단일 값을 갖는 형태로 바꾸면 [그림 3]과 같다.
[그림 3] 제1정규화 릴레이션: 다가속성
[그림 3]은 [그림 2]의 제1정규화 위반인 다가속성을 해결한 한 것으로, 취미라는 속성에 하나의 값만 들어가도록 바꾼 것이다. 이것을 모델로 표현해 보면 [그림 4]와 같다.
[그림 4] 정규화 모델
[그림 4]의 왼쪽은 [그림 2]의 모델이고, 오른쪽은 [그림 2]를 정규화한 [그림 3] 모델이다. 이와 같이 제1정규화 위반 중 하나인 다가속성 문제는 [그림 4]와 같이 분리함으로써 해결할 수 있다.
여기서 주의할 점은 [그림 4]의 왼쪽 모델과 엔터티 안의 ‘취미’라는 속성을 보면 아무런 문제가 없어 보인다. 그리고 취미 속성 안에 같은 종류의 여러 값을 갖고 있음은 알 수 없으므로 모델만 보지 말고 데이터를 꼭 확인해 봐야 한다.
다가속성과 비슷한 경우가 논리적으로 여러 값을 갖는 속성이 존재하는 엔터티다.
[그림 5] 논리적으로 여러 값을 갖는 엔터티
[그림 5] 모델을 보면 여러 개의 취미를 관리하기 위해 취미1, 취미2, 취미3이라는 속성을 만들어 관리하고 있다.
이 경우도 특별히 문제가 없어 보인다. 만약 취미를 3개 이상 관리해야 한다면, 속성에 취미4를 추가함으로써 요건을 만족할 수 있다. 하지만 관리해야 하는 취미가 늘어난다면, 계속 속성을 추가하는 모델 변경이 일어나야 하기 때문에 좋다고 말하기는 어렵다. 같은 성격의 데이터를 여러 속성으로 나열해 관리하는 것도 논리적으로는 여러 값을 관리하는 것이므로 이런 경우 역시 제1정규화를 위배한 사례다. [그림 4]의 오른쪽 모델과 같이 제1정규화를 해주는 것이 좋다.
데이터 모델의 정규화 이론에서는 [그림 4]의 오른쪽 모델과 같이 정규화해서 정규형을 만드는 것이 바람직하지만, 비즈니스 요건이 변화하지 않고 성능까지 고려하면 좋은 모델이 될 수 있으므로 비정규형으로 모델링할 수도 있다.
여기서 꼭 명심해야 할 점은, 정규화를 알고서 비정규형을 사용해야 하는 것이다. 단지 편하니까, 다들 그렇게 하니까라는 기준에 따라 비정규형을 사용하는 것은 지양해야 한다.
다음으로는 복합속성(composite attributes)이다. 복합속성은 하나의 속성이 여러 속성으로 분리될 수 있음을 말한다. 대표적인 예가 주소나 전화번호다. 온라인 쇼핑몰에서 물건을 주문하고 배송지 주소를 입력할 때를 생각해 보자. 기본 주소와 상세 주소를 나눠 입력할 것이다. 특정 온라인 서비스에서 회원 가입 시, 휴대전화번호를 하나의 필드에 입력하는 경우도 있고 000-0000-0000 형태로 나누어 넣는 경우가 있다.
회원을 관리할 때 휴대전화번호를 하나의 필드, 즉 속성으로 관리하는 것이 좋을지, 통신망식별번호-국번호-가입자 개별번호로 나눠 관리하는 것이 좋을지 생각해 보자.
제1정규화의 사상은 도메인의 원자성이다. 원자성이란 하나의 속성에 하나의 값만 관리하는 것이라고 소개했다. 또한 더 이상 분해할 수 없어야 한다. 전화번호 전체를 하나의 속성에 관리하면, 더 이상 분해할 수 없는 원자성을 만족했다고 판단하기 어렵다.
복합속성은 업무의 정의에 따라 그 원자성의 기준이 달라질 수 있다. 업무적으로 전화번호를 하나의 속성으로 관리하는 것으로 정의했다면, 전화번호는 복합속성이 아닌 원자성을 만족했다고 할 수 있다. 즉 원자성에 대한 판단 기준은 업무정의이므로 모델이나 데이터만 보고 판단해서는 안 된다.
제1정규화를 해야 하는 대상을 정리하면 다음과 같다.
- 다가속성일 때
- 유사한 속성이 반복적으로 사용될 때
- 복합속성이 사용될 때
제1정규화 위배는 주로 프로세스와 화면 종속적인 모델링에서 발생한다. 즉 화면에 보이는 모습대로 모델링하거나 하나의 로우(row)로 처리하려는 욕심에서 발생한다.
제1정규화를 위배하면 데이터의 재사용성석과 집계), 효과적인 인덱스 구성의 어려움, 성능저하 가능성 증가, 정렬의 난이도 증가 등의 문제를 불러올 수 있다.
제1정규형을 위반한 경우, 즉 하나 이상의 값을 가진다면 다수의 값을 가질 수 있는 구조로 바꿔줌으로써 해결할 수 있다. 즉 해당 속성을 분리해 자식(하위) 엔터티를 생성해주면 된다.
2) 제2정규화
제2정규화는 부분 함수종속제거다.
모든 속성(식별자를 제외한 모든 속성)은 식별자에 완전 함수종속(fully functional dependency)이 돼야 한다. 일부 식별자에 종속(partial functional dependency)돼 있다면 제 2 정규형이 아니며 제2정규화를 위배한 것이다. 다시 말하면 제2정규화는 엔터티 내의 모든 속성은 식별자 전체에 직접 종속되게 하는 것이다.
완전함수종속, 부분함수종속, 부분함수종속제거 같은 말이 어렵지만, 제2정규화는 식별자 속성이 1개 이상인 경우에 발생한다. 식별자 이외의 일반속성이 어느 하나의 식별자 속성에만 속하는 경우를 제2정규화를 위반했다고 한다.
여기서 말하는 식별자는 의미상의 식별자들로서 본질식별자가 없다면 제2정규화 확인은 불가능하다. 인조식별자는 임의의 값이므로 종속성 확인이 불가능하다. 꼭 그런 것은 아니지만, 본질식별자의 도출 없이 인조식별자를 사용하면 제2정규화를 위반할 가능성이 매우 높다.
[그림 6] 제2정규화 위배 사례
[그림 6] 모델을 보면 식별자가 과목코드와 강사코드이며, 일반속성은 과목명·강사명·최초담당일자다.
위 모델을 자세히 보면, 과목과 강사 정보가 같이 존재하고 있음을 알 수 있다. 과목과 강사에 대한 정보와 속성들을 분류해볼 필요가 있다. 과목은 과목코드와 과목명으로, 강사는 강사코드와 강사명으로 구분할 수 있다. 이때 최초담당일자는 명확하지 않아 일단 제외하고 넘어가자.
이렇게 분류해보면, 과목명은 과목코드에만 종속돼 있고 강사코드와는 관계가 없다. 강사명은 강사코드에만 종속돼 있고, 과목코드와는 관계가 없음을 알 수 있다.
이와 같이 속성이 일부 식별자에 종속된 경우가 제2정규화를 위배한 것이다. 식별자 전체에 완전함수종속이 아닌 일부, 즉 부분함수종속이 발생한 것이다. 발생한 부분함수종속을 제거해주는 것이 제2정규화의 부분 함수종속제거다.
[그림 7] 제2정규화
[그림 6] 모델을 정규화하면 과목과 강사를 분리해, [그림 7]과 같이 과목 엔터티, 강사 엔터티, 과목별강사의 릴레이션 엔터티로 모델링할 수 있다. 이렇게 제2정규화를 하고 나니 [그림 6]의 최초담당일자도 명확해졌다.
제2정규화 위배는 본질식별자의 정의없이 인조식별자를 도출해 사용하거나, 부모의 속성을 자식이 갖고 있는 경우에 주로 발생한다. 제2정규화를 위배하면 데이터 값을 하나로 맞출 수 없어 정합성이 훼손되거나, 유지에 많은 리소스가 투입될 가능성이 있다.
3) 제3정규화
제3정규화는 이행함수종속 제거다. 제3정규화는 제2정규화를 하지 안고서는 불가능하다. 모델링에는 순서가 없기는 하지만 제3정규화를 위해서는 꼭 제2정규화가 돼야 한다.
모델링을 하고 보면, 어느 일반속성이 다른 일반속성에 종속되는 경우, 즉 일반속성 간에 종속성이 존재하는 경우가 있다. 이는 제3정규화를 위반한 것으로 이행함수종속이라고 한다.
이행함수종속 제거란 식별자가 아닌 모든 속성은 서로 종속되지 않도록 하는 것이다. 이것이 제3정규화다. 제3정규화는 일반속성 간에 종속 관계를 떼어내면 된다.
[그림 8] 제3정규화 위배 사례
[그림 8]을 보면, 모델과 표에서 배송업체명이 식별자인 고객번호나 주문일시에 종속되지 않고 일반속성인 배송업체코드에 종속돼 있다. 이 경우가 제3정규화를 위배한 사례로서 일반속성 간에 종속관계가 발생한 이행함수종속이다.
이때는 배송업체명을 고객주문내역에서 삭제하고, 배송업체코드를 식별자로 한 배송업체 엔터티를 만들어 고객주문내역과 릴레이션함으로써 해결할 수 있다.
[그림 9] 그림 8을 제3정규화한 모델
새로운 배송업체 엔터티는 고객주문내역 엔터티와 1:M 관계로 생성된다.
1·2·3정규화 이외에도 Boyce-Codd(엔터티 내에 여러 개의 후보 식별자는 존재하지 않아야 한다), 제4정규화(하나의 엔터티 내에 복수 개의 독립적인 다가속성이 존재할 수 없다) 등이 있다.
당연히 1·2·3정규화 이외의 정규화에 대해서도 알고 모델에 적용할 수 있어야 한다. 하지만 이 책에서는 언급하지 않으니, 모델링에 조금 더 익숙해 지면 꼭 따로 공부하기를 바란다.
이상으로 모델링의 기초 연재를 마친다. ‘시작이 반’이라는 말을 실감하는 기회가 됐으면 한다. 모델링에 대한 감을 제대로 잡고 꼭 심화학습에 도전하기를 기원한다.
'Biz > Modeling' 카테고리의 다른 글
Data Vault Modeling (2) | 2024.09.05 |
---|---|
최상운의 사선(死線)에서 : 현행 모델 분석 (0) | 2024.07.04 |
DA# 다중 물리모델 설계 (0) | 2016.07.02 |
DA# 4 표기법 (0) | 2016.06.25 |
DA# 3리버스 (0) | 2016.06.18 |
댓글