web-dev-qa-db-ja.com

NOT IN句とNULL値

この問題は、1つがnot inwhere制約を使用し、もう1つがleft joinを使用して同一のクエリであると考えたレコード数が異なる場合に発生しました。 not 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が結果を返すことがわかりました。

225
Jamie Ide

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

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

3 = 3がtrueであるため、結果が得られます。

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

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

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

ansi_nullsがオフの場合、3 <> nullはtrueであるため、述部はtrueと評価され、行が取得されます。

262
Brannon

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)と同じであるため、TRUEに評価する必要があります。
そうではありません。

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

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

私がお勧めする別の記事は、 SQL Aggregate Functions and NULL です。

52
kristof

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

NULLは不明なので、可能な値のリストにNULLまたはNULLsを含むNOT INクエリは、NULL値がテスト対象の値ではないことを確認する方法がないため、常に0レコードを返します。

24
YonahW

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

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

つまり(1,2、null)の3の場合は「true」を選択し、(1,2、null)の3以外の場合は「true」を選択します

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

18
Sunny Milenov

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

SQL NOT IN制約とNULL値

質問のテキストから、問題はSQL DDL SELECTではなく、SQL DML CONSTRAINTクエリで発生したようです。

ただし、特にタイトルの文言を考えると、ここで行われたいくつかのステートメントは、誤解を招く可能性のあるステートメントであり、(言い換え)

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

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

質問の述語から直接取得された2つの制約を持つこの非常に単純なテーブルを検討してください(@Brannonによる優れた回答で説明されています)。

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では、WHEREがUNKNOWNと評価されると、結果から行が削除されます。これは、does条件「is true」を満たすためです。

SQL DDL(つまり、制約)では、行がUNKNOWNと評価されたときに結果から削除されません。これは、does条件「is not false」を満たすためです。

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

追伸私が書いているように「不明で制約を満たすことができない」などのロジックに従うのが難しいと思う場合は、SQL DDLのNULL許容列とSQL DMLの何かを避けることで、これらすべてを単純に省くことができると考えてください。 NULL(外部結合など)を生成します!

8
onedaywhen

ここでの回答から、NOT IN (subquery)はnullを正しく処理しないため、NOT EXISTSを優先して避ける必要があると結論付けることができます。ただし、このような結論は時期尚早かもしれません。 Chris Date(データベースプログラミングおよび設計、第2巻9号、1989年9月)が名乗る次のシナリオでは、NOT INではなく、NOT EXISTSがNULLを正しく処理し、正しい結果を返します。

部品(spname__)を数量(snoname__)で供給することが知られているサプライヤ(pnoname__)を表すテーブルqtyname__を考えます。テーブルには現在、次の値が含まれています。

      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つのテーブルを使用して実現できます。

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

spqname__がspname__を参照する外部キー制約があるはずです。

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

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;
6
onedaywhen

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

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

6
Dave Costa

ヌルは、データが存在しないことを意味します。つまり、データ値がゼロではなく、不明です。プログラミングのバックグラウンドの人にとっては、これを混同することは非常に簡単です。なぜなら、C型言語では、ポインターを使用するとき、nullは実際には何もないからです。

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

ただし、2番目では、

が(null)にない「true」を選択

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

6
Cruachan

NULLを含むサブクエリに対してNOT INを使用してフィルタリングする場合は、非NULLをチェックするだけです

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

これはボーイ向けです:

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

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

1
Rostand Abear

また、これは、join、exists、および http://weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx の論理的な違いを知るのに役立つかもしれません

0
Mladen