집합 연산
UNION · UNION ALL · INTERSECT · EXCEPT 로 결과끼리 합치고 뺍니다. 여기서는 NULL 을 같다고 보는 것도 함께 봅니다.
결과끼리 합치고 뺍니다
조인은 테이블을 옆으로 이어 열을 늘립니다(2.1). 집합 연산은 위아래로 이어 행을 늘리거나 걸러 냅니다.
| 연산 | 하는 일 |
|---|---|
| UNION | 양쪽을 합치고 중복을 없앱니다 |
| UNION ALL | 양쪽을 그대로 붙입니다 |
| INTERSECT | 양쪽에 다 있는 것만 |
| EXCEPT | 왼쪽에만 있는 것만 |
실습 데이터로 확인해 보겠습니다. 게시판마다 글을 작성한 사람이 몇 명인지 먼저 보아 두면 뒤의 결과가 읽힙니다.
| 게시판 | 작성자 수 |
|---|---|
| 공지사항 | 2 |
| 자유게시판 | 18 |
| 질문답변 | 12 |
UNION 과 UNION ALL
-- 중복을 없앱니다 SELECT user_num FROM Board.POSTS WHERE board_num = 1 UNION SELECT user_num FROM Board.POSTS WHERE board_num = 2; -- 그대로 붙입니다 SELECT user_num FROM Board.POSTS WHERE board_num = 1 UNION ALL SELECT user_num FROM Board.POSTS WHERE board_num = 2;
| 행 수 |
|---|
| 18 |
| 행 수 |
|---|
| 350 |
18 과 350 입니다. 공지 35건과 자유 315건을 합치면 350행인데,
UNION 은 같은 사람을 하나로 묶어 18명으로 줄였습니다.
공지를 작성한 두 사람이 자유게시판에도 작성했기 때문에 20 이 아니라 18 입니다.
중복을 없애는 데는 값이 듭니다. 데이터베이스가 결과를 정렬하거나
해시로 묶어 비교해야 하기 때문입니다. 겹칠 일이 없다면
UNION ALL 을 사용하십시오. 게시판별 목록을
이어 붙이는 것처럼 애초에 겹치지 않는 자료라면 그 비용이 순전히 낭비입니다.
지켜야 하는 규칙
위아래로 잇는 것이므로 열의 개수가 같아야 하고, 자리마다 형식이 맞아야 합니다.
SELECT num FROM Board.BOARDS UNION SELECT num, name FROM Board.BOARDS;
- 이름은 맞출 필요가 없습니다. 자리로 짝지어지고, 결과의 열 이름은 첫 번째 조회의 것을 따릅니다.
- 형식이 다르면 넓은 쪽으로 맞춰집니다. int 와 bigint 를 이으면 bigint 가 되고, 맞출 수 없으면 오류입니다.
ORDER BY는 맨 끝에 한 번만 적습니다. 중간에 적을 수 없습니다. 합친 뒤의 차례를 정하는 것이기 때문입니다.
INTERSECT 와 EXCEPT
INTERSECT 는 양쪽에 다 있는 것만 냅니다.
공지사항과 질문답변에 모두 글을 작성한 사람을 찾아봅니다.
SELECT U.nickname FROM ( SELECT user_num FROM Board.POSTS WHERE board_num = 1 INTERSECT SELECT user_num FROM Board.POSTS WHERE board_num = 3 ) AS T INNER JOIN Member.USERS AS U ON U.num = T.user_num ORDER BY U.nickname;
| nickname |
|---|
| 한소망 |
| 홍길동 |
공지사항을 작성한 사람이 애초에 이 둘뿐이고, 둘 다 질문답변에도 글을 썼습니다.
EXCEPT 는 왼쪽에서 오른쪽을 뺍니다.
자유게시판에는 작성했지만 공지사항에는 작성하지 않은 사람입니다.
SELECT COUNT(*) AS 자유만 FROM ( SELECT user_num FROM Board.POSTS WHERE board_num = 2 EXCEPT SELECT user_num FROM Board.POSTS WHERE board_num = 1 ) AS T;
| 자유만 |
|---|
| 16 |
자유게시판 작성자 18명에서 공지도 작성한 2명을 뺀 16명입니다. 차례를 바꾸면 뜻이 달라집니다. 공지에서 자유를 빼면 0 입니다. 공지를 작성한 두 사람이 자유게시판에도 작성했기 때문입니다.
INTERSECT 와 EXCEPT 도
중복을 없앱니다. ALL 을 붙이는 형태는
SQL Server 에 없습니다.
여기서는 NULL 을 같다고 봅니다
1.9 에서 NULL 은 자기 자신과도 같지 않다고 했습니다. 그런데 집합 연산에서 중복을 없앨 때는 NULL 끼리 같은 것으로 봅니다.
SELECT CAST(NULL AS INT) AS v UNION SELECT CAST(NULL AS INT); SELECT CAST(NULL AS INT) AS v UNION ALL SELECT CAST(NULL AS INT);
| 행 수 |
|---|
| 1 |
| 행 수 |
|---|
| 2 |
UNION 이 NULL 둘을 하나로
묶었습니다. INTERSECT 도 마찬가지로 짝을 맞춥니다 —
양쪽이 NULL 이면 같은 것으로 보아 1행을 냅니다.
모순이 아니라 적용되는 자리가 다릅니다.
| 어디서 | NULL 을 |
|---|---|
| WHERE · ON | 비교합니다. 같은지 알 수 없으므로 조건이 참이 못 됩니다(1.6 · 1.9) |
| UNION · INTERSECT · EXCEPT | 같은 값인지 가려냅니다. 값이 없다는 점이 같으므로 하나로 봅니다 |
| GROUP BY · DISTINCT | 집합 연산과 같습니다. NULL 끼리 한 묶음이 됩니다 |
"조건에서 견줄 때" 와 "같은 값을 골라낼 때" 의 규칙이 다르다고 기억해 두십시오.
조인·EXISTS 와 비교하면
INTERSECT 와 EXCEPT 로 하는 일은
EXISTS · NOT EXISTS(2.4)로도
됩니다. 무엇이 다를까요.
| 항목 | 집합 연산 | EXISTS |
|---|---|---|
| 중복 | 없애 줍니다 | 바깥 행을 그대로 둡니다 |
| 비교 열 | 적은 열 전부가 같아야 | 조건을 자유롭게 |
| 다른 열 가져오기 | 안 됩니다 | 바깥에서 마음대로 |
| 읽기 | "이 목록에서 저 목록을 뺀다" | "이런 것이 없는 행" |
목록끼리 비교하는 것이 뜻에 가까우면 집합 연산이 읽기 좋습니다.
거기에 다른 열을 붙여야 한다면 위 예제처럼 결과를 다시 조인하거나, 처음부터
EXISTS 로 적으십시오.
직접 해보기
글을 작성한 적도 있고 댓글을 단 적도 있는 회원이 몇 명인지 세어 보세요.
공지사항 글 3건과 자유게시판 글 3건을 한 결과로 내되, 어느 게시판 것인지 구분할 수 있게 해보세요.
- 조인은 옆으로, 집합 연산은 위아래로 잇습니다.
UNION은 중복을 없애느라 값이 듭니다. 겹칠 일이 없으면UNION ALL입니다.- 열 개수가 같아야 하고(오류 205), 이름은 첫 번째 조회의 것을 따릅니다.
ORDER BY는 맨 끝에 한 번만 적습니다.- 집합 연산은 NULL 을 같다고 봅니다.
WHERE에서 견줄 때와 규칙이 다릅니다. EXCEPT는 차례가 뜻을 바꿉니다. 왼쪽에서 오른쪽을 뺍니다.