265

この優れたSO の質問CTEでは、との違いsub-queriesについて説明しました。

具体的にお聞きしたいのですが、

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

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

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

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


編集

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

4

4 に答える 4

293

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

原則として、SQL エンジンと SQL オプティマイザーに最適なクエリ プランを見つけさせることをお勧めします。SQL エンジンの開発には何年にもわたる努力が必要です。

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

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

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

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

于 2012-06-23T13:32:05.577 に答える
92

ルールはありません。CTEの方が読みやすく、パフォーマンスの問題がない限り使用します。その場合、CTEが問題であると推測するのではなく、実際の問題を調査し、別のアプローチを使用して書き直そうとします。通常、この問題には、クエリで自分の意図を宣言的に述べるために選択した方法よりも多くのことがあります。

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

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

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

于 2012-06-23T15:55:53.093 に答える
24

#temp は具体化されていますが、CTE は具体化されていません。

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

SOにはテーブル変数が嫌いな人もいますが、マテリアライズされており、#tempよりも作成が速いので気に入っています。テーブル変数と比較して #temp を使用した方がクエリ オプティマイザーのパフォーマンスが向上する場合があります。

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

于 2012-06-25T01:03:15.573 に答える
13

CTE ではなく # Temp Table を使用することが常に望ましいと私が考える 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;
于 2016-06-27T09:50:43.393 に答える