「入力した項目だけで検索」に IS NOT FALSE を使うなら被検索列が NULL にならないか注意

わりとよくある、複数の項目に検索条件が入力できて、入力した項目だけを検索条件として使用し、未入力の項目は検索条件には使わない、というフォーム、クエリビルダのようなものを使わないなら IS NOT FALSE を使うと便利です。

SELECT * FROM t WHERE (
    a = :a AND b = :b AND c = :c
) IS NOT FALSE

NULL と比較すると UNKNOWN となり、UNKNOWN AND TRUEUNKNOWNUNKNOWN AND FALSEFALSE、というSQLの3論理値の特徴を利用しています。

  • a = :ab = :bTRUEc = :cUNKNOWN なら TRUE AND TRUE AND UNKNOWNUNKNOWN なので IS NOT FALSETRUE
  • a = :aTRUEb = :bFALSEc = :cUNKNOWN なら、TRUE AND FALSE AND UNKNOWNFALSE なので IS NOT FALSEFALSE

ただこれは被検索列の abcNOT NULL の場合のみ期待した結果になります。もし被検索列が NULL だと、例えば aNULL だった場合、:a がどんな値だったとしても結果は a = :aUNKNOWN になるため :a になにを入力してもヒットします。

具体例 v1

例えばあるテーブルに日付の FROM と TO が記録されていて、入力した FROM と TO と期間が重なるレコードを検索する、ただし FROM や TO が未入力なら条件として使わない、というクエリは IS NOT FALSE で次のように書けます。

SELECT * FROM t WHERE (
        date_from <= :date_to
    AND date_to   >= :date_from
) IS NOT FALSE

がしかし、もし t.date_fromNULL だったとすると :date_to になにを入力しても検索にヒットしてしまいます。

ん? t.date_fromNULL なら大抵の場合は開始日が無期限というか無限の過去みたいな扱いだろうので、何を入力してもヒットするのは正しいのでは。

というわけでこれは例が悪かったです。むしろ IS NOT FALSE を使えば簡単になる良い例でした。

具体例 v2

例えば、カテゴリとかで整数型な ID でカテゴリテーブルと関連させていて、カテゴリ未設定のときに NULL が入るようにしているとき。

SELECT * FROM t WHERE (
        date_from <= :date_to
    AND date_to   >= :date_from
    AND category   = :category
) IS NOT FALSE

検索条件に「カテゴリ=フルーツ」とか入れて 検索して、カテゴリが未設定の行が検索結果に表示されると変ですね。

どう書くのがキレイでしょうかね。

/* IS NOT FALSE の外に出して IS NULL を付ける */
SELECT * FROM t WHERE (
        date_from <= :date_to
    AND date_to   >= :date_from
) IS NOT FALSE
AND (category = :category OR :category IS NULL);
/* IS NOT FALSE の内側に置くなら <=> を使う */
SELECT * FROM t WHERE (
        date_from <=  :date_to
    AND date_to   >=  :date_from
    AND (category <=> :category OR :category IS NULL)
) IS NOT FALSE
/* IFNULL でもいいかも */
SELECT * FROM t WHERE (
        date_from <= :date_to
    AND date_to   >= :date_from
    AND category <=> IFNULL(:category, category)
) IS NOT FALSE

さいごに

要するに「入力した項目だけで検索」で IS NOT FALSE を使うときに被検索列が NULL になる可能性があるならその NULL が検索でどのように扱うのかを留意する必要があるということですね。

あと、↑のどの書き方でもインデックスはまあまともに使われないと思うので、実は NULL が来ることはないのだけどコピペで IS NOT FALSE にしてしまってパフォーマンス出ない、ということにならないように注意する必要もあります。