X → Y로 적고 "X를 알면 Y가 정해진다" 로 읽는다. 정규화는 이 관계를 따져 테이블을 쪼개는 작업이다.
정의는 간단하다
-
X → Y : X 값이 같으면 Y 값도 반드시 같다
-
회원ID → 회원명 — ✅ 같은 회원ID면 이름이 같다
-
회원명 → 회원ID — ❌ 동명이인이 있으면 성립하지 않는다
화살표 왼쪽 X를 결정자(determinant) 라 부른다. 정규화의 목표를 한 줄로 줄이면 이렇다.
모든 비키 속성이 기본키 전체에, 그리고 직접 의존하게 만든다.
"전체에"가 2NF, "직접"이 3NF다.
두 가지 나쁜 종속
[부분 종속] — 복합키의 '일부'에만 의존한다
주문상품(주문ID, 상품ID, 주문일, 상품명, 수량)
(주문ID, 상품ID) → 수량 ✅ 키 전체에 의존
주문ID → 주문일 ❌ 키의 일부에만
상품ID → 상품명 ❌ 키의 일부에만
[이행 종속] — 비키를 거쳐서 의존한다
회원(회원ID, 등급코드, 등급명)
회원ID → 등급코드 ✅
등급코드 → 등급명 ❌ 비키 → 비키
(회원ID → 등급코드 → 등급명 으로 '거쳐서' 의존)
어떻게 찾아내나 — 실제 데이터로 검증
-- '등급코드 → 등급명' 이 정말 성립하는가?
-- 같은 등급코드에 서로 다른 등급명이 있으면 종속이 깨진 것(=이미 모순 발생)
SELECT grade_code, count(DISTINCT grade_name) AS name_variants
FROM member GROUP BY grade_code HAVING count(DISTINCT grade_name) > 1;
-- 결과가 있으면 이미 갱신 이상이 터진 상태다
이 쿼리는 레거시 테이블을 정규화하기 전 실태 조사에 그대로 쓸 수 있다.
대리키를 쓰면 왜 편해지나
- 복합 기본키 (주문ID, 상품ID) — → 부분 종속이 생길 수 있다 → 2NF 를 따져야 한다
- 단일 대리키 id BIGINT — → 키가 하나뿐이라 '일부' 가 없다 → 2NF 자동 충족
실무에서 대리키(surrogate key)를 기본으로 쓰는 이유 중 하나다. 다만 자연키의 유일성 제약은 UNIQUE로 따로 걸어야 한다 — 대리키만 두면 중복 데이터가 들어온다.
CREATE TABLE order_item (
id BIGINT PRIMARY KEY, -- 대리키
order_id BIGINT NOT NULL,
product_id BIGINT NOT NULL,
UNIQUE (order_id, product_id) -- 자연키의 유일성은 별도로
);
면접 함정
- ❌ "함수 종속은 데이터를 보고 판단한다" → 현재 데이터에 중복이 없어도 업무 규칙상 성립하지 않으면 종속이 아니다. 동명이인이 지금 없어도
회원명 → 회원ID는 거짓이다. - ❌ "단일 PK면 정규화 고민이 없다" → 2NF만 자동이고, 3NF(이행 종속)는 여전히 따져야 한다.
이행 종속을 실제 스키마에서 찾아내기
-- '비키 → 비키' 후보를 찾는다: 한 칼럼 값이 다른 칼럼 값을 항상 결정하는가
SELECT count(*) AS violations FROM (
SELECT grade_code FROM member GROUP BY grade_code
HAVING count(DISTINCT grade_name) > 1
) t;
-- 0 이면 grade_code → grade_name 이 실제 데이터에서 성립한다
-- (성립한다는 건 그 종속이 존재한다는 뜻이고, 3NF 위반 후보라는 뜻이다)
여기서 헷갈리기 쉬운 점 — 종속이 성립하면 좋은 게 아니라 오히려 분리 대상이다. 비키끼리 종속이 있다는 건 그 사실이 별도 테이블에 속한다는 신호다.
결정자를 찾는 순서
- ① 각 칼럼에 대해 "이 값을 알면 무엇이 정해지는가" 를 묻는다
- ② 정해지는 것이 있고, 그 결정자가 키가 아니면 → 분리 후보
- ③ 업무 규칙으로 검증한다 (현재 데이터만으로 판단하지 않는다)