UNION·UNION ALL로 두 쿼리 결과를 합치고, INTERSECT·EXCEPT로 교집합·차집합을 구하는 집합 연산의 규칙과 활용법을 익힙니다.
🎯 학습 목표
이 강을 마치면 두 SELECT 결과를 하나로 합치는 UNION과 UNION ALL을 사용할 수 있습니다. INTERSECT(교집합)와 EXCEPT(차집합)의 동작 원리도 이해하고, 각 집합 연산을 사용해야 하는 상황을 판단할 수 있게 됩니다. 또한 집합 연산 사용 시 반드시 지켜야 하는 열 수·자료형 규칙을 정확하게 알 수 있습니다.
💡 집합 연산이란
이 섹션에서는 집합 연산의 개념과 JOIN과의 차이를 살펴보겠습니다.
JOIN은 행과 행을 연결해 열을 늘립니다(가로 확장). 반면 집합 연산(Set Operation)은 두 쿼리의 결과를 위아래로 쌓거나(합집합), 공통 부분만 남기거나(교집합), 한쪽에만 있는 것을 찾습니다(차집합). 결과는 열 구조는 그대로이고 행만 늘거나 줄어드는 세로 방향의 연산입니다.
| 연산자 | 의미 | 수학 기호 |
|---|---|---|
| UNION | 두 결과의 합집합 (중복 제거) | A ∪ B |
| UNION ALL | 두 결과의 합집합 (중복 유지) | A + B |
| INTERSECT | 두 결과의 교집합 (공통 행만) | A ∩ B |
| EXCEPT | 첫 번째 결과에서 두 번째를 뺀 차집합 | A - B |
집합 연산에서 반드시 지켜야 할 규칙이 있습니다. 첫째로 열의 수가 동일해야 합니다. 첫 번째 쿼리가 3열이면 두 번째도 3열이어야 합니다. 둘째로 각 열의 자료형이 호환되어야 합니다. 첫 번째 쿼리의 1번 열이 INTEGER면 두 번째 쿼리의 1번 열도 INTEGER여야 합니다. 셋째로 결과 열 이름은 첫 번째 쿼리를 따릅니다. 두 번째 쿼리의 열 이름은 무시됩니다.
🔗 UNION과 UNION ALL
이 섹션에서는 두 결과를 합치는 UNION과 UNION ALL의 차이와 사용법을 살펴보겠습니다.
UNION은 두 쿼리의 결과를 합치고 중복 행을 제거합니다. DISTINCT와 비슷하게 동작하므로 정렬 연산이 추가로 필요해 UNION ALL보다 느릴 수 있습니다. UNION ALL은 중복을 제거하지 않고 모두 포함합니다. 더 빠르고 중복이 없음을 보장할 수 있을 때 사용합니다.
-- VIP 회원과 PREMIUM 회원 이름 목록 (합집합, 중복 제거)
SELECT name, grade FROM members WHERE grade = 'VIP'
UNION
SELECT name, grade FROM members WHERE grade = 'PREMIUM'
ORDER BY name;
-- 동일한 결과 (WHERE IN으로도 가능하지만 UNION 예시)
SELECT name, grade FROM members WHERE grade IN ('VIP', 'PREMIUM')
ORDER BY name;
이 경우 WHERE IN이 더 간단합니다. UNION이 더 유용한 상황은 서로 다른 테이블의 결과를 합칠 때입니다.
-- 회원 이름 목록과 도서 저자 목록 합치기
SELECT name AS 이름, '회원' AS 구분 FROM members
UNION ALL
SELECT author AS 이름, '저자' AS 구분 FROM books
ORDER BY 구분, 이름;
members와 books는 서로 다른 테이블이지만, 이름을 나타내는 열이 하나씩 있으므로 UNION ALL로 합칩니다. 두 번째 열에 '회원'·'저자'를 리터럴로 추가해 어느 테이블 데이터인지 구분합니다. 결과에는 회원 이름 6개와 도서 저자 7개(중복 포함)가 모두 포함됩니다.
ORDER BY는 집합 연산 전체의 마지막에 한 번만 씁니다. 각각의 SELECT에 ORDER BY를 쓰면 오류가 납니다.
-- 월별 주문을 분기별로 묶어서 요약하는 UNION ALL 활용
SELECT '상반기' AS 기간, COUNT(*) AS 주문건수
FROM orders WHERE order_date BETWEEN '2024-01-01' AND '2024-06-30'
UNION ALL
SELECT '하반기' AS 기간, COUNT(*) AS 주문건수
FROM orders WHERE order_date BETWEEN '2024-07-01' AND '2024-12-31';
같은 테이블을 서로 다른 조건으로 집계한 결과를 UNION ALL로 세로로 쌓아 하나의 보고서처럼 만드는 패턴입니다.
🔵 INTERSECT와 EXCEPT
이 섹션에서는 교집합(INTERSECT)과 차집합(EXCEPT)을 살펴보겠습니다.
INTERSECT는 두 쿼리 결과 모두에 존재하는 행만 반환합니다. SQLite 3.35.0 이상에서 지원합니다.
-- IT 도서를 주문한 회원 AND VIP 등급 회원 (교집합)
SELECT member_id FROM orders AS o
INNER JOIN books AS b ON o.book_id = b.book_id
WHERE b.category = 'IT'
INTERSECT
SELECT member_id FROM members WHERE grade = 'VIP';
IT 도서를 주문한 회원 목록과 VIP 회원 목록 양쪽에 모두 있는 member_id만 반환됩니다. "IT 도서를 구매한 VIP 회원"을 구하는 것입니다. 이 결과는 16강의 서브쿼리(IN)나 15강의 다중 조인으로도 구현할 수 있습니다.
EXCEPT는 첫 번째 쿼리 결과에서 두 번째 쿼리 결과를 제외합니다. MySQL에서는 EXCEPT 대신 MINUS를 사용합니다(SQLite, PostgreSQL은 EXCEPT).
-- 주문한 적 없는 회원 (차집합)
SELECT member_id FROM members
EXCEPT
SELECT DISTINCT member_id FROM orders;
전체 회원 member_id에서 주문 테이블에 있는 member_id를 뺀 나머지를 반환합니다. "주문 없는 회원"을 구하는 것으로, 14강의 LEFT JOIN WHERE NULL 패턴과 동일한 결과입니다.
-- 소설 카테고리 도서는 주문했지만 IT 도서는 주문 안 한 회원
SELECT DISTINCT member_id FROM orders AS o
INNER JOIN books AS b ON o.book_id = b.book_id
WHERE b.category = '소설'
EXCEPT
SELECT DISTINCT member_id FROM orders AS o
INNER JOIN books AS b ON o.book_id = b.book_id
WHERE b.category = 'IT';
소설 카테고리 주문 회원에서 IT 카테고리 주문 회원을 뺀 차집합입니다.
⚠️ 집합 연산 주의사항
이 섹션에서는 집합 연산 사용 시 자주 만나는 규칙 위반과 함정을 살펴보겠습니다.
첫 번째는 열 수 불일치 오류입니다. 두 쿼리의 열 수가 다르면 오류가 발생합니다. 맞추기 어려울 때는 NULL을 리터럴로 추가해 열 수를 맞추는 방법을 씁니다.
-- 열 수를 NULL로 맞추는 패턴
SELECT name, grade, email FROM members
UNION ALL
SELECT title, category, NULL FROM books; -- email 열 자리에 NULL
두 번째는 UNION과 UNION ALL 선택입니다. 중복이 발생할 수 없는 것이 명확하면 UNION ALL을 쓰세요. 중복 제거를 위한 정렬 과정이 생략되어 성능이 좋습니다. 반대로 중복을 제거해야 하는 상황이면 UNION을 씁니다.
세 번째는 ORDER BY 위치입니다. ORDER BY는 마지막 SELECT 이후에 한 번만 씁니다. 열 이름은 첫 번째 SELECT의 열 이름(또는 별칭)을 사용합니다.
-- 올바른 ORDER BY 위치
SELECT name, grade FROM members WHERE grade = 'VIP'
UNION
SELECT name, grade FROM members WHERE grade = 'PREMIUM'
ORDER BY grade, name; -- 마지막에 한 번만
📝 핵심 요약
이번 강에서 배운 내용을 정리하겠습니다.
| 연산자 | 중복 처리 | 사용 상황 |
|---|---|---|
| UNION | 중복 제거 | 두 결과를 합치되 중복을 없앨 때 |
| UNION ALL | 중복 유지 | 모든 행이 필요하거나 중복이 없음을 아는 경우 |
| INTERSECT | 교집합 | 두 결과 모두에 있는 행만 |
| EXCEPT | 차집합 | 첫 번째에는 있지만 두 번째에는 없는 행 |
집합 연산은 같은 구조의 결과를 세로로 결합하거나 비교할 때 JOIN보다 직관적인 표현 방법이 됩니다. 다음 18강에서는 행 값에 따라 다른 결과를 반환하는 CASE 표현식을 배웁니다. IF-THEN-ELSE 로직을 SQL에서 직접 구현해 데이터를 분류·변환·레이블링하는 강력한 도구입니다.
관련 주제
- UNION
- UNION ALL
- INTERSECT
- EXCEPT
- 교집합
- 차집합
- 합집합
- 중복 제거
- 열 수 일치 규칙
- 개발·프로그래밍
- 개발·프로그래밍 강의
- SQL·데이터베이스 입문 24강
- 무료강의
- 무료 온라인 강의
- NUGUNA
- 누구나
📚 시리즈 전체 공유
SQL·데이터베이스 입문 24강
이 강의가 속한 시리즈는 총 24강, 모두 무료입니다. 처음부터 배우려는 동료에게 시리즈 전체를 알려 주세요.
댓글
불러오는 중...
