제1정규형 ~ 제3정규형
정규화는 단계별로 진행됩니다. 각 단계에서 어떤 문제를 해결하는지, 실제 테이블이 어떻게 분해되는지를 예제로 확인합니다. 정규화의 각 단계는 특정 유형의 함수 종속을 제거하여 갱신 이상(Anomaly)을 방지합니다.
함수 종속(Functional Dependency) 복습
정규화를 이해하려면 먼저 함수 종속 개념이 명확해야 합니다.
X → Y (Y는 X에 함수 종속)
의미: X 값이 같으면 Y 값도 반드시 같다.
예시
학번 → 이름 (학번이 같으면 이름이 같다) ✓
학번 → 수강과목 (학번이 같아도 여러 과목 수강) ✗
함수 종속의 유형
┌──────────────────────────────────────────────────┐
│ 완전 함수 종속 (Full FD) │
│ {학번, 과목명} → 성적 │
│ → 학번만으로는 성적을 결정할 수 없고, │
│ 학번+과목명 둘 다 있어야 성적이 결정됨 │
│ │
│ 부분 함수 종속 (Partial FD) │
│ {학번, 과목명} → 이름 │
│ → 학번만으로 이름이 결정됨 │
│ → 기본키의 일부에만 종속 = 부분 함수 종속 │
│ │
│ 이행 함수 종속 (Transitive FD) │
│ 학번 → 학과, 학과 → 학과장 │
│ → 학번 → 학과장 (학과를 거쳐서 종속) │
│ → 키가 아닌 속성을 통한 간접 종속 │
└──────────────────────────────────────────────────┘제1정규형 (1NF)
1NF의 조건: 모든 속성의 값이 원자값(Atomic Value)이어야 합니다. 하나의 셀에 여러 값이 들어가면 안 됩니다. 또한 반복 그룹(Repeating Group)이 없어야 합니다.
위반
┌────────┬────────┬──────────────────┐
│ 학번 │ 이름 │ 수강과목 │
├────────┼────────┼──────────────────┤
│ 1001 │ 김철수 │ DB, 알고리즘 │ ← 하나의 셀에 2개 값!
│ 1002 │ 이영희 │ DB │
└────────┴────────┴──────────────────┘
문제
* 특정 과목 수강자 검색이 어려움
WHERE 수강과목 = 'DB' → 1001의 'DB, 알고리즘'은 매칭 안 됨
* 과목 추가/삭제 시 문자열 파싱 필요
* 과목 수 증가 시 셀 크기 무한 확장위반
┌────────┬────────┬────────┬────────┬────────┐
│ 학번 │ 이름 │ 과목1 │ 과목2 │ 과목3 │
├────────┼────────┼────────┼────────┼────────┤
│ 1001 │ 김철수 │ DB │ 알고 │ NULL │
│ 1002 │ 이영희 │ DB │ NULL │ NULL │
└────────┴────────┴────────┴────────┴────────┘
문제
* 최대 과목 수가 컬럼 수에 제한됨
* NULL이 많아짐
* 과목 검색 시 모든 과목 컬럼을 확인해야 함
WHERE 과목1='DB' OR 과목2='DB' OR 과목3='DB'1NF 적용
┌────────┬────────┬──────────┐
│ 학번 │ 이름 │ 수강과목 │
├────────┼────────┼──────────┤
│ 1001 │ 김철수 │ DB │
│ 1001 │ 김철수 │ 알고리즘 │
│ 1002 │ 이영희 │ DB │
└────────┴────────┴──────────┘
* 각 셀에 하나의 값만 존재
* 기본키가 {학번, 수강과목}의 복합키로 변경
* WHERE 수강과목 = 'DB' → 정확한 검색 가능1NF를 적용하면 행이 늘어나지만, 각 셀에 하나의 값만 존재합니다. 이것이 관계형 모델의 가장 기본적인 제약입니다.
1NF와 실무
실무에서 1NF 위반은 의외로 흔합니다. JSON 컬럼, 콤마 구분 문자열 등이 대표적입니다.
사례 1: 태그를 콤마로 저장
┌─────┬────────────────────┐
│ id │ tags │
├─────┼────────────────────┤
│ 1 │ "react,nextjs,ts" │
│ 2 │ "python,django" │
└─────┴────────────────────┘
→ 특정 태그 검색: LIKE '%react%' (비효율, 부정확)
1NF 적용
posts (id, title, ...)
post_tags (post_id, tag_name) ← 별도 테이블
사례 2: JSON 컬럼에 배열 저장 (NoSQL적 사용)
addresses JSON = '[{"city":"서울"}, {"city":"부산"}]'
→ RDB에서는 별도 테이블로 분리가 원칙
→ 단, PostgreSQL/MySQL JSON 타입은 인덱싱 가능제2정규형 (2NF)
2NF의 조건: 1NF를 만족하면서, 부분 함수 종속이 없어야 합니다.
부분 함수 종속이란 복합 기본키의 일부에만 종속되는 속성이 있는 것입니다. 기본키가 단일 컬럼이면 부분 함수 종속이 발생할 수 없으므로 자동으로 2NF를 만족합니다.
수강 테이블 (PK: {학번, 과목명})
┌────────┬──────────┬────────┬────────┬────────┐
│ 학번 │ 과목명 │ 성적 │ 이름 │ 학과 │
├────────┼──────────┼────────┼────────┼────────┤
│ 1001 │ DB │ A │ 김철수 │ 컴공 │
│ 1001 │ 알고리즘 │ B+ │ 김철수 │ 컴공 │
│ 1002 │ DB │ A+ │ 이영희 │ 전자 │
└────────┴──────────┴────────┴────────┴────────┘
종속 관계 분석
{학번, 과목명} → 성적 (완전 함수 종속 ✓)
학번 → 이름 (부분 함수 종속 ✗) ← 문제!
학번 → 학과 (부분 함수 종속 ✗) ← 문제!삽입 이상 (Insert Anomaly)
* 아직 수강 과목이 없는 신입생 '박민수'를 등록할 수 없음
* 기본키의 일부인 '과목명'이 NULL이면 PK 위반
갱신 이상 (Update Anomaly)
* 김철수의 학과를 '소프트웨어'로 변경하려면
* 1001/DB 행과 1001/알고리즘 행 두 곳을 모두 수정
* 하나만 수정하면 데이터 불일치!
삭제 이상 (Delete Anomaly)
* 이영희가 DB를 수강 취소하면 해당 행 삭제
* 이영희의 이름, 학과 정보도 함께 소실!학생 테이블 (PK: 학번)
┌────────┬────────┬────────┐
│ 학번 │ 이름 │ 학과 │
├────────┼────────┼────────┤
│ 1001 │ 김철수 │ 컴공 │
│ 1002 │ 이영희 │ 전자 │
└────────┴────────┴────────┘
수강 테이블 (PK: {학번, 과목명})
┌────────┬──────────┬────────┐
│ 학번 │ 과목명 │ 성적 │
├────────┼──────────┼────────┤
│ 1001 │ DB │ A │
│ 1001 │ 알고리즘 │ B+ │
│ 1002 │ DB │ A+ │
└────────┴──────────┴────────┘
이상 현상 해소
* 삽입: 수강 없는 학생도 학생 테이블에 등록 가능
* 갱신: 학과 변경은 학생 테이블 1곳만 수정
* 삭제: 수강 취소해도 학생 정보 유지부분 함수 종속인 {이름, 학과}를 학번만으로 구성된 별도 테이블로 분리했습니다. 이제 김철수의 학과를 변경해도 한 곳만 수정하면 됩니다.
2NF 분해 판단 방법
1. 기본키가 복합키인가?
→ 단일 컬럼이면 2NF 자동 만족 → SKIP
2. 기본키가 복합키이면,
기본키의 일부 컬럼으로만 결정되는 속성이 있는가?
{A, B} → C ← 전체 키에 종속 = OK
A → D ← 키의 일부에 종속 = 부분 함수 종속!
3. 부분 함수 종속이 있으면,
결정자(A)를 PK로 하는 새 테이블을 만들고,
종속 속성(D)을 그 테이블로 이동제3정규형 (3NF)
3NF의 조건: 2NF를 만족하면서, 이행 함수 종속이 없어야 합니다.
이행 함수 종속이란 A → B, B → C일 때 A → C가 되는 것입니다. 키가 아닌 속성이 다른 키가 아닌 속성을 결정하는 관계입니다.
학생 테이블 (PK: 학번)
┌────────┬────────┬────────┬──────────┐
│ 학번 │ 이름 │ 학과 │ 학과장 │
├────────┼────────┼────────┼──────────┤
│ 1001 │ 김철수 │ 컴공 │ 정교수 │
│ 1002 │ 이영희 │ 전자 │ 한교수 │
│ 1003 │ 박민수 │ 컴공 │ 정교수 │
└────────┴────────┴────────┴──────────┘
이행 함수 종속
학번 → 학과 (직접 종속)
학과 → 학과장 (직접 종속)
─────────────────
학번 → 학과장 (이행적 종속!)
학과장은 학번에 직접 종속되는 것이 아니라
학과를 '거쳐서' 종속됨컴공의 학과장이 바뀌면 1001, 1003 두 행을 모두 수정해야 합니다 — 갱신 이상입니다.
갱신 이상
* 컴공의 학과장이 '정교수' → '최교수'로 변경
* 학생 1001과 1003 두 곳 수정 필요
* 하나만 수정하면 학과장 정보 불일치!
삽입 이상
* '기계공학과 - 박교수' 정보를 넣으려면
* 해당 학과 학생이 없으면 저장할 방법 없음
삭제 이상
* 학번 1002 이영희 삭제 시
* '전자 - 한교수' 정보도 함께 소실학생 테이블 (PK: 학번)
┌────────┬────────┬────────┐
│ 학번 │ 이름 │ 학과 │
├────────┼────────┼────────┤
│ 1001 │ 김철수 │ 컴공 │
│ 1002 │ 이영희 │ 전자 │
│ 1003 │ 박민수 │ 컴공 │
└────────┴────────┴────────┘
학과 테이블 (PK: 학과)
┌────────┬──────────┐
│ 학과 │ 학과장 │
├────────┼──────────┤
│ 컴공 │ 정교수 │
│ 전자 │ 한교수 │
└────────┴──────────┘
이상 현상 해소
* 갱신: 학과장 변경은 학과 테이블 1곳만 수정
* 삽입: 학생 없이도 학과+학과장 등록 가능
* 삭제: 학생 삭제해도 학과 정보 유지이행 함수 종속의 중간 속성(학과)을 기본키로 하는 새 테이블을 만들어 분리합니다.
3NF 분해 판단 방법
1. 키가 아닌 속성 A가 키가 아닌 속성 B를 결정하는가?
학생 테이블에서,
* 학과(키 아닌 속성) → 학과장(키 아닌 속성) ← 위반!
2. 이행 함수 종속이 있으면,
결정자(학과)를 PK로 하는 새 테이블을 만들고,
종속 속성(학과장)을 그 테이블로 이동
3NF 정의 (코드의 정의)
"모든 결정자가 후보키이거나,
모든 비키 속성이 키에만 직접 종속"실무 적용 사례: 주문 시스템
실제 주문 시스템을 1NF → 2NF → 3NF 순서로 정규화하는 과정을 봅니다.
주문 테이블 (비정규형)
┌──────┬────────┬──────┬─────────────────────┬─────────┐
│주문ID│ 고객명 │고객폰│ 상품목록 │ 총금액 │
├──────┼────────┼──────┼─────────────────────┼─────────┤
│ 001 │ 김철수 │ 010- │ 노트북(150만),마우스│ 153.5만 │
│ │ │ 1234 │ (3.5만) │ │
│ 002 │ 이영희 │ 010- │ 키보드(8만) │ 8만 │
│ │ │ 5678 │ │ │
└──────┴────────┴──────┴─────────────────────┴─────────┘
문제: 상품목록 다중 값 → 1NF 위반주문상세 테이블 (PK: {주문ID, 상품명})
┌──────┬────────┬──────┬────────┬──────┬────────┐
│주문ID│ 고객명 │고객폰│ 상품명 │ 단가 │ 수량 │
├──────┼────────┼──────┼────────┼──────┼────────┤
│ 001 │ 김철수 │ 010- │ 노트북 │ 150만│ 1 │
│ │ │ 1234 │ │ │ │
│ 001 │ 김철수 │ 010- │ 마우스 │ 3.5만│ 1 │
│ │ │ 1234 │ │ │ │
│ 002 │ 이영희 │ 010- │ 키보드 │ 8만 │ 1 │
│ │ │ 5678 │ │ │ │
└──────┴────────┴──────┴────────┴──────┴────────┘
✓ 원자값 달성
✗ 부분 함수 종속 존재: 주문ID → 고객명, 고객폰주문 테이블 (PK: 주문ID)
┌──────┬────────┬──────┐
│주문ID│ 고객명 │고객폰│
├──────┼────────┼──────┤
│ 001 │ 김철수 │ 010- │
│ │ │ 1234 │
│ 002 │ 이영희 │ 010- │
│ │ │ 5678 │
└──────┴────────┴──────┘
주문상세 테이블 (PK: {주문ID, 상품명})
┌──────┬────────┬──────┬──────┐
│주문ID│ 상품명 │ 단가 │ 수량 │
├──────┼────────┼──────┼──────┤
│ 001 │ 노트북 │ 150만│ 1 │
│ 001 │ 마우스 │ 3.5만│ 1 │
│ 002 │ 키보드 │ 8만 │ 1 │
└──────┴────────┴──────┴──────┘
✓ 부분 함수 종속 제거
✗ 이행 함수 종속: 주문ID → 고객명 → 고객폰
(고객명이 같으면 고객폰이 같다고 가정)고객 테이블 (PK: 고객ID)
┌──────┬────────┬──────┐
│고객ID│ 고객명 │고객폰│
├──────┼────────┼──────┤
│ C001 │ 김철수 │ 010- │
│ │ │ 1234 │
│ C002 │ 이영희 │ 010- │
│ │ │ 5678 │
└──────┴────────┴──────┘
주문 테이블 (PK: 주문ID)
┌──────┬──────┬──────────┐
│주문ID│고객ID│ 주문일 │
├──────┼──────┼──────────┤
│ 001 │ C001 │2024-01-15│
│ 002 │ C002 │2024-01-16│
└──────┴──────┴──────────┘
주문상세 테이블 (PK: {주문ID, 상품ID})
┌──────┬──────┬──────┬──────┐
│주문ID│상품ID│ 단가 │ 수량 │
├──────┼──────┼──────┼──────┤
│ 001 │ P001 │ 150만│ 1 │
│ 001 │ P002 │ 3.5만│ 1 │
│ 002 │ P003 │ 8만 │ 1 │
└──────┴──────┴──────┴──────┘
✓ 모든 이상 현상 해소
✓ 3NF 달성무손실 분해와 종속성 보존
정규화로 테이블을 분해할 때 두 가지 조건을 만족해야 합니다.
무손실 분해 (Lossless Decomposition)
* 분해한 테이블들을 다시 자연 조인하면 원래 테이블이 복원
* 정보 손실이 없어야 함
* 분해 시 공통 속성이 최소 한 쪽의 키여야 보장
원래: R(A, B, C)
분해: R1(A, B), R2(A, C)
조건: A가 R1 또는 R2의 키 → 무손실 보장
잘못된 분해
R1(A, B), R2(B, C)
→ B가 어느 쪽의 키도 아닌 경우 → 조인 시 가짜 튜플 발생!
종속성 보존 (Dependency Preservation)
* 원래 테이블의 모든 함수 종속이 분해된 테이블에서도 검증 가능
* 단일 테이블 내에서 제약 조건 확인 가능해야 함
원래 FD: A → B, B → C
분해: R1(A, B), R2(B, C)
→ A → B는 R1에서, B → C는 R2에서 검증 가능 ✓정규화 단계 정리
비정규형
│
│ ① 원자값 보장, 반복 그룹 제거
▼
1NF ── 모든 셀에 원자값만 존재
│
│ ② 부분 함수 종속 제거
▼
2NF ── 복합키 일부에만 종속되는 속성 분리
│
│ ③ 이행 함수 종속 제거
▼
3NF ── A→B→C 형태의 종속 분리
│
│ ④ 모든 결정자가 후보키
▼
BCNF ── 후보키가 아닌 결정자 분리
│
│ ⑤ 다치 종속 제거
▼
4NF
│
│ ⑥ 조인 종속 제거
▼
5NF| 정규형 | 제거 대상 | 핵심 질문 |
|---|---|---|
| 1NF | 다중 값, 반복 그룹 | 한 셀에 값이 하나인가? |
| 2NF | 부분 함수 종속 | 복합키 일부에만 종속되는 속성이 있는가? |
| 3NF | 이행 함수 종속 | 키가 아닌 속성이 다른 키가 아닌 속성을 결정하는가? |
정규형 판별 연습
주어진 테이블이 몇 정규형을 만족하는지 판별하는 연습입니다.
테이블 R(학번, 과목코드, 교수명, 학점)
PK: {학번, 과목코드}
FD: {학번, 과목코드} → 학점
과목코드 → 교수명
1NF? → 원자값이면 OK ✓
2NF? → 과목코드 → 교수명 = 부분 함수 종속 ✗
→ 현재 정규형: 1NF
→ 2NF로: 수강(학번, 과목코드, 학점) + 과목(과목코드, 교수명)테이블 R(사원번호, 프로젝트명, 부서코드, 부서명, 역할)
PK: {사원번호, 프로젝트명}
FD: {사원번호, 프로젝트명} → 역할
사원번호 → 부서코드
부서코드 → 부서명
1NF? ✓
2NF? → 사원번호 → 부서코드 = 부분 함수 종속 → ✗
→ 현재 정규형: 1NF
2NF 분해
배정(사원번호, 프로젝트명, 역할)
사원(사원번호, 부서코드, 부서명)
3NF? → 부서코드 → 부서명 = 이행 함수 종속 → 사원 테이블 ✗
3NF 분해
배정(사원번호, 프로젝트명, 역할)
사원(사원번호, 부서코드)
부서(부서코드, 부서명)
→ 3NF 달성 ✓Step 1: 기본키 확인
* 단일 컬럼? → 2NF 자동 만족 → 3NF만 확인
Step 2: 복합키이면
* 키의 일부 → 비키 속성? → 2NF 위반
Step 3: 비키 → 비키 종속 확인
* 비키 속성이 다른 비키 속성 결정? → 3NF 위반정규화와 실무 적용
┌──────────────────────────────────────────────────────┐
│ 실무 정규화 가이드 │
├──────────────────────────────────────────────────────┤
│ │
│ 일반 원칙: 3NF까지 적용 │
│ * 대부분의 실무 시스템은 3NF로 충분 │
│ * BCNF는 특수한 경우에만 필요 │
│ * 4NF/5NF는 이론적 의미 위주 │
│ │
│ OLTP 시스템 (운영계): │
│ * 3NF 엄격 적용 → 데이터 무결성 최우선 │
│ * 갱신 이상 방지가 핵심 │
│ │
│ OLAP 시스템 (분석계): │
│ * 스타 스키마 (반정규화) 적용 │
│ * 조회 성능이 최우선 │
│ * Dimension 테이블은 의도적으로 반정규화 │
│ │
│ 마이크로서비스: │
│ * 서비스 경계 내에서 3NF 적용 │
│ * 서비스 간에는 데이터 중복 허용 (도메인 분리) │
│ │
└──────────────────────────────────────────────────────┘다음 절에서는 3NF를 넘어 BCNF와 그 이상의 정규형을 살펴봅니다.