この問題は、1つがnot in
where
制約を使用し、もう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が結果を返すことがわかりました。
クエリ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と評価され、行が取得されます。
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 です。
NOT IN
は、不明な値と比較すると0レコードを返しますNULL
は不明なので、可能な値のリストにNULL
またはNULL
sを含むNOT IN
クエリは、NULL
値がテスト対象の値ではないことを確認する方法がないため、常に0
レコードを返します。
IS NULLを使用しない限り、nullとの比較は未定義です。
したがって、3とNULL(クエリA)を比較すると、undefinedが返されます。
つまり(1,2、null)の3の場合は「true」を選択し、(1,2、null)の3以外の場合は「true」を選択します
nOT(UNDEFINED)は未定義ですが、TRUEではないため、同じ結果が生成されます
執筆時点でのこの質問のタイトルは
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(外部結合など)を生成します!
ここでの回答から、NOT IN (subquery)
はnullを正しく処理しないため、NOT EXISTS
を優先して避ける必要があると結論付けることができます。ただし、このような結論は時期尚早かもしれません。 Chris Date(データベースプログラミングおよび設計、第2巻9号、1989年9月)が名乗る次のシナリオでは、NOT IN
ではなく、NOT EXISTS
がNULLを正しく処理し、正しい結果を返します。
部品(sp
name__)を数量(sno
name__)で供給することが知られているサプライヤ(pno
name__)を表すテーブルqty
name__を考えます。テーブルには現在、次の値が含まれています。
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
name__部品を供給することが知られているサプライヤーspq
name__サプライヤーspq
name__がsp
name__を参照する外部キー制約があるはずです。
結果は、「マイナス」関係演算子(標準SQLのEXCEPT
name__キーワードである)を使用して取得できます。
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;
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として扱われます。
ヌルは、データが存在しないことを意味します。つまり、データ値がゼロではなく、不明です。プログラミングのバックグラウンドの人にとっては、これを混同することは非常に簡単です。なぜなら、C型言語では、ポインターを使用するとき、nullは実際には何もないからです。
したがって、最初のケースでは3は実際に(1,2,3、null)のセットにあるため、trueが返されます
ただし、2番目では、
が(null)にない「true」を選択
したがって、パーサーは比較対象のセットについて何も知らないため、何も返されません。空のセットではなく、未知のセットです。 (1、2、null)を使用しても、(1,2)セットは明らかにfalseであるため役に立たないが、その後、あなたはそれを未知の、未知のに対して反対している。
NULLを含むサブクエリに対してNOT INを使用してフィルタリングする場合は、非NULLをチェックするだけです
SELECT blah FROM t WHERE blah NOT IN
(SELECT someotherBlah FROM t2 WHERE someotherBlah IS NOT NULL )
これはボーイ向けです:
select party_code
from abc as a
where party_code not in (select party_code
from xyz
where party_code = a.party_code);
これはANSI設定に関係なく機能します
また、これは、join、exists、および http://weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx の論理的な違いを知るのに役立つかもしれません