소스: sql-src/mid_04_set_ops/02_intersect_except.sql, 03_dialects.sql. NULL 을 보는 관점 차이, 두 테이블 대사 패턴, DB 별 문법 차이를 확인합니다.
= 비교의 NULL 처리 차이SELECT NULL AS v
INTERSECT
SELECT NULL AS v;V
----
NULL
(1행)SELECT CASE WHEN NULL = NULL THEN 'Y' ELSE 'N' END AS eq;EQ
--
N
(1행)INTERSECT 는 양쪽의 NULL 을 같은 값으로 보고 1행을 남겼습니다. 반면 = 로 NULL 과 NULL 을 비교하면 UNKNOWN 이 되어 CASE 의 ELSE(N)로 빠집니다. 같은 "NULL 끼리 비교"인데 두 문법의 결론이 다릅니다.
old_orders(이관 전 원본)와 new_orders(이관 후 복사본)를 준비했습니다. 401·403·405 번은 그대로 옮겨졌고, 402 번은 금액이 300에서 350으로 바뀌었고, 404 번은 새 시스템으로 안 옮겨졌고, 406 번은 원본에는 없던 행이 새 시스템에만 있습니다.
SELECT
id
, cust
, amt
, status
FROM old_orders
INTERSECT
SELECT
id
, cust
, amt
, status
FROM new_orders
ORDER BY id;ID | CUST | AMT | STATUS
----+----------+-----+-------
401 | 가나상사 | 500 | 완료
403 | 마바유통 | 700 | 완료
405 | 자차상사 | 450 | 완료
(3행)모든 컬럼 값이 완전히 같은 행 3개만 남았습니다. 이관이 정확했다고 확인할 수 있는 행입니다.
SELECT
id
, cust
, amt
, status
FROM old_orders
EXCEPT
SELECT
id
, cust
, amt
, status
FROM new_orders
ORDER BY id;ID | CUST | AMT | STATUS
----+----------+-----+-------
402 | 다라전자 | 300 | 완료
404 | 사아물산 | 200 | 취소
(2행)old_orders 의 값 그대로는 new_orders 어디에도 없는 행입니다. 402 번은 금액이 바뀌어 원래 값(300)이 새 테이블에 없고, 404 번은 아예 이관되지 않았습니다.
SELECT
id
, cust
, amt
, status
FROM new_orders
EXCEPT
SELECT
id
, cust
, amt
, status
FROM old_orders
ORDER BY id;ID | CUST | AMT | STATUS
----+----------+-----+-------
402 | 다라전자 | 350 | 완료
406 | 카타무역 | 150 | 보류
(2행)402 번은 바뀐 값(350)으로 여기 다시 나타나고, 406 번은 원본에 없던 행입니다. 변형 4 와 이 결과를 같이 보면 402 번은 "값이 바뀐 행", 404 번은 "빠진 행", 406 번은 "새로 생긴 행"이라고 정확히 구분됩니다.
핵심두 테이블을 대사할 때는
A EXCEPT B와B EXCEPT A를 함께 봐야 합니다. 한쪽만 보면 "달라진 행"인지 "한쪽에만 있는 행"인지 구분할 수 없습니다.
| 기능 | Oracle · Tibero | MySQL | MSSQL |
|---|---|---|---|
| 차집합 키워드 | MINUS, 21c 부터 EXCEPT 도 | EXCEPT(8.0.31 부터) | EXCEPT |
| INTERSECT | 지원 | 지원(8.0.31 부터) | 지원(2005 부터) |
| INTERSECT ALL · EXCEPT ALL | Oracle 21c 부터, Tibero 미지원 | 지원(8.0.31 부터) | 미지원(DISTINCT 만) |
| 8.0.30 이하 대체 | - | NOT EXISTS·LEFT JOIN | - |
-- Oracle · Tibero
SET MODE Oracle;
SELECT
cust
FROM orders_2024
MINUS
SELECT
cust
FROM orders_2025
ORDER BY cust;CUST
--------
다라전자
사아물산
자차상사
카타무역
(4행)Oracle·Tibero 는 EXCEPT 대신 MINUS 를 씁니다. SET MODE Regular·SET MODE MySQL·SET MODE MSSQLServer 로 바꿔 같은 쿼리를 EXCEPT 로 실행해도 결과는 똑같이 이 4행입니다. MINUS 는 Oracle 계열의 이름일 뿐 동작은 EXCEPT 와 같고, Oracle 21c 부터는 EXCEPT 라고 써도 됩니다.
H2 의 MySQL 모드는 버전을 흉내 내지 않아 EXCEPT 가 그대로 실행됩니다. 실제 MySQL 8.0.30 이하에서는 EXCEPT·INTERSECT 키워드 자체가 없어 문법 오류가 납니다. 이 버전 제약은 H2 로 재현할 수 없어 문법 검토만 했습니다.
8.0.30 이하라면 WHERE NOT EXISTS (...) 로 같은 결과를 만듭니다. sql-src/mid_04_set_ops/03_dialects.sql 에서 직접 실행해 위 MINUS 예제와 똑같이 4행이 나오는 것을 확인해 뒀습니다(중급 02 의 NOT EXISTS 안티 조인과 같은 패턴).
MSSQL 은 2005 버전부터 EXCEPT·INTERSECT 를 지원하며 MINUS 라는 이름은 쓰지 않지만, 결과는 다른 DB 와 동일합니다.
SELECT cust FROM orders_2024 INTERSECT ALL SELECT cust FROM orders_2025;예상 오류: Syntax error in SQL statementINTERSECT ALL·EXCEPT ALL 은 H2 자체가 지원하지 않아 문법 검토만 했습니다(2.5절 표 참고). MySQL 8.0.31 이상이라면 실제로 지원하는 문법이지만, H2 로는 재현할 수 없습니다.
주의"EXCEPT/INTERSECT 를 쓸 수 있는가"와 "ALL 옵션까지 쓸 수 있는가"는 DB 마다 별개의 질문입니다. 배포 대상 DB 와 버전을 먼저 확인하고, 확신이 없으면 표준 DISTINCT(기본값) 동작만 쓰는 편이 안전합니다.