SQL에서 서브쿼리를 사용할 때 NOT IN과 NOT EXISTS는 자주 혼용되지만, 두 연산자는 내부적으로 전혀 다른 방식으로 작동한다. 특히 서브쿼리 결과에 NULL이 포함될 경우, 이 차이는 실질적인 결과의 차이로 이어지며, 예상치 못한 버그를 유발할 수 있다.
본 문서에서는 NOT IN과 NOT EXISTS의 개념적 차이뿐 아니라 IN, EXISTS와의 비교, 그리고 NULL이 포함된 경우의 동작 방식까지 예제를 통해 알아보자.
1. IN vs EXISTS, NOT IN vs NOT EXISTS
IN
IN은 서브쿼리 결과 또는 값 리스트에 포함되는지 여부를 비교한다. 리스트형 비교로 이해할 수 있다.
SELECT *
FROM employees
WHERE department_id IN (
SELECT department_id FROM departments
);
EXISTS
EXISTS는 서브쿼리 결과가 존재하는지만 확인한다. 결과가 하나라도 존재하면 TRUE, 아니면 FALSE이다.
SELECT *
FROM employees e
WHERE EXISTS (
SELECT 1 FROM departments d
WHERE d.department_id = e.department_id
);
IN vs EXISTS
항목 | IN | EXISTS |
비교 방식 | 값 리스트에 포함 여부를 비교 | 행의 존재 여부 판단 |
성능 | 소규모 서브쿼리일 경우 유리 | 대규모 서브쿼리에서 인덱스 활용 가능 |
NULL 영향 | 서브쿼리에 NULL이 있어도 영향 없음 | NULL 상관없음 |
NOT IN vs NOT EXISTS
기본 구조는 반대이지만, 동작은 앞서 설명한 바와 같이 NULL 처리에서 큰 차이를 보인다.
항목 | NOT IN | NOT EXISTS |
비교 방식 | 값 목록과 비교 | 행 존재 여부 판단 |
NULL 처리 | NULL이 있으면 전체 결과 없음 | NULL 영향 없음 |
성능 | 서브쿼리 크기나 인덱스 유무에 따라 느릴 수 있음 | 조건 최적화 시 빠르게 작동 |
안전성 | NULL이 있는 경우 취약 | 일반적으로 안전 |
2. NULL이 포함된 경우의 동작 비교
다음과 같은 예제 테이블을 기준으로 동작을 비교한다.
예제 데이터
employees 테이블
employee_id | name | department_id |
1 | Alice | 10 |
2 | Bob | 20 |
3 | Carol | 30 |
4 | Dave | NULL |
departments 테이블
department_id |
10 |
20 |
NULL |
NOT IN 예시
SELECT *
FROM employees
WHERE department_id NOT IN (
SELECT department_id FROM departments
);
결과
반환되는 행 없음
이유
서브쿼리의 결과는 [10, 20, NULL]이다. NOT IN (10, 20, NULL) 조건은 내부적으로 다음과 같은 비교가 이루어진다:
- department_id ≠ 10
- AND department_id ≠ 20
- AND department_id ≠ NULL
SQL에서 NULL ≠ 값은 결과가 UNKNOWN이 된다. 이 조건의 전체 결과도 UNKNOWN이 되어, 어떤 행도 조건을 만족하지 못하게 된다. 이로 인해 결과가 반환되지 않는다.
NOT EXISTS 예시
SELECT *
FROM employees e
WHERE NOT EXISTS (
SELECT 1 FROM departments d
WHERE d.department_id = e.department_id
);
결과
employee_id | name | department_id |
3 | Carol | 30 |
이유
이 쿼리는 각 직원의 department_id가 departments 테이블에 존재하지 않는 경우만 필터링한다. department_id = 30인 Carol은 departments에 해당 부서가 없기 때문에 NOT EXISTS 조건을 만족하여 결과로 반환된다.
departments에 NULL이 있더라도 EXISTS 조건은 특정 행의 존재 여부만 판단하므로, NULL의 영향을 받지 않는다.
3. NULL-safe한 쿼리 작성법
NOT IN을 사용해야 하는 경우라면, 서브쿼리에서 반드시 NULL을 제거해야 한다. 다음과 같이 WHERE department_id IS NOT NULL 조건을 추가하면 된다.
SELECT *
FROM employees
WHERE department_id NOT IN (
SELECT department_id FROM departments WHERE department_id IS NOT NULL
);
이렇게 하면 NOT IN 조건이 NULL로 인해 전체 결과를 무효화시키는 상황을 방지할 수 있다.
결론
- IN/NOT IN은 리스트와 비교하고, EXISTS/NOT EXISTS는 조건을 만족하는 행의 존재 여부를 기준으로 동작한다.
- NOT IN은 서브쿼리 결과에 NULL이 포함되면 전체 결과가 무효화된다.
- 반면 NOT EXISTS는 NULL에 영향을 받지 않아 안정적이다.
- 대규모 데이터셋이나 신뢰할 수 없는 서브쿼리를 대상으로 할 경우 NOT EXISTS가 더 예측 가능하고 안전하다.
'Programming > DB' 카테고리의 다른 글
Oracle PL/SQL 프로시저 작성법: 기본 개념과 실습 예제 (0) | 2025.04.18 |
---|---|
Oracle MERGE 성능 이슈 및 해결 사례 (0) | 2025.04.18 |
Oracle DB에서 Redo 로그, Undo, 아카이브 로그의 개념 정리 (0) | 2025.04.15 |
RDB에서 페이징 쿼리의 중요성과 LIMIT, OFFSET 방식의 장단점 (0) | 2025.04.13 |
오라클 힌트(Oracle Hint) 정리 – 자주 사용하는 힌트와 최적화 전략 (1) | 2025.04.08 |