제외 목록에 NULL이 섞이면 NOT IN은 목록에 없는 값까지 걸러낼 수 있습니다. 값이 같은 제외 행이 하나도 없는지 검사하려면, 바깥 행의 코드와 제외 목록의 코드를 등호로 연결한 NOT EXISTS를 쓰세요. 요청 코드 자체가 비어 있으면 보류해야 하는 업무라면 바깥쪽 코드에 IS NOT NULL도 적용합니다.
차단된 A와 등록된 B, 코드가 비어 있는 요청
가상의 제외 목록에는 A와 NULL이 한 행씩 있습니다. 같은 요청을 네 조건으로 검사해 참·거짓과 NULL을 함께 봅니다.
import sqlite3
db = sqlite3.connect(":memory:")
rows = db.execute("""
WITH candidates(id, code) AS (VALUES (1, 'A'), (2, 'B'), (3, NULL)),
blocked(code) AS (VALUES ('A'), (NULL)),
checks AS (
SELECT c.id, c.code,
c.code NOT IN (SELECT code FROM blocked) AS not_in,
NOT EXISTS (
SELECT 1 FROM blocked AS b WHERE b.code = c.code
) AS not_exists,
c.code NOT IN (
SELECT code FROM blocked WHERE 0
) AS empty_not_in
FROM candidates AS c
)
SELECT id, code, not_in, not_exists,
code IS NOT NULL AND not_exists AS known_only,
empty_not_in
FROM checks
ORDER BY id
""")
print("id code not_in not_exists known_only empty_not_in")
for row in rows:
print(*row)
db.close()
id code not_in not_exists known_only empty_not_in
1 A 0 0 0 1
2 B None 1 1 1
3 None None 1 0 1
출력에서 1은 참, 0은 거짓, None은 SQL의 NULL입니다. B는 제외 목록의 A와 다릅니다. 남은 NULL과 비교한 결과는 알 수 없으므로, B NOT IN (...)도 NULL이 됩니다. WHERE는 조건이 참인 행만 남겨 B가 결과에서 빠집니다.
NOT EXISTS는 안쪽 WHERE b.code = c.code를 통과한 행이 있는지 봅니다. B와 같은 제외 행은 없어서 참입니다. 요청 코드가 NULL인 3번도 등호가 참인 제외 행이 없어서 참입니다. 코드가 있어야 처리할 수 있다면 known_only처럼 두 조건을 묶고, 누락 코드도 남겨 후속 처리한다면 그 정책에 맞게 조건을 선택합니다.
마지막 열은 WHERE 0으로 제외 목록을 비운 경우입니다. SQLite에서는 오른쪽이 빈 결과 집합이면 NOT IN이 왼쪽 NULL까지 참으로 평가합니다. 오른쪽에 NULL 행이 하나 들어 있는 경우와 결과가 다릅니다.
가상 요청은 Windows의 Python 3.14.3 내장 sqlite3에서 SQLite 3.50.4로 실행했습니다. 사용한 규칙은 SQLite의 IN과 NOT IN, EXISTS에서 확인할 수 있습니다.