[DB] 데이터베이스 개념노트 19편 - 집합 연산자 UNION, UNION ALL, INTERSECT, MINUS

데이터베이스 개념노트 19편 - 집합 연산자 UNION, UNION ALL, INTERSECT, MINUS

지난 글에서는 SQL문 안에 또 다른 SELECT문을 사용하는 서브쿼리(Subquery)에 대해 정리했습니다.

이번 글에서는 여러 SELECT문의 결과를 하나로 합치거나, 공통된 결과를 찾거나, 한쪽에만 있는 데이터를 찾을 때 사용하는 집합 연산자(Set Operator)에 대해 정리해 보겠습니다.

SQLD에서는 UNION, UNION ALL, INTERSECT, MINUS의 차이를 자주 묻습니다. 특히 중복 제거 여부, 컬럼 개수와 데이터 타입 조건, ORDER BY 사용 위치를 꼭 기억해야 합니다.


1. 집합 연산자란?

집합 연산자는 두 개 이상의 SELECT 결과를 하나의 결과로 결합하는 SQL 연산자입니다.

집합 연산자는 여러 SELECT문의 결과를 합치거나, 공통 부분을 찾거나, 차이를 구할 때 사용하는 연산자이다.

집합 연산자는 수학에서 말하는 합집합, 교집합, 차집합과 비슷하게 이해할 수 있습니다.

집합 개념 SQL 집합 연산자 의미
합집합 UNION 두 결과를 합치고 중복 제거
합집합 UNION ALL 두 결과를 합치고 중복 유지
교집합 INTERSECT 두 결과에 공통으로 있는 행만 조회
차집합 MINUS 첫 번째 결과에서 두 번째 결과를 제외

2. 집합 연산자가 필요한 이유

집합 연산자는 서로 다른 SELECT 결과를 하나의 결과로 보고 싶을 때 사용합니다.

예를 들어 온라인 회원 테이블과 오프라인 회원 테이블이 따로 있다고 해보겠습니다. 두 테이블의 회원 목록을 하나로 합쳐서 보고 싶다면 UNION 또는 UNION ALL을 사용할 수 있습니다.

또는 온라인과 오프라인에 모두 등록된 회원만 찾고 싶다면 INTERSECT를 사용할 수 있습니다. 반대로 온라인에는 있지만 오프라인에는 없는 회원을 찾고 싶다면 MINUS를 사용할 수 있습니다.


3. 예제 테이블

이번 글에서는 다음 두 테이블을 기준으로 예제를 살펴보겠습니다.

온라인회원 테이블

회원번호 회원명 가입경로
M001 김지훈 온라인
M002 이수진 온라인
M003 박민수 온라인

오프라인회원 테이블

회원번호 회원명 가입경로
M002 이수진 오프라인
M004 최유진 오프라인
M005 정하늘 오프라인

4. 집합 연산자의 기본 구조

집합 연산자는 SELECT문과 SELECT문 사이에 작성합니다.

SELECT 컬럼1, 컬럼2
FROM 테이블1

UNION

SELECT 컬럼1, 컬럼2
FROM 테이블2;

UNION 대신 UNION ALL, INTERSECT, MINUS를 사용할 수 있습니다.

SELECT 컬럼1, 컬럼2
FROM 테이블1

집합연산자

SELECT 컬럼1, 컬럼2
FROM 테이블2;

5. 집합 연산자 사용 조건

집합 연산자를 사용할 때는 몇 가지 조건을 만족해야 합니다.

  • 각 SELECT문의 컬럼 개수가 같아야 한다.
  • 각 SELECT문의 대응되는 컬럼 데이터 타입이 호환되어야 한다.
  • 컬럼명은 달라도 되지만, 결과 컬럼명은 첫 번째 SELECT문의 컬럼명을 따른다.
  • ORDER BY는 전체 집합 연산 결과의 마지막에 한 번만 작성한다.

이 조건들은 SQLD에서 자주 출제되는 포인트입니다.


6. UNION이란?

UNION은 두 SELECT 결과를 합친 뒤 중복된 행을 제거합니다.

UNION은 여러 SELECT 결과를 합치고, 중복된 행은 하나만 남기는 집합 연산자이다.

온라인회원과 오프라인회원의 회원번호, 회원명을 합쳐보겠습니다.

SELECT 회원번호, 회원명
FROM 온라인회원

UNION

SELECT 회원번호, 회원명
FROM 오프라인회원;

이 경우 M002 이수진은 온라인회원에도 있고 오프라인회원에도 있습니다. UNION은 중복된 결과를 제거하므로 M002 이수진은 한 번만 표시됩니다.

회원번호 회원명
M001 김지훈
M002 이수진
M003 박민수
M004 최유진
M005 정하늘

7. UNION ALL이란?

UNION ALL은 두 SELECT 결과를 합치되 중복을 제거하지 않습니다.

UNION ALL은 여러 SELECT 결과를 그대로 합치며, 중복된 행도 모두 유지하는 집합 연산자이다.

SELECT 회원번호, 회원명
FROM 온라인회원

UNION ALL

SELECT 회원번호, 회원명
FROM 오프라인회원;

UNION ALL은 중복을 제거하지 않으므로 M002 이수진이 두 번 표시될 수 있습니다.

회원번호 회원명
M001 김지훈
M002 이수진
M003 박민수
M002 이수진
M004 최유진
M005 정하늘

8. UNION과 UNION ALL 비교

구분 UNION UNION ALL
중복 제거 제거함 제거하지 않음
결과 행 수 중복이 제거되어 줄어들 수 있음 두 결과를 그대로 합침
성능 중복 제거 과정 때문에 상대적으로 비용이 들 수 있음 중복 제거가 없어 상대적으로 빠를 수 있음
사용 상황 중복 없는 목록이 필요할 때 중복까지 모두 보존해야 할 때

중복 제거가 필요 없다면 UNION ALL을 사용하는 것이 성능상 유리할 수 있습니다.


9. INTERSECT란?

INTERSECT는 두 SELECT 결과에 공통으로 존재하는 행만 조회합니다.

INTERSECT는 두 SELECT 결과의 교집합을 구하는 집합 연산자이다.

온라인회원과 오프라인회원에 모두 존재하는 회원을 조회해 보겠습니다.

SELECT 회원번호, 회원명
FROM 온라인회원

INTERSECT

SELECT 회원번호, 회원명
FROM 오프라인회원;

두 테이블에 모두 존재하는 회원은 M002 이수진입니다.

회원번호 회원명
M002 이수진

10. MINUS란?

MINUS는 첫 번째 SELECT 결과에서 두 번째 SELECT 결과를 뺀 나머지를 조회합니다.

MINUS는 첫 번째 SELECT 결과에는 있지만 두 번째 SELECT 결과에는 없는 행을 조회하는 집합 연산자이다.

온라인회원에는 있지만 오프라인회원에는 없는 회원을 조회해 보겠습니다.

SELECT 회원번호, 회원명
FROM 온라인회원

MINUS

SELECT 회원번호, 회원명
FROM 오프라인회원;

온라인회원에서 오프라인회원과 중복되는 M002 이수진을 제외하면 M001 김지훈과 M003 박민수가 남습니다.

회원번호 회원명
M001 김지훈
M003 박민수

11. MINUS는 순서가 중요하다

MINUS는 어느 SELECT문이 먼저 오느냐에 따라 결과가 달라집니다.

온라인회원에는 있지만 오프라인회원에는 없는 회원은 다음과 같습니다.

SELECT 회원번호, 회원명
FROM 온라인회원

MINUS

SELECT 회원번호, 회원명
FROM 오프라인회원;

반대로 오프라인회원에는 있지만 온라인회원에는 없는 회원은 다음과 같이 작성합니다.

SELECT 회원번호, 회원명
FROM 오프라인회원

MINUS

SELECT 회원번호, 회원명
FROM 온라인회원;

이 경우 결과는 M004 최유진, M005 정하늘이 됩니다.

회원번호 회원명
M004 최유진
M005 정하늘

12. 집합 연산자 한 번에 비교

연산자 의미 중복 처리
UNION 두 결과를 합침 중복 제거
UNION ALL 두 결과를 그대로 합침 중복 유지
INTERSECT 두 결과의 공통 행 조회 중복 제거
MINUS 첫 번째 결과에서 두 번째 결과를 제외 중복 제거

13. 컬럼 개수는 같아야 한다

집합 연산자를 사용할 때 각 SELECT문의 컬럼 개수는 반드시 같아야 합니다.

다음 SQL은 컬럼 개수가 다르므로 오류가 발생할 수 있습니다.

SELECT 회원번호, 회원명
FROM 온라인회원

UNION

SELECT 회원번호
FROM 오프라인회원;

첫 번째 SELECT는 컬럼이 2개이고, 두 번째 SELECT는 컬럼이 1개입니다. 집합 연산자는 위치별로 컬럼을 맞춰 비교하기 때문에 컬럼 개수가 같아야 합니다.

올바르게 작성하려면 두 SELECT문의 컬럼 개수를 맞춰야 합니다.

SELECT 회원번호, 회원명
FROM 온라인회원

UNION

SELECT 회원번호, 회원명
FROM 오프라인회원;

14. 데이터 타입은 호환되어야 한다

집합 연산자는 각 SELECT문의 같은 위치에 있는 컬럼끼리 비교합니다. 따라서 대응되는 컬럼의 데이터 타입이 호환되어야 합니다.

다음 SQL은 첫 번째 SELECT의 두 번째 컬럼은 문자이고, 두 번째 SELECT의 두 번째 컬럼은 숫자라면 오류가 발생할 수 있습니다.

SELECT 회원번호, 회원명
FROM 회원

UNION

SELECT 주문번호, 주문금액
FROM 주문;

회원명은 문자 데이터이고 주문금액은 숫자 데이터입니다. 같은 위치의 컬럼 타입이 맞지 않으므로 문제가 될 수 있습니다.

필요하다면 형 변환 함수를 사용해 타입을 맞출 수 있습니다.

SELECT 회원번호 AS 번호,
       회원명 AS 내용
FROM 회원

UNION

SELECT 주문번호 AS 번호,
       TO_CHAR(주문금액) AS 내용
FROM 주문;

15. 컬럼명은 첫 번째 SELECT문을 따른다

집합 연산자의 결과 컬럼명은 첫 번째 SELECT문의 컬럼명을 따릅니다.

SELECT 회원번호 AS 번호,
       회원명 AS 이름
FROM 온라인회원

UNION

SELECT 회원번호 AS 회원ID,
       회원명 AS 성명
FROM 오프라인회원;

두 번째 SELECT문에서 회원ID, 성명이라는 별칭을 사용해도 최종 결과 컬럼명은 첫 번째 SELECT문의 번호, 이름을 따릅니다.

번호 이름
M001 김지훈
M002 이수진

16. ORDER BY는 마지막에 한 번만 사용한다

집합 연산자를 사용할 때 ORDER BY는 전체 결과의 마지막에 한 번만 작성합니다.

SELECT 회원번호, 회원명
FROM 온라인회원

UNION

SELECT 회원번호, 회원명
FROM 오프라인회원

ORDER BY 회원번호;

위 SQL은 UNION 결과 전체를 회원번호 기준으로 정렬합니다.

각 SELECT문마다 ORDER BY를 따로 작성하는 것은 일반적으로 올바르지 않습니다.

SELECT 회원번호, 회원명
FROM 온라인회원
ORDER BY 회원번호

UNION

SELECT 회원번호, 회원명
FROM 오프라인회원
ORDER BY 회원번호;

위와 같은 방식은 오류가 발생할 수 있습니다. 집합 연산 전체 결과를 정렬하고 싶다면 마지막에 ORDER BY를 작성해야 합니다.


17. ORDER BY에서 컬럼 별칭 사용

집합 연산 결과를 정렬할 때는 첫 번째 SELECT문의 컬럼명이나 별칭을 사용할 수 있습니다.

SELECT 회원번호 AS 번호,
       회원명 AS 이름
FROM 온라인회원

UNION

SELECT 회원번호,
       회원명
FROM 오프라인회원

ORDER BY 번호;

결과 컬럼명이 첫 번째 SELECT문의 별칭인 번호, 이름이므로 ORDER BY에서도 번호를 사용할 수 있습니다.


18. 중복 판단 기준

UNION, INTERSECT, MINUS에서 중복 여부는 선택된 컬럼 전체 값을 기준으로 판단합니다.

예를 들어 다음 SQL을 보겠습니다.

SELECT 회원번호, 회원명, 가입경로
FROM 온라인회원

UNION

SELECT 회원번호, 회원명, 가입경로
FROM 오프라인회원;

온라인회원의 M002 이수진은 가입경로가 온라인이고, 오프라인회원의 M002 이수진은 가입경로가 오프라인입니다.

회원번호 회원명 가입경로
M002 이수진 온라인
M002 이수진 오프라인

회원번호와 회원명이 같더라도 가입경로가 다르면 전체 행이 다르기 때문에 중복으로 제거되지 않을 수 있습니다.

집합 연산자의 중복 판단은 선택한 컬럼 전체의 조합을 기준으로 합니다.


19. NULL과 집합 연산자

집합 연산자에서 중복을 판단할 때 NULL 값이 포함된 행도 비교 대상이 됩니다. 일반적인 비교 연산에서 NULL = NULL은 참이 아니지만, 집합 연산의 중복 제거 과정에서는 같은 위치의 NULL이 같은 값처럼 취급될 수 있습니다.

예를 들어 두 SELECT 결과에 동일한 컬럼 구성과 NULL 값이 있는 행이 있다면, UNION에서는 중복으로 제거될 수 있습니다.

SQLD에서는 NULL 비교의 일반 원칙과 집합 연산에서의 중복 제거 처리가 다를 수 있다는 점을 가볍게 알아두면 좋습니다.


20. UNION ALL이 성능상 유리할 수 있는 이유

UNION은 중복을 제거해야 합니다. 따라서 DBMS는 두 결과를 합친 뒤 중복 여부를 판단하는 작업을 수행해야 합니다. 이 과정에서 정렬이나 해시 작업이 필요할 수 있습니다.

반면 UNION ALL은 중복 제거를 하지 않고 결과를 그대로 이어 붙입니다. 따라서 중복 제거가 필요 없는 상황에서는 UNION ALL이 더 효율적일 수 있습니다.

상황 추천 연산자 이유
중복을 제거해야 함 UNION 중복 행 제거
중복이 없어도 되거나 중복까지 필요함 UNION ALL 중복 제거 비용 없음

21. JOIN과 집합 연산자의 차이

JOIN과 집합 연산자는 모두 여러 테이블을 다룰 때 사용하지만 목적이 다릅니다.

구분 JOIN 집합 연산자
결합 방향 좌우로 컬럼을 연결 위아래로 행을 연결
기준 테이블 간 관계 조건 SELECT 결과의 컬럼 구조
대표 사용 회원명과 주문정보를 함께 조회 온라인회원과 오프라인회원 목록 합치기
필요 조건 JOIN 조건 컬럼 개수와 타입 호환

JOIN은 테이블을 옆으로 붙이는 느낌이고, 집합 연산자는 SELECT 결과를 위아래로 붙이는 느낌입니다.


22. 집합 연산자와 SELECT 실행 흐름

집합 연산자는 각 SELECT문을 실행한 뒤 그 결과를 대상으로 연산합니다.

  1. 첫 번째 SELECT문 실행
  2. 두 번째 SELECT문 실행
  3. 집합 연산자 적용
  4. 중복 제거 또는 유지 처리
  5. ORDER BY가 있으면 최종 결과 정렬

실제 실행 계획은 DBMS 옵티마이저가 결정하지만, 학습 단계에서는 각 SELECT 결과가 만들어진 뒤 집합 연산이 적용된다고 이해하면 좋습니다.


23. UNION 예제 - 여러 기간 데이터 합치기

실무에서는 월별 또는 연도별로 분리된 테이블의 데이터를 합칠 때 집합 연산자를 사용할 수 있습니다.

SELECT 주문번호,
       회원번호,
       주문금액,
       주문일자
FROM 주문_2025

UNION ALL

SELECT 주문번호,
       회원번호,
       주문금액,
       주문일자
FROM 주문_2026;

2025년 주문과 2026년 주문을 하나의 결과로 조회합니다. 연도별 주문 테이블에 중복이 없다는 것이 확실하다면 UNION ALL을 사용할 수 있습니다.


24. INTERSECT 예제 - 공통 고객 찾기

2025년에도 주문했고 2026년에도 주문한 회원을 찾고 싶다면 INTERSECT를 사용할 수 있습니다.

SELECT 회원번호
FROM 주문_2025

INTERSECT

SELECT 회원번호
FROM 주문_2026;

이 SQL은 두 기간에 모두 주문 이력이 있는 회원번호만 조회합니다.


25. MINUS 예제 - 올해 주문하지 않은 고객 찾기

작년에는 주문했지만 올해는 주문하지 않은 회원을 찾고 싶다면 MINUS를 사용할 수 있습니다.

SELECT 회원번호
FROM 주문_2025

MINUS

SELECT 회원번호
FROM 주문_2026;

첫 번째 SELECT 결과에서 두 번째 SELECT 결과를 제외하므로, 2025년에는 주문했지만 2026년에는 주문하지 않은 회원이 조회됩니다.


26. 집합 연산자에서 자주 하는 실수

실수 주의할 점
SELECT문의 컬럼 개수를 다르게 작성함 집합 연산에 참여하는 SELECT문의 컬럼 개수는 같아야 함
대응 컬럼의 데이터 타입을 맞추지 않음 같은 위치의 컬럼 타입은 호환되어야 함
UNION과 UNION ALL의 중복 처리 차이를 헷갈림 UNION은 중복 제거, UNION ALL은 중복 유지
MINUS의 순서를 반대로 작성함 MINUS는 첫 번째 결과에서 두 번째 결과를 빼는 연산
ORDER BY를 각 SELECT문마다 작성함 ORDER BY는 전체 집합 연산 결과 마지막에 작성
중복 판단이 특정 컬럼 하나만 기준이라고 착각함 선택한 전체 컬럼 조합을 기준으로 중복 판단

27. SQLD 관점에서 꼭 기억할 내용

이번 글에서 SQLD 공부를 위해 꼭 기억해야 할 내용은 다음과 같습니다.

  • 집합 연산자는 여러 SELECT 결과를 결합한다.
  • UNION은 합집합을 구하고 중복을 제거한다.
  • UNION ALL은 합집합을 구하지만 중복을 제거하지 않는다.
  • INTERSECT는 교집합을 구한다.
  • MINUS는 첫 번째 결과에서 두 번째 결과를 제외한다.
  • 집합 연산에 참여하는 SELECT문의 컬럼 개수는 같아야 한다.
  • 대응되는 컬럼의 데이터 타입은 호환되어야 한다.
  • 결과 컬럼명은 첫 번째 SELECT문의 컬럼명을 따른다.
  • ORDER BY는 전체 집합 연산 결과의 마지막에 한 번만 작성한다.
  • 중복 판단은 선택한 전체 컬럼 조합을 기준으로 한다.
  • 중복 제거가 필요 없다면 UNION ALL이 성능상 유리할 수 있다.

28. 핵심 요약

연산자 의미 중복 처리 핵심 포인트
UNION 두 결과를 합침 중복 제거 중복 없는 통합 목록
UNION ALL 두 결과를 그대로 합침 중복 유지 중복까지 모두 포함, 상대적으로 빠를 수 있음
INTERSECT 두 결과의 공통 부분 중복 제거 양쪽에 모두 존재하는 데이터
MINUS 첫 번째 결과에서 두 번째 결과 제외 중복 제거 순서가 중요함

29. 마무리

이번 글에서는 여러 SELECT 결과를 결합하는 집합 연산자에 대해 정리했습니다.

UNION은 중복을 제거하며 결과를 합치고, UNION ALL은 중복을 유지한 채 결과를 합칩니다. INTERSECT는 두 결과에 공통으로 존재하는 행을 조회하고, MINUS는 첫 번째 결과에서 두 번째 결과를 제외합니다.

SQLD에서는 집합 연산자의 의미뿐만 아니라 컬럼 개수와 데이터 타입 조건, ORDER BY 사용 위치, 중복 처리 방식, MINUS의 순서까지 정확히 구분해야 합니다.

다음 글에서는 SQLD에서 분석 함수로 자주 등장하는 윈도우 함수(Window Function)에 대해 정리해 보겠습니다.