모든 결정자(X → Y의 X)가 후보키여야 한다는 조건. 3NF보다 엄격하다.
3NF인데 BCNF가 아닌 경우
드물지만 후보키가 여럿이고 서로 겹칠 때 생긴다.
-
수강(학생, 과목, 교수)
-
규칙 ① (학생, 과목) → 교수 — 한 학생이 한 과목에서 배우는 교수는 하나
-
규칙 ② 교수 → 과목 — 한 교수는 한 과목만 가르친다
후보키: (학생, 과목) · (학생, 교수)
- 결정자: 교수 — ← 후보키가 아니다 → BCNF 위반
교수는 무언가를 결정하는데(과목) 후보키는 아니다. 3NF는 통과한다 — 비키 속성이 이행 종속되지 않았기 때문이다.
무엇이 문제인가
-- 교수 한 명의 담당 과목이 바뀌면 그 교수의 모든 수강 행을 고쳐야 한다
UPDATE enrollment SET subject = 'DB2' WHERE professor = '김교수';
-- 하나라도 빠지면 '한 교수 = 한 과목' 규칙이 깨진다
중복이 남아 있으니 갱신 이상이 그대로 살아 있다.
분해하면 — 대신 잃는 것이 있다
-- BCNF 분해
CREATE TABLE teaches (professor VARCHAR(50) PRIMARY KEY, subject VARCHAR(50));
CREATE TABLE takes (student BIGINT, professor VARCHAR(50),
PRIMARY KEY (student, professor));
이제 규칙 ②(교수 → 과목)는 teaches의 PK로 강제된다. 그런데 규칙 ①((학생, 과목) → 교수)은 어느 한 테이블에서도 검사할 수 없다 — 두 테이블을 조인해야만 확인된다.
-
무손실 분해(lossless) — 조인하면 원래 데이터가 그대로 복원된다 종속성 보존(dependency preservation)
- 모든 함수 종속을 조인 없이 한 테이블에서 검사할 수 있다
여기가 시험·면접 단골이다
| 무손실 분해 | 종속성 보존 | |
|---|---|---|
| 3NF | 항상 보장 | 항상 보장 |
| BCNF | 항상 보장 | 보장 못 할 수 있다 |
"3NF는 둘 다, BCNF는 무손실만" 이 핵심 한 줄이다. 그래서 실무는 대개 3NF에서 멈추고, BCNF 위반이 실제 문제를 일으킬 때만 분해한다.
종속성을 못 지키면 어떻게 하나
-- 조인 없이 검사할 수 없는 규칙은 애플리케이션이나 트리거로 강제한다
CREATE UNIQUE INDEX uq_student_subject
ON takes (student, (SELECT subject FROM teaches t WHERE t.professor = takes.professor));
-- ↑ 대부분의 DB 가 이런 서브쿼리 인덱스를 허용하지 않는다
-- → 트리거 또는 애플리케이션 레벨 검증이 현실적인 답이 된다
분해가 검증 책임을 DB 밖으로 밀어낸다는 것이 BCNF의 실질적 비용이다.
면접 함정
- ❌ "BCNF가 3NF보다 항상 낫다" → 종속성 보존을 잃을 수 있어 트레이드오프다.
- ❌ "3NF면 BCNF다" → 대부분 그렇지만, 후보키가 겹치는 경우에 갈린다.
실무에서 실제로 만나는 경우
대부분의 테이블은 대리키 PK 를 쓰므로 후보키가 하나뿐이다
- 후보키가 겹칠 일이 없다
- 3NF 를 만족하면 자동으로 BCNF 다
BCNF 위반이 나타나는 곳
- 자연 복합키를 그대로 PK 로 쓴 테이블 (코드성 · 이력성)
- 여러 유니크 제약이 서로 칼럼을 공유하는 테이블
판별 절차
-- ① 모든 함수 종속을 나열한다
-- ② 각 종속의 왼쪽(결정자)이 후보키인지 확인한다
-- ③ 후보키가 아닌 결정자가 있으면 BCNF 위반
-- 후보키 확인: 그 칼럼(들)만으로 행이 유일해지는가
SELECT count(*) AS total, count(DISTINCT professor) AS distinct_prof FROM enrollment;
-- 두 값이 다르면 professor 는 후보키가 아니다
"결정자인데 후보키가 아니다" 를 확인하는 이 두 줄이 판별의 전부다.