0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【SQL】IN句とNULLの怖さ

Last updated at Posted at 2025-04-19

はじめに

業務で最近SQLをよく読むことが多くなったのだが、その際にNULL関連で勉強になったことがあったので備忘と啓発のための記事です

要約

samplesテーブル

id name
1 SQL
2 Python
3 JavaScript
NULL C#

idが2でも3でもNULLでもないレコードを検索したい
今回の場合、ID=1であるレコードだけを出力したい

SELECT * FROM samples WHERE id NOT IN (2, 3, NULL);

↓出力結果(対象レコードなし)

id name

NULLの比較によって検索条件が想定外のものになってしまうのだろう

原因

検索条件を式変形していくと原因らしいものを特定できた

id NOT IN (2, 3, NULL)
=(IN句をORに同値変換)
NOT ((id = 2) OR (id = 3) OR (id = NULL))
=(∵ドモルガンの法則)
(id <> 2) AND (id <> 3) AND (id <> NULL)
=(∵id <> NULLは常にunknownを返す)
(id <> 2) AND (id <> 3) AND unknown
=(∵true AND unknown = unknown, false AND unknown = false)
false もしくは unknown

よって必ずtrueになることはないので条件に合うレコードは存在しなくなる

暫定対応策

NULLを特別扱いするのが一番簡単

SELECT * FROM samples WHERE id NOT IN (2, 3) AND id IS NOT NULL;

考えられる問題

対策でNULLを特別扱いすればよいとは言ったが、例えば以下の場合に考慮しにくい。

名簿テーブル

id 部署 役割
1 S A
2 S B
3 S C
4 T A
5 T D
6 T NULL

T部署に存在しないS部署の役割を探す場合
今回の場合、S(A, B, C) でT(A, D, NULL)なので役割BとCを出力して欲しい

SELECT 役割 FROM 名簿
WHERE
    役割 NOT IN
        (SELECT 役割 FROM 名簿 WHERE 部署 = 'T')
    AND 部署 = 'S'
;

これで残念ながらIN句にNULLが入ってしまい出力は0レコードとなってしまう

上記のSQLのように「存在する/しない」を判断するならば以下のようにEXISTSを使って書いた方が安全だろう

SELECT 役割 FROM 名簿 m1
WHERE
	NOT EXISTS (
		SELECT 1 FROM 名簿 m2
		WHERE m2.部署 = 'T' AND m2.役割 = m1.役割
	)
    AND s1.部署 = 'S'
;

結論として、SQLを読み書きする際は常にNULLを意識した方が良いのだろう

0
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?