Exists 1またはExists *を使用したサブクエリ


88

以前は、次のようにEXISTSチェックを記述していました。

IF EXISTS (SELECT * FROM TABLE WHERE Columns=@Filters)
BEGIN
   UPDATE TABLE SET ColumnsX=ValuesX WHERE Where Columns=@Filters
END

前世のDBAの1人が、EXISTS節を実行するときは、SELECT 1代わりにSELECT *

IF EXISTS (SELECT 1 FROM TABLE WHERE Columns=@Filters)
BEGIN
   UPDATE TABLE SET ColumnsX=ValuesX WHERE Columns=@Filters
END

これは本当に違いがありますか?


1
EXISTS(SELECT NULL FROM ...)を忘れました。これは最近質問されました
OMGポニー

16
PSは新しいDBAを取得します。迷信はIT、特にデータベース管理(元DBAからのもの)には場所がありません
Matt Rogish

回答:


135

いいえ、SQL Serverはスマートで、EXISTSに使用されていることを認識しており、システムにデータを返しません。

Quoth Microsoft:http ://technet.microsoft.com/en-us/library/ms189259.aspx?ppud =4

EXISTSによって導入されたサブクエリの選択リストは、ほとんど常にアスタリスク(*)で構成されています。サブクエリで指定された条件を満たす行が存在するかどうかをテストするだけなので、列名をリストする理由はありません。

自分自身を確認するには、次のコマンドを実行してみてください。

SELECT whatever
  FROM yourtable
 WHERE EXISTS( SELECT 1/0
                 FROM someothertable 
                WHERE a_valid_clause )

SELECTリストで実際に何かをしている場合は、div by zeroエラーがスローされます。そうではありません。

編集:注、SQL標準は実際にこれについて話します。

ANSI SQL 1992 Standard、pg 191 http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt

3)ケース:
a)<select list>「*」が単にに含まれているa <subquery> に含まれている<exists predicate>場合、<select list><value expression> 任意のであると同等<literal>です。


1
EXISTS1/0 のトリックはこれに拡張することもできますSELECT 1 WHERE EXISTS(SELECT 1/0)... 2番目SELECTFROM句に句がないため、ステップはより抽象的なようです
whytheq

1
@whytheq-またはSELECT COUNT(*) WHERE EXISTS(SELECT 1/0)SELECTせずFROM、それが(から選択する例えば、同様の単一の行テーブルにアクセスしたかのようにSQL Serverでは、処理されたdual他のRDBMSのテーブル)
マーティン・スミス

@MartinSmithは乾杯しSELECTます。つまり1/0、1行のテーブルがまだごみであるとしても、他の処理を行う前に1行のテーブルを作成することがポイントEXISTSです。
whytheq

これは常にそうでしたか、それともSQL Serverの特定のバージョンで導入された最適化ですか?
Martin Brown

1
@MartinSmith TIL "quoth"。修正していただきありがとうございます。
ガーウィンダーシン

111

この誤解の理由は、おそらくすべての列を読み取ることになると考えられているためです。これが当てはまらないことは簡単にわかります。

CREATE TABLE T
(
X INT PRIMARY KEY,
Y INT,
Z CHAR(8000)
)

CREATE NONCLUSTERED INDEX NarrowIndex ON T(Y)

IF EXISTS (SELECT * FROM T)
    PRINT 'Y'

計画を与える

予定

これは、インデックスにすべての列が含まれていないにもかかわらず、SQL Serverが利用可能な最も狭いインデックスを使用して結果を確認できたことを示しています。インデックスアクセスは、準結合演算子の下にあります。つまり、最初の行が返されるとすぐにスキャンを停止できます。

したがって、上記の信念が間違っていることは明らかです。

ただし、クエリオプティマイザーチームのConor Cunninghamは、クエリのコンパイルでパフォーマンスにわずかな違いが生じる可能性があるため、この場合に通常使用することを説明します。SELECT 1

QPは*パイプラインの早い段階ですべてのを取得して展開し、それらをオブジェクト(この場合は列のリスト)にバインドします。次に、クエリの性質上、不要な列を削除します。

したがって、次のEXISTSような単純なサブクエリの場合:

SELECT col1 FROM MyTable WHERE EXISTS (SELECT * FROM Table2 WHERE MyTable.col1=Table2.col2)*いくつかの潜在的に大きな列のリストに展開されますし、の意味があると判断される EXISTSので、基本的にすべてのそれらの除去することができ、これらの列のいずれかを必要としません。

" SELECT 1"は、クエリのコンパイル中にそのテーブルの不要なメタデータを調べる必要を回避します。

ただし、実行時には、クエリの2つの形式は同じであり、実行時間は同じです。

さまざまな数の列を持つ空のテーブルでこのクエリを表現する4つの方法をテストしました。SELECT 1SELECT *SELECT Primary_KeySELECT Other_Not_Null_Column

クエリをループOPTION (RECOMPILE)で実行し、1秒あたりの平均実行数を測定しました。以下の結果

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

+-------------+----------+---------+---------+--------------+
| Num of Cols |    *     |    1    |   PK    | Not Null col |
+-------------+----------+---------+---------+--------------+
| 2           | 2043.5   | 2043.25 | 2073.5  | 2067.5       |
| 4           | 2038.75  | 2041.25 | 2067.5  | 2067.5       |
| 8           | 2015.75  | 2017    | 2059.75 | 2059         |
| 16          | 2005.75  | 2005.25 | 2025.25 | 2035.75      |
| 32          | 1963.25  | 1967.25 | 2001.25 | 1992.75      |
| 64          | 1903     | 1904    | 1936.25 | 1939.75      |
| 128         | 1778.75  | 1779.75 | 1799    | 1806.75      |
| 256         | 1530.75  | 1526.5  | 1542.75 | 1541.25      |
| 512         | 1195     | 1189.75 | 1203.75 | 1198.5       |
| 1024        | 694.75   | 697     | 699     | 699.25       |
+-------------+----------+---------+---------+--------------+
| Total       | 17169.25 | 17171   | 17408   | 17408        |
+-------------+----------+---------+---------+--------------+

見て分かるようにSELECT 1SELECT *との間に一貫した勝者はなく、2つのアプローチの違いは無視できます。わずかに速いけれども表示されます。SELECT Not Null colSELECT PK

テーブルの列数が増えると、4つのクエリすべてのパフォーマンスが低下します。

テーブルが空であるため、この関係は列のメタデータの量によってのみ説明できるようです。以下のためにCOUNT(1)に書き換えされることを確認するために簡単です。COUNT(*)下からのプロセスの中でいくつかの点で。

SET SHOWPLAN_TEXT ON;

GO

SELECT COUNT(1)
FROM master..spt_values

これは次の計画を与えます

  |--Compute Scalar(DEFINE:([Expr1003]=CONVERT_IMPLICIT(int,[Expr1004],0)))
       |--Stream Aggregate(DEFINE:([Expr1004]=Count(*)))
            |--Index Scan(OBJECT:([master].[dbo].[spt_values].[ix2_spt_values_nu_nc]))

デバッガーをSQL Serverプロセスにアタッチし、以下を実行中にランダムに中断する

DECLARE @V int 

WHILE (1=1)
    SELECT @V=1 WHERE EXISTS (SELECT 1 FROM ##T) OPTION(RECOMPILE)

私は、例の表は、最も時間の1024列、確かにしても、時間ローディング列メタデータの大部分を費やしていることを示す以下のようなもののようなコールスタックルックス有する場合ことを発見しSELECT 1た場合のために(使用されているがテーブルに1つの列があり、ランダムに分割しても、10回の試行でコールスタックのこのビットにヒットしませんでした)

sqlservr.exe!CMEDAccess::GetProxyBaseIntnl()  - 0x1e2c79 bytes  
sqlservr.exe!CMEDProxyRelation::GetColumn()  + 0x57 bytes   
sqlservr.exe!CAlgTableMetadata::LoadColumns()  + 0x256 bytes    
sqlservr.exe!CAlgTableMetadata::Bind()  + 0x15c bytes   
sqlservr.exe!CRelOp_Get::BindTree()  + 0x98 bytes   
sqlservr.exe!COptExpr::BindTree()  + 0x58 bytes 
sqlservr.exe!CRelOp_FromList::BindTree()  + 0x5c bytes  
sqlservr.exe!COptExpr::BindTree()  + 0x58 bytes 
sqlservr.exe!CRelOp_QuerySpec::BindTree()  + 0xbe bytes 
sqlservr.exe!COptExpr::BindTree()  + 0x58 bytes 
sqlservr.exe!CScaOp_Exists::BindScalarTree()  + 0x72 bytes  
... Lines omitted ...
msvcr80.dll!_threadstartex(void * ptd=0x0031d888)  Line 326 + 0x5 bytes C
kernel32.dll!_BaseThreadStart@8()  + 0x37 bytes 

この手動プロファイリングの試みは、VS 2012コードプロファイラーによってバックアップされます。このプロファイラーは、2つのケース(上位15関数1024列上位15関数1列)のコンパイル時間を消費する関数の非常に異なる選択を示しています。

SELECT 1SELECT *バージョンの両方で列の権限のチェックが終了し、ユーザーがテーブル内のすべての列へのアクセスを許可されていない場合は失敗します。

ヒープでの会話から書いた例

CREATE USER blat WITHOUT LOGIN;
GO
CREATE TABLE dbo.T
(
X INT PRIMARY KEY,
Y INT,
Z CHAR(8000)
)
GO

GRANT SELECT ON dbo.T TO blat;
DENY SELECT ON dbo.T(Z) TO blat;
GO
EXECUTE AS USER = 'blat';
GO

SELECT 1
WHERE  EXISTS (SELECT 1
               FROM   T); 
/*  ↑↑↑↑ 
Fails unexpectedly with 

The SELECT permission was denied on the column 'Z' of the 
           object 'T', database 'tempdb', schema 'dbo'.*/

GO
REVERT;
DROP USER blat
DROP TABLE T

したがって、使用時の小さな明らかな違いSELECT some_not_null_colは、特定の列のアクセス許可のチェックのみが終了することです(ただし、すべてのメタデータをロードします)。ただし、基礎となるテーブルの列の数が増えるにつれて何かが小さくなると、2つのアプローチのパーセンテージの差が大きくなるため、これは事実と一致しないようです。

いずれにせよ、私は急いですべてのクエリをこの形式に変更することはしません。違いはごくわずかであり、クエリのコンパイル中にのみ明らかであるためです。OPTION (RECOMPILE)後続の実行がキャッシュされたプランを使用できるようにを削除すると、次のようになります。

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

+-------------+-----------+------------+-----------+--------------+
| Num of Cols |     *     |     1      |    PK     | Not Null col |
+-------------+-----------+------------+-----------+--------------+
| 2           | 144933.25 | 145292     | 146029.25 | 143973.5     |
| 4           | 146084    | 146633.5   | 146018.75 | 146581.25    |
| 8           | 143145.25 | 144393.25  | 145723.5  | 144790.25    |
| 16          | 145191.75 | 145174     | 144755.5  | 146666.75    |
| 32          | 144624    | 145483.75  | 143531    | 145366.25    |
| 64          | 145459.25 | 146175.75  | 147174.25 | 146622.5     |
| 128         | 145625.75 | 143823.25  | 144132    | 144739.25    |
| 256         | 145380.75 | 147224     | 146203.25 | 147078.75    |
| 512         | 146045    | 145609.25  | 145149.25 | 144335.5     |
| 1024        | 148280    | 148076     | 145593.25 | 146534.75    |
+-------------+-----------+------------+-----------+--------------+
| Total       | 1454769   | 1457884.75 | 1454310   | 1456688.75   |
+-------------+-----------+------------+-----------+--------------+

私が使用したテストスクリプトはここにあります


3
+1この回答は、実際のデータを取得するために必要な努力に対してより多くの賛成票を投じる価値があります。
Jon

1
これらの統計が生成されたSQL Serverのバージョンは何かわかりますか?
マーティンブラウン

3
@MartinBrown-IIRCはもともと2008年でしたが、最近の編集のために2012年に最近のテストをやり直し、同じことがわかりました。
マーティン・スミス

8

知る最良の方法は、両方のバージョンのパフォーマンステストを行い、両方のバージョンの実行プランを確認することです。列の多いテーブルを選びます。


2
+1。なぜこれが反対票だったのかはわかりません。ただ魚を与えるよりも、魚を教える方がいいといつも思っていました。人々はどうやって何かを学ぶのでしょうか?
Ogre Psalm33 2010年

5

SQL Serverに違いはなく、SQL Serverで問題が発生したことはありません。オプティマイザは、それらが同じであることを認識しています。実行プランを見ると、それらが同じであることがわかります。


1

個人的には、同じクエリプランに最適化されていないことを信じることは非常に困難です。しかし、特定の状況で知る唯一の方法は、それをテストすることです。もしそうなら、報告してください!


-1

実際の違いはありませんが、パフォーマンスへの影響はごくわずかです。経験則として、必要以上のデータを要求しないでください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.