기본 키는 테이블에서 행 하나를 콕 집어 가리키는 열이고, 외래 키는 다른 테이블의 기본 키 값을 담아 두 표를 잇는 열입니다. 이 두 열 덕분에 회원 표와 주문 표를 따로 두고도 「이 주문은 누구 것인지」를 언제든 찾을 수 있죠. 저도 SQL 을 처음 배우면서 기본 키 외래 키라는 말이 가장 낯설었는데요, 이번 글에서는 두 키가 무엇이고 표와 표가 어떤 관계로 이어지는지 작은 문구점 예시로 함께 알아보겠습니다. 지난 글: 관계형 데이터베이스 구조: 테이블·행·열·스키마
SQL 기초 시리즈 2/12 · 전체 커리큘럼
오늘 배울 개념
엑셀에서 VLOOKUP 을 써 보셨다면 이미 키를 써 보신 셈인데요, 주문 시트의 「회원번호」로 회원 시트에서 이름을 찾아올 때 회원번호가 바로 키 역할을 하기 때문입니다. 비유하면 기본 키는 주민등록번호처럼 사람마다 하나뿐인 번호이고, 외래 키는 택배 송장에 적힌 그 번호입니다. 송장만 보고도 받는 사람을 찾을 수 있지만, 없는 번호를 적은 송장은 갈 곳이 없죠.
| 구분 | 기본 키(PK, Primary Key) | 외래 키(FK, Foreign Key) |
|---|---|---|
| 하는 일 | 내 테이블의 행을 하나씩 구별 | 다른 테이블의 행을 가리킴 |
| 값의 조건 | 겹치면 안 되고 비어 있으면 안 됨 | 가리키는 테이블에 있는 값이거나 NULL |
| 한 테이블에 | 최대 하나 | 여러 개 가능 |
| 엑셀로 치면 | 회원 시트의 회원번호 열 | 주문 시트에 적은 회원번호 열 |
PostgreSQL 제약 조건 문서에 따르면 기본 키는 값이 겹치지 않으면서(unique) 비어 있지도 않아야(not null) 하고, 한 테이블에 최대 하나만 둘 수 있습니다. 같은 문서는 외래 키를 「한 열의 값이 다른 테이블 어떤 행의 값과 일치해야 한다」는 제약으로 설명하고, 이것으로 두 테이블 사이의 참조 무결성(가리키는 값이 실제로 있는 상태)을 지킨다고 적고 있습니다.
예시로 이해하기
지난 글의 문구점에 주문 테이블을 붙여 보겠습니다(가상 데이터).
members (회원)
| member_id (PK) | name | city |
|---|---|---|
| 1 | 김하늘 | 서울 |
| 2 | 이도윤 | 부산 |
| 3 | 박서연 | 서울 |
| 4 | 최지우 | 대전 |
orders (주문)
| order_id (PK) | member_id (FK) | ordered_on |
|---|---|---|
| 101 | 1 | 2026-10-01 |
| 102 | 2 | 2026-10-03 |
| 103 | 1 | 2026-10-07 |
| 104 | 3 | 2026-10-12 |
| 105 | 1 | 2026-10-20 |
orders 에는 회원 이름이 없고 member_id 만 있는데요, 주문 105의 주인이 궁금하면 member_id 1을 들고 members 에서 1번 행을 찾으면 김하늘 님이 나옵니다. 이름이 바뀌어도 members 한 곳만 고치면 되죠.
PostgreSQL 문서는 이때 orders 처럼 가리키는 쪽을 참조하는 테이블, members 처럼 가리켜지는 쪽을 참조되는 테이블이라고 부르고, SQLite 문서는 각각 자식 테이블·부모 테이블이라고 부릅니다.
개념 그림: 주문 표의 외래 키가 회원 표의 기본 키를 가리킵니다
스키마로 적으면 이렇습니다. 문법은 6회부터 다루니 PRIMARY KEY 와 REFERENCES 두 단어만 눈여겨보시면 됩니다.
CREATE TABLE members (
member_id INTEGER PRIMARY KEY,
name VARCHAR(50),
city VARCHAR(20)
);
CREATE TABLE orders (
order_id INTEGER PRIMARY KEY,
member_id INTEGER REFERENCES members (member_id),
ordered_on DATE
);이렇게 만들어 두면 members 에 없는 9번 회원의 주문을 넣으려 할 때 PostgreSQL 이 외래 키 위반 오류를 내고 받아들이지 않습니다. 이미 있는 2번으로 회원을 하나 더 넣으려 해도 기본 키 위반으로 막히죠.
표와 표는 어떤 관계로 이어질까요?
MySQL 용어집은 데이터베이스가 일대일·일대다·다대일 같은 관계를 표현하고 지킨다고 설명하는데요, 처음 배울 때는 세 가지로 나눠 보면 충분합니다.
| 관계 | 뜻 | 예시 | 키를 두는 법 |
|---|---|---|---|
| 1:1 (일대일) | 한 행이 상대 표의 행 하나와만 이어짐 | 회원 – 회원 상세 정보 | 외래 키 열이 그 표의 기본 키도 겸함 |
| 1:N (일대다) | 한 행이 상대 표의 여러 행과 이어짐 | 회원 – 주문 | 「다」 쪽(주문)에 외래 키 |
| N:M (다대다) | 양쪽 모두 여러 행과 이어짐 | 주문 – 상품 | 연결 표를 따로 만듦 |
위 예시는 1:N 입니다. 김하늘 님은 주문이 셋, 이도윤·박서연 님은 하나씩, 최지우 님은 아직 없죠. 외래 키를 「다」 쪽에 두는 이유는, 지난 글에서 본 것처럼 열 개수가 고정돼 있어 회원 표에 주문 번호 칸을 몇 개 만들어 둘지 정할 수 없기 때문입니다.
그렇다면 주문 하나에 상품이 여러 개 담기고, 상품 하나도 여러 주문에 들어간다면 어떻게 할까요? PostgreSQL 문서는 이런 다대다 관계를 외래 키 두 개를 가진 표로 만드는 예시를 보여 줍니다.
order_items (주문 상품, 연결 표)
| order_id (FK) | product_id (FK) | quantity |
|---|---|---|
| 101 | 1 | 2 |
| 101 | 2 | 1 |
| 102 | 3 | 3 |
| 103 | 1 | 1 |
| 104 | 2 | 2 |
| 105 | 3 | 1 |
product_id 는 볼펜(1)·노트(2)·형광펜(3)이 담긴 products 표를 가리킵니다. 주문 101에는 상품이 둘이고, 볼펜은 주문 101과 103에 들어 있죠.
이 표는 두 열을 묶어 PRIMARY KEY (order_id, product_id) 로 기본 키를 삼는데요, 열 하나로는 겹치지만 둘을 묶으면 같은 조합이 두 번 나오지 않기 때문입니다. SQLite 문서는 이렇게 여러 열로 된 키를 복합 키(composite key)라고 부릅니다.
개념 그림: 주문과 상품의 다대다 관계는 주문 상품 표가 가운데서 잇습니다
기본 키는 무엇으로 고를까요?
MySQL 용어집은 기본 키 값을 고를 때 다른 데서 가져온 의미 있는 값(자연 키)보다 아무 의미 없는 일련번호(인조 키)를 고려하라고 권하고, 자연 키는 값이 바뀌면 손볼 곳이 많아진다고 설명합니다.
| 후보 | 예시 | 장점 | 주의할 점 |
|---|---|---|---|
| 인조 키 | 1, 2, 3… 회원번호 | 바뀔 일이 없음 | 번호만 봐서는 누구인지 모름 |
| 자연 키 | 이메일, 휴대폰 번호 | 따로 번호를 만들 필요 없음 | 사람이 바꿀 수 있고, 바뀌면 손볼 곳이 많음 |
| 복합 키 | (주문번호, 상품번호) | 연결 표에 잘 맞음 | 가리키는 쪽도 열 두 개를 들고 있어야 함 |
엑셀 시트를 표로 옮긴다면 저는 이 기준으로 기본 키를 골라 보려고 합니다.
- 이 열 값만으로 행이 하나로 정해지는가? 「이름」은 동명이인이 있으니 안 됩니다.
- 비어 있는 행이 없는가? 기본 키는 NULL 을 받지 않습니다.
- 값이 바뀔 일이 없는가? 바뀔 수 있다면 일련번호를 따로 두고, 이메일은 겹치지 않게만
UNIQUE를 겁니다.
PostgreSQL 문서는 관계형 이론상 모든 테이블에 기본 키가 있어야 하지만 PostgreSQL 이 이를 강제하지는 않으며, 그래도 따르는 편이 대개 좋다고 적고 있습니다.
연습 문제
1. (쉬움) 위 orders 표에서 기본 키와 외래 키는 각각 어느 열인가요? 외래 키는 어느 표를 가리키나요?
정답 보기
기본 키는 order_id, 외래 키는 member_id 입니다. 외래 키 member_id 는 members 표의 기본 키 member_id 를 가리킵니다. 두 열의 이름이 같을 필요는 없지만 같게 지으면 읽기 쉽죠.
2. (보통) 다음 중 orders 에 넣으려 할 때 막히는 행을 모두 고르세요.
(가) (106, 4, 2026-10-25) (나) (107, 9, 2026-10-26) (다) (103, 2, 2026-10-27)
정답 보기
(나)와 (다)입니다. (나)는 members 에 9번 회원이 없어 외래 키에 걸리고, (다)는 주문번호 103이 이미 있어 기본 키에 걸립니다. (가)는 4번 최지우 님의 첫 주문이라 들어갑니다.
3. (보통) 강의 사이트에 「수강생」 표와 「강의」 표가 있습니다. 수강생 한 명이 여러 강의를 듣고, 강의 하나를 여러 수강생이 듣는다면 어떤 관계이고, 표를 어떻게 설계할까요?
정답 보기
다대다(N:M) 관계입니다. 「수강 신청」 같은 연결 표를 만들고 수강생 번호와 강의 번호를 외래 키로 둡니다. 같은 사람이 같은 강의를 두 번 신청하지 못하게 하려면 두 열을 묶어 기본 키로 삼으면 됩니다.
헷갈리기 쉬운 것
- 외래 키 열에 NULL 은 들어갈 수 있습니다: PostgreSQL 문서에 따르면 외래 키 열이 NULL 이면 일치하는 행이 없어도 제약에 걸리지 않습니다. 「회원 없는 주문」을 막고 싶다면 외래 키 열에
NOT NULL을 함께 걸어야 합니다. - 가리켜지는 행을 지우면 기본으로는 막힙니다: 주문이 남아 있는 2번 회원을 지우려 하면 오류가 나는데요, 외래 키를 만들 때
ON DELETE CASCADE(주문도 함께 삭제)나SET NULL(외래 키를 NULL 로) 같은 동작을 고를 수 있습니다. PostgreSQL 문서는 두 표가 서로 독립된 대상이면 막아 두는 쪽이 더 알맞다고 설명하는데요, 회원과 주문도 그런 경우로 볼 수 있습니다. - 제품마다 외래 키를 지키는 정도가 다릅니다: SQLite 는 외래 키 검사가 기본으로 꺼져 있어
PRAGMA foreign_keys = ON;으로 연결마다 켜야 합니다. 외래 키를 적어 두었다고 늘 검사된다고 보지 마시고 쓰는 제품 문서를 확인하시길 권합니다.
개념 그림: 외래 키는 없는 회원번호만 막고, 비어 있는 값은 막지 않습니다
마치며
기본 키는 행을 하나씩 구별하는 열, 외래 키는 그 기본 키를 가리켜 두 표를 잇는 열이고, 이 둘로 1:1·1:N·N:M 관계를 표현합니다. 엑셀에서 VLOOKUP 으로 찾아오던 회원번호가 데이터베이스에서는 약속으로 지켜진다고 생각하시면 이해가 쉬울 겁니다. 저도 시리즈를 따라가며 공부하고 있으니 전체 커리큘럼을 참고해 주세요. 다음 글에서는 표를 왜 여러 개로 나누는지, 정규화를 다룹니다.
자주 묻는 질문
- 기본 키와 외래 키의 차이는 무엇인가요?
- 기본 키는 자기 테이블의 행을 하나씩 구별하는 열로, 값이 겹치거나 비어 있으면 안 되고 테이블마다 최대 하나입니다. 외래 키는 다른 테이블의 기본 키(또는 UNIQUE 열) 값을 가리키는 열로, 가리키는 테이블에 없는 값은 넣을 수 없으며 한 테이블에 여러 개 둘 수 있습니다.
- 다대다(N:M) 관계는 어떻게 만드나요?
- 두 테이블 사이에 연결 표를 하나 더 만들고, 양쪽 테이블의 기본 키를 외래 키로 담습니다. 주문과 상품이라면 주문번호와 상품번호를 담은 주문 상품 표를 두고, 두 열을 묶어 기본 키로 삼는 방식이 PostgreSQL 문서에 예시로 나옵니다.
- primary key(PK)는 꼭 있어야 하나요?
- 관계형 이론에서는 모든 테이블에 기본 키가 있어야 한다고 보지만, PostgreSQL 은 이를 강제하지 않습니다. 다만 같은 행이 두 번 들어가는 일을 막고 다른 표가 가리킬 대상이 되므로 두는 편이 대개 좋습니다.
관련 글 더 보기
- 관계형 데이터베이스 구조: 테이블·행·열·스키마 - SQL 기초 시리즈 1회, 테이블과 스키마
- 데이터베이스란? 엑셀 시트와 무엇이 다를까 - SQL 기초 시리즈 0회
- VLOOKUP XLOOKUP 차이 | 엑셀 찾기 함수, 언제 무엇을 쓸까 - 엑셀에서 키 값으로 다른 표의 정보를 찾아오는 방법
