3-5 연습 문제 — 값을 따라가
연결된 행을 찾습니다
이번 강의에서 배운 것 — 자식의 FK → 부모의 PK, 그리고 참조 무결성. 오늘은 주문에서 회원 이름까지 직접 따라가 보고, 어떤 저장과 삭제가 데이터베이스에게 거부되는지 판정해 봅니다.
섹션 3 에서의 위치
완료 ✓
강의 완료 ✓
지금 여기
테이블 직접 만들기
2-4에서 만든 AI 과외 선생님 세션을 그대로 쓰세요. 막힐 때 답부터 묻지 말고 — “이 삭제가 왜 막히는지 판단하려면 무엇부터 확인해야 해?” 처럼 판단 순서를 물어보세요. 다 푼 뒤에는 본인이 적은 근거를 붙여서 검증받는 게 가장 남습니다.
문제 1. 이 주문은 누구의 주문일까요
아래는 shop DB 의 실제 데이터 일부입니다. 세 표를 오가며 값을 따라가 보세요.
| member_id (PK) | member_name |
|---|---|
| 1 | 김철수 |
| 2 | 이영희 |
| 3 | 박민수 |
| 10 | 한예원 |
| 15 | 권현석 |
| 16 | 노수아 |
| order_id (PK) | member_id (FK) | ordered_at |
|---|---|---|
| 2 | 2 | 2025-02-15 |
| 3 | 3 | 2025-03-01 |
| 8 | 2 | 2025-05-05 |
| 13 | 10 | 2025-07-15 |
| 18 | 2 | 2025-09-15 |
| 24 | 15 | 2025-11-15 |
| 33 | 2 | 2026-02-05 |
| product_id (PK) | product_name | category_id (FK) |
|---|---|---|
| 1 | 라운드 티셔츠 | 1 |
| 9 | 무선 이어폰 | 2 |
| 16 | 프리미엄 그래놀라 | 3 |
네 가지를 각각 찾아보세요. 어떤 값을 들고 어느 표로 갔는지까지 함께 적으면 좋습니다.
| # | 찾아야 할 것 |
|---|---|
| ① | order_id 3 번 주문은 어느 회원의 주문인가 |
| ② | order_id 13 번 주문은 어느 회원의 주문인가 |
| ③ | 이영희 회원이 한 주문의 order_id 를 모두 고르면 |
| ④ | product_id 9 (무선 이어폰) 은 어느 카테고리 상품인가 |
정답 보기
| # | 답 | 따라간 경로 |
|---|---|---|
| ① | 박민수 | 주문 3 → member_id 3 → members 3번 → 박민수 |
| ② | 한예원 | 주문 13 → member_id 10 → members 10번 → 한예원 |
| ③ | 2 · 8 · 18 · 33 (4건) | 이영희 = member_id 2 → orders 에서 member_id 가 2인 행 |
| ④ | 전자기기 | 상품 9 → category_id 2 → categories 2번 → 전자기기 |
①·②·④는 자식에서 부모로 가는 방향입니다. 자식 행의 외래키 값을 들고 부모 테이블의 기본키를 찾아가면 됩니다 — 답은 언제나 한 행이에요. 부모의 기본키는 중복이 없으니까요.
③은 방향이 반대라서 조금 다릅니다. 부모(이영희)에서 출발해 그 번호를 가리키고 있는 자식 행들을 모으는 겁니다. 이쪽은 답이 여러 행일 수 있어요 — 한 부모를 여러 자식이 가리킬 수 있으니까요.
문제 2. 부모와 자식, 화살표는 어느 쪽으로
shop DB 에는 외래키 관계가 네 쌍 있습니다. 각 쌍에서 어느 쪽이 부모이고 어느 쪽이 자식인지 고른 뒤, 화살표를 자식.컬럼 → 부모.컬럼 형태로 적어보세요.
| # | 두 테이블 |
|---|---|
| ① | members 와 orders |
| ② | categories 와 products |
| ③ | orders 와 order_items |
| ④ | products 와 order_items |
판단 기준은 하나입니다 — 기본키를 가지고 가리킴을 받는 쪽이 부모, 그 값을 자기 컬럼에 적어둔 쪽이 자식. “어느 테이블이 더 중요한가”로 고르면 틀립니다.
정답 보기
| # | 부모 | 자식 | 화살표 (자식 FK → 부모 PK) |
|---|---|---|---|
| ① | members | orders | orders.member_id → members.member_id |
| ② | categories | products | products.category_id → categories.category_id |
| ③ | orders | order_items | order_items.order_id → orders.order_id |
| ④ | products | order_items | order_items.product_id → products.product_id |
여기서 얻어갈 게 두 가지 있습니다.
첫째, order_items 는 자식 역할을 두 번 합니다. 부모가 orders 와 products 둘이에요. 한 테이블이 외래키를 여러 개 가질 수 있습니다.
둘째, orders 는 members 의 자식이면서 동시에 order_items 의 부모입니다. 부모·자식은 테이블에 영구히 붙은 신분이 아니라 — 관계 하나하나마다 정해지는 역할이에요.
방향은 언제나 자식의 FK 에서 부모의 PK 로. 관계가 몇 개든, 표가 몇 개든 이 방향은 바뀌지 않습니다.
문제 3. 이 시도는 성공할까요, 차단될까요
우리 shop DB 에 아래 다섯 가지를 시도합니다. 각각 성공하는지 차단되는지 판정하고 이유를 적으세요.
| # | 시도 |
|---|---|
| ① | orders 에 member_id = 999 인 새 주문을 저장 |
| ② | members 에서 20번 백서윤 회원을 삭제 |
| ③ | members 에서 1번 김철수 회원을 삭제 |
| ④ | products 에 category_id = 9 인 새 상품을 저장 |
| ⑤ | orders 에 member_id 를 비운 채(NULL) 새 주문을 저장 |
members 는 120번, 5번뿐입니다. 백서윤(20번)을 가리키는
주문은 없고, 김철수(1번)를 가리키는 주문은 6건(1 · 6 · 14 · 20 · 27 · 34)
있습니다. 그리고 categories 는 1orders.member_id 는 NOT NULL 입니다.
정답 보기
| # | 결과 | 이유 |
|---|---|---|
| ① | 차단 | members 에 999번이 없습니다 — 없는 부모는 가리킬 수 없어요 |
| ② | 성공 | 백서윤을 가리키는 자식 행이 하나도 없습니다 |
| ③ | 차단 | 주문 6건이 이 회원을 가리키고 있습니다 |
| ④ | 차단 | categories 는 1~5번뿐입니다 — 9번 카테고리가 없어요 |
| ⑤ | 차단 | orders.member_id 가 NOT NULL 이라서 — 외래키 때문이 아닙니다 |
①과 ④는 같은 규칙입니다. 테이블만 다를 뿐, 둘 다 존재하지 않는 부모를 가리키려 한 시도예요. 여기서 꼭 짚을 것 — 잘못된 값이 저장된 뒤에 문제가 되는 게 아니라, 저장 자체가 거부됩니다. 참조 무결성은 사후 점검이 아니라 사전 차단이에요.
②와 ③의 차이가 삭제 규칙의 전부입니다. 가리키는 자식이 있으면 부모를 지울 수 없고, 없으면 지울 수 있습니다. 백서윤이 삭제 가능한 이유는 특별해서가 아니라 그저 자식이 없어서예요.
⑤가 오늘의 함정입니다. 차단되는 건 맞지만 이유가 다릅니다. 외래키가 NULL 을 금지하는 게 아니라, 우리가 이 컬럼을 NOT NULL 로 정했기 때문이에요. 만약 NULL 을 허용하도록 설계된 외래키 컬럼이라면 “아직 연결된 부모가 없다” 는 뜻으로 비워둘 수 있습니다.
NULL 을 허용하는 외래키는 개념 설명을 위한 가상의 설계입니다. 실제
shop DB 의 orders.member_id 와 products.category_id 는 둘 다 NOT NULL 이에요.
문제 4 (도전). 이 말들은 맞을까요, 틀릴까요
외래키를 처음 배우면 자주 나오는 말들입니다. 각각 참인지 거짓인지 판정하고, 거짓이라면 어디가 틀렸는지 한 줄로 고쳐 쓰세요.
| # | 진술 |
|---|---|
| ① | members 와 orders 에 member_id 라는 같은 이름의 컬럼이 있으니, 따로 규칙을 적지 않아도 두 테이블은 연결된다 |
| ② | 외래키 컬럼에는 NULL 을 넣을 수 없다 — 외래키가 곧 NOT NULL 이다 |
| ③ | member_id 가 999 인 주문은 일단 저장되고, 나중에 조회할 때 회원을 못 찾아 문제가 된다 |
| ④ | 회원 행이 있으려면 그 회원의 주문이 최소 한 건은 있어야 한다 |
| ⑤ | orders 는 members 의 자식이면서 동시에 order_items 의 부모다 |
| ⑥ | shop DB 의 외래키는 모두 부모 테이블의 기본키를 가리킨다 |
정답 보기
| # | 판정 | 근거 |
|---|---|---|
| ① | 거짓 | 이름이 같은 건 사람이 읽기 쉬우라는 관례일 뿐입니다 |
| ② | 거짓 | 빈값 규칙은 외래키와 별개로 정하는 약속입니다 |
| ③ | 거짓 | 저장된 뒤 문제가 되는 게 아니라 저장 자체가 거부됩니다 |
| ④ | 거짓 | 부모는 자식 없이도 존재할 수 있습니다 — 20번 백서윤 |
| ⑤ | 참 | 부모·자식은 관계마다 정해지는 역할입니다 |
| ⑥ | 참 | 우리 샘플 DB 의 외래키 네 개는 전부 부모의 PK 를 가리킵니다 |
① — 연결을 만드는 건 이름이 아니라 규칙입니다.
설계도에 이 한 줄이 실제로 있어야 합니다.
CONSTRAINT fk_orders_member
FOREIGN KEY (member_id) REFERENCES members(member_id)이 규칙이 없으면 데이터베이스가 아는 건 “두 컬럼의 이름이 같다” 는 사실뿐이라, 999를 넣어도 막아주지 않습니다. 그리고 반대도 성립해요 — 컬럼 이름이 서로 달라도 FOREIGN KEY 규칙으로 연결할 수 있습니다.
② — 외래키와 NOT NULL 은 별개의 약속입니다.
외래키가 NULL 을 허용하도록 설계됐다면 “아직 연결된 부모가 없다” 는 뜻으로 비워둘 수 있습니다. 우리 orders.member_id 를 비울 수 없는 건 그 컬럼에 NOT NULL 을 따로 걸어두었기 때문이에요.
③ — 순서를 정확히 기억하세요.
데이터베이스는 저장하기 전에 부모 테이블을 확인합니다. 999번 회원이 없으면 그 자리에서 오류를 내고 거부해요. “잘못된 데이터가 들어간 뒤 나중에 깨진다” 가 아니라 “애초에 들어가지 못한다” 입니다.
④ — 가입만 하고 주문하지 않은 회원은 정상입니다.
20번 백서윤 회원은 members 에는 있지만 주문이 없습니다. 오류도, 누락도 아니에요. 자식이 부모 없이 존재할 수 없을 뿐, 부모는 자식 없이 얼마든지 존재할 수 있습니다.
⑤ — 한 테이블이 두 역할을 겸합니다.
orders.member_id 로 보면 orders 는 자식이고, order_items.order_id 로 보면 orders 는 부모입니다. 그래서 주문 1번을 지우려 하면 order_items 때문에 차단돼요.
⑥ — 다만 문법적으로는 다른 선택지도 있습니다.
외래키가 UNIQUE 처럼 중복을 허용하지 않는 다른 키를 참조할 수도 있습니다. 우리 샘플 DB 가 전부 기본키를 가리키고 있을 뿐이에요. 입문 단계에서는 이 구조부터 확실히 익히면 충분합니다.
외래키는 “값이 같아 보이는 것”이 아니라 “연결이 보장되는 것”입니다. 그 보장을 만드는 건 컬럼 이름이 아니라 — 설계도에 적힌 FOREIGN KEY 규칙 이에요.
다 풀었다면
표 두 개를 보고 어느 쪽이 부모인지 바로 말할 수 있고, 어떤 저장·삭제가 왜 차단되는지 근거를 대며 설명할 수 있다면 오늘 목표는 달성입니다.
DBeaver 좌측 트리에서 shop → Tables → orders 를 더블클릭한 뒤 Foreign
Keys 탭을 열어보세요. fk_orders_member 가 어느 테이블의 어느 컬럼을
가리키는지 그대로 보입니다. 이어서 shop 을 더블클릭 → ER Diagram 탭을 열면
다섯 테이블의 화살표가 한눈에 보여요 — 모든 화살표가 자식에서 부모로 향하는지
확인해 보세요.
여기까지 오면 테이블을 만드는 재료를 전부 읽을 수 있게 됐습니다. 컬럼과 타입, 기본키, 그리고 외래키까지. 다음은 이 재료를 SQL 로 직접 적어서 새 테이블을 만들 차례입니다.