web-dev-qa-db-ja.com

EXISTS(SELECT 1 ...)vs EXISTS(SELECT * ...)どちらか?

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

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
       )

条件がEXISTSではなくNOT 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. SELECT 1の代わりにSELECT *を使用する場合、(スタイル以外の)違いはありますか?
    同じように動作しないコーナーケースはありますか?

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

  3. 明示的にアンチジョインを作成する利点はありますか?
    コンテンポラリープランナー/オプティマイザーはNOT EXISTS句とは異なる扱いをしますか?

42
joanolo

いいえ、すべての主要なDBMSで(NOT) EXISTS (SELECT 1 ...)(NOT) EXISTS (SELECT * ...)の効率に違いはありません。 (NOT) EXISTS (SELECT NULL ...)が使用されることもよくあります。

場合によっては、(NOT) EXISTS (SELECT 1/0 ...)と書くこともでき、結果は同じです-(ゼロによる除算)エラーなしで、そこにある式が評価されないことを証明します。


LEFT JOIN / IS NULLアンチジョインメソッドについて、修正:これはNOT EXISTS (SELECT ...)と同等です。

この場合、NOT EXISTSLEFT 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 ;

NOT INを使用するアンチジョイン用の3番目のメソッドもありますが、これは内部テーブルの列が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でも同様の計画が作成されます。

47
ypercubeᵀᴹ

SELECT 1SELECT *が互換性のないケースには1つのカテゴリがあります。具体的には、これらのケースでは常に1つが受け入れられ、ほとんどの場合は受け入れられません。

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

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

ただし、SELECT *を同じように使用することはできません。

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

コメントに続く追加メモ

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

PostgreSQLはSELECT *が失敗する可能性があるRDBMSの1つですが、場合によっては引き続き機能する可能性があります。特に、PKでグループ化している場合、SELECT *は正常に機能します。それ以外の場合は、次のメッセージで失敗します。

エラー:列 "T.C2"はGROUP BY句に指定するか、集計関数で使用する必要があります

11
Andriy M

少なくともSQL Serverで、EXISTS句を書き換えて間違いのないクエリを作成する、間違いなく興味深い方法は次のとおりです。

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 (...)に関連するヌルチェックの問題は<> ALL (...)には問題がありますが、NOT EXISTS (...)はその問題の影響を受けません。 NULL可能列を持つ次の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 
    );

次の計画があります:

enter image description here

NULL値は等式の確認を不可能にするため、クエリは行を返しません。

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

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

enter image description here

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

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

計画:

enter image description here

そのクエリの結果:

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

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

5
Max Vernon

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

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

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

同様に、COUNT(*)COUNT(0)に変換されます。

注意すべきもう1つのこと:最適化の改善が最近のバージョンで行われました。 EXISTSとアンチ結合を比較する価値があるかもしれません。あなたのバージョンはどちらか一方に対してより良い仕事をするかもしれません。

4
Rick James

一部のデータベースでは、この最適化はまだ機能しません。 たとえば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仕様にGROUP BYがEXISTS()

4
Evan Carroll