テーブルに行があるかどうかを確認する必要があるときはいつでも、次のような条件を常に書く傾向があります。
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
がそれほど明白でない場合、または主キーまたは結合条件が複数列である場合(特に、列)。ただし、他の誰かが作成したコードを維持している場合があります。
SELECT 1
の代わりにSELECT *
を使用する場合、(スタイル以外の)違いはありますか?
同じように動作しないコーナーケースはありますか?
私が書いたのは(AFAIK)標準SQLですが、データベースや古いバージョンによってそのような違いはありますか?
明示的にアンチジョインを作成する利点はありますか?
コンテンポラリープランナー/オプティマイザーはNOT EXISTS
句とは異なる扱いをしますか?
いいえ、すべての主要な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 EXISTS
とLEFT 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でも同様の計画が作成されます。
SELECT 1
とSELECT *
が互換性のないケースには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句に指定するか、集計関数で使用する必要があります
少なくとも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
);
次の計画があります:
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 (...)
を使用すると問題のある結果が発生しやすくなります。
それらが(MySQLで)同一であることの「証明」は、
_EXPLAIN EXTENDED
SELECT EXISTS ( SELECT * ... ) AS x;
SHOW WARNINGS;
_
次に_SELECT 1
_で繰り返します。どちらの場合も、「拡張」出力は、それが_SELECT 1
_に変換されたことを示しています。
同様に、COUNT(*)
はCOUNT(0)
に変換されます。
注意すべきもう1つのこと:最適化の改善が最近のバージョンで行われました。 EXISTS
とアンチ結合を比較する価値があるかもしれません。あなたのバージョンはどちらか一方に対してより良い仕事をするかもしれません。
一部のデータベースでは、この最適化はまだ機能しません。 たとえば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()