DB 정규화와 연결 표¶
한 줄 요약: 같은 정보는 한 곳에만 두고, 다른 곳에서는 ID로 가리키게 표를 나누는 원칙이다. 여러 개끼리 이어지는 관계(N:M)는 연결 표로 담는다.
📍 만난 상황¶
- 2026-11-09 — 2번 물음 → 그날 일지
- 15:41 처음 말한 메타정보가 ERD의 표 6개에 나뉘어 들어 있다는 설명을 들었다. ERD를 보다가 물었다. "
dataset_tag에 tag의 kind·name을 다 써도 될 것 같은데, 왜 두 개로 나눴나?" - 15:42 태그 표를 나눈 이유(DB 정규화)를 듣고, 이어서 표 13개를 나눈 기준 전체를 설명해 달라고 했다.
- 같은 날 오전에 "H3 정규화"라는 말을 계속 썼다. 이름은 같지만 전혀 다른 뜻이라는 점도 함께 짚었다.
🧩 쉽게 말하면¶
학교 반 명단을 떠올리면 된다. 학생 카드마다 담임 이름과 전화번호를 적어 두면, 담임 번호가 바뀔 때 카드 30장을 다 고쳐야 한다. 한 장이라도 빠뜨리면 번호가 두 개가 된다.
그래서 담임 정보는 교사 명단 한 장에만 쓰고, 학생 카드에는 "담임: 교사 3번"이라고만 적는다. 이것이 정규화다.
📖 제대로 알기¶
정의¶
DB 정규화(normalization)는 중복을 없애도록 표를 나누는 설계 원칙이다. 같은 정보는 한 곳에만 두고, 다른 표는 FK(외래 키: 다른 표의 행을 가리키는 열)로 가리킨다.
일부러 중복을 허용하는 설계는 비정규화(denormalization)라고 한다. 틀린 게 아니라 선택이다.
중복이 부르는 세 가지 문제(이상 현상)¶
| 이름 | 무슨 일이 생기나 |
|---|---|
| 갱신 이상 | 이름 하나를 바꾸려면 여러 줄을 고쳐야 하고, 빠뜨리면 값이 갈린다 |
| 삽입 이상 | 아직 연결이 없는 값(쓰이지 않은 태그)은 넣을 곳이 없다 |
| 삭제 이상 | 마지막 연결 줄을 지우면 그 값 자체가 사라진다 |
정규형(참고)¶
교과서에서는 단계로 부른다.
- 1정규형: 칸 하나에 값 하나. "즐겨찾기1, 즐겨찾기2" 같은 반복 열을 두지 않는다.
- 2·3정규형: 키가 아닌 열이 키에만 의존하게 한다. 태그 이름은 dataset_tag의 키가 아니라 태그에 딸린 정보이므로 tag 표로 뺀다.
관계별로 담는 법¶
| 관계 | 처리 |
|---|---|
| 1:N | N쪽 표에 FK 열 하나 (inquiry.user_id) |
| N:M | 연결 표를 새로 만듦 (favorite, dataset_tag) |
| 1:1 | 보통 합침. 나눌 이유가 있을 때만 나눔 |
연결 표(junction table)는 두 표의 ID 쌍만 담는 표다. 줄 하나가 "이것과 저것이 이어져 있다"는 사실 하나다.
⚙️ 어떻게 동작하나¶
그날 들은 표 나누는 기준 중 핵심 네 가지다.
- 기능 문장에서 명사를 찾는다. "사람이 데이터셋을 즐겨찾기한다" → 사람(
app_user), 데이터셋(dataset). 동사(즐겨찾기한다)는 보통 표가 아니라 관계가 된다. - 둘 사이의 개수를 센다. 사람 한 명은 여러 데이터셋을, 데이터셋 하나는 여러 사람이 즐겨찾기한다 → N:M → 연결 표
favorite. - 반복되는 값은 목록 표로 뺀다. "침수"가 여러 데이터셋에 반복된다 →
tag표. "강남구 = 11680"도 같은 이유로region_code. - 쓰는 주체가 다르면 나눈다.
dataset은 동기화 프로그램이,dataset_meta는 사람이 쓴다. 1:1이지만 나눠서, 동기화가 사람이 쓴 설명을 덮어쓰는 일을 구조적으로 막는다.
erDiagram
DATASET ||--o{ DATASET_TAG : "하나에 태그 여러 개"
TAG ||--o{ DATASET_TAG : "하나가 여러 데이터셋에"
APP_USER ||--o{ FAVORITE : "한 사람이 여러 개"
DATASET ||--o{ FAVORITE : "여러 사람이 찜"
DATASET ||--|| DATASET_META : "설명은 하나"
가운데의 DATASET_TAG, FAVORITE가 연결 표다. 양쪽 표와 1:N으로 이어지고, 그 둘을 합치면 N:M이 된다.
🧪 예시¶
-
예시 1 · 일상에 빗대어: 도서관에서 책 3권에 "재난" 분류 스티커를 손글씨로 붙였다. 분류 이름을 "재해·재난"으로 바꾸려면 3권을 다 찾아야 한다. 대신 스티커에 "분류 7번"만 쓰고 분류표 한 장에 "7번 = 재난"이라 적으면, 분류표 한 줄만 고치면 된다.
-
예시 2 · 실제로는: 두 방식을 같은 데이터로 비교한다.
[방식 A] dataset_tag에 이름을 직접
dataset_id | kind | name
A(침수흔적) | disaster | 침수
B(강우) | disaster | 침수
C(지반함몰) | disaster | 지반함몰
[방식 B] tag 표를 따로 (지금 ERD)
tag: id | kind | name
1 | disaster | 침수
2 | disaster | 지반함몰
3 | disaster | 교량붕괴
dataset_tag: (A,1) (B,1) (C,2)
tag 1번 한 줄만 고친다.
② 오타: A는 "칩수"가 들어와도 못 막는다. B는 tag_id가 FK라 없는 번호를 넣을 수 없다.
③ 빈 태그: "교량붕괴" 데이터가 아직 없어도 B는 선택지(3번)에 미리 있다. A에는 어디에도 없다.
④ 확장: 태그에 색·표시 순서를 붙이려면 B는 tag에 열 하나를 더하면 된다.
필터 선택지는 SELECT name FROM app.tag로 바로 나온다.
⚖️ 비교 · 흔한 오해¶
| 이름 | 하는 일 |
|---|---|
| H3 정규화 | 제각각인 위치·시간 형식을 같은 기준(H3 칸, 시각)으로 맞추는 일 |
| DB 정규화 | 중복을 없애도록 표를 나누는 일 |
- 헷갈렸던 점: "한 표에 다 쓰면 더 간단하다" — 표가 적을수록 쉬워 보이고, 지금 규모(데이터셋 수십 개, 태그 10개 정도)에서는 실제로 동작한다. 문제는 이름을 바꾸거나 오타가 들어올 때 드러난다. 그래서 처음부터 나눠 둔다.
- 오해: "정규화는 많이 할수록 좋다" — 표가 너무 잘게 나뉘면 조회마다 JOIN(표 이어 붙이기)이 늘어난다.
column_meta는 자동 값과 사람 값이 한 표에 섞여 있지만, 더 나누면 지나치다고 보고 두었다. - 비정규화를 일부러 쓰는 예: 정규화 파이프라인이 데이터 줄에
sgg_code = 11680과 함께sgg_name = 강남구를 미리 적어 두는 안이 있었다. 플랫폼 SQL 창구는 JOIN을 못 하니, 화면에 이름을 바로 보여 주려고 중복을 허용하는 것이다.
🛠️ 실무에서는¶
- 정규화가 맞는 곳: 사용자 데이터·설정처럼 자주 쓰고 고치는 서비스 DB(PostgreSQL 같은 OLTP).
- 비정규화가 흔한 곳: 분석용 DB(ClickHouse 같은 OLAP). 한 번 쓰고 많이 읽으며, JOIN이 비싸다.
- 체크리스트
- 같은 글자가 여러 줄에 반복되고, 나중에 바뀔 수 있나 → 목록 표로 뺀다.
- 연결 표의 PK는 두 FK의 조합인가(
PRIMARY KEY (user_id, dataset_id)) → 같은 연결이 두 번 생기지 않는다. - 비정규화한 값은 원본이 바뀔 때 누가 같이 고치나 정해 두었나.
- 문제가 생기면 먼저 볼 것: 같은 대상이 두 이름으로 갈려 있으면(“침수”, “침수 ”) 중복 저장이 원인이다.
SELECT DISTINCT로 값 목록을 뽑아 본다.
❓ 자주 묻는 질문¶
- Q. 연결 표에 다른 열을 넣어도 되나?
A. 된다. 그 연결 자체의 정보라면 넣는다. 예:
favorite.created_at(언제 찜했나)은 사람도 데이터셋도 아닌 "찜"의 정보다. - Q. 태그 표에 ID 대신 이름을 PK로 쓰면 안 되나? A. 가능하지만, 이름을 바꿀 때 연결 표의 값도 다 바뀌어야 한다. 바뀔 수 있는 값은 PK로 쓰지 않는 편이 안전하다.
- Q. 1:1인데 왜 나누나?
A. 쓰는 주체나 바뀌는 때가 다를 때다.
dataset(프로그램)과dataset_meta(사람)처럼 덮어쓰기를 막으려고 나눈다.
🔭 더 깊이 보기¶
- JOIN은 어떻게 동작하나? 나눈 표를 다시 합쳐 읽는 법이다. 정규화의 비용을 이해하는 열쇠다.
- 인덱스는 연결 표에 왜 필요한가?
favorite에서 "이 데이터셋을 찜한 사람"을 빨리 찾으려면 반대 방향 인덱스가 필요하다. - OLTP와 OLAP의 차이는? PostgreSQL과 ClickHouse가 왜 다르게 설계되는지 알 수 있다.
- 관련 주제: OIDC 로그인 흐름 —
app_user와favorite가 생기는 과정
📚 참고 자료¶
- 데이터베이스 정규화 기본 사항 설명 (Microsoft Learn 한국어) — 1·2·3정규형을 예시 표로 설명 · 링크
- 관계형 데이터 모델링 (생활코딩, opentutorials.org) — 개념·논리 모델링과 정규화 입문 강의. 사이트에서 제목으로 검색
- PostgreSQL Documentation: Constraints (영문) — PK·FK·UNIQUE 제약 문법 · 링크
🗓️ 이력¶
- 2026-11-09: 태그를 연결 표에 직접 쓰지 않고 나눈 이유와 표를 나누는 기준을 물음 → 일지