[DB-003][입문] 두 표를 JOIN으로 연결해 보기
페이지 정보

본문
[이번 수업]
데이터를 한 표에 모두 넣으면 고객 이름이 주문마다 반복되고 수정 실수가 생기기 쉽습니다. 고객과 주문을 두 표로 나눈 뒤 JOIN(조인, 관련 행을 조건으로 연결하는 연산)으로 다시 함께 조회합니다. INNER JOIN과 LEFT JOIN의 차이도 실행 결과로 확인합니다.
[선수지식]
DB-001의 데이터베이스·표·행·열과 DB-002의 기본 SQL을 알면 좋습니다. SQLite 명령줄 도구가 필요합니다.
[학습목표]
1. 기본키와 외래키로 표의 관계를 표현합니다.
2. ON 조건으로 두 표를 연결합니다.
3. INNER JOIN과 LEFT JOIN 결과를 구분합니다.
[핵심개념]
기본키(primary key)는 표에서 한 행을 유일하게 찾는 열입니다. 외래키(foreign key)는 다른 표의 기본키를 가리켜 관계를 만듭니다. 주문의 `customer_id`가 고객의 `id`를 참조하면 이름을 주문마다 복사하지 않아도 됩니다.
INNER JOIN은 ON 조건이 맞는 행만 반환합니다. 주문이 없는 고객은 결과에서 빠집니다. LEFT JOIN은 왼쪽 표의 모든 행을 남기고, 오른쪽에 일치하는 행이 없으면 그 열을 NULL(값 없음)로 채웁니다. 여러 주문을 합산할 때 `SUM`과 `GROUP BY`를 쓰며, `COALESCE`는 NULL을 0으로 바꿉니다.
[따라하기]
아래 내용을 `join.sql`로 저장하세요.
```sql
PRAGMA foreign_keys = ON;
DROP TABLE IF EXISTS orders;
DROP TABLE IF EXISTS customers;
CREATE TABLE customers (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
customer_id INTEGER NOT NULL REFERENCES customers(id),
amount INTEGER NOT NULL CHECK (amount >= 0)
);
INSERT INTO customers VALUES
(1, '민수'), (2, '지우'), (3, '서연');
INSERT INTO orders VALUES
(101, 1, 12000), (102, 1, 5000), (103, 2, 8000);
.headers on
.mode column
.print '--- INNER JOIN ---'
SELECT c.name, o.id AS order_id, o.amount
FROM customers AS c
INNER JOIN orders AS o ON o.customer_id = c.id
ORDER BY o.id;
.print '--- LEFT JOIN + 합계 ---'
SELECT c.name, COALESCE(SUM(o.amount), 0) AS total
FROM customers AS c
LEFT JOIN orders AS o ON o.customer_id = c.id
GROUP BY c.id, c.name
ORDER BY c.id;
```
macOS/Linux는 `sqlite3 lesson.db ".read join.sql"`, Windows PowerShell은 `sqlite3.exe lesson.db ".read join.sql"`을 실행합니다. SQLite가 없다면 공식 다운로드 안내를 먼저 따르세요. INNER JOIN 결과는 민수의 주문 두 건과 지우의 한 건, 총 3행입니다. LEFT JOIN 합계는 민수 17000, 지우 8000, 주문이 없는 서연 0까지 총 3행입니다. 같은 파일을 다시 실행해도 DROP 후 재생성되므로 결과가 같습니다.
[흔한 실수]
ON 조건을 빠뜨리면 두 표의 모든 조합이 생겨 행 수가 폭증합니다. `SELECT *`는 같은 이름의 열이 섞여 의미가 모호하므로 별칭과 필요한 열을 적으세요. LEFT JOIN 뒤 WHERE 절에서 오른쪽 표의 값을 무심코 제한하면 NULL 행이 빠져 INNER JOIN처럼 될 수 있습니다. 한 고객에 주문이 여러 개라 결과 행이 늘어나는 것은 관계상 정상입니다.
[보안 주의]
예제는 본인 소유의 로컬 DB에서만 실행하세요. 실제 프로그램에서는 사용자 값을 SQL 문자열에 이어 붙이지 말고 드라이버의 매개변수 바인딩을 사용해 SQL 삽입을 막습니다. 외래키와 CHECK는 데이터 정합성을 돕지만 권한 검사를 대신하지 않습니다. 운영 스키마 변경 전에는 백업과 복구 절차를 확인하고 최소 권한 계정을 사용합니다.
[직접 해볼 과제]
주문이 없는 ‘하늘’을 추가하고 LEFT JOIN 결과에 0으로 표시되는지 확인하세요. 이어서 `COUNT(o.id)` 열을 추가해 고객별 주문 수를 함께 출력합니다.
[확인문제]
1. 주문 표에 고객 이름 대신 customer_id를 두는 이유는 무엇인가요?
2. 주문이 없는 고객까지 보려면 INNER JOIN과 LEFT JOIN 중 무엇을 써야 하나요?
3. JOIN의 ON 조건을 빠뜨리면 어떤 문제가 생기나요?
[다음 학습]
전체 순환의 다음 수업은 OS-003 ‘프로세스와 스레드’입니다. 데이터베이스 과정을 이어가려면 DB-004 ‘인덱스와 실행 계획’을 학습하세요.
[공식 참고 자료]
SQLite SELECT와 JOIN: https://sqlite.org/lang_select.html
SQLite 외래키: https://sqlite.org/foreignkeys.html
SQLite CREATE TABLE: https://sqlite.org/lang_createtable.html
SQLite 명령줄 도구: https://sqlite.org/cli.html
데이터를 한 표에 모두 넣으면 고객 이름이 주문마다 반복되고 수정 실수가 생기기 쉽습니다. 고객과 주문을 두 표로 나눈 뒤 JOIN(조인, 관련 행을 조건으로 연결하는 연산)으로 다시 함께 조회합니다. INNER JOIN과 LEFT JOIN의 차이도 실행 결과로 확인합니다.
[선수지식]
DB-001의 데이터베이스·표·행·열과 DB-002의 기본 SQL을 알면 좋습니다. SQLite 명령줄 도구가 필요합니다.
[학습목표]
1. 기본키와 외래키로 표의 관계를 표현합니다.
2. ON 조건으로 두 표를 연결합니다.
3. INNER JOIN과 LEFT JOIN 결과를 구분합니다.
[핵심개념]
기본키(primary key)는 표에서 한 행을 유일하게 찾는 열입니다. 외래키(foreign key)는 다른 표의 기본키를 가리켜 관계를 만듭니다. 주문의 `customer_id`가 고객의 `id`를 참조하면 이름을 주문마다 복사하지 않아도 됩니다.
INNER JOIN은 ON 조건이 맞는 행만 반환합니다. 주문이 없는 고객은 결과에서 빠집니다. LEFT JOIN은 왼쪽 표의 모든 행을 남기고, 오른쪽에 일치하는 행이 없으면 그 열을 NULL(값 없음)로 채웁니다. 여러 주문을 합산할 때 `SUM`과 `GROUP BY`를 쓰며, `COALESCE`는 NULL을 0으로 바꿉니다.
[따라하기]
아래 내용을 `join.sql`로 저장하세요.
```sql
PRAGMA foreign_keys = ON;
DROP TABLE IF EXISTS orders;
DROP TABLE IF EXISTS customers;
CREATE TABLE customers (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
customer_id INTEGER NOT NULL REFERENCES customers(id),
amount INTEGER NOT NULL CHECK (amount >= 0)
);
INSERT INTO customers VALUES
(1, '민수'), (2, '지우'), (3, '서연');
INSERT INTO orders VALUES
(101, 1, 12000), (102, 1, 5000), (103, 2, 8000);
.headers on
.mode column
.print '--- INNER JOIN ---'
SELECT c.name, o.id AS order_id, o.amount
FROM customers AS c
INNER JOIN orders AS o ON o.customer_id = c.id
ORDER BY o.id;
.print '--- LEFT JOIN + 합계 ---'
SELECT c.name, COALESCE(SUM(o.amount), 0) AS total
FROM customers AS c
LEFT JOIN orders AS o ON o.customer_id = c.id
GROUP BY c.id, c.name
ORDER BY c.id;
```
macOS/Linux는 `sqlite3 lesson.db ".read join.sql"`, Windows PowerShell은 `sqlite3.exe lesson.db ".read join.sql"`을 실행합니다. SQLite가 없다면 공식 다운로드 안내를 먼저 따르세요. INNER JOIN 결과는 민수의 주문 두 건과 지우의 한 건, 총 3행입니다. LEFT JOIN 합계는 민수 17000, 지우 8000, 주문이 없는 서연 0까지 총 3행입니다. 같은 파일을 다시 실행해도 DROP 후 재생성되므로 결과가 같습니다.
[흔한 실수]
ON 조건을 빠뜨리면 두 표의 모든 조합이 생겨 행 수가 폭증합니다. `SELECT *`는 같은 이름의 열이 섞여 의미가 모호하므로 별칭과 필요한 열을 적으세요. LEFT JOIN 뒤 WHERE 절에서 오른쪽 표의 값을 무심코 제한하면 NULL 행이 빠져 INNER JOIN처럼 될 수 있습니다. 한 고객에 주문이 여러 개라 결과 행이 늘어나는 것은 관계상 정상입니다.
[보안 주의]
예제는 본인 소유의 로컬 DB에서만 실행하세요. 실제 프로그램에서는 사용자 값을 SQL 문자열에 이어 붙이지 말고 드라이버의 매개변수 바인딩을 사용해 SQL 삽입을 막습니다. 외래키와 CHECK는 데이터 정합성을 돕지만 권한 검사를 대신하지 않습니다. 운영 스키마 변경 전에는 백업과 복구 절차를 확인하고 최소 권한 계정을 사용합니다.
[직접 해볼 과제]
주문이 없는 ‘하늘’을 추가하고 LEFT JOIN 결과에 0으로 표시되는지 확인하세요. 이어서 `COUNT(o.id)` 열을 추가해 고객별 주문 수를 함께 출력합니다.
[확인문제]
1. 주문 표에 고객 이름 대신 customer_id를 두는 이유는 무엇인가요?
2. 주문이 없는 고객까지 보려면 INNER JOIN과 LEFT JOIN 중 무엇을 써야 하나요?
3. JOIN의 ON 조건을 빠뜨리면 어떤 문제가 생기나요?
[다음 학습]
전체 순환의 다음 수업은 OS-003 ‘프로세스와 스레드’입니다. 데이터베이스 과정을 이어가려면 DB-004 ‘인덱스와 실행 계획’을 학습하세요.
[공식 참고 자료]
SQLite SELECT와 JOIN: https://sqlite.org/lang_select.html
SQLite 외래키: https://sqlite.org/foreignkeys.html
SQLite CREATE TABLE: https://sqlite.org/lang_createtable.html
SQLite 명령줄 도구: https://sqlite.org/cli.html
- 이전글[OS-003][입문] 파일 경로와 권한을 안전하게 확인하기 26.08.30
- 다음글[BACK-003][입문] 할 일 REST API 설계하고 실행하기 26.08.30
댓글목록
등록된 댓글이 없습니다.
