SQL 코딩테스트를 풀다 보면 로직이 완벽함에도 불구하고 결과가 아예 나오지 않는(Empty Set) 현상을 마주할 때가 있다. 특히 서브쿼리와 함께 NOT IN을 사용할 때 이 현상이 자주 발생하는데, 그 원인은 바로 데이터베이스의 3값 논리(Three-Valued Logic) 와 NULL 처리에 있다.
-- 1. 정상 작동
WHERE ID IN (SELECT PARENT_ID FROM ECOLI_DATA)
-- 2. 결과가 아예 안 나옴 (Empty Set)
WHERE ID NOT IN (SELECT PARENT_ID FROM ECOLI_DATA)
서브쿼리 결과에 NULL이 포함되어 있을 때, 단지 NOT 하나만 붙였을 뿐인데 모든 데이터가 증발해 버린다. 일반적인 프로그래밍 언어의 2값 논리(True/False)로는 이해하기 힘든 이 현상의 원인을 파헤쳐 보자.
SQL에는 참과 거짓 외에 UNKNOWN(알 수 없음) 이라는 상태가 존재한다. NULL이 포함된 비교 연산(예: 1 = NULL, 1 != NULL)은 무조건 UNKNOWN을 반환한다.
IN 연산자의 내부 동작 (OR 연산)IN (1, 2, NULL)은 내부적으로 OR로 풀린다.
ID = 1 OR ID = 2 OR ID = NULLTRUE OR FALSE OR UNKNOWN 최종 결과: TRUEOR 연산은 하나라도 참이면 전체가 참이므로, 일치하는 값이 있다면 NULL이 섞여 있어도 정상적으로 통과한다.NOT IN 연산자의 내부 동작 (AND 연산)NOT IN (1, 2, NULL)은 내부적으로 AND로 풀린다.
ID != 1 AND ID != 2 AND ID != NULLTRUE AND TRUE AND UNKNOWN 최종 결과: UNKNOWNAND 연산은 모두 참이어야 참이다. 하지만 ID != NULL이 UNKNOWN을 뱉어내기 때문에, 전체 논리식이 UNKNOWN이 되어버린다. WHERE 절은 오직 TRUE인 행만 통과시키므로, 서브쿼리에 NULL이 단 하나라도 있다면 모든 데이터가 필터링되어 사라진다.이 함정을 피하는 가장 직관적인 방법은 서브쿼리 내부에서 원천적으로 NULL을 제거하는 것이다.
SELECT ID
FROM TABLE_A
WHERE ID NOT IN (
SELECT PARENT_ID
FROM ECOLI_DATA
WHERE PARENT_ID IS NOT NULL -- NULL 명시적 제거
)
[핵심 요약]
서브쿼리와 함께 NOT IN을 사용할 때는 기계적으로 "서브쿼리 결과에 NULL이 섞여 있지는 않은가?" 를 의심하고 방어 코드를 작성하는 습관을 들이자.