SELECT * FROM orders o WHERE o.customer_id NOT IN (SELECT id FROM blocked)는 blocked.id에 NULL이 하나라도 들어가는 순간 아무것도 돌려주지 않습니다. 오류도 없습니다. 0행입니다.
이유는 기계적입니다. x NOT IN (a, b, NULL)은 x <> a AND x <> b AND x <> NULL이라는 뜻입니다. 마지막 비교는 참이 될 수 없고 언제나 unknown입니다. unknown이 섞인 AND는 거짓이나 unknown은 될 수 있어도 참은 될 수 없고, WHERE는 조건이 참인 행만 남깁니다.
고치는 방법은 세 가지이고 서로 같지 않습니다.
- 서브쿼리에
WHERE id IS NOT NULL을 붙입니다. 맞는 방법이지만 앞으로 이 쿼리를 만지는 모든 사람이 기억해야 합니다. NOT EXISTS (SELECT 1 FROM blocked b WHERE b.id = o.customer_id)로 바꿉니다.blocked에 있는 NULL은 어떤 값과도 일치하지 않으니 아무도 막지 않습니다. 저는 이쪽을 씁니다.blocked.id를 NOT NULL로 선언합니다. 그 열이 애초에 모르는 값을 담을 이유가 없었다면 셋 중 가장 낫고, 차단 id 목록이라면 실제로 이유가 없었습니다.
IN에는 같은 문제가 더 약한 형태로 있습니다. 거짓을 기대한 자리에서 unknown을 돌려줄 수 있고, 누군가 그 결과를 부정하는 순간부터 문제가 됩니다.