UNKNOWNを返すことができるブール式を反転します


11

テーブルがあります

ID  myField
------------
 1  someValue
 2  NULL
 3  someOtherValue

そして、TRUE、FALSE、または(SQLの3値論理による)UNKNOWNに評価できるT-SQLブール式:

SELECT * FROM myTable WHERE myField = 'someValue'

-- yields record 1

他のすべてのレコードを取得する場合、式を単純に否定することはできません

SELECT * FROM myTable WHERE NOT (myField = 'someValue')

-- yields only record 3

なぜこれが発生するのか(3値論理)、この特定の問題を解決する方法を知っています。

私は使用できることを知ってmyField = 'someValue' AND NOT myField IS NULLおり、不明な結果を決してもたらさない「可逆」式を取得します。

SELECT * FROM myTable WHERE NOT (myField = 'someValue' AND myField IS NOT NULL)

-- yields records 2 and 3, hooray!

一般的なケース

それでは、一般的なケースについて話しましょう。myField = 'someValue'たくさんのフィールドと条件、おそらくサブクエリを含むいくつかの複雑な式があるとしましょう:

SELECT * FROM myTable WHERE ...some complex Boolean expression...

この表現を「反転」する一般的な方法はありますか?部分式で機能する場合のボーナスポイント:

SELECT * FROM myTable 
 WHERE ...some expression which stays... 
   AND ...some expression which I might want to invert...

SQL Server 2008-2014をサポートする必要がありますが、2008より新しいバージョンを必要とするエレガントなソリューションがある場合は、それについても知りたいです。

回答:


15

バイナリの結果を返すCASE式で条件を囲むことができます(例:1または0)。

SELECT
  ...
FROM
  ...
WHERE
  CASE WHEN someColumn = someValue THEN 1 ELSE 0 END = 1
;

式を否定すると、同じデータソースからの他のすべての行が得られます。これには、someColumnがnullのも含まれます。

SELECT
  ...
FROM
  ...
WHERE
  NOT CASE WHEN someColumn = someValue THEN 1 ELSE 0 END = 1
  -- or: CASE WHEN someColumn = someValue THEN 1 ELSE 0 END <> 1
;

SQL Server 2012以降には、IIF関数も用意されています。これは、上記のようなバイナリCASEの単なるラッパーです。したがって、このCASE式:

CASE WHEN someColumn = someValue THEN 1 ELSE 0 END

IIFを使用して書き換えると、次のようになります。

IIF(someColumn = someValue, 1, 0)

また、CASE式とまったく同じ方法で使用できます。パフォーマンスに違いはありません。コードが少しだけ簡潔になり、おそらくよりクリーンになります。


それはいい考えです!CASEを使用してブール式を操作可能な式に「変換」し、比較を使用してブール式に「変換」して戻します。
ハインツィ

10

私が最初に思いつくのは、

DECLARE @T AS table (c1 integer NULL);

INSERT @T (c1)
VALUES (1), (NULL), (2);

-- Original expression c1 = 1
SELECT T.c1
FROM @T AS T
WHERE c1 = 1;

戻り値:

結果

-- Negated
SELECT T.c1
FROM @T AS T
WHERE NOT EXISTS (SELECT 1 WHERE c1 = 1);

戻り値:

否定された結果

これは、途中に依存してEXISTS、常に返しますまたは決して、不明。の必要性SELECT 1 WHEREは残念ながら必要ですが、たとえば次のように、要件に応じて実行できる場合があります。

sql = "
    SELECT * 
    FROM someTable 
    WHERE " + someExpression + 
    " AND NOT EXISTS (SELECT 1 WHERE " + 
    someOtherExpression + ")";
result = executeAndShow(sql);

EXISTS(Transact-SQL)を参照してください


個々の述語を逆転させるためにいずれEXISTSかのCASE/IIF方法またはメソッドを適用する方法を示す、もう少し複雑な作業例:

DECLARE @T AS table 
(
    c1 integer NULL,
    c2 integer NULL,
    c3 integer NULL
);

INSERT @T 
    (c1, c2, c3)
VALUES 
    (1, NULL, 2),
    (2, 2, 3),
    (NULL, 1, 4);

コード:

-- Original
SELECT 
    T.c1,
    T.c2,
    T.c3
FROM @T AS T
WHERE
    1 = 1
    -- Predicate #1
    AND T.c1 = 2
    -- Predicate #2
    AND T.c2 =
    (
        SELECT MAX(T2.c2)
        FROM @T AS T2
        WHERE T2.c2 IS NOT NULL
    )
    -- Predicate #3
    AND T.c3 IN (3, 4)
    ;

-- Invert predicates #1 and #2
SELECT 
    T.c1,
    T.c2,
    T.c3
FROM @T AS T
WHERE
    1 = 1
    AND NOT EXISTS (SELECT 1 WHERE 1 = 1
        -- Predicate #1
        AND T.c1 = 2)
    AND NOT EXISTS (SELECT 1 WHERE 1 = 1
        -- Predicate #2
            AND T.c2 =
            (
                SELECT MAX(T2.c2)
                FROM @T AS T2
                WHERE T2.c2 IS NOT NULL
            ))
    -- Predicate #3
    AND T.c3 IN (3, 4)
    ;

3

部分式を前もって書き直してもかまわない場合は、次のように使用できますCOALESCE

SELECT *
FROM myTable
WHERE NOT (COALESCE(myField, 'notSomeValue') = 'someValue')

とは異なることを確認する必要あります。できれば、それは列に対して完全に違法な値になるでしょう。(もちろん、にすることもできません。)リストが長い場合でも、これは簡単に否定できます。'notSomeValue''someValue'NULL

SELECT *
FROM myTable
WHERE NOT (
    COALESCE(myField, 'notSomeValue') = 'someValue' AND
    COALESCE(myField2, 'notSomeValue') = 'someValue2' AND
    COALESCE(myField3, 'notSomeValue') = 'someValue3' AND
    COALESCE(myField4, 'notSomeValue') = 'someValue4'
)

私の意見では、CASEor よりも、よりクリーンで、シンプルで、より明白ですIIF。主な欠点は、等しくないことがわかっている2番目の値があることですが、これは実際の値が事前にわからない場合にのみ問題になります。その場合、Hanno Binderが提案して使用するように実行できますCOALESCE(myField, CONCAT('not', 'someValue')) = 'someValue''someValue'実際にはパラメーター化されます)。

COALESCE SQL Server 2005以降で利用できるように文書化されています。

このようにクエリをいじると(ここで推奨されている方法のいずれかを使用)、データベースがクエリを最適化するのがより困難になる可能性があることに注意してください。大規模なデータセットの場合、IS NULLバージョンを最適化する方が簡単です。


1
COALESCE(myField, CONCAT('not', 'someValue')) = 'someValue'「someValue」とテーブル内のデータに対して機能するはずです。
JimmyB 2016年

2

組み込みのEXCEPTセット演算子があります。これは、2番目のクエリの結果を最初のクエリから効果的に削除します。

select * from table
except
select * from table
where <really complex predicates>

それが小さなテーブルであることを望みましょう:-)
Lennart

-4

COALESCEは利用できますか?

SELECT * FROM myTable WHERE NOT COALESCE(myField = 'someValue', FALSE)

4
はい。COALESCEは使用できますが、使用できません。(a)COALESCEはブール式を受け入れません(ちなみにISNULLは使用できません)。(b)真理値FALSEはSQLで直接使用できません。リテラル。試してみると、構文エラーが発生します。
ハインツィ

@Heinzi-私はそれを試しました、それはうまくいきました、それが私がそれを投稿した理由です。T-SQLでは機能しないかもしれませんが、PostgresやMySQLでは問題ありません。
Malvolio 2016年

2
@Malvolio:質問がされてタグ付けされたsql-server、しかし、ではありませんmysqlpostgresql
Andriy M

@Malvolioは、PostgresにBOOLEAN型があり、MySQLには関数のBOOLEANパラメーターである(偽の)型があるためCOALESCE()です。質問にsql-agnosticまたはのタグが付いている場合、回答は問題ありsql-standardません。
ypercubeᵀᴹ

@ypercubeᵀᴹ-ええ、何を言えますか?より良いデータベースを入手してください。
Malvolio 2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.