두 테이블을 INNER JOIN으로 결합하고, 세 테이블 연결·WHERE 필터·GROUP BY 집계까지 결합한 실전 조인 쿼리를 작성합니다.
🎯 학습 목표
이 강을 마치면 INNER JOIN을 사용해 두 테이블을 실제로 연결하는 쿼리를 작성할 수 있습니다. 테이블 별칭을 활용해 가독성 높은 조인 쿼리를 만들고, WHERE와 ORDER BY를 결합해 원하는 데이터를 정확히 뽑아낼 수 있습니다. 또한 JOIN과 GROUP BY를 결합해 "회원별 총 주문 금액"처럼 여러 테이블에 걸친 집계 쿼리도 작성할 수 있게 됩니다.
🔨 두 테이블 INNER JOIN 기본 실습
이 섹션에서는 orders 테이블과 members 테이블을 INNER JOIN으로 연결하는 기본 실습을 진행합니다.
12강에서 배운 INNER JOIN 문법을 직접 실습해 봅니다. orders 테이블만으로는 회원 이름을 알 수 없었지만, members를 조인하면 한 번에 볼 수 있습니다.
-- 기본 INNER JOIN: 주문 + 회원 정보
SELECT
o.order_id AS 주문번호,
m.name AS 회원이름,
m.grade AS 등급,
o.order_date AS 주문일,
o.quantity AS 수량,
o.status AS 상태
FROM orders AS o
INNER JOIN members AS m ON o.member_id = m.member_id
ORDER BY o.order_date DESC;
결과에는 각 주문의 회원 이름과 등급이 함께 나타납니다. orders에 10개 행이 있고 모든 member_id가 members 테이블에 존재한다면 10개 행이 그대로 반환됩니다. INNER JOIN은 두 테이블 모두에 일치하는 행만 반환하므로, orders에 없는 회원 정보나 members에 없는 주문은 결과에서 제외됩니다.
이번에는 주문과 도서 정보를 결합해 봅니다.
-- 주문 + 도서 정보 JOIN
SELECT
o.order_id AS 주문번호,
b.title AS 도서제목,
b.category AS 카테고리,
b.price AS 도서가격,
o.quantity AS 주문수량,
b.price * o.quantity AS 주문금액,
o.status AS 상태
FROM orders AS o
INNER JOIN books AS b ON o.book_id = b.book_id
ORDER BY 주문금액 DESC;
이제 각 주문의 도서 제목, 가격, 주문금액(가격 × 수량)을 한 행에서 볼 수 있습니다. 주문금액은 두 테이블의 열을 조합한 계산 열입니다. 이처럼 JOIN 후에도 SELECT에서 산술 표현식을 자유롭게 사용할 수 있습니다.
🔗 세 테이블 INNER JOIN
이 섹션에서는 세 개 테이블을 동시에 연결하는 다중 조인 쿼리를 살펴보겠습니다.
실무에서는 세 개 이상의 테이블을 한 번에 조인하는 경우가 많습니다. INNER JOIN을 연속으로 추가하면 됩니다. orders를 중심으로 members와 books를 동시에 조인해 회원 이름, 도서 제목, 주문 정보를 한 쿼리로 가져옵니다.
-- 세 테이블 동시 조인
SELECT
o.order_id AS 주문번호,
m.name AS 회원이름,
m.grade AS 등급,
b.title AS 도서제목,
b.category AS 카테고리,
b.price AS 정가,
o.quantity AS 수량,
b.price * o.quantity AS 주문금액,
o.order_date AS 주문일,
o.status AS 상태
FROM orders AS o
INNER JOIN members AS m ON o.member_id = m.member_id
INNER JOIN books AS b ON o.book_id = b.book_id
ORDER BY o.order_date DESC, o.order_id;
이제 단 하나의 쿼리로 온라인 서점의 주문 현황을 완전하게 파악할 수 있습니다. 세 테이블 모두에 일치하는 행만 포함됩니다. INNER JOIN을 추가할 때마다 해당 연결 조건을 만족하지 않는 행은 결과에서 제외됩니다.
세 테이블 조인의 실행 순서는 일반적으로 왼쪽에서 오른쪽 방향으로 순차 적용됩니다. orders를 기준으로 members와 조인한 결과에 다시 books를 조인합니다. 쿼리 최적화기가 실제 실행 계획을 최적화하지만, 논리적으로는 이 순서로 이해하면 됩니다.
🔍 JOIN + WHERE 조합
이 섹션에서는 INNER JOIN에 WHERE 조건을 추가해 원하는 데이터만 정확히 추출하는 방법을 살펴보겠습니다.
JOIN 후에 WHERE를 사용하면 조인된 결과에서 특정 조건의 행만 남길 수 있습니다. WHERE는 조인이 완성된 후 적용됩니다.
-- VIP 회원의 완료된 주문만 보기
SELECT
m.name AS 회원이름,
b.title AS 도서제목,
b.price * o.quantity AS 주문금액,
o.order_date AS 주문일
FROM orders AS o
INNER JOIN members AS m ON o.member_id = m.member_id
INNER JOIN books AS b ON o.book_id = b.book_id
WHERE m.grade = 'VIP'
AND o.status = 'DONE'
ORDER BY o.order_date;
세 테이블 조인 후 VIP 회원이면서 완료된 주문만 나타납니다. WHERE에서 m.grade, o.status처럼 테이블 접두사를 붙여 어느 테이블의 열인지 명시합니다.
IT 카테고리 도서를 2개 이상 주문한 내역을 조회합니다.
SELECT
m.name AS 회원이름,
b.title AS 도서제목,
b.category AS 카테고리,
o.quantity AS 수량
FROM orders AS o
INNER JOIN members AS m ON o.member_id = m.member_id
INNER JOIN books AS b ON o.book_id = b.book_id
WHERE b.category = 'IT'
AND o.quantity >= 2
ORDER BY o.quantity DESC;
IT 카테고리 도서를 2권 이상 한 번에 주문한 내역만 나타납니다. WHERE에서 조인된 두 테이블의 열을 자유롭게 조건에 활용할 수 있습니다.
📊 JOIN + GROUP BY 집계
이 섹션에서는 JOIN과 GROUP BY를 결합해 여러 테이블에 걸친 집계 쿼리를 작성하는 방법을 살펴보겠습니다.
JOIN과 GROUP BY를 결합하면 강력한 집계 쿼리가 됩니다. "회원별 총 주문 금액"처럼 여러 테이블의 정보를 합산하는 보고서 쿼리가 가능합니다.
-- 회원별 총 주문 금액과 주문 건수
SELECT
m.name AS 회원이름,
m.grade AS 등급,
COUNT(*) AS 주문건수,
SUM(b.price * o.quantity) AS 총주문금액
FROM orders AS o
INNER JOIN members AS m ON o.member_id = m.member_id
INNER JOIN books AS b ON o.book_id = b.book_id
WHERE o.status = 'DONE'
GROUP BY m.member_id, m.name, m.grade
ORDER BY 총주문금액 DESC;
완료된 주문을 기준으로 회원별 주문 건수와 총 주문 금액이 집계됩니다. GROUP BY에 m.member_id를 포함시켜 이름이 같은 서로 다른 회원을 구분합니다(이름은 중복될 수 있으므로 PK인 member_id를 기준으로 그룹화하는 것이 안전합니다).
카테고리별 판매 통계를 내봅니다.
-- 카테고리별 판매 건수 + 총 판매금액 (취소 제외)
SELECT
b.category AS 카테고리,
COUNT(*) AS 판매건수,
SUM(o.quantity) AS 총판매수량,
SUM(b.price * o.quantity) AS 총판매금액,
ROUND(AVG(b.price * o.quantity), 0) AS 평균주문금액
FROM orders AS o
INNER JOIN books AS b ON o.book_id = b.book_id
WHERE o.status != 'CANCEL'
GROUP BY b.category
ORDER BY 총판매금액 DESC;
카테고리별로 판매 통계가 집계됩니다. 취소된 주문을 WHERE로 먼저 제외한 뒤 GROUP BY로 집계하므로 정확한 실판매 통계를 얻을 수 있습니다.
⚠️ INNER JOIN 주의사항
이 섹션에서는 INNER JOIN을 사용할 때 자주 겪는 문제를 살펴보겠습니다.
첫 번째 주의사항은 행이 예상보다 많이 나올 때입니다. 만약 orders에 같은 회원의 주문이 여러 개 있고 한 주문에 같은 도서가 여러 번 있다면(일반적으로 없지만) 예상보다 많은 행이 반환될 수 있습니다. 조인 조건이 1:N 관계인지, M:N 관계인지 파악하는 것이 중요합니다. orders.member_id → members.member_id는 N:1 관계입니다. 한 회원이 여러 주문을 가질 수 있으므로 orders의 행 수만큼 결과가 나옵니다.
두 번째는 ON 조건을 잘못 쓰는 경우입니다. ON에 잘못된 열을 연결하면 카테시안 곱에 가까운 엄청난 수의 행이 반환되거나 0개가 반환될 수 있습니다. 항상 FK = PK 형태의 올바른 연결 조건을 확인하세요.
-- 주의: 잘못된 ON 조건 예시
-- ON o.order_id = m.member_id -- 의미 없는 조건, 우연한 일치만 반환
-- ON 1 = 1 -- 모든 행 조합 (카테시안 곱) — 매우 위험
-- 올바른 ON 조건
-- ON o.member_id = m.member_id -- FK = PK 형태
세 번째는 NULL이 있는 열로 조인할 때입니다. 만약 orders.book_id가 NULL인 행이 있다면, INNER JOIN에서 해당 주문은 결과에서 제외됩니다. books 테이블에 NULL과 일치하는 book_id가 없기 때문입니다. 이런 경우에는 14강에서 배울 LEFT JOIN이 더 적합합니다.
📝 핵심 요약
이번 강에서 배운 내용을 정리하겠습니다.
| 패턴 | 문법 요약 |
|---|---|
| 두 테이블 JOIN | FROM A INNER JOIN B ON A.fk = B.pk |
| 세 테이블 JOIN | FROM A INNER JOIN B ON ... INNER JOIN C ON ... |
| 테이블 별칭 | FROM orders AS o — o.열명으로 접근 |
| JOIN + WHERE | JOIN 후에 WHERE 절로 행 필터링 |
| JOIN + GROUP BY | 여러 테이블 집계 — GROUP BY에 PK 포함 권장 |
| 계산 열 | b.price * o.quantity AS 주문금액 |
INNER JOIN은 일치하는 행만 결과에 포함합니다. 그러나 "주문하지 않은 회원도 목록에 보여주고 싶다"거나 "도서 중 한 번도 주문되지 않은 도서도 보여주고 싶다"는 요구사항이 생기면 INNER JOIN으로는 불가능합니다. 다음 14강에서는 한쪽 테이블의 행을 모두 유지하는 OUTER JOIN을 배웁니다. LEFT JOIN, RIGHT JOIN, FULL OUTER JOIN의 차이와 어떤 상황에 사용하는지 익히게 됩니다.
관련 주제
- INNER JOIN
- ON 조건
- 테이블 별칭
- 다중 테이블 결합
- JOIN과 GROUP BY
- 개발·프로그래밍
- 개발·프로그래밍 강의
- SQL·데이터베이스 입문 24강
- 무료강의
- 무료 온라인 강의
- NUGUNA
- 누구나
📚 시리즈 전체 공유
SQL·데이터베이스 입문 24강
이 강의가 속한 시리즈는 총 24강, 모두 무료입니다. 처음부터 배우려는 동료에게 시리즈 전체를 알려 주세요.
댓글
불러오는 중...
