EXISTS(SELECT 1…)vs EXISTS(SELECT *…)どちらですか?


38

テーブル内の行の存在を確認する必要があるときはいつでも、次のような条件を常に書く傾向があります。

SELECT a, b, c
  FROM a_table
 WHERE EXISTS
       (SELECT *  -- This is what I normally write
          FROM another_table
         WHERE another_table.b = a_table.b
       )

他の人は次のように書きます:

SELECT a, b, c
  FROM a_table
 WHERE EXISTS
       (SELECT 1   --- This nice '1' is what I have seen other people use
          FROM another_table
         WHERE another_table.b = a_table.b
       )

条件がのNOT EXISTS代わりにEXISTSある場合:場合によってはLEFT JOIN、追加の条件(antijoinと呼ばれることもある)を使用して記述することがあります

SELECT a, b, c
  FROM a_table
       LEFT JOIN another_table ON another_table.b = a_table.b
 WHERE another_table.primary_key IS NULL

私はそれを避けるようにします。なぜなら、特にあなたのものprimary_keyがそれほど明白ではない場合、または主キーまたは結合条件が複数列である場合(そして列の1つを簡単に忘れることができる場合)、意味があまり明確ではないと思うからです。ただし、他の誰かが書いたコードを維持することもありますが、それはそこにあります。

  1. SELECT 1代わりに使用する(スタイル以外の)違いはありますSELECT *か?
    それが同じように動作しないコーナーケースはありますか?

  2. 私が書いたのは(AFAIK)標準SQLですが、異なるデータベース/古いバージョンでそのような違いはありますか?

  3. 明示的に反結合を記述することに利点はありますか?
    現代のプランナー/オプティマイザーはNOT EXISTS条項とは異なる扱いをしますか?


5
PostgreSQLは列なしの選択をサポートしているため、単にを書くことができますEXISTS (SELECT FROM ...)
右折16

1
私は数年前にSO上のほぼ同じ質問をされています:stackoverflow.com/questions/7710153/...
アーウィンBrandstetter

回答:


45

いいえ、間に効率の違いはありません (NOT) EXISTS (SELECT 1 ...)し、(NOT) EXISTS (SELECT * ...)すべての主要なDBMSでは。私(NOT) EXISTS (SELECT NULL ...)もよく使われているのを見てきました。

一部の人は書くことさえでき(NOT) EXISTS (SELECT 1/0 ...)、結果は同じです-(ゼロ除算)エラーなしで、そこの式が評価されていないことを証明します。


関して LEFT JOIN / IS NULL反結合法訂正:これはと同等NOT EXISTS (SELECT ...)です。

この場合、NOT EXISTSvsLEFT JOIN / IS NULL、異なる実行計画を取得できます。たとえば、MySQLでは、ほとんどの場合、古いバージョン(5.7より前)で計画はかなり似ていますが、同一ではありません。他のDBMS(SQL Server、Oracle、Postgres、DB2)のオプティマイザーは、私が知る限り、これら2つのメソッドを書き直し、両方に対して同じ計画を検討する能力があります。それでも、そのような保証はなく、最適化を行う場合、各オプティマイザーが書き換えない場合があるため、異なる同等の書き換えから計画をチェックすることをお勧めします(例えば、多くの結合や派生テーブルを持つ複雑なクエリ/サブクエリ内のサブクエリ。複数のテーブルの条件、結合条件で使用される複合列)、またはオプティマイザの選択とプランは、利用可能なインデックス、設定などによって異なる影響を受けます。

またUSING、すべてのDBMS(SQL Serverなど)で使用できるわけではないことに注意してください。JOIN ... ONどこでもより一般的な作品。
また、SELECT結合の際のエラー/あいまいさを避けるために、列の前にテーブル名/エイリアスを付ける必要があります。
また、通常、結合された列をIS NULLチェックすることを好みます(PKまたはNULL不可の列は問題ありませんがLEFT JOIN、非クラスター化インデックスを使用する計画の効率化に役立つ場合があります)。

SELECT a_table.a, a_table.b, a_table.c
  FROM a_table
       LEFT JOIN another_table 
           ON another_table.b = a_table.b
 WHERE another_table.b IS NULL ;

アンチジョインの3番目の方法もありNOT INますが、内部テーブルの列がNULL可能の場合、これは異なるセマンティクス(および結果!)を使用します。ただし、を使用して行を除外するNULLことにより、クエリを以前の2つのバージョンと同等にできます。

SELECT a, b, c
  FROM a_table
 WHERE a_table.b NOT IN 
       (SELECT another_table.b
          FROM another_table
         WHERE another_table.b IS NOT NULL
       ) ;

これは通常、ほとんどのDBMSで同様の計画をもたらします。


1
MySQLのごく最近のバージョンまで[NOT] IN (SELECT ...)は、同等ではありますが、パフォーマンスは非常に劣っていました。避けてください!
リックジェームズ

4
これはPostgreSQLには当てはまりませんSELECT *確かにより多くの仕事をしています。簡単にするために使用をお勧めしますSELECT 1
エヴァンキャロル

11

例1つのカテゴリがある場合SELECT 1と、SELECT *互換性がありませんが-より具体的には、他にはほとんどありませんが1は、常にそのような場合に受け入れられます。

グループ化されたセットの行の存在を確認する必要がある場合について説明しています。テーブルTに列がC1ありC2、特定の条件に一致する行グループの存在を確認している場合、次のSELECT 1ように使用できます。

EXISTS
(
  SELECT
    1
  FROM
    T
  GROUP BY
    C1
  HAVING
    AGG(C2) = SomeValue
)

しかし、使用できません SELECT *、同じ方法で。

これは単なる構文上の側面です。両方のオプションが構文的に受け入れられる場合、他の回答で説明されているように、パフォーマンスまたは返される結果に関してほとんど違いはありません。

コメントに続く追加メモ

多くのデータベース製品が実際にこの区別をサポートしていないようです。SQL Server、Oracle、MySQL、SQLiteなどの製品SELECT *は、上記のクエリでエラーなしで問題なく受け入れます。つまり、おそらくEXISTS SELECTを特別な方法で処理します。

PostgreSQLは1つのRDBMSでSELECT *あり、障害が発生する可能性がありますが、場合によっては動作する可能性があります。特に、PKでグループ化する場合はSELECT *正常に機能しますが、そうでない場合は次のメッセージで失敗します。

エラー:列 "T.C2"はGROUP BY句に表示されるか、集約関数で使用される必要があります


1
良い点ですが、これは私が心配していたことではありません。これは概念的な違いを示しています。なぜなら、の場合GROUP BY、の概念*は無意味です(または、少なくとも、それほど明確ではありません)。
joanolo 16

5

EXISTS少なくともSQL Serverで、クリーンで、おそらく誤解を招くことの少ないクエリをもたらす句を書き直す、間違いなく興味深い方法は次のとおりです。

SELECT a, b, c
  FROM a_table
 WHERE b = ANY
       (
          SELECT b
          FROM another_table
       );

それの反準結合バージョンは次のようになります。

SELECT a, b, c
  FROM a_table
 WHERE b <> ALL
       (
          SELECT b
          FROM another_table
       );

通常、両方ともWHERE EXISTSまたはと同じプランに最適化されWHERE NOT EXISTSますが、その意図は間違いなく、「奇妙な」1またはがありません*

興味深いことに、に関連付けられてNOT IN (...)いるnullチェックの問題はに問題がありますが<> ALL (...)、には問題がありNOT EXISTS (...)ません。ヌル可能列を持つ次の2つのテーブルを検討してください。

IF OBJECT_ID('tempdb..#t') IS NOT NULL
BEGIN
    DROP TABLE #t;
END;
CREATE TABLE #t 
(
    ID INT NOT NULL IDENTITY(1,1)
    , SomeValue INT NULL
);

IF OBJECT_ID('tempdb..#s') IS NOT NULL
BEGIN
    DROP TABLE #s;
END;
CREATE TABLE #s 
(
    ID INT NOT NULL IDENTITY(1,1)
    , SomeValue INT NULL
);

いくつかのデータを両方に追加し、いくつかの行は一致し、いくつかは一致しません:

INSERT INTO #t (SomeValue) VALUES (1);
INSERT INTO #t (SomeValue) VALUES (2);
INSERT INTO #t (SomeValue) VALUES (3);
INSERT INTO #t (SomeValue) VALUES (NULL);

SELECT *
FROM #t;
+ -------- + ----------- +
| ID | SomeValue |
+ -------- + ----------- +
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | NULL |
+ -------- + ----------- +
INSERT INTO #s (SomeValue) VALUES (1);
INSERT INTO #s (SomeValue) VALUES (2);
INSERT INTO #s (SomeValue) VALUES (NULL);
INSERT INTO #s (SomeValue) VALUES (4);

SELECT *
FROM #s;
+ -------- + ----------- +
| ID | SomeValue |
+ -------- + ----------- +
| 1 | 1 |
| 2 | 2 |
| 3 | NULL |
| 4 | 4 |
+ -------- + ----------- +

NOT IN (...)クエリ:

SELECT *
FROM #t 
WHERE #t.SomeValue NOT IN (
    SELECT #s.SomeValue
    FROM #s 
    );

次の計画があります:

ここに画像の説明を入力してください

NULL値により等価性を確認できないため、クエリは行を返しません。

次のクエリ<> ALL (...)は、同じプランを表示し、行を返しません。

SELECT *
FROM #t 
WHERE #t.SomeValue <> ALL (
    SELECT #s.SomeValue
    FROM #s 
    );

ここに画像の説明を入力してください

を使用するバリアントNOT EXISTS (...)は、わずかに異なる平面形状を示し、行を返します。

SELECT *
FROM #t 
WHERE NOT EXISTS (
    SELECT 1
    FROM #s 
    WHERE #s.SomeValue = #t.SomeValue
    );

計画:

ここに画像の説明を入力してください

そのクエリの結果:

+ -------- + ----------- +
| ID | SomeValue |
+ -------- + ----------- +
| 3 | 3 |
| 4 | NULL |
+ -------- + ----------- +

これにより、の使用<> ALL (...)と同様に問題のある結果が生じやすくなりNOT IN (...)ます。


3
私は*奇妙だとは思わない:EXISTS (SELECT * FROM t WHERE ...) AS を読んでいると言わなければなりませんthere is a _row_ in table _t_ that...。とにかく、私は代替手段を持ちたいです、そして、あなたのものは明らかに読めます。1つの疑問/警告:bnull 可能であれば、どのように動作しますか?[によって引き起こさmisstake見つけるためにしようとしたとき、私は悪い経験と、いくつかの短い夜を持っていたx IN (SELECT something_nullable FROM a_table)]を
joanolo

EXISTSは、テーブルに行があり、trueまたはfalseを返すかどうかを示します。EXISTS(SELECT x FROM(values(null))is true。IN is = ANY&NOT IN is <> ALL。これら4つはNULLを持つRHS行を一致可能性があります。(x)= ANY(values(null))& (x)<> ALL(値(null))は不明/ nullですが、EXISTS(値(null))はtrueです(IN&= ANYには、同じ "NOT IN(...)[null check problem]があります[& ] <> ALL(...)」。ANY&ALL反復ORまたはAND。ただし、セマンティクスを意図したとおりに編成しないと、「問題」しかありません。)これらをEXISTSに使用することはお勧めしません。 、「あまり誤解を招く」ではない。
philipxy

@philliprxy-私が間違っていても、それを認めても問題ありません。気になる場合は、自由に回答を追加してください。
マックスヴァーノン

4

(MySQLで)それらが同一であることの「証明」は、

EXPLAIN EXTENDED
    SELECT EXISTS ( SELECT * ... ) AS x;
SHOW WARNINGS;

で繰り返しSELECT 1ます。どちらの場合も、「拡張」出力は、それがに変換されたことを示していSELECT 1ます。

同様に、 COUNT(*)に変わりCOUNT(0)ます。

注意すべきもう1つの点は、最近のバージョンで最適化が改善されたことです。EXISTS反結合と比較する価値があるかもしれません。あなたのバージョンは、他のバージョンよりも優れた仕事をするかもしれません。


4

一部のデータベースでは、この最適化はまだ機能しません。たとえば、PostgreSQLのバージョン9.6のように、これは失敗します。

SELECT *
FROM ( VALUES (1) ) AS g(x)
WHERE EXISTS (
  SELECT *
  FROM ( VALUES (1),(1) )
    AS t(x)
  WHERE g.x = t.x
  HAVING count(*) > 1
);

そして、これは成功します。

SELECT *
FROM ( VALUES (1) ) AS g(x)
WHERE EXISTS (
  SELECT 1  -- This changed from the first query
  FROM ( VALUES (1),(1) )
    AS t(x)
  WHERE g.x = t.x
  HAVING count(*) > 1
);

以下が失敗するため失敗しますが、それでも違いがあることを意味します。

SELECT *
FROM ( VALUES (1),(1) ) AS t(x)
HAVING count(*) > 1;

この特定の癖と仕様違反についての詳細は、質問への私の答えで見つけることができます、SQL仕様はEXISTS()でGROUP BYを必要としますか


まれなコーナーケース、少し奇妙なかもしれませんが、データベースを設計する際に多くの妥協をしなければならないことを証明しています
...-joanolo

-1

私はいつも使ってきました select top 1 'x'ます(SQL Server)

理論的にselect top 1 'x'select *、前者は修飾行の存在に関する定数を選択した後に完了するため、後者はすべてを選択するため、より効率的です。

しかし、非常に早い段階で関連性があったかもしれませんが、最適化により、おそらくすべての主要なRDBSで違いは無関係になりました。


理にかなっています。それは、top nなしorder byが良いアイデアである非常に少数のケースの1つであるかもしれません。
joanolo 16

3
「理論的には、....」いいえ、理論的には式select top 1 'x'より効率的であってはなりません。オプティマイザーが準最適に動作している場合、実際にはより効率的かもしれませんが、理論的には両方の式は同等です。select *Exist
miracle173

-4

IF EXISTS(SELECT TOP(1) 1 FROM現在のプラットフォーム/バージョンがどれだけ良いか悪いかを心配する必要さえないという理由だけで、長期的かつプラットフォーム間でより良い習慣です。そして、SQLはTOP nparameterizableに移行していますTOP(n)。これは、1回学習するスキルである必要があります。


3
「プラットフォームとはどういう意味ですか?TOP有効なSQLでもありません。
ypercubeᵀᴹ

「SQLは動いています。」は明らかに間違っています。TOP (n)「SQL」にはない-標準クエリ言語。T-SQLには、Microsoft SQL Serverが使用している方言があります。
a_horse_with_no_name

元の質問のタグは「SQL Server」です。しかし、私が言ったことに反対票を投じて異議を申し立てることは大丈夫です-このサイトの目的は、簡単な下票を可能にすることです。パレードでは、細部につまらない注意を払って誰に雨を降らせますか?
アジェ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.