데이터 모델 종류와 릴레이션: 관계형 테이블의 중복·NULL 이해하기
1장. 같은 상품번호가 두 번 나오면 중복일까#
판매 보고서를 확인하던 담당자가 이상한 점을 발견했습니다.
주문ID 상품ID 수량
O101 P1 2
O102 P1 1상품 P1이 두 번 나타납니다.
담당자가 말합니다.
“P1이 중복됐으니 하나를 삭제해야 하는 것 아닌가요?”
겉으로만 보면 같은 값이 두 번 등장합니다.
하지만 두 행을 삭제하면 안 됩니다.
첫 번째 P1은 주문 O101에서 판매된 상품이고, 두 번째 P1은 주문 O102에서 판매된 상품입니다.
두 행은 같은 상품을 가리키지만 서로 다른 거래 사실을 표현합니다.
이 차이를 이해하지 못하면 데이터 정제 과정에서 정상 데이터를 삭제할 수 있습니다.
관계형 데이터베이스를 제대로 이해하려면 가장 먼저 다음 질문을 해야 합니다.
같은 값이 있는가?
보다 더 중요한 질문은 이것입니다.
한 행이 무엇을 의미하는가?
데이터 모델은 바로 이런 질문에 답하기 위한 방법입니다.
2장. 데이터 모델은 데이터를 바라보는 규칙이다#
**데이터 모델(Data Model)**은 데이터를 어떤 구조로 표현하고, 서로 어떻게 연결하며, 어떤 연산과 제약을 적용할 것인지 정의하는 개념과 규칙의 집합입니다.
쉽게 말하면 데이터베이스 세계를 바라보는 방식입니다.
같은 회원·주문·상품 데이터라도 어떤 데이터 모델을 사용하느냐에 따라 구조가 달라질 수 있습니다.
예를 들어 다음과 같은 업무가 있다고 하겠습니다.
회원 M01
├─ 주문 O101
│ ├─ 상품 P1
│ └─ 상품 P2
│
└─ 주문 O102
└─ 상품 P1이 데이터를 표현하는 방법은 하나만 있는 것이 아닙니다.
어떤 모델에서는 부모와 자식 관계로 표현할 수 있고, 어떤 모델에서는 여러 연결 경로를 사용할 수 있으며, 관계형 모델에서는 값을 이용해 서로 다른 테이블을 연결합니다.
데이터 모델이 달라지면 데이터를 저장하는 방법뿐 아니라 데이터를 찾아가는 사고방식도 달라집니다.
3장. 데이터 모델의 세 가지 핵심 요소#
데이터 모델은 크게 세 가지 관점으로 이해할 수 있습니다.
- 구조
- 연산
- 제약조건
이 세 가지를 함께 봐야 합니다.
3.1 구조 — 어떤 데이터가 존재하고 어떻게 연결되는가#
쇼핑몰이라면 다음과 같은 대상이 존재할 수 있습니다.
회원
주문
상품
주문상품그리고 다음과 같은 관계가 있습니다.
회원 1 : N 주문
주문 N : M 상품회원 한 명은 여러 주문을 할 수 있습니다.
하나의 주문에는 여러 상품이 들어갈 수 있고, 하나의 상품 역시 여러 주문에 포함될 수 있습니다.
이것이 데이터 구조입니다.
3.2 연산 — 데이터를 어떻게 읽고 변경할 것인가#
데이터는 저장하는 것만으로 끝나지 않습니다.
예를 들어 다음 작업이 필요합니다.
- 회원 주문 조회
- 특정 상품의 구매자 조회
- 주문 생성
- 주문 수량 변경
- 주문 취소
- 상품별 판매량 집계
이러한 작업을 어떻게 수행할 것인지가 데이터 모델의 연산에 해당합니다.
관계형 데이터베이스에서는 대표적으로 SQL을 사용합니다.
SELECT *
FROM orders
WHERE member_id = 'M01';3.3 제약조건 — 어떤 데이터를 허용하지 않을 것인가#
데이터베이스는 아무 값이나 받아들여서는 안 됩니다.
예를 들어 다음과 같은 규칙이 있을 수 있습니다.
존재하지 않는 회원은 주문할 수 없다.
주문 수량은 1 이상이어야 한다.
같은 주문ID는 중복될 수 없다.이러한 규칙이 제약조건입니다.
구조만 잘 만들어 놓았다고 해서 좋은 데이터 모델이 되는 것은 아닙니다.
잘못된 상태를 막을 수 있어야 합니다.
| 요소 | 핵심 질문 | 주문 예시 |
|---|---|---|
| 구조 | 어떤 대상과 관계가 있는가 | 회원·주문·상품 |
| 연산 | 데이터를 어떻게 조회·변경하는가 | 주문 조회, 주문 생성 |
| 제약 | 어떤 상태를 허용하지 않는가 | 없는 회원 주문 금지 |
데이터 모델을 이해할 때 이 세 가지를 항상 함께 생각하는 것이 좋습니다.
4장. 같은 데이터를 모델별로 표현하면#
데이터 모델은 역사적으로 여러 종류가 사용돼 왔습니다.
대표적인 모델을 살펴보겠습니다.
5장. 계층형 데이터 모델#
계층형 모델은 데이터를 부모와 자식 관계의 트리 구조로 표현합니다.
예를 들어 회원을 최상위에 두고 주문을 그 아래 배치할 수 있습니다.
회원 M01
├─ 주문 O101
└─ 주문 O102회원 한 명이 여러 주문을 가지고 있는 구조는 계층형 모델에 잘 맞습니다.
한 부모 아래 여러 자식이 있는 1:N 관계를 표현하기 쉽기 때문입니다.
5.1 문제가 되는 것은 N:M 관계다#
이번에는 상품까지 추가해 보겠습니다.
주문 O101
├─ 상품 P1
└─ 상품 P2
주문 O102
└─ 상품 P1상품 P1이 O101과 O102에 동시에 포함됩니다.
즉 주문과 상품은 N:M 관계입니다.
단순한 트리에서는 하나의 자식이 여러 부모에 동시에 직접 속하는 구조를 표현하기 어렵습니다.
따라서 상품을 여러 곳에 복사하거나 별도의 구조를 두어야 합니다.
여기서 복사 자체가 반드시 잘못이라는 뜻은 아닙니다.
문제는 복사된 값 가운데 어느 것이 원본이며 어떤 값을 변경해야 하는지 관리가 어려워질 수 있다는 점입니다.
계층형 모델은 탐색 경로가 명확한 업무에서는 직관적이지만 구조가 복잡해질수록 경로 의존성이 커질 수 있습니다.
6장. 네트워크형 데이터 모델#
네트워크형 데이터 모델은 계층형보다 복잡한 연결을 허용합니다.
하나의 레코드가 여러 관계에 참여할 수 있기 때문에 계층형보다 다양한 관계를 표현할 수 있습니다.
예를 들어 다음처럼 생각할 수 있습니다.
회원
↓
주문
↓
주문상품
↑
상품주문상품이라는 연결 데이터를 두면 주문과 상품의 관계를 표현할 수 있습니다.
상품 P1에서 주문상품을 따라가면 P1이 포함된 여러 주문을 찾을 수 있습니다.
계층형보다 유연하지만 응용 프로그램이 어떤 연결 경로를 따라가야 하는지 알아야 하는 경우가 많습니다.
따라서 데이터 구조가 바뀌면 프로그램의 탐색 경로도 영향을 받을 수 있습니다.
7장. 관계형 모델의 등장#
현대의 많은 업무 시스템에서 가장 널리 사용하는 방식이 관계형 데이터 모델입니다.
관계형 모델에서는 데이터를 릴레이션(Relation)이라는 구조로 표현합니다.
실무에서는 대부분 테이블 형태로 접합니다.
예를 들어 회원 테이블이 있습니다.
| 회원ID | 이름 |
|---|---|
| M01 | 김하늘 |
| M02 | 이바다 |
주문 테이블이 있습니다.
| 주문ID | 회원ID |
|---|---|
| O101 | M01 |
| O102 | M01 |
| O103 | M02 |
두 테이블 사이에 직접적인 레코드 포인터를 사용자가 지정하지 않습니다.
대신 값으로 연결합니다.
주문.회원ID = 회원.회원ID이 조건을 이용하면 주문과 회원을 결합할 수 있습니다.
SELECT
o.order_id,
m.name
FROM orders o
JOIN member m
ON m.member_id = o.member_id;관계형 모델의 중요한 특징 중 하나가 바로 이것입니다.
데이터 사이의 관계를 물리적인 위치가 아니라 값의 대응으로 표현합니다.
8장. 주문과 상품의 N:M 관계는 어떻게 표현할까#
이번에는 주문과 상품을 살펴보겠습니다.
주문 O101에는 P1과 P2가 들어 있습니다.
O101 → P1
O101 → P2그리고 P1은 다른 주문 O102에도 포함될 수 있습니다.
O102 → P1따라서 주문과 상품은 N:M 관계입니다.
관계형 데이터베이스에서는 보통 중간 테이블을 둡니다.
주문상품예를 들면 다음과 같습니다.
| 주문ID | 상품ID | 수량 |
|---|---|---|
| O101 | P1 | 2 |
| O101 | P2 | 1 |
| O102 | P1 | 1 |
여기서 매우 중요한 부분이 있습니다.
수량은 주문의 속성일까요?
아닙니다.
상품의 속성일까요?
그것도 아닙니다.
상품 P1의 수량은 O101에서는 2이고 O102에서는 1입니다.
따라서 수량은 주문과 상품이 만나는 관계에서 결정되는 값입니다.
즉 다음 조합이 수량을 결정합니다.
주문ID + 상품ID데이터를 어디에 저장해야 하는지 고민할 때 매우 유용한 질문이 있습니다.
이 값은 누구의 속성인가?
그리고 한 단계 더 나아가야 합니다.
어떤 값의 조합이 이 값을 결정하는가?
이 질문은 이후 함수 종속과 정규화를 배울 때도 매우 중요하게 사용됩니다.
9장. 릴레이션은 단순한 표가 아니다#
관계형 데이터베이스에서는 릴레이션을 흔히 테이블이라고 설명합니다.
학습 단계에서는 충분히 이해하기 쉬운 설명입니다.
하지만 정확히는 릴레이션과 SQL 테이블은 완전히 같은 개념이 아닙니다.
릴레이션은 수학적인 집합 개념에 기반합니다.
릴레이션에는 다음과 같은 요소가 있습니다.
- 속성
- 도메인
- 튜플
- 차수
- 카디널리티
이 용어를 하나씩 살펴보겠습니다.
10장. 속성과 튜플#
다음 회원 데이터가 있다고 하겠습니다.
| 회원ID | 이름 | 등급 |
|---|---|---|
| M01 | 김하늘 | 우수 |
| M02 | 이바다 | 일반 |
회원ID, 이름, 등급을 속성(Attribute)이라고 합니다.
테이블에서는 열에 해당합니다.
회원ID
이름
등급반면 한 행은 튜플(Tuple)이라고 합니다.
(M01, 김하늘, 우수)이 튜플 하나는 특정 회원에 대한 하나의 사실을 표현합니다.
SQL 테이블에서는 흔히 다음처럼 대응해 이해할 수 있습니다.
속성 ≈ 열
튜플 ≈ 행단, 이후 살펴보겠지만 SQL 테이블과 수학적 릴레이션에는 차이가 있습니다.
11장. 도메인은 단순히 자료형이 아니다#
도메인(Domain)은 하나의 속성이 가질 수 있는 값의 범위입니다.
예를 들어 회원 등급이 다음 세 가지만 허용된다고 하겠습니다.
일반
우수
VIP그러면 등급 속성의 업무 도메인은 다음처럼 생각할 수 있습니다.
{일반, 우수, VIP}여기서 매우 중요한 구분이 있습니다.
VARCHAR와 도메인은 같은 개념이 아닙니다.
예를 들어 다음과 같은 테이블이 있다고 하겠습니다.
status VARCHAR(20)자료형만 보면 다음 값도 저장할 수 있습니다.
접수
발송
취소
배송중
모름
TEST
ABC
아무거나문자열이기 때문입니다.
하지만 실제 주문 업무에서 허용하는 상태가 다음 세 개뿐일 수 있습니다.
접수
발송
취소이것이 업무적으로 허용하는 도메인입니다.
따라서 다음과 같은 제약조건을 둘 수 있습니다.
CHECK (status IN ('접수', '발송', '취소'))자료형은 값의 물리적 표현 범위를 결정하고, 업무 도메인은 업무적으로 의미 있는 허용 값의 범위를 결정합니다.
둘을 구분해야 합니다.
12장. 데카르트 곱으로 릴레이션 이해하기#
릴레이션을 수학적으로 표현하면 다음과 같습니다.
R ⊆ D1 × D2 × ... × Dn처음 보면 상당히 어렵게 느껴집니다.
하지만 의미는 단순합니다.
도메인이 두 개 있다고 해 보겠습니다.
회원등급:
{일반, 우수}회원상태:
{활성, 탈퇴}두 집합의 가능한 조합을 모두 만들면 다음과 같습니다.
(일반, 활성)
(일반, 탈퇴)
(우수, 활성)
(우수, 탈퇴)이렇게 가능한 모든 조합을 만드는 것을 데카르트 곱이라고 합니다.
하지만 실제 데이터베이스에 네 조합이 모두 존재할 필요는 없습니다.
현재 회원 데이터가 다음 두 행뿐일 수 있습니다.
(일반, 활성)
(우수, 활성)즉 릴레이션은 가능한 조합 전체 중 실제로 존재하는 일부 튜플의 집합입니다.
가능한 모든 조합
↓
업무 규칙에 따라
↓
실제로 존재하는 일부 조합
↓
릴레이션그래서 수식에서 R이 데카르트 곱의 부분집합이라고 표현합니다.
13장. 스키마와 인스턴스#
다음 정의를 보겠습니다.
회원상태(
회원ID,
상태
)이것은 데이터 구조를 정의합니다.
즉 스키마입니다.
실제 데이터는 다음과 같습니다.
| 회원ID | 상태 |
|---|---|
| M01 | 활성 |
| M02 | 휴면 |
이것은 특정 시점의 실제 데이터 상태입니다.
이를 인스턴스(Instance)라고 합니다.
다음 날 M02가 다시 활동을 시작했다면 데이터는 이렇게 바뀔 수 있습니다.
| 회원ID | 상태 |
|---|---|
| M01 | 활성 |
| M02 | 활성 |
인스턴스는 바뀌었습니다.
하지만 구조는 여전히 다음과 같습니다.
회원상태(
회원ID,
상태
)스키마는 그대로입니다.
정리하면 다음과 같습니다.
스키마 = 구조
인스턴스 = 현재 저장된 실제 데이터14장. 차수와 카디널리티#
릴레이션을 설명할 때 자주 등장하는 두 용어가 있습니다.
차수(Degree)와 카디널리티(Cardinality)입니다.
다음 테이블을 보겠습니다.
| 학번 | 이름 | 학과 | 평점 |
|---|---|---|---|
| S01 | 김하늘 | 컴퓨터공학 | 4.1 |
| S02 | 이바다 | 경영학 | 3.8 |
| S03 | 박구름 | 컴퓨터공학 | 3.7 |
열은 네 개입니다.
따라서 차수는 4입니다.
차수 = 속성의 개수행은 세 개입니다.
따라서 현재 카디널리티는 3입니다.
카디널리티 = 튜플의 개수정리하면 다음과 같습니다.
| 개념 | 의미 |
|---|---|
| 차수 | 속성의 수 |
| 카디널리티 | 튜플의 수 |
다만 실제 DBMS 문서에서는 카디널리티라는 용어를 서로 다른 값의 개수 등 다른 의미로 사용하는 경우도 있기 때문에 문맥을 확인해야 합니다.
15장. 수학적 릴레이션에는 중복이 없다#
여기서 중요한 차이가 등장합니다.
수학적 릴레이션은 집합입니다.
집합에는 같은 원소가 두 번 존재하지 않습니다.
예를 들어 다음 집합을 생각해 보겠습니다.
{P1, P1, P2}집합 관점에서는 사실상 다음과 같습니다.
{P1, P2}P1이 두 번 있다고 해서 다른 원소가 되는 것이 아니기 때문입니다.
수학적 릴레이션에서도 같은 튜플이 완전히 동일하게 두 번 존재하는 것은 의미가 없습니다.
하지만 실제 SQL에서는 상황이 다릅니다.
16장. SQL은 중복 행을 유지할 수 있다#
다음 주문상품 테이블을 보겠습니다.
| 주문ID | 상품ID |
|---|---|
| O101 | P1 |
| O102 | P1 |
| O103 | P2 |
다음 SQL을 실행합니다.
SELECT product_id
FROM order_item;결과는 다음처럼 나올 수 있습니다.
P1
P1
P2P1이 두 번 나타납니다.
SQL에서는 일반적으로 조회 결과가 중복을 자동으로 제거하지 않습니다.
이런 성질을 다중집합(Bag 또는 Multiset) 관점으로 설명하기도 합니다.
중복을 제거하려면 명시적으로 요청해야 합니다.
SELECT DISTINCT product_id
FROM order_item;결과는 다음과 같습니다.
P1
P2여기서 중요한 것은 어느 쪽이 더 올바른 결과냐가 아닙니다.
질문이 무엇인지가 중요합니다.
17장. 같은 데이터라도 질문에 따라 중복의 의미가 달라진다#
다음 판매 데이터가 있다고 하겠습니다.
| 주문ID | 상품ID |
|---|---|
| O101 | P1 |
| O102 | P1 |
| O103 | P2 |
“어떤 상품이 판매됐는가?”라고 묻는다면 다음 결과가 필요할 수 있습니다.
P1
P2즉 상품 종류가 궁금하므로 DISTINCT가 적합합니다.
SELECT DISTINCT product_id
FROM order_item;하지만 다음 질문이라면 어떨까요?
상품이 몇 건 판매되었는가?
이때 P1 두 건을 하나로 줄여 버리면 정보가 사라집니다.
O101에서 P1 판매
O102에서 P1 판매두 번의 판매는 서로 다른 사건입니다.
따라서 중복 제거를 하면 안 됩니다.
데이터 분석에서 가장 위험한 실수 가운데 하나가 바로 이것입니다.
같은 값이 반복됐으니 제거한다.
중복 여부는 단순히 값이 같다는 사실만으로 판단할 수 없습니다.
그 행이 표현하는 사실의 단위가 무엇인지 확인해야 합니다.
18장. 한 행은 무엇을 의미하는가#
데이터베이스 테이블을 새로 받았을 때 가장 먼저 해야 할 일은 SQL을 실행하는 것이 아닙니다.
한 행을 말로 설명해 보는 것이 좋습니다.
예를 들어 회원 테이블이라면:
한 행은 회원 한 명을 나타낸다.
주문 테이블이라면:
한 행은 주문 한 건을 나타낸다.
주문상품 테이블이라면:
한 행은 하나의 주문 안에 포함된 하나의 상품을 나타낸다.
이것을 흔히 **행의 입도(Grain)**라고 생각할 수 있습니다.
행의 의미가 명확해지면 중복 판단도 쉬워집니다.
예를 들어 다음 두 행이 있습니다.
O101 | P1
O102 | P1상품ID만 보면 P1이 중복입니다.
하지만 행의 단위가 주문 상품이라면 서로 다른 행입니다.
반면 다음 두 행이 있다면 어떨까요?
O101 | P1
O101 | P1그리고 업무 규칙상 같은 주문에서 같은 상품은 하나의 행에 수량으로 관리하도록 정했다면 이 경우에는 진짜 중복일 가능성이 있습니다.
O101 | P1 | 수량 2로 관리해야 할 수도 있습니다.
즉 중복은 컬럼 하나만 보고 판단하는 것이 아닙니다.
행 전체의 의미와 키를 확인해야 합니다.
19장. 관계형 모델에서 행의 순서는 의미가 없다#
릴레이션은 집합이므로 튜플의 순서는 본질적인 의미를 갖지 않습니다.
SQL에서도 ORDER BY를 지정하지 않으면 결과 순서를 보장해서는 안 됩니다.
다음 SQL을 실행했다고 하겠습니다.
SELECT product_id
FROM order_item;오늘은 이렇게 나왔다고 가정합니다.
P1
P1
P2다음 실행에서는 다음처럼 나올 수도 있습니다.
P2
P1
P1데이터의 논리적인 결과는 같습니다.
특정 순서가 필요하다면 반드시 정렬 조건을 명시해야 합니다.
SELECT product_id
FROM order_item
ORDER BY product_id;실무에서 의외로 자주 발생하는 버그가 이것입니다.
개발 환경에서 우연히 일정한 순서로 나왔다고 해서 그 순서를 데이터베이스가 보장한다고 생각하는 것입니다.
인덱스가 바뀌거나 실행 계획이 달라지면 순서가 바뀔 수 있습니다.
20장. 관계형 모델의 원자값은 무엇을 의미할까#
관계형 모델을 설명할 때 다음과 같은 표현을 자주 봅니다.
하나의 속성에는 하나의 값이 들어가야 한다.
이를 원자값이라는 개념으로 설명하기도 합니다.
예를 들어 상품ID 열에 다음처럼 여러 상품을 문자열 하나로 넣는 구조를 생각해 보겠습니다.
O101 | P1,P2,P3처음에는 편해 보입니다.
하지만 곧 문제가 생깁니다.
P2가 포함된 주문을 어떻게 검색해야 할까요?
상품별 수량은 어디에 저장할까요?
상품 P2를 삭제하거나 변경하면 문자열을 직접 파싱해야 합니다.
외래키도 걸기 어렵습니다.
그래서 일반적인 관계형 설계에서는 다음처럼 별도의 행으로 표현합니다.
| 주문ID | 상품ID |
|---|---|
| O101 | P1 |
| O101 | P2 |
| O101 | P3 |
여기서 중요한 것은 무조건 문자열에 쉼표가 들어가면 안 된다는 식의 단순한 규칙이 아닙니다.
해당 속성의 도메인에서 하나의 값으로 취급할 수 있는 단위가 무엇인지가 중요합니다.
예를 들어 전체 주소를 하나의 문자열 값으로 관리하는 것이 항상 잘못인 것은 아닙니다.
업무에서 도로명, 건물번호, 상세주소 등을 별도로 검색하고 처리해야 하는지에 따라 모델링 방법이 달라질 수 있습니다.
21장. SQL의 NULL은 수학적 릴레이션을 더 복잡하게 만든다#
실제 SQL에는 또 하나의 중요한 요소가 있습니다.
NULL입니다.
NULL은 일반적인 값과 다르게 취급됩니다.
예를 들어 회원의 전화번호가 아직 입력되지 않았다고 하겠습니다.
| 회원ID | 이름 | 전화번호 |
|---|---|---|
| M01 | 김하늘 | 010-1234-5678 |
| M02 | 이바다 | NULL |
M02의 전화번호가 NULL이라는 것은 보통 다음과 같은 상황을 표현할 수 있습니다.
- 아직 모름
- 입력되지 않음
- 적용되지 않음
NULL을 단순히 빈 문자열과 같은 값으로 생각하면 안 됩니다.
NULL ≠ ''그리고 SQL에서는 다음 조건이 원하는 결과를 주지 않습니다.
WHERE phone = NULLNULL 여부를 확인하려면 다음처럼 사용합니다.
WHERE phone IS NULL왜 이렇게 다를까요?
NULL은 일반적인 값처럼 비교되지 않기 때문입니다.
SQL에서는 NULL이 포함된 비교에 UNKNOWN이라는 상태가 등장합니다.
22장. SQL의 3값 논리#
일반적인 논리에서는 다음 두 값만 생각합니다.
TRUE
FALSE하지만 SQL에서는 NULL 때문에 다음 상태가 추가됩니다.
TRUE
FALSE
UNKNOWN예를 들어 전화번호가 NULL일 때 다음 비교를 생각해 보겠습니다.
phone = '010-1234-5678'전화번호가 무엇인지 알 수 없으므로 참인지 거짓인지 확정할 수 없습니다.
결과는 UNKNOWN이 됩니다.
WHERE 절에서는 TRUE인 행만 선택됩니다.
따라서 NULL 처리에 대한 이해가 없으면 예상과 다른 결과가 나올 수 있습니다.
이것이 수학적인 관계형 모델과 실제 SQL 구현을 완전히 동일한 것으로 보면 안 되는 이유 가운데 하나입니다.
23장. NULL은 아무 의미 없이 사용하면 위험하다#
NULL은 편리하지만 의미가 모호해질 수 있습니다.
예를 들어 배송일이 NULL이라고 하겠습니다.
이것이 무엇을 의미할까요?
아직 배송되지 않음
배송일을 모름
배송 대상이 아님세 상황은 전혀 다릅니다.
그런데 모두 NULL 하나로 표현하면 나중에 의미를 구분하기 어려워집니다.
따라서 NULL을 사용할 때는 다음을 명확히 해야 합니다.
NULL이 정확히 어떤 상태를 의미하는가?
데이터 설계에서는 값뿐 아니라 값이 없는 상태의 의미도 정의해야 합니다.
24장. 같은 상품 P1이 여러 번 나오는 이유#
이제 처음 사례로 돌아가겠습니다.
| 주문ID | 상품ID | 수량 |
|---|---|---|
| O101 | P1 | 2 |
| O102 | P1 | 1 |
| O103 | P2 | 1 |
P1이 두 번 등장합니다.
상품 카탈로그라면 P1이 두 행 존재하는 것은 이상할 수 있습니다.
예를 들어:
| 상품ID | 상품명 |
|---|---|
| P1 | 키보드 |
| P1 | 키보드 |
상품ID가 기본키라면 허용해서는 안 되는 중복입니다.
하지만 판매 내역에서 P1이 여러 번 등장하는 것은 자연스럽습니다.
O101에서 판매됨
O102에서도 판매됨즉 동일한 값의 반복이라도 테이블이 무엇을 표현하는지에 따라 의미가 달라집니다.
| 데이터 | P1 반복의 의미 |
|---|---|
| 상품 마스터 | 오류일 가능성이 큼 |
| 주문상품 | 여러 판매 기록일 수 있음 |
| 판매 집계 | 집계 기준에 따라 정상 |
| 상품 목록 | 중복 제거가 필요할 수 있음 |
중복은 절대적인 개념이 아니라 데이터의 의미와 키를 기준으로 판단하는 문제입니다.
25장. 학생 수와 수강 건수를 혼동하는 문제#
이 문제는 실제 분석에서도 자주 발생합니다.
학생 테이블이 있습니다.
| 학생ID | 이름 |
|---|---|
| S01 | 김하늘 |
| S02 | 이바다 |
수강 테이블이 있습니다.
| 학생ID | 과목 |
|---|---|
| S01 | 데이터베이스 |
| S01 | 알고리즘 |
| S02 | 데이터베이스 |
두 테이블을 조인하면 다음과 같습니다.
| 학생ID | 이름 | 과목 |
|---|---|---|
| S01 | 김하늘 | 데이터베이스 |
| S01 | 김하늘 | 알고리즘 |
| S02 | 이바다 | 데이터베이스 |
행은 세 개입니다.
그렇다면 학생은 세 명일까요?
아닙니다.
학생은 두 명입니다.
세 행은 수강 기록 세 건입니다.
학생 수를 구하려면 다음처럼 해야 합니다.
SELECT COUNT(DISTINCT student_id)
FROM enrollment;결과는 2입니다.
반면 수강 건수를 알고 싶다면 다음을 사용할 수 있습니다.
SELECT COUNT(*)
FROM enrollment;결과는 3입니다.
같은 테이블에서 숫자가 다르게 나오는 이유는 무엇을 세고 있는지가 다르기 때문입니다.
데이터 분석에서는 항상 먼저 질문해야 합니다.
무엇의 개수를 구하고 있는가?
26장. 관계형 모델은 표를 그리는 기술이 아니다#
관계형 데이터베이스를 처음 배우면 테이블을 만드는 것이 핵심처럼 보입니다.
하지만 진짜 핵심은 표의 모양이 아닙니다.
관계형 모델은 다음을 명확하게 정의하는 방법입니다.
무엇을 하나의 사실로 볼 것인가
어떤 값이 그 사실을 식별하는가
어떤 값이 서로 연결되는가
어떤 값이 허용되는가
어떤 중복은 정상이고 어떤 중복은 오류인가즉 관계형 모델링은 현실의 사건과 대상을 값과 관계로 표현하는 작업입니다.
27장. 객체지향형과 객체관계형 모델#
관계형 모델 외에도 다른 데이터 모델이 존재합니다.
객체지향형 데이터 모델#
객체지향 데이터 모델은 객체 개념을 데이터 저장에도 적용합니다.
다음과 같은 요소를 사용할 수 있습니다.
- 객체 식별자
- 속성
- 클래스
- 상속
- 객체 참조
예를 들어 주문 객체가 회원 객체를 직접 참조하는 형태로 표현할 수 있습니다.
Order O101
└─ Member M01복잡한 객체 구조를 그대로 유지해야 하는 특정 업무에서 활용할 수 있습니다.
다만 제품마다 구현과 질의 방식이 다르기 때문에 객체형이라는 이름만 보고 동일한 기능을 기대해서는 안 됩니다.
객체관계형 데이터 모델#
객체관계형 모델은 관계형 모델을 기반으로 객체적인 기능을 추가한 형태입니다.
DBMS에 따라 다음 기능을 지원할 수 있습니다.
- 복합 타입
- 배열
- 사용자 정의 타입
- JSON
- 다양한 확장 자료형
즉 기본적인 테이블과 SQL 구조를 유지하면서 복잡한 값을 다룰 수 있도록 확장합니다.
28장. 데이터 모델별 차이 정리#
| 모델 | 주요 구조 | 연결 방식 | 특징 |
|---|---|---|---|
| 계층형 | 트리 | 부모·자식 경로 | 1:N 구조에 직관적 |
| 네트워크형 | 연결망 | 여러 연결 경로 | 복수 관계 표현 가능 |
| 관계형 | 릴레이션 | 값의 일치 | SQL과 키·무결성 중심 |
| 객체지향형 | 객체 | 객체 참조 | 복잡한 객체 구조 표현 |
| 객체관계형 | 관계형 + 확장 타입 | 값 + 확장 구조 | 관계형과 복합 데이터 결합 |
현대 시스템에서는 하나의 방식만 사용하지 않는 경우도 많습니다.
관계형 데이터베이스와 문서 데이터베이스, 검색엔진, 그래프 데이터베이스 등을 함께 사용하는 경우도 있습니다.
따라서 데이터 모델은 우열을 가르는 문제보다 업무의 구조와 접근 패턴에 맞는 표현 방법을 선택하는 문제로 보는 것이 좋습니다.
29장. 새 테이블을 보면 가장 먼저 해야 하는 질문#
처음 보는 테이블을 받았다고 생각해 보겠습니다.
많은 사람이 가장 먼저 컬럼 이름을 확인합니다.
물론 중요합니다.
하지만 그보다 먼저 해야 할 질문이 있습니다.
한 행은 무엇을 의미하는가?
예를 들어 다음과 같이 정의합니다.
회원 테이블
→ 한 행은 회원 한 명
주문 테이블
→ 한 행은 주문 한 건
주문상품 테이블
→ 한 행은 하나의 주문에 포함된 하나의 상품
결제이력 테이블
→ 한 행은 결제 시도 한 건이 정의가 명확하면 다음 질문에 답하기 쉬워집니다.
- 기본키는 무엇인가
- 중복은 무엇인가
- 수량을 어디에 저장해야 하는가
- 외래키는 어디에 필요한가
- 어떤 단위로 집계해야 하는가
좋은 데이터 모델은 결국 한 행의 의미가 명확한 모델입니다.
30장. 핵심 정리#
데이터 모델은 데이터를 어떤 구조로 표현하고, 어떻게 연산하며, 어떤 상태를 허용할지 정의하는 규칙의 집합입니다.
대표적인 데이터 모델에는 다음이 있습니다.
- 계층형 모델
- 네트워크형 모델
- 관계형 모델
- 객체지향형 모델
- 객체관계형 모델
관계형 모델에서는 데이터를 릴레이션으로 표현합니다.
릴레이션의 주요 요소는 다음과 같습니다.
- 속성: 열에 해당하는 데이터 항목
- 튜플: 하나의 행에 해당하는 값의 조합
- 도메인: 속성이 가질 수 있는 값의 범위
- 차수: 속성의 수
- 카디널리티: 현재 튜플의 수
- 스키마: 데이터 구조와 제약의 정의
- 인스턴스: 특정 시점의 실제 데이터
수학적 릴레이션은 집합이므로 동일한 튜플의 중복을 허용하지 않습니다.
하지만 실제 SQL에서는 중복 행이 존재할 수 있으며 SELECT 결과 역시 기본적으로 중복을 유지할 수 있습니다.
중복을 제거하려면 다음처럼 명시합니다.
SELECT DISTINCT ...또한 SQL에서는 NULL을 사용하기 때문에 참과 거짓뿐 아니라 UNKNOWN이 포함되는 3값 논리가 사용됩니다.
따라서 순수한 관계형 이론과 실제 SQL 구현은 구분해서 이해해야 합니다.
마지막으로 가장 중요한 것은 이것입니다.
같은 값이 반복됐다는 이유만으로 중복 데이터라고 판단해서는 안 된다. 먼저 한 행이 어떤 사실을 나타내는지 확인해야 한다.
상품 마스터에서 상품ID P1이 두 번 존재한다면 잘못된 중복일 수 있습니다.
하지만 서로 다른 두 주문에 상품 P1이 포함되어 있다면 두 행 모두 필요한 거래 기록입니다.
관계형 데이터베이스를 제대로 읽는 출발점은 테이블의 모양이 아니라 한 행의 의미와 그 행을 구별하는 기준을 이해하는 것입니다.