読者です 読者をやめる 読者になる 読者になる

この日記は私的なものであり、所属会社の見解ではありません。 GitHub: takahashikzn

3値論理を理解する

SQL

今更ながら、こちらのページにて3値論理を勉強しました。

式の評価結果がTRUEでもFALSEでもない、という考え方に最初は戸惑いましたが、慣れてみると結構使えそうな感触。

イントロ

SQLでは、あるカラムがNULLであるか否かを調べるのに

   xxx = NULL

ではなく

   xxx IS NULL

と書きます。これまで僕は、この式の意味を深く考えずに
「NULLの場合は、"="は使えない」と機械的に考えるだけでした。


ところが、3値演算のことが理解できるようになると、"IS NULL"の本当の意味が良く分かるようになりました。

要するに3値論理って何?

3値論理を理解するキモは、「わからない(NULL/UNKNOWN)」の意味を難しく考えないことです。

以下のように、普通の言葉で置き換えるとわかりやすいでしょう。

  • TRUE: 正しい
  • NULL(UNKNOWN): わからない
  • FALSE: 正しくない


そして、「意味の重さ」は次の通りです。

  正しくない >> わからない >> 正しい


言葉の重さとは、要するに

  • 他の要素が何であれ、一つでも「正しくない」モノがあれば、全体としては「正しくない」。
  • 他の要素が全て「正しい」モノであっても、一つでも「わからない」モノが混入してしまえば、全体としては「わからない」。
  • 絶対に「正しい」と言えるのは、全ての要素が「正しい」時だけ。

ということです。


すなわち、

  「正しいもの」&「わからないもの」=「わからないもの」  つまり (TRUE AND NULL) IS NULL

が真ですし、

  「わからないもの」&「正しくないもの」=「正しくないもの」 つまり (NULL AND FALSE) IS FALSE

も真です。

3値論理の応用方法

最初に紹介したページにある通りですが、SQLを固定的に書くことができます。


例えばエンティティの検索画面ではユーザが任意に検索条件を入力しますが、
検索条件値はあったりなかったりするわけです。
だからSQLを動的に生成せざるを得ず、次のようなコードを書かざるを得ないことになります。

    /**
     * 入力された検索条件に応じてSQLを組み立てて実行する
     */
    ResultSet select(Map<String, String> conditionValues) {
        // SQLを組みたてる
        String sql = "SELECT * FROM ABC WHERE ";

        List<String> whereSql = new ArrayList<String>();

        if (conditionValues.get("column1") != null) {
            whereSql.add("column1 = ?");
        }

        if (conditionValues.get("column2") != null) {
            whereSql.add("column2 = ?");
        }

        if (conditionValues.get("column3") != null) {
            whereSql.add("column3 = ?");
        }

        sql += StringUtils.join(whereSql, " and ");


        // パラメータをセットする
        PreparedStatement ps = conn.prepareStatement(sql);
        int position = 1;

        if (conditionValues.get("column1") != null) {
            ps.setString(position, conditionValues.get("column1"));
            position++;
        }

        if (conditionValues.get("column2") != null) {
            ps.setString(position, conditionValues.get("column2"));
            position++;
        }

        if (conditionValues.get("column3") != null) {
            ps.setString(position, conditionValues.get("column3"));
            position++;
        }

        return ps.executeQuery();
    }


ここで3値論理を導入すると、WHERE句の各条件の評価結果がTRUEまたはNULLであればよいので、
式全体が少なくとも偽でない、すなわち"IS NOT FALSE"であれば十分ということになります。

    /**
     * 入力された検索条件に応じてSQLを実行する
     */
    ResultSet select(Map<String, String> conditionValues) {
        String sql = "SELECT * FROM ABC WHERE " + 
            "(" +
            "column1 = ? and" +
            "column2 = ? and" +
            "column3 = ?" +
            ") IS NOT FALSE";

        // パラメータをセットする
        PreparedStatement ps = conn.prepareStatement(sql);

        ps.setString(1, conditionValues.get("column1"));
        ps.setString(2, conditionValues.get("column2"));
        ps.setString(3, conditionValues.get("column3"));

        return ps.executeQuery();
    }

と書くだけで済むのです。かなりスッキリしていますね。