ERD 관계와 카디널리티(1:N)¶
한 줄 요약: ERD의 상자 하나는 서비스 전체에 하나뿐인 표다. 선 끝의 1과 N은 표 개수가 아니라 줄(행) 개수를 말한다. 이걸 알아야 표를 설계하고 남의 ERD를 읽을 수 있다.
📍 만난 상황¶
- 2026-11-09 — 대시보드 1차 기능(즐겨찾기, 마이페이지 등)을 정하고 표를 설계하던 중이었다. 4번 물음 → 그날 일지
- 15:07 표 초안을 받고 "user마다 즐겨찾기 테이블이 하나씩 있어야 하지 않나? ERD는 전체 서비스 DB인가, 한 유저의 DB인가?"를 물었다.
- 15:10 "데이터셋 1 : N 즐겨찾기면, 데이터셋 하나에 즐겨찾기 테이블이 여러 개 매핑된다는 거 아니야?"
- 15:47 ERD를 보며 "dataset의 한 행이 column_meta의 여러 행과 매칭되는 거 맞지?" → 맞다.
- 15:48 "dataset의 id가 dataset_meta에 여러 개 있을 수 있다는 거지?" → 표 이름이 바뀌었다.
dataset_meta는 1:1이었다.
🧩 쉽게 말하면¶
학교의 학생 명부와 숙제 제출 장부를 떠올리면 된다. 제출 장부는 학교에 한 권뿐이다. 학생마다 장부를 따로 만들지 않는다.
장부의 줄마다 "누가 냈나(학생 번호)"를 적는다. 그러면 학생 1명에 제출 줄이 여러 개 붙는다. 이게 1:N이다.
학생마다 장부를 따로 만들면 어떻게 될까. 학생이 300명이면 장부가 300권이다. "이번 주 숙제를 낸 사람 전체"를 보려면 300권을 다 펼쳐야 한다.
📖 제대로 알기¶
용어¶
- ERD(Entity-Relationship Diagram: 표와 표 사이 관계를 그린 그림)는 서비스 전체 DB를 그린다. 한 사용자의 DB를 그리는 그림이 아니다.
- 엔티티: 기억해야 할 대상. 보통 표 하나가 된다(사람, 데이터셋, 즐겨찾기).
- PK(기본 키: 표에서 한 줄을 콕 집어내는 값. 겹칠 수 없다).
- FK(외래 키: 다른 표의 PK를 가리키는 열).
- 카디널리티: 한쪽 표의 한 줄에 다른 쪽 표의 줄이 몇 개 붙나.
관계 세 가지¶
| 관계 | 뜻 | 담는 방법 |
|---|---|---|
| 1:1 | 한 줄 ↔ 딱 한 줄 | FK 열을 그 표의 PK로 |
| 1:N | 한 줄 ↔ 0~여러 줄 | N쪽 표에 FK 열 하나 |
| N:M | 여러 줄 ↔ 여러 줄 | 연결 표를 새로 만듦 |
즐겨찾기는 "사람 N : 데이터셋 M"이다. 그래서 favorite이라는 연결 표를 둔다. 그 결과 app_user 1 : N favorite, dataset 1 : N favorite 두 관계가 생긴다.
까마귀발 표기¶
ERD 선 끝의 모양으로 개수를 읽는다.
| 기호 | 뜻 |
|---|---|
\|\| (막대 둘) |
정확히 하나 |
o\| |
없거나 하나 |
o{ (까마귀발) |
없거나 여러 개 |
\|{ |
하나 이상 여러 개 |
⚙️ 어떻게 동작하나¶
관계를 읽는 순서다.
- 상자 = 표 하나라고 읽는다. 서비스 전체에 그 표는 하나뿐이다.
- FK 열을 찾는다. 다른 표를 가리키는 열이다. 예:
favorite.dataset_id. - FK 값이 반복될 수 있는지 본다. 반복될 수 있으면 그 표가 N쪽이다.
- 반복 여부는 PK를 보면 안다.
- FK 열이 혼자서 PK이면 반복될 수 없다 → 1:1
- FK 열이 PK의 일부이거나 PK가 아니면 반복될 수 있다 → 1:N
- 그림에서는 까마귀발이 붙은 쪽 = FK를 가진 쪽 = N쪽이다.
erDiagram
APP_USER ||--o{ FAVORITE : "한 사람이 여러 개"
DATASET ||--o{ FAVORITE : "한 데이터셋에 여러 개"
DATASET ||--o{ COLUMN_META : "열 설명 여러 개"
DATASET ||--|| DATASET_META : "설명 하나"
까마귀발은 모두 FK를 가진 표 쪽(FAVORITE, COLUMN_META)에 붙는다. DATASET_META만 양쪽이 막대라서 1:1이다.
왜 사용자마다 표를 만들지 않을까. 표는 종류를 나타내고, 줄은 그 종류의 하나하나를 나타내도록 설계됐기 때문이다. "누구의 것인지"는 표 이름이 아니라 줄 안의 값(user_id)으로 구분한다.
🧪 예시¶
-
예시 1 · 일상에 빗대어: 도서관 대출 장부가 한 권 있다. 줄은 4개다. 회원 1번이 책 A를, 회원 2번이 책 A를, 회원 1번이 책 B를 빌렸다. 책 C는 아무도 안 빌렸다. 그러면 책 A 한 권에 대출 줄 2개, 책 B에 1개, 책 C에 0개가 붙는다. N은 0이어도 된다. 장부는 끝까지 한 권이다.
-
예시 2 · 실제로는: 그날 본 표 세 개다.
dataset (데이터셋 하나 = 한 줄)
| id | title |
|---|---|
| A | 침수흔적 |
| B | 강우 10분 |
| C | 지반함몰 |
favorite (즐겨찾기 한 번 = 한 줄, 서비스 전체에 이 표 하나)
| user_id | dataset_id |
|---|---|
| 1 | A |
| 2 | A |
| 1 | B |
dataset_id 칸을 세로로 훑으면 A가 두 번 나온다. 반복되는 쪽이 N쪽이다. dataset.id는 PK라서 A, B, C가 한 번씩만 나온다.
-- column_meta: 두 열을 합친 것이 PK → dataset_id만 보면 반복 가능 → 1:N
CREATE TABLE app.column_meta (
dataset_id text REFERENCES app.dataset(id),
column_name text,
PRIMARY KEY (dataset_id, column_name)
);
-- dataset_meta: dataset_id 혼자 PK → 같은 값 두 번 불가 → 1:1
CREATE TABLE app.dataset_meta (
dataset_id text PRIMARY KEY REFERENCES app.dataset(id),
description text
);
-- 사용자 7의 즐겨찾기: 표는 하나, 줄을 user_id로 골라냄
SELECT dataset_id FROM app.favorite WHERE user_id = 7;
column_meta의 PK는 (A, t_start), (A, h3_r9)처럼 조합이 겹치지 않으면 된다. 그래서 A가 여러 번 나올 수 있다.
② dataset_meta는 dataset_id 자체가 PK다. A를 두 번 넣으면 DB가 거절한다.
③ "내 즐겨찾기"는 내 표를 여는 게 아니다. 공용 표에서 내 user_id 줄만 고르는 것이다.
⚖️ 비교 · 흔한 오해¶
| 헷갈린 점 | 실제 |
|---|---|
| 사용자마다 즐겨찾기 표 | 표 하나 + user_id로 구분 |
| 1:N = 표 N개 | 1:N = 줄 N개 |
column_meta ≈ dataset_meta |
1:N vs 1:1(PK가 다름) |
왜 "사용자마다 표"라고 생각하기 쉬울까. 화면의 "내 즐겨찾기"가 나만의 목록처럼 보여서다. 엑셀에서 사람마다 시트를 하나씩 만들던 습관도 한몫한다. DB에서는 시트(표)를 늘리지 않고 줄을 늘린다.
왜 "1:N = 표 N개"로 읽기 쉬울까. ERD 상자가 표이니, 선 끝의 숫자도 상자(표)에 붙은 것처럼 보인다. 숫자는 상자가 아니라 상자 안의 줄을 센다고 기억하면 된다.
왜 column_meta와 dataset_meta를 섞기 쉬울까. 이름이 비슷하고 둘 다 dataset_id를 FK로 가진다. 차이는 PK에서만 드러난다. FK를 찾은 다음 반드시 PK를 확인한다.
그 밖의 흔한 오해
- "N쪽에는 최소 하나가 있어야 한다" → 아니다. o{는 0개도 허용한다. 즐겨찾기 0개인 데이터셋도 정상이다.
- "PK가 두 열이면 두 열 각각이 겹치면 안 된다" → 아니다. 겹치면 안 되는 것은 조합 전체다.
🛠️ 실무에서는¶
- 표를 설계할 때 예시 줄 3개를 직접 써 본다. 값이 반복되는 열이 보이면 관계가 보인다.
- 1:N은 N쪽에 FK 열 하나, N:M은 연결 표. 이 두 규칙으로 대부분 해결된다.
- 1:1은 보통 한 표로 합친다. 쓰는 주체가 다를 때(프로그램 vs 사람)처럼 이유가 있을 때만 나눈다.
- 연결 표에는
(user_id, dataset_id)같은 복합 PK를 걸어 같은 줄이 두 번 생기지 않게 한다. 별을 두 번 눌러도 한 줄이다.
체크리스트
- [ ] 모든 FK 열에 REFERENCES가 걸려 있나
- [ ] 1:1로 의도한 관계에서 FK 열이 PK(또는 UNIQUE)인가
- [ ] 사람이나 날짜마다 표를 늘리는 설계가 섞여 있지 않나
- [ ] N쪽 FK 열에 인덱스가 있나(그 열로 자주 고르므로)
❓ 자주 묻는 질문¶
- Q.
favorite의 PK가(user_id, dataset_id)면 같은user_id가 여러 번 나올 수 있나? A. 나올 수 있다. (1, A), (1, B)처럼 조합이 다르면 된다. 그래서app_user 1 : N favorite이다. - Q. 문의 표(
inquiry)와 사용자는 어떤 관계인가? A. 같은 사람이 여러 번 문의하면inquiry.user_id가 반복된다. 그래서app_user 1 : N inquiry다. - Q. 사용자가 탈퇴하면 그 사람의 즐겨찾기 줄은?
A. FK에
ON DELETE CASCADE를 걸면 함께 지워진다. 걸지 않으면 남은 줄 때문에 삭제가 거절된다.
🔭 더 깊이 보기¶
- N:M 관계와 연결 표는 언제 쓰나? — 즐겨찾기·태그처럼 그날 설계한 표 상당수가 이 모양이라서.
- DB 정규화(중복을 없애도록 표를 나누는 원칙)는 무엇인가? — "왜 표를 나누나"에 대한 일반 규칙이라서.
- JOIN은 1:N 관계를 어떻게 이어 붙이나? — 관계를 그린 다음 실제로 꺼내 쓰는 방법이라서.
- 관련 주제: PostgreSQL 데이터베이스와 스키마 · 메타데이터 카탈로그 기반 조회
📚 참고 자료¶
- 데이터베이스 정규화 기본 사항 설명 (Microsoft Learn 한국어) — 표를 나누고 키로 잇는 기본 원리 · 링크
- Constraints — Primary Keys, Foreign Keys (PostgreSQL 공식 문서, 영문) — PK·FK·복합 키·
ON DELETE동작 · 링크 - Entity Relationship Diagrams (Mermaid 공식 문서, 영문) — 까마귀발 기호(
||,o{)의 정확한 뜻 · 링크
🗓️ 이력¶
- 2026-11-09: 사용자마다 표가 있나, 1:N은 표 개수인가,
column_meta와dataset_meta관계를 4번 물음 → 일지