바이브 코딩 시대의 데이터베이스4-7 모델링 종합 실습 ⭐
테크타니 LEARN — 섹션 04 · DB 모델링

모델링 종합 실습 —
요구사항에서 ERD까지

섹션 4의 마지막 강의입니다. 새로운 이론은 없어요. 대신 지금까지 배운 판단을 한 번에 사용합니다. 기존 쇼핑몰 DB에 “회원이 상품에 별점과 후기를 남기는 기능”을 추가해 볼 거예요 — 요구사항을 질문으로 채우고, 테이블과 관계를 정하고, 작은 사례로 검산하는 데까지 직접 완성합니다.

난이도보통소요9분준비물4-1 ~ 4-6 완료오늘설계 한 바퀴 · 종합 실습 ⭐

이번 섹션의 여정

4-5 정규화

완료 ✓

4-6 실무 ERD 분석

완료 ✓

4-7 모델링 종합 실습

지금 여기

섹션 5 SQL 핵심 문법

설계한 구조에서 꺼내기

오늘 만들 것 — 상품 후기 기능

우리 shop에는 회원, 카테고리, 상품, 주문, 주문상세 다섯 테이블이 있습니다. 여기에 기능 하나를 얹어 볼게요.

오늘의 업무 문장“회원이 상품에 별점과 후기를 남긴다.”
⚠ 가상 예시입니다

이번에 설계할 product_reviews실습 DB shop 에 존재하지 않는 새 테이블입니다. 지금 DBeaver 를 열어도 보이지 않아요. 다만 이 테이블이 연결될 membersproducts실제 스키마 그대로이므로, 관계와 키는 진짜 구조에 맞춰 정합니다.

이 문장 하나만 들고 바로 테이블을 그리기 시작하면 — 거의 반드시 다시 고치게 됩니다. 순서를 지켜서 가겠습니다.

오늘 밟을 다섯 단계
① 요구사항의 빈칸을 질문으로 채운다
② 새 테이블과 한 행의 의미를 정한다
③ 키 · 관계 · 제약을 업무 문장에서 옮긴다
④ 작은 사례로 검산한다
⑤ AI에게 설계가 아니라 검토를 맡긴다

① 요구사항의 빈칸부터 질문합니다

“회원이 상품에 후기를 남긴다” 는 문장만으로는 설계할 수 없습니다. 이 문장에는 정해지지 않은 것이 너무 많아요. 그러니 테이블을 만들기 전에 업무 담당자에게 물어봐야 합니다.

설계 전에 반드시 물어볼 질문
① 한 회원이 같은 상품에 후기를 여러 번 쓸 수 있는가?
② 구매하지 않은 상품에도 후기를 쓸 수 있는가?
③ 별점의 범위는 몇 점부터 몇 점까지이고, 소수점을 허용하는가?
④ 별점과 후기 글은 각각 필수인가 선택인가?
⑤ 후기가 하나도 없는 상품 · 회원이 있어도 되는가?
⑥ 작성 시각과 수정 시각을 기록해야 하는가?
⑦ 후기를 삭제하면 어떻게 처리하는가?

이번 실습에서는 담당자에게 이렇게 확인받았다고 하겠습니다.

확인한 요구사항 (이번 실습의 결정)
질문이번 실습의 결정
같은 회원이 같은 상품에 여러 번 작성?최대 한 번 · 이후에는 기존 후기를 수정
별점필수 · 1 ~ 5 사이의 정수
후기 글선택 입력 — 비워도 된다
시각작성 시각 · 수정 시각을 기록
후기 0개인 회원 · 상품허용
여기까지가 '확인한 규칙' — 구조로 옮길 수 있는 내용입니다

그리고 이번 범위에서 정하지 않은 것도 있습니다. 이것을 따로 적어두는 게 중요해요.

아직 정하지 않은 규칙 (다음 범위로 미룸)
항목이번 버전의 처리
구매자만 후기를 쓸 수 있는가?이번 버전에서는 조건으로 삼지 않는다
후기 삭제 정책다음 범위로 미룬다
후기 이미지 첨부이번 범위 밖
미정 항목은 '없는 규칙'이 아니라 '아직 답을 못 받은 규칙'입니다 — 지워버리지 말고 남겨 둡니다
⭐ 첫 단계의 핵심

확인한 규칙과 아직 정하지 않은 규칙을 섞지 않는 것 — 이것이 설계의 첫 단계입니다. 섞이는 순간, 내가 임의로 가정한 내용이 요구사항인 척하기 시작해요.

② 새 테이블과 한 행의 의미를 정합니다

회원 정보는 members에, 상품 정보는 products에 이미 있습니다. 새로 필요한 건 회원과 상품 사이에 생긴 후기라는 기록이에요. 그래서 테이블 하나를 새로 만듭니다 — product_reviews.

그림보다 먼저 정할 것은 4-1에서 배운 그것입니다.

한 행의 의미product_reviews 의 한 행 = 한 회원이 한 상품에 작성한 후기 한 건

한 행의 의미가 정해지면 어떤 값이 필요한지가 따라 나옵니다.

product_reviews 에 담을 값 (가상 설계)
컬럼규칙
review_id후기 번호기본키
member_id작성한 회원 번호외래키 · 필수
product_id평가한 상품 번호외래키 · 필수
rating별점정수 1 ~ 5 · 필수
review_content후기 내용선택 — 비워도 된다
created_at작성 시각필수
updated_at수정 시각아직 수정하지 않았다면 비어 있다
일곱 개뿐입니다 — 회원 이름도, 상품 이름도, 상품 가격도 여기 없습니다

여기서 넣고 싶어지는 컬럼이 하나씩 떠오를 거예요. 후기 목록에 회원 이름이랑 상품 이름도 같이 보여야 하는데, member_name이랑 product_name도 넣어둘까? — 4-5에서 다룬 그 유혹입니다.

후기에는 후기에만 속한 사실만

회원 이름은 members 의 사실이고, 상품 이름은 products 의 사실입니다. 후기에 복사해 두면 회원이 이름을 바꾸는 순간 두 곳의 값이 달라져요. 후기 화면에 함께 보여주는 건 저장이 아니라 조회의 일입니다 — 다음 섹션에서 배울 JOIN 이 그 역할을 합니다.

반대로 rating, review_content, created_at그 후기에만 속한 사실입니다. 다른 어느 테이블에도 주인이 없어요. 그래서 여기에 둡니다.

③ 키 · 관계 · 제약을 업무 문장에서 옮깁니다

먼저 입니다. 후기 한 건은 review_id로 구별합니다. member_id는 기존 회원을, product_id는 기존 상품을 가리키는 외래키예요.

다음은 관계입니다. 4-2에서 배운 대로 양쪽 방향에서 문장을 만들어 봅니다.

관계를 양쪽으로 읽기
방향읽은 문장표기
회원 → 후기회원 한 명은 후기가 없을 수도, 여러 개일 수도 있다0..N
후기 → 회원후기 한 건에는 작성자가 정확히 한 명 있다1..1
상품 → 후기상품 하나는 후기가 없을 수도, 여러 개가 달릴 수도 있다0..N
후기 → 상품후기 한 건은 상품 정확히 하나에 속한다1..1
양쪽 모두 1:N — members 와 products 가 부모, product_reviews 가 자식입니다

order_itemsordersproducts 두 부모를 참조했던 것과 같은 모양이에요. product_reviews도 부모가 둘입니다.

members  1..1 ───── 0..N  product_reviews  0..N ───── 1..1  products

product_reviews.member_id  → members.member_id
product_reviews.product_id → products.product_id

그런데 여기서 멈추면 요구사항 하나가 빠집니다.

관계선이 막지 못하는 규칙

“한 회원은 같은 상품에 후기를 최대 한 번” 은 관계선만으로 지켜지지 않습니다. 지금 구조는 김철수 회원이 상품 23번에 후기를 열 개 써도 아무 문제 없이 저장해요. 두 외래키는 “존재하는 회원·상품인가”만 검사하기 때문입니다.

그래서 관계선 밖의 제약을 따로 적습니다.

선 밖에 적어야 하는 제약
제약업무 문장으로 읽으면
UNIQUE (member_id, product_id)같은 회원 번호와 상품 번호의 조합은 두 번 저장할 수 없다
rating — 정수 1 ~ 5 · NOT NULL별점은 1점부터 5점 사이의 정수이고 반드시 입력한다
review_content — NULL 허용후기 글은 비워도 된다
관계와 컬럼 규칙을 함께 적어야 요구사항이 실제 구조가 됩니다
복합키가 아니라 UNIQUE 인 이유

(member_id, product_id) 를 기본키로 잡을 수도 있습니다. 다만 후기는 나중에 “후기 12번”처럼 한 건을 짧게 가리킬 일이 많아서, 여기서는 review_id 를 기본키로 두고 중복 금지는 UNIQUE 로 따로 걸었습니다. 어느 쪽이든 “같은 조합은 한 번뿐”이라는 규칙은 지켜져요.

설계 결정이 코드로 어떻게 옮겨지는지

지금까지의 결정을 그대로 CREATE TABLE로 옮기면 이렇게 됩니다. 3-6에서 읽는 법을 배웠으니 — 문법을 외우려 하지 말고, 위에서 내린 결정이 어느 줄이 되었는지만 따라가 보세요.

CREATE TABLE product_reviews (
  review_id      INT      NOT NULL AUTO_INCREMENT,
  member_id      INT      NOT NULL,
  product_id     INT      NOT NULL,
  rating         INT      NOT NULL,
  review_content TEXT         NULL,
  created_at     DATETIME NOT NULL,
  updated_at     DATETIME     NULL,
  PRIMARY KEY (review_id),
  UNIQUE KEY uq_review_member_product (member_id, product_id),
  CONSTRAINT chk_review_rating CHECK (rating BETWEEN 1 AND 5),
  CONSTRAINT fk_review_member
    FOREIGN KEY (member_id)  REFERENCES members(member_id),
  CONSTRAINT fk_review_product
    FOREIGN KEY (product_id) REFERENCES products(product_id)
);
설계 결정 → 코드 한 줄
우리가 내린 결정코드에서는 이 줄
후기 한 건을 구별한다PRIMARY KEY (review_id)
작성자는 기존 회원 한 명FOREIGN KEY (member_id) REFERENCES members(member_id)
대상은 기존 상품 하나FOREIGN KEY (product_id) REFERENCES products(product_id)
같은 회원·상품 조합은 한 번만UNIQUE KEY uq_review_member_product (member_id, product_id)
별점은 1 ~ 5 정수, 필수rating INT NOT NULL + CHECK (rating BETWEEN 1 AND 5)
후기 글은 선택review_content TEXT NULL
아직 수정 안 했으면 비어 있다updated_at DATETIME NULL
설계에서 한 줄씩 정한 것이 코드에서 한 줄씩 나타납니다 — 순서가 반대가 되면 안 됩니다
CHECK 제약은 MySQL 8.0.16 이상에서 실제로 검사합니다 — 2-1 에서 설치한 8.0.46 은 해당됩니다

shop의 다른 테이블들과 비교해 보면 새로 등장한 제약은 두 개뿐입니다. UNIQUE KEYCHECK — 둘 다 요구사항을 지키기 위해 우리가 직접 결정해서 추가한 줄이에요.

④ 작은 사례로 검산합니다

여기까지가 설계입니다. 그런데 설계는 그려놓고 끝내는 게 아니라 검산해야 합니다. 실제 값을 몇 개 넣어보고, 처음 확인한 요구사항대로 허용되거나 막히는지 확인하는 거예요.

우리 shop의 진짜 데이터를 쓰겠습니다. 회원 1번은 김철수, 상품 23번은 데이터 모델링 실전(32,000원, 도서)입니다.

검산 — 이 사례들이 어떻게 되어야 하는가
넣어보는 사례예상 결과지켜주는 규칙
다크 초콜릿(상품 20번)에 후기가 0개인 상태허용상품 → 후기는 0..N
김철수(1번)가 상품 23번에 별점 5점허용정상 회원 · 정상 상품 · 별점 범위 안
이영희(2번)도 상품 23번에 별점 4점허용회원·상품 조합이 다르다
김철수(1번)가 상품 23번에 두 번째 후기차단UNIQUE (member_id, product_id)
별점 6점차단CHECK — 별점 1 ~ 5
후기 글 없이 별점만 입력허용review_content 는 선택
회원 99번이 작성 (없는 회원)차단외래키 → members
상품 500번에 작성 (없는 상품)차단외래키 → products
이 결과가 ① 에서 확인한 요구사항과 전부 일치하면 — 현재 설계는 일관됩니다
김철수와 이영희는 실제로 상품 23번을 주문한 적이 있습니다 (주문 14 · 34 · 33) — 다만 이번 버전은 구매 여부를 조건으로 삼지 않습니다

여덟 줄을 하나씩 짚어보면, 우리가 표로 적어둔 요구사항이 하나도 빠짐없이 구조 어딘가에서 지켜지고 있다는 걸 확인할 수 있습니다. 반대로 어떤 요구사항이 어느 줄에서도 지켜지지 않는다면 — 그건 설계에 구멍이 있다는 신호예요.

⭐ 검산이 실패했을 때

그림을 예쁘게 고칠 것이 아니라, 빠진 업무 규칙이나 제약을 찾아야 합니다.

검산에서 걸러지지 않는 것도 있습니다. 예를 들어 “구매하지 않은 상품에도 후기를 쓸 수 있다” 는 지금 구조에서 그냥 통과해요. 이건 버그가 아니라 — 우리가 ①에서 “이번 버전에서는 조건으로 삼지 않는다”고 결정했기 때문입니다. 확인한 규칙과 미정 규칙을 분리해 뒀기 때문에, 통과한 이유를 설명할 수 있는 거죠.

⑤ AI는 설계자가 아니라 검토자로 씁니다

완성한 설계는 AI에게 검토를 맡길 수 있습니다. 다만 맡기는 방식이 결과를 완전히 바꿔요.

“후기 기능 ERD 만들어줘”
  • 말하지 않은 규칙중복 작성 · 별점 범위를 AI가 임의로 가정한다

  • 그럴듯한 결과맞는지 틀린지 판단할 기준이 나에게 없다

  • 남는 것내가 이해하지 못한 구조 하나

“요구사항과 제 설계를 비교해 주세요” ✓
  • 기준이 있다내가 확인한 요구사항이 채점표가 된다

  • 반례를 받는다내 구조가 막지 못하는 사례를 찾아준다

  • 남는 것근거를 아는 구조 + 검토받은 목록

새 ERD를 다시 만들어달라고 하지 마세요. 확인한 요구사항과 내 1차 설계를 함께 주고 — 반례를 찾게 하는 겁니다.

이렇게 요청하면 됩니다.

아래 요구사항과 제 설계를 비교해 주세요.
새 ERD를 다시 만들지 말고 다음 네 가지를 구분해서 알려주세요.

1. 제가 임의로 가정한 내용
2. 현재 구조가 막지 못하는 잘못된 사례
3. 빠진 제약
4. 최소 수정안
최종 결정은 사람의 몫입니다

AI가 찾아준 제안을 그대로 반영하는 것이 아니라 — 실제 업무 규칙과 비교해 받아들일지 말지 정하는 것까지가 설계입니다. AI는 반례를 잘 찾지만, 우리 서비스의 업무 규칙은 모르니까요.

섹션 4가 쌓아 올린 것

오늘 한 바퀴 돌아본 과정은 사실 4-1부터 하나씩 배운 것들입니다.

섹션 4 전체 정리
강의무엇을 쌓았나오늘 어디서 썼나
4-1 관계의 구조무엇을 기록할지 · 한 행은 무엇인지② 한 행 = 후기 한 건
4-2 부모 · 자식외래키를 어느 쪽에 둘지③ 후기가 회원·상품 번호를 가진다
4-3 관계의 종류1:1 · 1:N · N:M 을 문장으로 가려내기③ 양쪽 모두 1:N
4-4 ERD 읽는 법최소·최대 개수를 기호로 읽기③ 0..N 과 1..1
4-5 정규화사실의 주인을 한 곳에 두기② 이름을 복사하지 않는다
4-6 실무 ERD 분석관계선이 보여주지 않는 규칙 찾기③ 중복 금지를 따로 적는다
4-7 종합 실습요구사항 → 구조 → 검산오늘 전부
새 이론 없이, 배운 판단을 순서대로 이어 붙인 것이 오늘의 설계입니다
⭐ 섹션 4의 한 문장

좋은 모델은 그럴듯한 그림이 아니라 — 확인한 업무 규칙을 구조로 옮기고 실제 사례로 검산한 결과입니다.

한눈에 정리

단계하는 일후기 설계에서는
① 요구사항 질문빈칸을 질문으로 채우고 미정과 분리중복 작성 · 별점 범위 · 필수 여부 확인 / 삭제 정책 보류
② 한 행의 의미새 테이블과 한 행의 뜻을 확정한 회원이 한 상품에 작성한 후기 한 건
③ 키 · 관계 · 제약업무 문장을 PK · FK · 제약으로 옮김PK review_id / FK 둘 / UNIQUE + 별점 1~5
④ 검산실제 값을 넣어 허용·차단을 확인김철수 — 상품 23번 사례 여덟 줄
⑤ AI 검토설계가 아니라 반례·누락·최소 수정안 요청요구사항 + 1차 설계를 함께 전달

관계선은 “존재하는 부모를 가리키는가”까지만 지켜줍니다. 중복 금지와 값의 범위처럼 선 밖의 규칙은 우리가 따로 적어야 해요. 그리고 그 모든 결정은 — 작은 사례를 넣어 검산했을 때 처음 확인한 요구사항과 같은 결과가 나와야 비로소 완성입니다.

여기까지가 데이터의 길을 만드는 일이었습니다. 이제 그 길에서 원하는 데이터를 실제로 꺼낼 차례예요. 모델링에서 만든 구조가 곧 SQL을 작성하는 지도가 됩니다.

NEXT LESSON5-1 SELECT 의 본질 — 섹션 5 시작

이제 설계한 구조에서 원하는 데이터를 실제로 꺼내봅니다. SQL 핵심 문법으로 이어집니다.

GO ▸ 섹션 5 시작하기