두 SELECT 결과를 합치거나 비교하는 UNION·UNION ALL·INTERSECT·MINUS의 중복 처리, 사용 조건, 연산 순서를 기출 함정 중심으로 익힙니다.
빈출기출 11회분 중 29문항 · 많이 나온 주제: 집합연산(26)
학습 목표
두 SELECT 결과를 합치거나 비교하는 UNION·UNION ALL·INTERSECT·MINUS의 중복 처리, 사용 조건, 연산 순서를 기출 함정 중심으로 익힙니다.
01
네 가지 집합 연산자
집합 연산자는 두 개 이상의 SELECT 결과를 행 단위로 합치거나 비교하는 도구입니다. 합집합(UNION·UNION ALL), 교집합(INTERSECT), 차집합(MINUS) 네 가지가 있습니다.
도시락 A = {김밥, 사과, 우유}, B = {사과, 빵, 우유}라면 UNION은 {김밥, 사과, 우유, 빵}, INTERSECT는 {사과, 우유}, MINUS(A − B)는 {김밥}입니다. UNION ALL만 중복을 그대로 두고, 나머지 셋은 모두 중복을 제거합니다.
양쪽에 있는 이·박이 UNION ALL에서는 두 번씩 남고, UNION에서는 한 번씩만 남습니다. UNION = UNION ALL + 중복 제거로 기억합니다.
-- UNION 자리를 UNION ALL · INTERSECT · MINUS로 바꿔 보기SELECT NAME FROM EMP_A
UNIONSELECT NAME FROM EMP_B;
같은 두 명단, 연산자별 결과
연산자
결과 NAME
행 수
UNION
김, 이, 박, 최
4
UNION ALL
김, 이, 박, 이, 박, 최
6
INTERSECT
이, 박
2
MINUS
김
1
02
집합 연산의 조건
집합 연산에 참여하는 SELECT들은 열 개수가 같아야 하고, 같은 자리의 열은 데이터 타입이 호환되어야 합니다. 완전히 같은 타입일 필요는 없고 숫자끼리, 문자끼리처럼 호환되면 됩니다. 문자 열과 숫자 열을 짝지으면 ORA-01790 오류가 나므로 TO_CHAR 등으로 맞춥니다. 열 이름은 달라도 되지만, 열은 위치로 짝지어지므로 순서를 바꾸면 다른 값끼리 비교하게 됩니다.
결과의 열 이름은 첫 번째 SELECT의 열 이름(별칭)을 따릅니다. ORDER BY는 맨 마지막에 한 번만 쓰고, 첫 SELECT의 열 이름·별칭이나 열 위치 번호(ORDER BY 1)로 정렬합니다. 두 번째 SELECT에서만 쓴 이름으로는 정렬할 수 없습니다.
1열 개수조건 1모든 SELECT의 열 수가 같음
2타입 호환조건 2같은 자리 열은 호환 타입 (동일 X)
3열 이름규칙 3첫 SELECT 기준
4ORDER BY규칙 4맨 마지막에 한 번
SELECT NAME AS 이름 FROM EMP_A
UNIONSELECT NAME AS 성명 FROM EMP_B
ORDERBY 이름;-- 가능: 첫 SELECT 별칭-- ORDER BY 1; -- 가능: 열 위치 번호-- ORDER BY 성명; -- 오류: 두 번째 SELECT에만 있는 별칭
03
UNION과 UNION ALL
UNION은 두 결과를 합친 뒤 중복 행을 제거합니다. 두 SELECT 사이의 중복뿐 아니라 한 SELECT 안에 이미 있던 중복도 하나로 줄입니다. 중복을 찾느라 내부적으로 정렬이 수반될 수 있어 UNION ALL보다 비용이 큽니다. 그렇다고 결과가 정렬된다고 보장하지는 않으므로 순서가 필요하면 ORDER BY를 씁니다.
UNION ALL은 중복 제거 없이 그대로 이어 붙여 가장 빠릅니다. 행 수는 양쪽 행 수의 합입니다. 중복이 없다고 확신하거나 중복을 그대로 둬도 될 때 씁니다.
중복 판정은 SELECT한 모든 열 값의 조합이 같을 때만 합니다. 코드는 같고 시간대가 다르면 다른 행입니다. 또 집합 연산에서는 NULL과 NULL을 같은 값으로 보아 중복으로 처리합니다. 일반 비교 NULL = NULL이 UNKNOWN인 것과 다릅니다.
UNION vs UNION ALL
비교
UNION
UNION ALL
중복 제거
함
안 함
정렬 작업
수반될 수 있음
없음
성능
느림
빠름
행 수
중복 뺀 수
양쪽 합
행 수 계산 연습
입력
연산
결과
행 수
X(1, 1, 2) · Y(2, 3)
UNION ALL
1, 1, 2, 2, 3
5
X(1, 1, 2) · Y(2, 3)
UNION
1, 2, 3
3
월(1, 3, 5) · 연도(2024, 2024, 2024)
UNION
1, 3, 5, 2024
4
X(NULL, 7) · Y(NULL)
UNION
NULL, 7
2
04
INTERSECT와 MINUS·EXCEPT
INTERSECT는 양쪽 결과에 모두 있는 행만, MINUS는 앞 SELECT에만 있는 행만 남깁니다. 둘 다 중복을 제거합니다. NULL끼리도 같은 값으로 보므로 양쪽에 NULL이 있으면 INTERSECT 결과에 NULL 행이 남습니다. MINUS는 Oracle의 이름이고 표준 SQL·SQL Server에서는 EXCEPT입니다.
차집합은 방향이 있어A MINUS B와 B MINUS A의 결과가 다릅니다. 앞의 예에서 EMP_A MINUS EMP_B는 {김}, EMP_B MINUS EMP_A는 {최}입니다.
같은 질문, 다른 표현
찾는 것
집합 연산
서브쿼리
양쪽에 있는 학번
INTERSECT
IN · EXISTS
앞에만 있고 뒤에 없는 학번
MINUS
NOT IN · NOT EXISTS
05
연산 순서와 괄호
Oracle에서 집합 연산자는 우선순위가 모두 같아 괄호가 없으면 위에서 아래로 차례대로 계산합니다. INTERSECT를 먼저 계산하지 않습니다. 괄호가 있으면 괄호 안을 먼저 계산해 중간 결과를 확정합니다.
행 수 문제는 중간 결과마다 중복 제거 여부를 적용해야 합니다. UNION ALL이 아닌 연산이 끝나면 그 시점의 결과에서 중복이 사라집니다.
A = {김, 이, 박} · B = {이, 박, 최} · C = {최, 최, 정}
A UNION ALL B MINUS C
A UNION ALL B → 김, 이, 박, 이, 박, 최→
MINUS C → 김, 이, 박→
3행
A UNION ALL (B MINUS C)
(B MINUS C) → 이, 박→
A UNION ALL → 김, 이, 박, 이, 박→
5행
괄호 위치만 다른데 결과가 3행과 5행으로 갈립니다. 마지막이 MINUS면 중복도 함께 사라집니다.
보충 실습여기부터는 테이블을 직접 만들어 보는 긴 SQL 실습입니다. 처음 공부할 때는 건너뛰고, 위 내용을 이해한 뒤 돌아와도 됩니다.
06
SQL 실전: 체험 신청 명단으로 행 수 세기
보충 실습
축제 체험 신청을 온라인(6건)과 현장(5건)에서 받았습니다. 온라인 2·3번은 같은 (P02, PM)이고, 프로그램이나 시간대를 아직 고르지 않은 신청은 NULL입니다. 집합 연산은 SELECT한 열 값만 비교하므로 ENTRY_ID를 빼면 2·3번은 같은 행이 됩니다.
TABLEONLINE_APPLY — 온라인 6건6행
ENTRY_ID
PROGRAM_CODE
SLOT_CODE
1
P01
AM
2
P02
PM
3
P02
PM
4
P03
AM
5
P04
NULL
6
NULL
AM
TABLEONSITE_APPLY — 현장 5건5행
ENTRY_ID
PROGRAM_CODE
SLOT_CODE
101
P02
PM
102
P03
PM
103
P04
NULL
104
P05
AM
105
NULL
AM
-- 처음 한 번 실행: 같은 이름의 테이블이 없는 연습 스키마CREATETABLEONLINE_APPLY(
ENTRY_ID NUMBER(3)PRIMARYKEY,
PROGRAM_CODE VARCHAR2(3),
SLOT_CODE VARCHAR2(2));CREATETABLEONSITE_APPLY(
ENTRY_ID NUMBER(3)PRIMARYKEY,
PROGRAM_CODE VARCHAR2(3),
SLOT_CODE VARCHAR2(2));INSERTINTO ONLINE_APPLY VALUES(1,'P01','AM');INSERTINTO ONLINE_APPLY VALUES(2,'P02','PM');INSERTINTO ONLINE_APPLY VALUES(3,'P02','PM');INSERTINTO ONLINE_APPLY VALUES(4,'P03','AM');INSERTINTO ONLINE_APPLY VALUES(5,'P04',NULL);INSERTINTO ONLINE_APPLY VALUES(6,NULL,'AM');INSERTINTO ONSITE_APPLY VALUES(101,'P02','PM');INSERTINTO ONSITE_APPLY VALUES(102,'P03','PM');INSERTINTO ONSITE_APPLY VALUES(103,'P04',NULL);INSERTINTO ONSITE_APPLY VALUES(104,'P05','AM');INSERTINTO ONSITE_APPLY VALUES(105,NULL,'AM');COMMIT;
TABLEPROGRAM_CODE 한 열의 등장 횟수7행
PROGRAM_CODE
ONLINE
ONSITE
UNION
UNION_ALL
P01
1
0
1
1
P02
2
1
1
3
P03
1
1
1
2
P04
1
1
1
2
P05
0
1
1
1
NULL
1
1
1
2
합계
6
5
6
11
-- UNION 6행 / UNION ALL 11행SELECT PROGRAM_CODE FROM ONLINE_APPLY
UNIONSELECT PROGRAM_CODE FROM ONSITE_APPLY
ORDERBY1NULLSLAST;-- INTERSECT 4행: P02, P03, P04, NULLSELECT PROGRAM_CODE FROM ONLINE_APPLY
INTERSECTSELECT PROGRAM_CODE FROM ONSITE_APPLY
ORDERBY1NULLSLAST;-- MINUS 1행: P01 (순서를 바꾸면 P05)SELECT PROGRAM_CODE FROM ONLINE_APPLY
MINUSSELECT PROGRAM_CODE FROM ONSITE_APPLY;
비교하는 열을 두 개로 늘리면 (코드, 시간대) 조합 전체가 같아야 같은 행입니다. P03은 한 열 INTERSECT에는 남지만, 온라인은 (P03, AM)·현장은 (P03, PM)이라 두 열 INTERSECT에서는 빠집니다.
비교 열 수에 따라 달라지는 결과
연산
PROGRAM_CODE 한 열
PROGRAM_CODE · SLOT_CODE 두 열
UNION
6행
7행
INTERSECT
4행: P02, P03, P04, NULL
3행: (P02, PM), (P04, NULL), (NULL, AM)
온라인 MINUS 현장
1행: P01
2행: (P01, AM), (P03, AM)
-- 두 열 조합이 모두 같아야 교집합: 3행SELECT PROGRAM_CODE, SLOT_CODE FROM ONLINE_APPLY
INTERSECTSELECT PROGRAM_CODE, SLOT_CODE FROM ONSITE_APPLY
ORDERBY1NULLSLAST,2NULLSLAST;-- 정렬 기준은 첫 SELECT의 별칭 CODESELECT PROGRAM_CODE AS CODE FROM ONLINE_APPLY
UNIONSELECT PROGRAM_CODE AS OTHER FROM ONSITE_APPLY
ORDERBY CODE NULLSLAST;
ENTRY_ID까지 SELECT하면 접수번호가 모두 달라 UNION을 써도 11행이 모두 남습니다. 참고: Oracle은 UNION의 중복 제거를 정렬 대신 해시 방식으로 할 수도 있어, 출력 순서는 ORDER BY로만 정해집니다.