openlang

NOT IN과 NULL 하나

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는 조건이 참인 행만 남깁니다.

고치는 방법은 세 가지이고 서로 같지 않습니다.

  1. 서브쿼리에 WHERE id IS NOT NULL을 붙입니다. 맞는 방법이지만 앞으로 이 쿼리를 만지는 모든 사람이 기억해야 합니다.
  2. NOT EXISTS (SELECT 1 FROM blocked b WHERE b.id = o.customer_id)로 바꿉니다. blocked에 있는 NULL은 어떤 값과도 일치하지 않으니 아무도 막지 않습니다. 저는 이쪽을 씁니다.
  3. blocked.id를 NOT NULL로 선언합니다. 그 열이 애초에 모르는 값을 담을 이유가 없었다면 셋 중 가장 낫고, 차단 id 목록이라면 실제로 이유가 없었습니다.

IN에는 같은 문제가 더 약한 형태로 있습니다. 거짓을 기대한 자리에서 unknown을 돌려줄 수 있고, 누군가 그 결과를 부정하는 순간부터 문제가 됩니다.

이어지는 대화 2

서로의 근거를 따라
  1. 고유 링크

    Julia에서 1 in [2, missing]은 missing을 돌려줍니다. 정확히 말씀하신 NOT IN 경우입니다. 같은 삼값 규칙이 전반에 적용됩니다. missing == 1은 missing이고, true | missing은 true, false & missing은 false로, SQL의 OR과 AND와 같습니다.

    차이는 SQL의 WHERE라면 행을 조용히 버릴 지점에서 납니다. if missing은 TypeError를 던지고, missing을 돌려주는 술어를 넘긴 filter도 마찬가지입니다. 함정이 조용히 작동할 수 없습니다. coalesce, skipmissing, isequal 중 하나를 고르게 되고, 그 선택이 코드에 남습니다.

  2. 고유 링크

    여기서 값이 없다는 건 어떤 뜻인가요? 그 목록의 NULL은 입력이 덜 끝난 행일 수도 있고, id를 잃어버린 차단 계정일 수도 있어요. NOT EXISTS는 그게 아무도 막지 않는다고 조용히 정해 버려요. 세 번째 방법은 그걸 스키마가 대신 말하게 해요.

같은 질문의 다른 갈래

  1. 전체 개수와 페이지가 같은 스냅샷을 써야 합니까?

    PostgreSQL 18, Read Committed, 세션 두 개를 기준으로 보겠습니다. 보고 화면이 조건에 맞는 주문 12건을 셉니다. 다른 세션이 조건에 맞는 주문을 하나 넣고 커밋합니다. 그다음 화면이 첫 페이지를 가…