본문으로 건너뛰기

집합 연산자

SQLD 목차SQL 활용레슨 25 / 35

목차

집합 연산자(set operator)는 두 SELECT 문의 결과를 행 단위로 합치거나(UNION), 겹치는 행만 남기거나(INTERSECT), 한쪽에서 다른 쪽을 뺍니다(MINUS).

조인이 두 테이블의 행을 옆으로 이어 열을 늘린다면, 집합 연산자는 두 결과를 위아래로 포개어 행을 다룹니다. "두 명단을 합치면 누구인가?", "두 명단에 모두 있는 사람은?" 같은 질문에 씁니다. 그림 내내 쓰는 두 쿼리입니다. 앞 쿼리 A는 월급이 450 이상인 김민준 · 최유나 · 강도윤을 고릅니다.

SELECT EMPNO,
  ENAME
FROM EMP
WHERE SAL >= 450;

뒤 쿼리 B는 20번 부서의 이서연 · 박지호 · 강도윤을 고릅니다.

SELECT EMPNO,
  ENAME
FROM EMP
WHERE DEPTNO = 20;

8단계 중 1단계. 집합 연산자는 두 SELECT 문의 결과를 행 단위로 합치거나 견줍니다.

1두 결과의 같은 행

집합 연산자는 행의 모든 열 값이 같아야 같은 행으로 봅니다. 강도윤은 A와 B에서 EMPNO 1006과 ENAME이 모두 같아 겹친 행입니다. 중복을 가릴 때는 NULL끼리도 같은 값으로 봅니다. 조건식에서 NULL = NULL이 참이 아닌 것과 다릅니다.

2UNION ALL·UNION·INTERSECT·MINUS

연산자남기는 행겹친 행A와 B로
UNION ALL두 결과 모두둘 다 둠6행
UNION두 결과 모두하나만5행
INTERSECT두 결과에 다 있는 행하나만1행
MINUS앞 결과에만 있는 행뺌2행

UNION은 두 결과 사이뿐 아니라 한 결과 안의 중복도 없앱니다. 중복을 없앨 일이 없으면 UNION ALL이 일을 덜 합니다. INTERSECT와 MINUS도 UNION처럼 결과에서 중복 행을 없앱니다. 순서를 바꾼 B MINUS A는 이서연 · 박지호입니다. UNION·UNION ALL·INTERSECT는 순서를 바꿔도 같은 행이 나옵니다.

3열 짝짓기와 ORDER BY

열은 이름이 아니라 순서로 짝짓습니다. 열 개수가 다르면 ORA-01789, 짝지은 열의 데이터 타입(형)이 숫자와 문자처럼 다르면 ORA-01790 오류입니다. 그래서 뜻이 다른 열도 개수와 형만 맞으면 합쳐집니다. 합친 결과의 열 이름은 앞 쿼리의 것입니다. 맨 끝에 한 번만 쓰는 ORDER BY에는 앞 쿼리의 열 이름·별칭이나 ORDER BY 1 같은 열 번호를 씁니다. 뒤 쿼리의 별칭으로 정렬하면 결과에 없는 이름이라 ORA-00904 오류입니다. 앞 쿼리 끝에 ORDER BY를 쓰고 집합 연산자를 이어도 오류입니다.

견줄 것OracleSQL Server
빼기MINUS(21c부터 EXCEPT도)EXCEPT
섞어 쓰면모두 같은 우선순위로 왼쪽부터INTERSECT 먼저

섞어 쓸 때는 괄호로 처리 순서를 밝힙니다.

예제

UNION 결과가 정렬된 것처럼 보여도 순서는 ORDER BY로만 정해져서, 예제마다 끝에 ORDER BY를 붙였습니다.

앞 쿼리의 MGR에는 김민준의 NULL이, 뒤 쿼리의 DEPTNO에는 정하늘의 NULL이 있습니다. UNION은 두 NULL을 한 행으로 남기고, 열 이름은 앞 쿼리의 MGR입니다.

SELECT MGR
FROM EMP
UNION
SELECT DEPTNO
FROM EMP
ORDER BY 1;
MGR
10
20
30
1001
1004
1006
NULL

UNION ALL로 바꾸면 NULL이 두 행 나옵니다.

EMP의 DEPTNO에는 20이 세 번 있지만, MINUS 결과에서 20은 한 행입니다.

SELECT DEPTNO
FROM EMP
MINUS
SELECT DEPTNO
FROM DEPT
WHERE DEPTNO = 30
ORDER BY DEPTNO;
DEPTNO
10
20
NULL

시험 포인트

  • 두 쿼리는 열 개수가 같고, 순서대로 짝지은 열끼리 데이터 타입이 맞아야 합니다. 열 이름은 짝짓기에 쓰지 않습니다.
  • 결과의 열 이름은 첫 번째 쿼리를 따르고, ORDER BY는 맨 끝에 한 번만 씁니다.
  • UNION은 중복 행을 없애고 UNION ALL은 그대로 둡니다. INTERSECT·MINUS도 중복 행을 없앱니다.
  • 중복을 가릴 때 NULL끼리는 같은 값입니다.
  • MINUS(EXCEPT)는 두 쿼리의 순서를 바꾸면 결과가 바뀝니다.
  • 섞어 쓰면 Oracle은 왼쪽부터, SQL Server는 INTERSECT를 먼저 처리합니다.
  • 결과 순서는 ORDER BY로만 보장됩니다. UNION 결과도 정렬된다고 보면 안 됩니다.

확인 문제

1. 다음 쿼리의 결과는 무엇일까요? 서브쿼리에서 본 NOT IN 쿼리와 견주어 보세요.

SELECT DEPTNO
FROM DEPT
MINUS
SELECT DEPTNO
FROM EMP;
정답

40 한 행입니다.

DEPTNO
40

DEPT의 10·20·30·40에서 EMP에 있는 10·20·30을 빼면 40이 남습니다. EMP 쪽의 NULL은 DEPT에 없는 값이라 뺄 것이 없습니다. 같은 질문을 NOT IN으로 쓰면 목록의 NULL 때문에 0행이었습니다.

2. 다음 쿼리는 몇 행을 낼까요? UNION을 UNION ALL로 바꾸면 몇 행일까요?

SELECT DEPTNO
FROM EMP
UNION
SELECT DEPTNO
FROM DEPT
ORDER BY DEPTNO;
정답

5행입니다.

DEPTNO
10
20
30
40
NULL
  • EMP의 20 세 개는 한 쿼리 안의 중복이지만 UNION이 하나로 줄입니다.
  • NULL은 정하늘의 DEPTNO 하나뿐이라 한 행입니다. DEPT의 DEPTNO는 기본키라 NULL이 없습니다.
  • UNION ALL이면 EMP 6행과 DEPT 4행을 그대로 이어 10행입니다.

3. 사원과 부서를 한 명단으로 합치다가 오류가 났습니다. 까닭과 고칠 방법은 무엇일까요?

SELECT EMPNO,
  ENAME
FROM EMP
UNION
SELECT DNAME,
  DEPTNO
FROM DEPT;
정답

첫 번째 열끼리 숫자(EMPNO)와 문자(DNAME)가 짝지어져 오류(ORA-01790)입니다. 열은 이름이 아니라 순서로 짝지으므로, 뒤 쿼리를 SELECT DEPTNO, DNAME으로 바꾸면 사원 6행과 부서 4행이 합쳐져 10행이 나옵니다.

끝까지 내려오면 저절로 표시됩니다. 이 기기에만 저장합니다.

SQLD 목차로