실무 ERD 분석 —
지금 한 행은 무엇인가
ERD에서 길을 찾았다고 답이 바로 나오지는 않습니다. 관계를 하나 지날 때마다 한 행의 뜻이 바뀌고, 같은 대상이 여러 번 나타나기도 해요. 오늘은 실습 DB의 실제 데이터를 따라가며 행의 단위를 확인하는 습관을 만듭니다.
이번 섹션의 여정
완료 ✓
완료 ✓
지금 여기
요구사항 → ERD
길을 찾았다고 답이 나오지는 않습니다
앞 강의까지 우리는 ERD에서 필요한 길을 찾는 법과, 각 사실을 알맞은 테이블로 나누는 이유를 배웠습니다.
그런데 길을 찾은 다음이 진짜 시작이에요. 관계를 하나 지날 때마다 행이 늘어날 수 있고, 같은 대상이 여러 번 나타날 수도 있으니까요.
이 두 질문만 놓치지 않으면 됩니다. 그럼 실제 데이터로 확인해 볼게요.
회원 한 명 → 주문 6건 → 주문상세 10행
우리 shop의 김철수 회원(member_id 1번) 에서 출발합니다.
회원 1행에서 출발했는데 주문으로 넘어가자 6행이 되고, 주문상세로 한 번 더 내려가자 10행이 됐습니다. 실제로 어떤 줄들인지 펼쳐 보겠습니다.
| order_id | product_id | product_name | quantity |
|---|---|---|---|
| 1 | 1 | 라운드 티셔츠 | 1 |
| 1 | 16 | 프리미엄 그래놀라 | 2 |
| 6 | 9 | 무선 이어폰 | 1 |
| 14 | 22 | SQL 입문서 | 1 |
| 14 | 23 | 데이터 모델링 실전 | 1 |
| 14 | 24 | AI 시대 개발자 | 1 |
| 20 | 8 | 패딩 점퍼 | 1 |
| 27 | 9 | 무선 이어폰 | 1 |
| 27 | 13 | 무선 마우스 | 1 |
| 34 | 23 | 데이터 모델링 실전 | 1 |
주문 하나가 상세 여러 줄로 펼쳐지기 때문에 주문 6건이 상세 10행이 됐어요.
10행과 11개는 서로 다른 질문의 답입니다
방금 표에서 quantity를 모두 더하면 11개입니다. 행은 분명 10줄인데 왜 숫자가 다를까요?
주문상세 한 행은 주문에 들어간 상품 한 종류를 뜻하고, 그 행 안의 수량은 1보다 클 수 있기 때문입니다. 김철수의 주문 1번에 담긴 프리미엄 그래놀라가 2개였죠.
| 질문 | 보는 것 | 답 |
|---|---|---|
| 주문상세가 몇 줄인가? | 행의 개수 | 10행 |
| 상품 수량을 모두 더하면 몇 개인가? | 행 안의 quantity 합 | 11개 |
이 둘을 섞으면 이런 일이 생깁니다
보고서에“김철수가 상품 10개를 샀다”고 잘못 적는다
재고 계산에서실제로 나간 개수보다 1개 적게 잡힌다
더 나쁜 건숫자가 비슷해서 틀린 줄 모르고 넘어간다
“몇 줄인가”주문상세 행을 센다 — 10행
“몇 개인가”행 안의 quantity 를 더한다 — 11개
결과어떤 숫자를 말하는지 스스로도, 듣는 사람도 안다
행 수와 수량 합계는 애초에 단위가 다릅니다. 같은 표에서 나왔다고 같은 종류의 숫자가 아니에요.
반대 방향 — 상품 하나에서 회원 네 명으로
이번에는 출발점을 바꿔서 상품 23번, 데이터 모델링 실전에서 시작해 보겠습니다.
| order_id | product_id | quantity | 그 주문의 member_id | 회원 |
|---|---|---|---|---|
| 5 | 23 | 1 | 5 | 최지훈 |
| 14 | 23 | 1 | 1 | 김철수 |
| 28 | 23 | 1 | 9 | 조성민 |
| 33 | 23 | 1 | 2 | 이영희 |
| 34 | 23 | 1 | 1 | 김철수 |
| 40 | 23 | 1 | 9 | 조성민 |
회원번호를 순서대로 늘어놓으면 5 · 1 · 9 · 2 · 1 · 9입니다. 여기서 중복을 빼면 남는 번호는 1 · 2 · 5 · 9 — 네 명이에요.
| 질문 | 보는 것 | 답 |
|---|---|---|
| 이 상품이 몇 개 주문에 들어갔는가? | 주문상세 행을 그대로 센다 | 6행 |
| 이 상품이 들어 있는 주문의 서로 다른 회원은 몇 명인가? | 중복 회원번호를 한 번씩만 센다 | 4명 |
현재 orders 에는 취소·결제 상태 컬럼이 없습니다. 그래서 이 네 명은
“상품 23번이 들어 있는 주문의 회원” 일 뿐이에요. 데이터에 없는 의미를 이름에
얹지 않는 것도 분석의 일부입니다.
경로를 지날 때마다 “한 행”을 다시 묻습니다
이제 두 경로를 나란히 놓고, 관계를 하나 지날 때마다 한 행의 뜻이 어떻게 바뀌는지 정리해 볼게요.
| 지금 있는 곳 | 한 행의 뜻 | 김철수 기준 |
|---|---|---|
| members | 회원 한 명 | 1행 |
| orders | 주문 한 건 | 6행 |
| order_items | 한 주문에 담긴 상품 한 종류 | 10행 |
| order_items 의 quantity 합 | 행이 아니라 낱개 수량 | 11개 |
| 지금 있는 곳 | 한 행의 뜻 | 상품 23번 기준 |
|---|---|---|
| products | 상품 한 개 | 1행 |
| order_items | 그 상품이 담긴 상세 한 줄 | 6행 |
| orders | 그 상세가 속한 주문 한 건 | 6행 |
| members (중복 그대로) | 주문마다 딸려온 회원 | 6행 |
| members (중복 제거) | 서로 다른 회원 | 4명 |
“지금 한 행은 회원인가, 주문인가, 주문상세인가?” 이 질문을 건너뛰면 숫자는 나오지만 그 숫자가 무엇인지 모르게 됩니다.
그리고 마지막에는 하나 더 정해야 해요. 중복을 그대로 셀지, 서로 다른 대상만 셀지.
섹션 5에서 여러 테이블을 한 번에 조회하면 결과 행이 예상보다 많아지는 순간이 옵니다. 그때 당황하지 않는 이유가 바로 오늘 만든 이 감각이에요. 문법은 그때 배우고, 지금은 값을 따라가는 눈만 챙겨 갑니다.
관계선이 보여주지 않는 규칙 ① — 중복
ERD의 선은 누가 누구와 연결되는지를 보여줍니다. 하지만 업무 규칙 중에는 선만으로 표현되지 않는 것들이 있어요.
아래 나오는 users, posts, post_likes, follows, rooms, reservations
는 설명을 위해 지어낸 가상 테이블입니다. 우리 실습 DB 에는 존재하지
않아요.
SNS의 좋아요를 생각해 봅시다. 사용자와 게시글 사이에 좋아요 테이블을 두면 누가 어떤 글을 좋아했는지는 깔끔하게 연결됩니다.
| user_id | post_id | 외래키가 보장하는 것 |
|---|---|---|
| 7 | 101 | 존재하는 사용자 · 존재하는 게시글만 참조 가능 |
| 7 | 101 | ← 그런데 이 두 번째 줄을 외래키는 막지 못합니다 |
같은 사용자가 같은 글에 좋아요를 두 번 저장하지 못하게 하려면 “이 두 번호의 조합은 중복될 수 없다” 는 규칙이 따로 필요합니다. (user_id, post_id) 조합에 거는 유일성 제약이죠.
order_items 의 기본키는 (order_id, product_id) 복합키입니다. 그래서
같은 주문에 같은 상품이 두 줄로 들어가는 일이 구조적으로 막혀 있어요. 좋아요
중복 금지와 정확히 같은 원리입니다.
팔로우는 한술 더 뜹니다. follows 테이블에 follower_id와 following_id가 나란히 있고 둘 다 users를 참조한다고 해볼게요.
| 저장하려는 행 | 외래키 판정 | 실제 업무에서는 |
|---|---|---|
| follower 7 → following 12 | 통과 | 정상 |
| follower 7 → following 7 | 통과 (7번은 실존 사용자) | 자기 자신 팔로우 — 막아야 함 |
| follower 7 → following 12 (또) | 통과 | 같은 사람 두 번 팔로우 — 막아야 함 |
관계선이 보여주지 않는 규칙 ② — 시간
객실 예약도 마찬가지입니다. 회원과 객실을 reservations 테이블로 연결하면 누가 어느 객실을 예약했는지는 표현됩니다. 그런데 예약 업무의 진짜 규칙은 대부분 시간에 있어요.
| 업무 규칙 | 관계선·외래키로 되는가 |
|---|---|
| 시작 시각 < 종료 시각 | 아니오 — 두 값 사이의 조건이라 선과 무관 |
| 같은 객실의 두 예약은 시간이 겹치면 안 된다 | 아니오 — 다른 행과 비교해야 알 수 있다 |
| 이미 지난 시각으로는 예약할 수 없다 | 아니오 — 지금 시각과 비교해야 한다 |
| 존재하지 않는 객실·회원은 예약할 수 없다 | 예 — 이건 외래키가 막아 줍니다 |
특히 시간 겹침은 성격이 다릅니다. 한 행만 봐서는 옳고 그름을 판단할 수 없고, 같은 객실의 다른 예약들과 비교해야만 알 수 있으니까요.
| 규칙의 성격 | 막는 곳 | 가상 예시 |
|---|---|---|
| 연결 대상이 실제로 존재하는가 | 외래키 (FK) | reservations.room_id → rooms |
| 같은 조합이 두 번 들어오지 않는가 | 유일성 제약 (UNIQUE · 복합 PK) | (user_id, post_id) 좋아요 중복 금지 |
| 한 행 안의 값들이 말이 되는가 | 행 조건 검사 (CHECK) | 시작 시각 < 종료 시각 |
| 다른 행·현재 시각과 비교해야 하는가 | 애플리케이션 로직 · 트랜잭션 | 같은 객실 시간 겹침 · 과거 시각 예약 |
ERD의 선은 누가 누구와 연결되는지를 보여줍니다. 중복 금지나 시간 충돌처럼 선만으로 부족한 규칙은 글과 제약으로 따로 남겨야 합니다.
ERD를 다 그린 뒤에 “이 그림에 안 적힌 규칙은 무엇인가?” 를 한 번 물어보세요. 실무에서 사고는 대부분 그 빈칸에서 납니다.
분석을 끝내기 전 세 가지 질문
여기에 하나만 더 붙이면 분석이 끝납니다. 관계선만으로 막지 못하는 업무 규칙은 무엇인가.
한눈에 정리
| 물어볼 것 | 왜 묻는가 | 오늘 확인한 것 |
|---|---|---|
| 지금 한 행은? | 관계를 지날 때마다 단위가 바뀐다 | shop — 회원 1행 → 주문 6행 → 주문상세 10행 |
| 셀까, 더할까? | 행 수와 수량 합계는 단위가 다르다 | shop — 주문상세 10행 ≠ 수량 합계 11개 |
| 중복을 뺄까? | 거슬러 올라가면 같은 대상이 반복된다 | shop — 상품 23번 → 상세 6행, 서로 다른 회원 4명 |
| 선 밖의 규칙은? | FK 는 연결만 보장한다 | 가상 예시 — 중복 금지 · 시간 겹침 · 과거 예약 |
ERD의 길을 따라갈 때마다 지금 한 행이 무엇인지, 중복을 뺄지, 관계선 밖에 남은 규칙은 무엇인지를 다시 묻습니다. 이 세 가지만 습관이 되면 처음 보는 ERD 앞에서도 무엇부터 봐야 할지 알게 돼요.
이제 남은 건 하나입니다. 읽고 분석했으니 — 직접 그려 볼 차례예요.