본문 바로가기

Programming/DB

SQL에서 IN, NOT IN vs EXISTS, NOT EXISTS 차이 — 그리고 NULL에 대한 함정

반응형

SQL에서 서브쿼리를 사용할 때 NOT INNOT EXISTS는 자주 혼용되지만, 두 연산자는 내부적으로 전혀 다른 방식으로 작동한다. 특히 서브쿼리 결과에 NULL이 포함될 경우, 이 차이는 실질적인 결과의 차이로 이어지며, 예상치 못한 버그를 유발할 수 있다.

본 문서에서는 NOT INNOT 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_iddepartments 테이블에 존재하지 않는 경우만 필터링한다. department_id = 30인 Carol은 departments에 해당 부서가 없기 때문에 NOT EXISTS 조건을 만족하여 결과로 반환된다.

departmentsNULL이 있더라도 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 EXISTSNULL에 영향을 받지 않아 안정적이다.
  • 대규모 데이터셋이나 신뢰할 수 없는 서브쿼리를 대상으로 할 경우 NOT EXISTS가 더 예측 가능하고 안전하다.

 

반응형