SQLのNULLは奇妙
(jirevwe.github.io)-
SQLではNULL値が独特に扱われる。UNIQUE制約のある列には複数のNULL値を入れられる。
- これは、各NULL値が他のNULLとは異なる独立した値として見なされるため。
- SQLite、Postgres、MySQLはすべて同じように動作する。
-
基準の確認
select '' = ''; -- Returns 1 (true) 빈 문자열은 같음 select 1 = 1; -- Returns 1 (true) 숫자는 같음 select 1 = 0; -- Returns 0 (false) 숫자가 다름 select null = null; -- Returns NULL (null) 응?- NULLは「未知の値」を表すプレースホルダーなので、2つの未知の値が同一だとは見なされないため。
IS演算子を使うとNULLの同一性を確認できる。たとえば、null is nullはTRUEを返す。
-
一意性について
- UNIQUE制約のある列にNULL値が含まれている場合、NULL値は互いに異なるものとして扱われるため、一意性制約に違反しない。
- たとえば、
('ray@mail.com', NULL)と('ray@mail.com', NULL)は別々の行として見なされる。
-
NULLがこのように扱われる理由
- SQLiteや他のSQL互換データベースは、NULLを他のデータベースと一貫した形で扱うためにこのように実装されている。SQL標準文書ではNULLはあらゆる場所で一意であるべきだと示唆されているが、実際にはほとんどのSQLエンジンがSELECT DISTINCTやUNIONでNULLを一意なものとして扱わない。
-
一意性を保証する方法
-
生成列を使う
- 常にNULLではない決定論的な値を持つ列を生成することで、この問題を緩和できる。たとえば、
COALESCE(deleted_at, '1970-01-01')を使ってNULL値を置き換えられる。 - この方法ではテーブルに追加フィールドが増えるため、領域を消費する可能性がある。
- 常にNULLではない決定論的な値を持つ列を生成することで、この問題を緩和できる。たとえば、
-
部分インデックスを使う
deleted_atがNULLの場合にだけemailに対する部分インデックスを作成することで、一意性を保証できる。- 部分インデックスはテーブルを広げず、使用領域も少なく、同じレコードの組を繰り返し削除してもエラーが発生しない。
-
-
更新
- Oracleは空文字列をNULLとして扱う。
-
結論
- ORMを使っていると見えにくいが、SQLのNULLの独特な扱い方は混乱を招くことがある。SQL標準文書は公開されておらず、入手には費用がかかる。
2件のコメント
すべての null は奇妙ですよね。
だから、まともな SQL の null のほうが、かえって奇妙に見えるという…。
片目の国では両目の人が異常……
Hacker Newsのコメント
SQL の NULL は、クリーンの TRUE-FALSE-UNKNOWN 論理に基づいている。NULL を UNKNOWN と読めば、多くの演算がより直感的に理解できる
NULLS NOT DISTINCTを使って一意インデックスを作成できる1970年代に NULL の概念が導入されたとき、将来多くの混乱を招くと思っていた。45年経った今でも、なお議論されている
NULL の直感的な理解: 特定のテーブルセルの NULL 値は「値がない」ことを表す方法である。一意な値を求めるとき、値がない場合は考慮すべきではない
ORM 利用への懐疑論: ORM は便利だが、リレーショナルデータベースが実際にどう動くかを学ばない世代を生み出した。SQL NULL の挙動は基本的な関係代数と一致しており、問題なのは C スタイルの NULL である
Blackadder のエピソード中の会話から、NULL 比較のユーモアを思い出す
Oracle では NULL が空文字列と同じである点が奇妙だと思う
オブジェクト指向の文脈では、"null" は特定の属性に値がないことを示すのに有用である。JavaScript には null と undefined があり、undefined は値が不明で、null は値がないことを意味すると考えられる
NULL は重複がないという意味で奇妙ではない。NULL 同士は等しくないため、重複にはなりえない。NULL のセマンティクスが気に入らないなら、センチネル値を使うこともできる
SQL NULL は、存在しない値を持つレコードに対して関係論理がどう機能してほしいかを考えれば、奇妙ではない