콘텐츠로 이동

ERD 관계와 카디널리티(1:N)

11/9(월)

한 줄 요약: 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{ (까마귀발) 없거나 여러 개
\|{ 하나 이상 여러 개

⚙️ 어떻게 동작하나

관계를 읽는 순서다.

  1. 상자 = 표 하나라고 읽는다. 서비스 전체에 그 표는 하나뿐이다.
  2. FK 열을 찾는다. 다른 표를 가리키는 열이다. 예: favorite.dataset_id.
  3. FK 값이 반복될 수 있는지 본다. 반복될 수 있으면 그 표가 N쪽이다.
  4. 반복 여부는 PK를 보면 안다.
  5. FK 열이 혼자서 PK이면 반복될 수 없다 → 1:1
  6. FK 열이 PK의 일부이거나 PK가 아니면 반복될 수 있다 → 1:N
  7. 그림에서는 까마귀발이 붙은 쪽 = 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번 물음 → 일지

🕸️ 연결된 주제