icon

안동민 개발노트

8장 : 정규화

제1정규형 ~ 제3정규형


정규화는 단계별로 진행됩니다. 각 단계에서 어떤 문제를 해결하는지, 실제 테이블이 어떻게 분해되는지를 예제로 확인합니다. 정규화의 각 단계는 특정 유형의 함수 종속을 제거하여 갱신 이상(Anomaly)을 방지합니다.


함수 종속(Functional Dependency) 복습

정규화를 이해하려면 먼저 함수 종속 개념이 명확해야 합니다.

함수 종속의 정의
X → Y  (Y는 X에 함수 종속)

의미: X 값이 같으면 Y 값도 반드시 같다.

예시
  학번 → 이름     (학번이 같으면 이름이 같다) ✓
  학번 → 수강과목  (학번이 같아도 여러 과목 수강) ✗

함수 종속의 유형
┌──────────────────────────────────────────────────┐
│ 완전 함수 종속 (Full FD)                         │
│   {학번, 과목명} → 성적                          │
│   → 학번만으로는 성적을 결정할 수 없고,          │
│     학번+과목명 둘 다 있어야 성적이 결정됨       │
│                                                  │
│ 부분 함수 종속 (Partial FD)                      │
│   {학번, 과목명} → 이름                          │
│   → 학번만으로 이름이 결정됨                     │
│   → 기본키의 일부에만 종속 = 부분 함수 종속      │
│                                                  │
│ 이행 함수 종속 (Transitive FD)                   │
│   학번 → 학과, 학과 → 학과장                     │
│   → 학번 → 학과장 (학과를 거쳐서 종속)           │
│   → 키가 아닌 속성을 통한 간접 종속              │
└──────────────────────────────────────────────────┘

제1정규형 (1NF)

1NF의 조건: 모든 속성의 값이 원자값(Atomic Value)이어야 합니다. 하나의 셀에 여러 값이 들어가면 안 됩니다. 또한 반복 그룹(Repeating Group)이 없어야 합니다.

1NF 위반 유형 1: 다중 값
위반
┌────────┬────────┬──────────────────┐
│  학번  │  이름  │     수강과목     │
├────────┼────────┼──────────────────┤
│  1001  │ 김철수 │ DB, 알고리즘     │  ← 하나의 셀에 2개 값!
│  1002  │ 이영희 │        DB        │
└────────┴────────┴──────────────────┘

문제
  * 특정 과목 수강자 검색이 어려움
    WHERE 수강과목 = 'DB' → 1001의 'DB, 알고리즘'은 매칭 안 됨
  * 과목 추가/삭제 시 문자열 파싱 필요
  * 과목 수 증가 시 셀 크기 무한 확장
1NF 위반 유형 2: 반복 그룹
위반
┌────────┬────────┬────────┬────────┬────────┐
│  학번  │  이름  │ 과목1  │ 과목2  │ 과목3  │
├────────┼────────┼────────┼────────┼────────┤
│  1001  │ 김철수 │   DB   │  알고  │  NULL  │
│  1002  │ 이영희 │   DB   │  NULL  │  NULL  │
└────────┴────────┴────────┴────────┴────────┘

문제
  * 최대 과목 수가 컬럼 수에 제한됨
  * NULL이 많아짐
  * 과목 검색 시 모든 과목 컬럼을 확인해야 함
    WHERE 과목1='DB' OR 과목2='DB' OR 과목3='DB'
1NF 적용 — 원자값으로 변환
1NF 적용
┌────────┬────────┬──────────┐
│  학번  │  이름  │ 수강과목 │
├────────┼────────┼──────────┤
│  1001  │ 김철수 │    DB    │
│  1001  │ 김철수 │ 알고리즘 │
│  1002  │ 이영희 │    DB    │
└────────┴────────┴──────────┘

* 각 셀에 하나의 값만 존재
* 기본키가 {학번, 수강과목}의 복합키로 변경
* WHERE 수강과목 = 'DB' → 정확한 검색 가능

1NF를 적용하면 행이 늘어나지만, 각 셀에 하나의 값만 존재합니다. 이것이 관계형 모델의 가장 기본적인 제약입니다.


1NF와 실무

실무에서 1NF 위반은 의외로 흔합니다. JSON 컬럼, 콤마 구분 문자열 등이 대표적입니다.

실무에서의 1NF 위반 사례
사례 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를 만족합니다.

2NF 위반 사례
수강 테이블 (PK: {학번, 과목명})
┌────────┬──────────┬────────┬────────┬────────┐
│  학번  │  과목명  │  성적  │  이름  │  학과  │
├────────┼──────────┼────────┼────────┼────────┤
│  1001  │    DB    │   A    │ 김철수 │  컴공  │
│  1001  │ 알고리즘 │   B+   │ 김철수 │  컴공  │
│  1002  │    DB    │   A+   │ 이영희 │  전자  │
└────────┴──────────┴────────┴────────┴────────┘

종속 관계 분석
  {학번, 과목명} → 성적     (완전 함수 종속 ✓)
  학번 → 이름               (부분 함수 종속 ✗) ← 문제!
  학번 → 학과               (부분 함수 종속 ✗) ← 문제!
부분 함수 종속이 일으키는 이상 현상
삽입 이상 (Insert Anomaly)
  * 아직 수강 과목이 없는 신입생 '박민수'를 등록할 수 없음
  * 기본키의 일부인 '과목명'이 NULL이면 PK 위반

갱신 이상 (Update Anomaly)
  * 김철수의 학과를 '소프트웨어'로 변경하려면
  * 1001/DB 행과 1001/알고리즘 행 두 곳을 모두 수정
  * 하나만 수정하면 데이터 불일치!

삭제 이상 (Delete Anomaly)
  * 이영희가 DB를 수강 취소하면 해당 행 삭제
  * 이영희의 이름, 학과 정보도 함께 소실!
2NF 적용 — 테이블 분해
학생 테이블 (PK: 학번)
┌────────┬────────┬────────┐
│  학번  │  이름  │  학과  │
├────────┼────────┼────────┤
│  1001  │ 김철수 │  컴공  │
│  1002  │ 이영희 │  전자  │
└────────┴────────┴────────┘

수강 테이블 (PK: {학번, 과목명})
┌────────┬──────────┬────────┐
│  학번  │  과목명  │  성적  │
├────────┼──────────┼────────┤
│  1001  │    DB    │   A    │
│  1001  │ 알고리즘 │   B+   │
│  1002  │    DB    │   A+   │
└────────┴──────────┴────────┘

이상 현상 해소
  * 삽입: 수강 없는 학생도 학생 테이블에 등록 가능
  * 갱신: 학과 변경은 학생 테이블 1곳만 수정
  * 삭제: 수강 취소해도 학생 정보 유지

부분 함수 종속인 {이름, 학과}를 학번만으로 구성된 별도 테이블로 분리했습니다. 이제 김철수의 학과를 변경해도 한 곳만 수정하면 됩니다.


2NF 분해 판단 방법

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가 되는 것입니다. 키가 아닌 속성이 다른 키가 아닌 속성을 결정하는 관계입니다.

3NF 위반 사례
학생 테이블 (PK: 학번)
┌────────┬────────┬────────┬──────────┐
│  학번  │  이름  │  학과  │  학과장  │
├────────┼────────┼────────┼──────────┤
│  1001  │ 김철수 │  컴공  │  정교수  │
│  1002  │ 이영희 │  전자  │  한교수  │
│  1003  │ 박민수 │  컴공  │  정교수  │
└────────┴────────┴────────┴──────────┘

이행 함수 종속
  학번 → 학과      (직접 종속)
  학과 → 학과장    (직접 종속)
  ─────────────────
  학번 → 학과장    (이행적 종속!)
  
  학과장은 학번에 직접 종속되는 것이 아니라
  학과를 '거쳐서' 종속됨

컴공의 학과장이 바뀌면 1001, 1003 두 행을 모두 수정해야 합니다 — 갱신 이상입니다.

3NF 위반이 일으키는 이상 현상
갱신 이상
  * 컴공의 학과장이 '정교수' → '최교수'로 변경
  * 학생 1001과 1003 두 곳 수정 필요
  * 하나만 수정하면 학과장 정보 불일치!

삽입 이상
  * '기계공학과 - 박교수' 정보를 넣으려면
  * 해당 학과 학생이 없으면 저장할 방법 없음

삭제 이상
  * 학번 1002 이영희 삭제 시
  * '전자 - 한교수' 정보도 함께 소실
3NF 적용 — 테이블 분해
학생 테이블 (PK: 학번)
┌────────┬────────┬────────┐
│  학번  │  이름  │  학과  │
├────────┼────────┼────────┤
│  1001  │ 김철수 │  컴공  │
│  1002  │ 이영희 │  전자  │
│  1003  │ 박민수 │  컴공  │
└────────┴────────┴────────┘

학과 테이블 (PK: 학과)
┌────────┬──────────┐
│  학과  │  학과장  │
├────────┼──────────┤
│  컴공  │  정교수  │
│  전자  │  한교수  │
└────────┴──────────┘

이상 현상 해소
  * 갱신: 학과장 변경은 학과 테이블 1곳만 수정
  * 삽입: 학생 없이도 학과+학과장 등록 가능
  * 삭제: 학생 삭제해도 학과 정보 유지

이행 함수 종속의 중간 속성(학과)을 기본키로 하는 새 테이블을 만들어 분리합니다.


3NF 분해 판단 방법

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 위반
1단계: 1NF 적용
주문상세 테이블 (PK: {주문ID, 상품명})
┌──────┬────────┬──────┬────────┬──────┬────────┐
│주문ID│ 고객명 │고객폰│ 상품명 │ 단가 │  수량  │
├──────┼────────┼──────┼────────┼──────┼────────┤
│ 001  │ 김철수 │ 010- │ 노트북 │ 150만│   1    │
│      │        │ 1234 │        │      │        │
│ 001  │ 김철수 │ 010- │ 마우스 │ 3.5만│   1    │
│      │        │ 1234 │        │      │        │
│ 002  │ 이영희 │ 010- │ 키보드 │ 8만  │   1    │
│      │        │ 5678 │        │      │        │
└──────┴────────┴──────┴────────┴──────┴────────┘

✓ 원자값 달성
✗ 부분 함수 종속 존재: 주문ID → 고객명, 고객폰
2단계: 2NF 적용
주문 테이블 (PK: 주문ID)
┌──────┬────────┬──────┐
│주문ID│ 고객명 │고객폰│
├──────┼────────┼──────┤
│ 001  │ 김철수 │ 010- │
│      │        │ 1234 │
│ 002  │ 이영희 │ 010- │
│      │        │ 5678 │
└──────┴────────┴──────┘

주문상세 테이블 (PK: {주문ID, 상품명})
┌──────┬────────┬──────┬──────┐
│주문ID│ 상품명 │ 단가 │ 수량 │
├──────┼────────┼──────┼──────┤
│ 001  │ 노트북 │ 150만│  1   │
│ 001  │ 마우스 │ 3.5만│  1   │
│ 002  │ 키보드 │ 8만  │  1   │
└──────┴────────┴──────┴──────┘

✓ 부분 함수 종속 제거
✗ 이행 함수 종속: 주문ID → 고객명 → 고객폰
   (고객명이 같으면 고객폰이 같다고 가정)
3단계: 3NF 적용
고객 테이블 (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이행 함수 종속키가 아닌 속성이 다른 키가 아닌 속성을 결정하는가?

정규형 판별 연습

주어진 테이블이 몇 정규형을 만족하는지 판별하는 연습입니다.

연습 1
테이블 R(학번, 과목코드, 교수명, 학점)
PK: {학번, 과목코드}
FD: {학번, 과목코드} → 학점
    과목코드 → 교수명

1NF? → 원자값이면 OK ✓
2NF? → 과목코드 → 교수명 = 부분 함수 종속 ✗
→ 현재 정규형: 1NF
→ 2NF로: 수강(학번, 과목코드, 학점) + 과목(과목코드, 교수명)
연습 2
테이블 R(사원번호, 프로젝트명, 부서코드, 부서명, 역할)
PK: {사원번호, 프로젝트명}
FD: {사원번호, 프로젝트명} → 역할
    사원번호 → 부서코드
    부서코드 → 부서명

1NF? ✓
2NF? → 사원번호 → 부서코드 = 부분 함수 종속 → ✗
→ 현재 정규형: 1NF

2NF 분해
  배정(사원번호, 프로젝트명, 역할)
  사원(사원번호, 부서코드, 부서명)

3NF? → 부서코드 → 부서명 = 이행 함수 종속 → 사원 테이블 ✗

3NF 분해
  배정(사원번호, 프로젝트명, 역할)
  사원(사원번호, 부서코드)
  부서(부서코드, 부서명)
→ 3NF 달성 ✓
연습 3: 빠른 판별 요령
Step 1: 기본키 확인
  * 단일 컬럼? → 2NF 자동 만족 → 3NF만 확인

Step 2: 복합키이면
  * 키의 일부 → 비키 속성? → 2NF 위반

Step 3: 비키 → 비키 종속 확인
  * 비키 속성이 다른 비키 속성 결정? → 3NF 위반

정규화와 실무 적용

실무에서의 정규화 적용 수준
┌──────────────────────────────────────────────────────┐
│               실무 정규화 가이드                     │
├──────────────────────────────────────────────────────┤
│                                                      │
│  일반 원칙: 3NF까지 적용                             │
│  * 대부분의 실무 시스템은 3NF로 충분                 │
│  * BCNF는 특수한 경우에만 필요                       │
│  * 4NF/5NF는 이론적 의미 위주                        │
│                                                      │
│  OLTP 시스템 (운영계):                               │
│  * 3NF 엄격 적용 → 데이터 무결성 최우선              │
│  * 갱신 이상 방지가 핵심                             │
│                                                      │
│  OLAP 시스템 (분석계):                               │
│  * 스타 스키마 (반정규화) 적용                       │
│  * 조회 성능이 최우선                                │
│  * Dimension 테이블은 의도적으로 반정규화            │
│                                                      │
│  마이크로서비스:                                     │
│  * 서비스 경계 내에서 3NF 적용                       │
│  * 서비스 간에는 데이터 중복 허용 (도메인 분리)      │
│                                                      │
└──────────────────────────────────────────────────────┘

다음 절에서는 3NF를 넘어 BCNF와 그 이상의 정규형을 살펴봅니다.

목차