CTE、サブクエリ、一時テーブル、またはテーブル変数の間にパフォーマンスの違いはありますか?


222

この素晴らしいではSOの質問、との違いCTEとはsub-queries議論されました。

私は具体的に尋ねたいと思います:

次のそれぞれがどのような状況でより効率的/高速ですか?

  • CTE
  • サブクエリ
  • 一時テーブル
  • テーブル変数

伝統的に、私はtemp tables開発で多くのことを使用しましたstored procedures-それらは絡み合った多くのサブクエリよりも読みやすいように見えるからです。

Non-recursive CTEsはデータのセットを非常にうまくカプセル化し、非常に読みやすいですが、常により良いパフォーマンスを発揮できると言える特定の状況はありますか?それとも、最も効率的なソリューションを見つけるために、常にさまざまなオプションをいじる必要があるのでしょうか?


編集

効率性の観点から、一時テーブルは関連するヒストグラム、つまり統計があるため、最初の選択肢として適していると最近言われました。


4
一般的な答え:場合によります。そして、それはいくつかの多くの要因に依存し、一般的なステートメントは誤っている可能性があります-状況によっては。基本的には、テストと測定を行う必要があります。どちらが最適かを確認してください。
marc_s

@marc_s-わかりました。たぶん、この質問は主観的であるために閉じるべきですか?SOに関する多くのSQLの質問が主観的であると判断される可能性があることに注意してください。
whytheq

1
それは広すぎるとして閉じられるかもしれません-そして私はあなたに同意します-SQLの多くの事柄やトピックは本当にそれに依存する答えを得るでしょう。時には、2つまたは3つの基準をリストして決定を下すことができますが、ここでの質問では、適切なアドバイスをすることはほぼ不可能です-それは、テーブル構造、それらのテーブルのデータ、使用しているクエリ、インデックス作成の戦略などなど
marc_s

@marc_s試して保存しておくとよいでしょう。OPをより具体的かつ狭くするために可能な編集に関するアドバイスはありますか?
whytheq

この質問はSQL Serverに固有のものであることに注意してください。postgresなどの他のDBの場合、CTEは多くの場合、同等のサブクエリよりもかなり遅くなります(http://blog.2ndquadrant.com/postgresql-ctes-are-optimization-fences/を参照)
Jay

回答:


243

SQLは宣言型言語であり、手続き型言語ではありません。つまり、必要な結果を記述するSQLステートメントを作成します。SQLエンジンに作業方法を指示していません。

原則として、SQLエンジンとSQLオプティマイザに最適なクエリプランを見つけさせることをお勧めします。SQLエンジンの開発には多くの人年の努力が費やされています。そのため、エンジニアに知っていることを実行してもらいます。

もちろん、クエリプランが最適でない状況もあります。次に、クエリヒントの使用、クエリの再構成、統計の更新、一時テーブルの使用、インデックスの追加などを行い、パフォーマンスを向上させます。

あなたの質問も。CTEとサブクエリのパフォーマンスは、どちらもクエリオプティマイザーに同じ情報を提供するため、理論的には同じである必要があります。1つの違いは、複数回使用されたCTEは簡単に識別および計算できることです。その後、結果を保存して複数回読み取ることができます。残念ながら、SQL Serverはこの基本的な最適化方法を利用していないようです(この一般的なサブクエリの削除と呼ばれる場合があります)。

クエリの実行方法についてより多くのガイダンスを提供しているため、一時テーブルは別の問題です。主な違いの1つは、オプティマイザが一時テーブルの統計を使用してクエリプランを確立できることです。これにより、パフォーマンスが向上する可能性があります。また、複数回使用される複雑なCTE(サブクエリ)がある場合、それを一時テーブルに格納すると、パフォーマンスが向上することがよくあります。クエリは1回だけ実行されます。

質問に対する答えは、期待どおりのパフォーマンスを得るために、特に定期的に実行される複雑なクエリの場合は、いろいろ試してみる必要があるということです。理想的な世界では、クエリオプティマイザーは完璧な実行パスを見つけます。頻繁に発生しますが、パフォーマンスを向上させる方法を見つけることができる場合があります。


11
この分野での可能な将来の改善に関するいくつかのマイクロソフトリサーチは、「クエリ処理のための同様の部分式の効率的な活用」の出版物である、ここから利用可能
マーティン・スミス

3
その論文が2007年に発表されたことを考えると、彼らがSQL Server 2012に組み込んだかどうかについての考えはありますか?
ゴードンリノフ

3
すばらしい答えです!強調するだけです。SQLは宣言型言語であり、データのプル方法を制御しません。したがって、パフォーマンス/速度はクエリごとに異なります。
Simcha Khabinsky 2014年

2
@RGS。。。一時テーブルのインデックスは、永続テーブルのインデックスと同様に、これらのインデックスを利用できるクエリを確実に改善します。ただし、サブクエリを一時テーブルとして実体化すると、元のテーブルのインデックスの利点が失われる可能性があります。
Gordon Linoff

2
@RGS。。データベースエンジンは、複雑なクエリの実行中にサブクエリ/ CTEを具体化しても、具体化にインデックスを追加しません。これは、一時テーブルを使用して手動で行うことができます。
ゴードンリノフ2016

77

ルールはありません。CTEの方が読みやすく、パフォーマンスの問題が発生しない限り使用します。この場合、CTEが問題であると推測するのではなく、実際の問題を調査し、別の方法で書き直します。通常、この問題には、クエリで意図を宣言的に表明する方法よりも多くのことが含まれます。

CTEを解明したり、サブクエリを削除して#tempテーブルに置き換えたり、期間を短縮したりできるケースは確かにあります。これは、古い統計、正確な統計(テーブル値関数への結合など)を取得できないこと、並列処理、クエリの複雑さのために最適な計画を生成できないことなど、さまざまな原因が考えられます(その場合、それを分割すると、オプティマイザに戦いのチャンスが与えられる可能性があります)。ただし、#tempテーブルの作成に関連するI / Oが、CTEを使用した特定のプラン形状をあまり魅力的でない他のパフォーマンスの側面よりも上回る場合もあります。

正直なところ、質問に対する「正しい」答えを提供するには変数が多すぎます。クエリが一つのアプローチまたは別の賛成で傾けることがときを知るための予測可能な方法はありません-ちょうどそれを知って、理論的には、CTEまたは単一サブクエリで同じセマンティクスがなければならない、まったく同じを実行します。これが当てはまらない場合を提示すると、質問の価値が高まると思います。オプティマイザで制限を発見した(または既知の制限を発見した)か、クエリが意味的に同等でない可能性がありますまたは、最適化を妨げる要素が含まれている。

したがって、私はあなたにとって最も自然に見える方法でクエリを書くことをお勧めします。そして、オプティマイザが持っている実際のパフォーマンスの問題を発見したときにのみ逸脱します。個人的には、CTEMPをランク付けし、次にサブクエリをランク付けします。#tempテーブルは最後の手段です。


4
+1はかなり主観的な質問であることが判明しました。これまでの回答は参考情報であるため、漠然としていてクローズされないことを願っています。私は気づきます:-)質問が変わったときそれが好きではありませんが、OPで質問を絞り込むための提案はありますか?
whytheq

2
私はこの質問は問題ないと思いますが、まだ締め切り投票が1つもないことに気づくでしょうが、回答が乱立し始めると、おそらくシャットダウンされます。私の回答で示唆したように、CTEとサブクエリの間に大きな違いが見られる特定のケースがある場合は、実際のクエリと実行プランを使用して新しい質問を開始します(そして、dba.seに適している可能性があります)。 。そのクエリに役立つ答えが、同じシナリオの別のクエリの同じ答えではない可能性があることを理解しください。
アーロンバートランド

質問のすぐ下にリンクがあります。質問link / edit / close / flagを閉じるための投票があった場合、質問を閉じるために投票したユーザーの数がclose (n)どこにあるかがわかりnます。リンクをクリックすると、それらのユーザーが選択した理由が表示されます。
アーロンバートランド

@whytheqは、Bob Beaucheminによる最近のブログ投稿も参照してください。CTEとサブクエリを具体的には扱いませんが、同じ種類の概念が適用されます。パフォーマンス上の理由で直感的でないパターンを選択した場合は、そのパターンを文書化し、再度確認して、発見した癖がまだ本物であることを確認します。以前のバージョンを保持する信頼できるソース管理システムがない限り、クエリのより自然なバージョンをコメント化したままにすることをお勧めします。
アーロンバートランド

1
上記の固定リンク:sqlskills.com/blogs/bobb/...
ADJenks

19

#tempは物質化されており、CTEはそうではありません。

CTEは単なる構文なので、理論的にはサブクエリです。実行されます。#tempが具現化されます。そのため、何度も実行される結合の高価なCTEは、#tempの方が優れている場合があります。反対に、数回実行されない簡単な評価であれば、#tempのオーバーヘッドに値しません。

SOではテーブル変数が嫌いな人もいますが、具体化されており、#tempよりも作成が速いので、私は彼らが好きです。クエリオプティマイザーは、テーブル変数と比較して#tempの方が優れている場合があります。

#tempまたはテーブル変数にPKを作成する機能は、クエリオプティマイザーにCTEよりも多くの情報を提供します(CTEではPKを宣言できないため)。


「TVP」の頭字語は何ですか... #tempに似ていますか?
whytheq 2012年

TVPは印象的に聞こえるため(一部の人にとって)、一般的な用語になりつつあります。つまり、TVPはパラメータとして渡されるテーブルです。Table変数を使用したことのある人なら誰でも、すぐに家にいるでしょう。
WonderWorker

1
警告-TVPには実行計画がありません!TVPは、最も単純で短いルックアップリスト以外には使用しないでください。複雑な結合、挿入、または更新を行うと、大規模な最適化の問題が発生する可能性があります。私を信じて、私はこれで焦げました。
-Heliac、

12

CTEではなく#一時テーブルを使用することが常に望ましいと思うのは、次の2つだけです。

  1. CTEに主キーを置くことはできないため、CTEがアクセスするデータは、一時テーブルのPKまたはインデックスにアクセスするだけでなく、CTEのテーブルの各インデックスをトラバースする必要があります。

  2. 制約、インデックス、および主キーをCTEに追加できないため、侵入するバグや不良データが発生しやすくなります。


-いつか昨日

#table制約が悪いデータを防ぐことができる例はここにありますが、CTEの場合はそうではありません

DECLARE @BadData TABLE ( 
                       ThisID int
                     , ThatID int );
INSERT INTO @BadData
       ( ThisID
       , ThatID
       ) 
VALUES
       ( 1, 1 ),
       ( 1, 2 ),
       ( 2, 2 ),
       ( 1, 1 );

IF OBJECT_ID('tempdb..#This') IS NOT NULL
    DROP TABLE #This;
CREATE TABLE #This ( 
             ThisID int NOT NULL
           , ThatID int NOT NULL
                        UNIQUE(ThisID, ThatID) );
INSERT INTO #This
SELECT * FROM @BadData;
WITH This_CTE
     AS (SELECT *
           FROM @BadData)
     SELECT *
       FROM This_CTE;

3
ALWAYS少し遠すぎますが、答えてくれてありがとう。読みやすさの点では、CTEの使用は良いことです。
whytheq 2016年

3
2点目が全然わかりません。私の見たところ、CTEを定義するクエリは、一時テーブルに設定する制約に似ています。前者は任意の複雑な述語を含むことができ、後者ははるかに制限されます(たとえばCHECK、複数の行/テーブルを参照する制約は禁止されている)。CTEが、対応する一時テーブルにないバグを示す例を投稿できますか?
2016
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.