NOT IN句内のNULL値


244

この問題は、1つはnot in where制約を使用し、もう1つはaを使用した同一のクエリであると私が考えたものに対して異なるレコード数を取得したときに発生しましたleft joinnot in制約内のテーブルには1つのnull値(不正なデータ)があり、そのクエリはレコード数0を返しました。理由はある程度理解できましたが、コンセプトを完全に理解するためにいくつかの助けを借りることができました。

簡単に言うと、クエリAが結果を返すのにBは返さないのはなぜですか?

A: select 'true' where 3 in (1, 2, 3, null)
B: select 'true' where 3 not in (1, 2, null)

これはSQL Server 2005でset ansi_nulls off発生しました。また、呼び出しによってBが結果を返すこともわかりました。

回答:


283

クエリAは次と同じです。

select 'true' where 3 = 1 or 3 = 2 or 3 = 3 or 3 = null

以来3 = 3真である、あなたは結果を得ます。

クエリBは次と同じです。

select 'true' where 3 <> 1 and 3 <> 2 and 3 <> null

ansi_nullsがオンの場合、3 <> nullはUNKNOWNなので、述部はUNKNOWNと評価され、行は取得されません。

ときはansi_nullsオフになって、3 <> null真であるので、真の述語評価し、あなたは行を取得します。


11
NOT IN一連の<> and変化に変換することで、このセットないものの意味的動作が別のものに変わることを誰かが指摘したことがありますか?
Ian Boyd

8
@Ian-"A NOT IN( 'X'、 'Y')"は実際にはSQLのA <> 'X' AND A <> 'Y'のエイリアスです。(私はあなたがstackoverflow.com/questions/3924694/…でこれを自分で発見したようですが、この質問であなたの異論に対処したことを確認したかったのです。)
Ryan Olson

これSELECT 1 WHERE NULL NOT IN (SELECT 1 WHERE 1=0);が、期待した空の結果セットの代わりに行を生成する理由を説明していると思います。
binki

2
これはSQLサーバーの非常に悪い動作です。 "IS NULL"を使用してNULL比較を期待する場合は、IN句を同じ動作に拡張し、誤ったセマンティクスをそれ自体に適用しないでください。
OzrenTkalcecKrznaric 2016

@binki、クエリは、ここで実行するとrextester.com/l/sql_server_online_compilerが実行されますが、ここで実行すると機能しませんsqlcourse.com/cgi-bin/interpreter.cgi
Istiaque Ahmed 2017

52

NULLを使用するときはいつでも、実際には3値ロジックを処理しています。

最初のクエリは、WHERE句が次のように評価されるときに結果を返します。

    3 = 1 or 3 = 2 or 3 = 3 or 3 = null
which is:
    FALSE or FALSE or TRUE or UNKNOWN
which evaluates to 
    TRUE

2つ目:

    3 <> 1 and 3 <> 2 and 3 <> null
which evaluates to:
    TRUE and TRUE and UNKNOWN
which evaluates to:
    UNKNOWN

UNKNOWNはFALSEと同じではありません。次を呼び出すことで簡単にテストできます。

select 'true' where 3 <> null
select 'true' where not (3 <> null)

どちらのクエリでも結果は得られません

UNKNOWNがFALSEと同じ場合、最初のクエリでFALSEが返されると想定すると、2番目のクエリはNOT(FALSE)と同じであるため、2番目のクエリはTRUEと評価される必要があります。
そうではありません。

SqlServerCentralには、この問題に関する非常に優れた記事があります。

NULLと3値論理の問題全体は、最初は少し混乱する可能性がありますが、TSQLで正しいクエリを作成するには理解することが不可欠です

私がお勧めするもう1つの記事は、SQL集計関数とNULLです。


33

NOT IN 不明な値と比較すると0レコードを返します

以来NULL不明で、NOT IN含むクエリNULLまたはNULL可能な値のリスト中のSは常に返されます0ことを確認する方法がないので、レコードをNULL値がテストされている値ではありませんが。


3
これは一言で言えば答えです。例がなくてもわかりやすいことがわかりました。
Govind Rai

18

IS NULLを使用しない限り、nullとの比較は未定義です。

したがって、3をNULL(クエリA)と比較すると、未定義が返されます。

つまり、SELECT 'true' where 3 in(1,2、null)およびSELECT 'true' where 3 not in(1,2、null)

NOT(UNDEFINED)は未定義ですが、TRUEではないため、同じ結果が生成されます


素晴らしい点。(null)のnullが行(ansi)を返さない1を選択します。
crokusek 2012

9

執筆時点でのこの質問のタイトルは

SQL NOT IN制約とNULL値

質問のテキストから、問題はSELECTSQL DDLではなくSQL DML クエリで発生しているように見えますCONSTRAINT

ただし、特にタイトルの言い回しを考えると、ここで行われた一部のステートメントは、(言い換え)

述語がUNKNOWNと評価された場合、行は取得されません。

これはSQL DMLの場合ですが、制約を考慮すると効果は異なります。

質問の述語から直接取られた(そして@Brannonによる優れた回答で取り上げられた)2つの制約を持つこの非常に単純なテーブルを考えてみましょう:

DECLARE @T TABLE 
(
 true CHAR(4) DEFAULT 'true' NOT NULL, 
 CHECK ( 3 IN (1, 2, 3, NULL )), 
 CHECK ( 3 NOT IN (1, 2, NULL ))
);

INSERT INTO @T VALUES ('true');

SELECT COUNT(*) AS tally FROM @T;

@Brannonの回答に従って、最初の制約(を使用IN)はTRUEと評価され、2番目の制約(を使用NOT IN)はUNKNOWNと評価されます。しかし、挿入は成功します!したがって、この場合は実際に行が挿入されているため、「行が取得されない」と言うのは厳密には正しくありません。

上記の効果は、SQL-92標準に関しては確かに正しいものです。SQL-92仕様の次のセクションを比較対照してください

7.6 where句

の結果は、検索条件の結果が真であるTの行のテーブルです。

4.10整合性制約

テーブルのチェック制約は、指定された検索条件がテーブルのどの行に対してもfalseでない場合にのみ満たされます。

言い換えると:

SQL DMLでは、「is true」という条件を満たさないため、WHERE UNKNOWNと評価されると、結果から行が削除されます。

SQL DDL(つまり、制約)では、行がUNKNOWNと評価されて、「偽ではない」という条件を満たしているため、行は結果から削除され ません。

SQL DMLとSQL DDLの効果はそれぞれ矛盾しているように見えるかもしれませんが、UNKNOWNの結果に制約を満たすことを許可することで(より正確には、制約を満たすことに失敗しないようにすることで)、「疑いの恩恵」を与えることには実際的な理由があります。 :この動作がなければ、すべての制約はnullを明示的に処理する必要があり、言語設計の観点からは非常に不十分です(言うまでもなく、コーダーにとって正しい苦痛です!)。

私がそれを書いているように、「未知は制約を満たすのに失敗しない」などのロジックに従うのが難しいと感じる場合は、SQL DDLのnull可能列とSQLの何かを単に回避することでこれをすべて省くことができると考えてくださいnullを生成するDML(例:外部結合)!


正直に言って、この件に関して言うべきことが残っているとは思いませんでした。面白い。
Jamie Ide、

2
@ジェイミー井出:実際、私はこの問題について別の答えを持っています:nullを含めるとNOT IN (subquery)予期しない結果が生じる可能性があるためIN (subquery)、常にNOT EXISTS (subquery)nullを正しく処理しているように思われるため、完全に避けて(かつてのように)常に使用するのは魅力的です。ただし、予期した結果がNOT IN (subquery)得られ、予想NOT EXISTS (subquery)外の結果が得られる場合があります。この件について自分のノートが見つかれば、これを書き上げることができるかもしれません(直感的でないため、ノートが必要です!)結論は同じですが、nullを避けてください!
2011

@oneday一貫性のある動作(内部的には一貫性があり、仕様とは一致しない)を実現するにはNULLを特別なケースにする必要があるというあなたの主張に混乱したとき。4.10を変更して「指定した検索条件がtrueの場合にのみテーブルチェック制約が満たされる」と読むだけでは不十分でしょうか。
DylanYoung

@DylanYoung:いいえ、スペックが重要な理由のためにそのように言葉で表現される:3つの値ロジックからSQLを抱えている、これらの値はTRUEFALSEUNKNOWN。4.10は、「指定された検索条件がTRUEまたはUNKNOWNである場合にのみ、テーブルのチェック制約が満たされる」と読むことができたと思います-文章の終わりの変更に注意してください-省略しました- -すべてのために」を「すべてのために」から真『と『偽の』自然言語で確実に、古典的な2値論理を参照しなければなりません。『の意味はので、私は論理値を大文字にする必要性を感じています。』
onedaywhen

1
考慮してくださいCREATE TABLE T ( a INT NOT NULL UNIQUE, b INT CHECK( a = b ) );-ここでの意図は、b等しいaかnull でなければならないということです。制約が満たされるためにTRUEにする必要がある場合、明示的にnullを処理するように制約を変更する必要がありCHECK( a = b OR b IS NULL )ます。したがって、すべての制約には、...OR IS NULL関係するnull許容列ごとにユーザーが追加するロジックが必要です。複雑さを増す、忘れた場合のバグが増えるなどです。したがって、SQL標準委員会は実用的なものにしようとしただけだと思います。
onedaywhen

7

Aでは、3がセットの各メンバーに対して等しいかどうかがテストされ、(FALSE、FALSE、TRUE、UNKNOWN)が生成されます。要素の1つがTRUEであるため、条件はTRUEです。(また、ここで何らかの短絡が発生する可能性があるため、最初のTRUEに到達するとすぐに停止し、3 = NULLと評価されることはありません。)

Bでは、条件をNOT(3 in(1,2、null))として評価していると思います。セット3に対する同等性について3をテストすると、UNKNOWNに集約された結果が得られます(FALSE、FALSE、UNKNOWN)。NOT(UNKNOWN)はUNKNOWNを生成します。したがって、全体的に状態の真実は不明であり、最終的には基本的にFALSEとして扱われます。


7

ここでの回答から、NOT IN (subquery)nullを正しく処理しないと結論付けることができるため、を使用しないでくださいNOT EXISTS。ただし、そのような結論は時期尚早かもしれません。次のシナリオでは、Chris Date(データベースプログラミングとデザイン、第2巻、第9号、1989年9月)の功績が認められておりNOT IN、nullを正しく処理し、ではなく正しい結果を返しますNOT EXISTS

部品()を大量に供給することが知られているspサプライヤー(sno)を表すテーブルを考えてみますpnoqty)。テーブルは現在次の値を保持しています。

      VALUES ('S1', 'P1', NULL), 
             ('S2', 'P1', 200),
             ('S3', 'P1', 1000)

数量はNULL可能であることに注意してください。つまり、サプライヤが部品を提供していることがわかっていても、その数量が不明であっても記録できます。

タスクは、供給部品番号「P1」がわかっているが、数量が1000ではないサプライヤーを見つけることです。

以下はNOT IN、サプラ​​イヤー「S2」のみを正しく識別するために使用します。

WITH sp AS 
     ( SELECT * 
         FROM ( VALUES ( 'S1', 'P1', NULL ), 
                       ( 'S2', 'P1', 200 ),
                       ( 'S3', 'P1', 1000 ) )
              AS T ( sno, pno, qty )
     )
SELECT DISTINCT spx.sno
  FROM sp spx
 WHERE spx.pno = 'P1'
       AND 1000 NOT IN (
                        SELECT spy.qty
                          FROM sp spy
                         WHERE spy.sno = spx.sno
                               AND spy.pno = 'P1'
                       );

ただし、以下のクエリは同じ一般的な構造を使用していますが、 NOT EXISTSが、結果にサプライヤ 'S1'が誤って含まれています(つまり、数量がnullの場合)。

WITH sp AS 
     ( SELECT * 
         FROM ( VALUES ( 'S1', 'P1', NULL ), 
                       ( 'S2', 'P1', 200 ),
                       ( 'S3', 'P1', 1000 ) )
              AS T ( sno, pno, qty )
     )
SELECT DISTINCT spx.sno
  FROM sp spx
 WHERE spx.pno = 'P1'
       AND NOT EXISTS (
                       SELECT *
                         FROM sp spy
                        WHERE spy.sno = spx.sno
                              AND spy.pno = 'P1'
                              AND spy.qty = 1000
                      );

だからNOT EXISTS、それは登場したかもしれない銀の弾丸ではありません!

もちろん、問題の原因はヌルの存在にあります。したがって、「本当の」解決策はそれらのヌルを排除することです。

これは、(他の可能な設計の中でも)2つのテーブルを使用して実現できます。

  • sp 部品を供給することが知られているサプライヤー
  • spq 部品を既知の量で供給することが知られているサプライヤー

spq参照がある外部キー制約があるはずであることに注意してくださいspです。

次に、「マイナス」関係演算子(EXCEPT標準SQLのキーワード)を使用して結果を取得できます。

WITH sp AS 
     ( SELECT * 
         FROM ( VALUES ( 'S1', 'P1' ), 
                       ( 'S2', 'P1' ),
                       ( 'S3', 'P1' ) )
              AS T ( sno, pno )
     ),
     spq AS 
     ( SELECT * 
         FROM ( VALUES ( 'S2', 'P1', 200 ),
                       ( 'S3', 'P1', 1000 ) )
              AS T ( sno, pno, qty )
     )
SELECT sno
  FROM spq
 WHERE pno = 'P1'
EXCEPT 
SELECT sno
  FROM spq
 WHERE pno = 'P1'
       AND qty = 1000;

1
ああ、神様。実際にこれを書いてくれてありがとう....これは私を夢中にさせた..
Govind Rai

6

Nullはデータが存在しないことを意味します。つまり、データの値が不明ではなく、不明です。C型言語ではポインタを使用する場合、nullは実際には何もないため、プログラミングの背景を持つ人々がこれを混乱させるのは非常に簡単です。

したがって、最初のケースでは、3は確かに(1,2,3、null)のセットに含まれるため、trueが返されます。

ただし、2番目では、次のように減らすことができます。

3でない(null)の場合は「true」を選択します

したがって、パーサーは比較対象のセットについて何も知らないため、何も返されません。これは空のセットではなく、不明なセットです。(1、2)セットは明らかにfalseであるため、(1、2、null)を使用しても効果はありませんが、unknownに対してそれを行っています。


6

NULLを含むサブクエリに対してNOT INでフィルタリングしたい場合は、nullでないことを確認してください

SELECT blah FROM t WHERE blah NOT IN
        (SELECT someotherBlah FROM t2 WHERE someotherBlah IS NOT NULL )

特別な状況でレコードを返さない外部結合クエリで問題が発生したため、Nullと既存のレコードの両方のシナリオについてこのソリューションを確認し、それが機能しました。別の問題が発生した場合は、ここで言及します。ありがとうございます。
QMaster

1

これは男の子用です:

select party_code 
from abc as a
where party_code not in (select party_code 
                         from xyz 
                         where party_code = a.party_code);

これはansi設定に関係なく機能します


元の質問の場合:B:select in true in where 3 not in(1、2、null)nullを削除する方法を実行する必要があります。たとえば、select 'true' where 3 not in(1、2、isnull(null、0) )全体的なロジックは、NULLが原因である場合、クエリのあるステップでNULL値を削除する方法を見つけます。

よくあるparty_codeない(party_codeがnullでないxyzから選択party_code)がありますが、フィールドを忘れてしまった場合は幸運ではヌルを許可するようにABCからparty_codeを選択

1

SQLは真理値に3値論理を使用します。INクエリが期待される結果を生成します。

SELECT * FROM (VALUES (1), (2)) AS tbl(col) WHERE col IN (NULL, 1)
-- returns first row

ただし、aを追加しNOTても結果は反転しません。

SELECT * FROM (VALUES (1), (2)) AS tbl(col) WHERE NOT col IN (NULL, 1)
-- returns zero rows

これは、上記のクエリが次と同等であるためです。

SELECT * FROM (VALUES (1), (2)) AS tbl(col) WHERE NOT (col = NULL OR col = 1)

where句の評価方法は次のとおりです。

| col | col = NULL (1) | col = 1 | col = NULL OR col = 1 | NOT (col = NULL OR col = 1) |
|-----|----------------|---------|-----------------------|-----------------------------|
| 1   | UNKNOWN        | TRUE    | TRUE                  | FALSE                       |
| 2   | UNKNOWN        | FALSE   | UNKNOWN (2)           | UNKNOWN (3)                 |

次のことに注意してください。

  1. NULL利回りの比較UNKNOWN
  2. ORオペランドのいずれもない発現TRUE及び少なくとも一つのオペランドがあるUNKNOWN収率UNKNOWNREF
  3. 利回り(REFNOTUNKNOWNUNKNOWN

上記の例を3つ以上の値(たとえば、NULL、1、2)に拡張できますが、結果は同じになります。値の1つNULLが一致しない場合、一致する行はありません。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.