web-dev-qa-db-ja.com

SQL ServerはSELECTリストの計算を1回だけ実行しますか?

次の例を見てください。

SELECT <CalculationA> As ColA,
       <CalculationB> As ColB,
       <CalculationA> + <CalculationB> As ColC
FROM TableA

CalculationAとCalculationBはそれぞれ2回計算されますか?
それとも、オプティマイザはそれらを1回計算して結果を2回使用するのに十分賢いでしょうか?

自分で結果を確認するためのテストを実行したいのですが、このようなものをどのように確認できるかわかりません。

私の想定では、計算が2回実行されることです。
どの場合、関連する計算に応じて、派生テーブルまたはネストされたビューを使用する方が良いでしょうか?以下を検討してください。

SELECT TableB.ColA,
       TableB.ColB,
       TableB.ColA + TableB.ColB AS ColC,
FROM(    
      SELECT <CalculationA> As ColA,
             <CalculationB> As ColB
      FROM TableA
    ) As TableB

この場合、計算が一度だけ実行されることを望みますか?

誰かが私の仮定を確認または反論できますか?または、このようなものを自分でテストする方法を教えてください。

ありがとう。

29
Gravitate

必要な情報のほとんどは、実行計画(および計画XML)に含まれます。

次のクエリを使用します。

_SELECT COUNT(val) As ColA,
       COUNT(val2) As ColB,
       COUNT(val) +  COUNT(val2) As ColC
FROM dbo.TableA;
_

実行計画sentryone plan Explorerで開かれると、実行された手順が表示されます。

enter image description here

EXPR1005EXPR1006の値を集約するストリーム集約を使用

enter image description here

これらが何かを知りたい場合は、クエリプランXMLからこれらの式に関する正確な情報を取得できます。

_<ColumnReference Column="Expr1005" />
<ScalarOperator ScalarString="COUNT([Database].[dbo].[TableA].[val])">
<Aggregate AggType="COUNT_BIG" Distinct="false">
_

最初の計算スカラー計算_ColA & ColB_の場合:

enter image description here

そして、最後の計算スカラーは単純な追加です:

enter image description here

これは、データが流れるときに読み取ることです。理論的には、論理的な実行を超える場合は、左から右に読み取る必要があります。

その場合、_EXPR1004_は他の式_EXPR1002_&_EXPR1003_を呼び出します。次に、これらは_EXPR1005_&_EXPR1006_を呼び出します。

CalculationAとCalculationBはそれぞれ2回計算されますか?または、オプティマイザは一度計算して結果を2回使用するのに十分賢いでしょうか?

以前のテストでは、この場合ColCは、ColAColBとして定義されている計算の追加として簡略化されています。

その結果、ColAColBは1回だけ計算されます。


200の異なる値によるグループ化

200の異なる値(val3)でグループ化している場合、同じものが表示されます。

_SET STATISTICS IO, TIME ON;
SELECT SUM(val) As ColA,
       SUM(val2) As ColB,
       SUM(val) +  SUM(val2) As ColC
FROM dbo.TableA
GROUP BY val3;
_

_val3_でこれら200の異なる値に集約

enter image description here

valとval2で合計を実行し、それらをColCに追加します。

enter image description here

一意ではない値を1つを除いてすべてグループ化している場合でも、計算スカラーに同じ追加が見られるはずです。


ColA&ColBへの関数の追加

クエリを次のように変更しても、

_SET STATISTICS IO, TIME ON;
SELECT ABS(SUM(val)) As ColA,
       ABS(SUM(val2)) As ColB,
       SUM(val) +  SUM(val2) As ColC
FROM dbo.TableA
_

集計はまだ2回計算されることはありません。単にABS()関数を集計の結果セットに追加します。これは1行です。

enter image description here

enter image description here

もちろん、SUM(ABS(ColA)SUM(ABS(ColB))を実行すると、オプティマイザはColCの計算に同じ式を使用できなくなります。


これが発生したときにさらに深く知りたい場合は、Paul Whiteによる Query Optimizer Deep Dive-Part 1 (part 4まで)を目指します。

クエリ実行フェーズをさらに掘り下げる別の方法は、次のヒントを追加することです。

_OPTION 
(
    RECOMPILE, 
    QUERYTRACEON 3604,
    QUERYTRACEON 8605
);
_

これにより、オプティマイザによって作成された入力ツリーが公開されます。

次に、前の2つの計算値を加算してColCを取得すると、次のように変換されます。

_AncOp_PrjEl COL: Expr1004 

ScaOp_Arithmetic x_aopAdd

    ScaOp_Identifier COL: Expr1002 

    ScaOp_Identifier COL: Expr1003 
_

この情報は、入力ツリーにすでに存在し、簡略化フェーズが行われる前でも、オプティマイザは実行する必要がないことをすぐに認識しています同じ計算を2回。

38
Randi Vertongen

計算の最初の部分が関数ではなく実際の計算(Col1 + Col2)である場合、個々の計算は「計算」ステップごとに実行されます。

SELECT <CalculationA> As ColA,
       <CalculationB> As ColB,
       <CalculationA> + <CalculationB> As ColC
FROM TableA

ステートメントの<CalculationA>を、テーブルのColAColBを使用した有効な計算で置き換え、これを後続の<CalculationB>,...ステップごとに繰り返すと、実際のタスク結果の計算は、ステップごとに個別に実行されます。

ステートメントを再現するには、次のコードスニペットをSQL Server Management Studioに貼り付けて実行します。オプションInclude Actual Execution Planがオンになっていることを確認してください。

Include Actual Execution Plan

データベース、テーブルを作成し、テーブルにデータを入力して、実行プランを作成する計算を実行します。

 CREATE DATABASE Q252661 
 GO 
 USE Q252661 
 GO 
 
 CREATE TABLE dbo.Q252661_TableA(
 ColA INT 、
 ColB INT、
 ColC INT、
 ColD INT)
 
 GO 
 
 INSERT INTO Q252661_TableA 
(
 ColA、
 ColB、
 ColC、
 ColD 
)
 VALUES 
( 
 1、
 2、
 3、
 4 
)、(
 2、
 4、
 8、
 16 
)
 
 GO 
 SELECT ColA + ColB AS ColA、
 ColC + ColD AS ColB、
 ColA + ColB + ColC + ColD AS ColC 
 FROM Q252661_TableA 
 
 GO 

クエリが実行され、次のようなグラフィカルな実行プランが生成されます。

Graphical Execution Plan of ADDing values値を追加するグラフィカル実行計画

ランディの回答と同様に、Compute Scalar演算子に焦点を当てます。

SSMSでクエリ実行プランをクリックして右クリックすると、実際のプランが表示されます。

Show Execution Plan XML...

..次のXMLが見つかります(Compute Scalar部分に焦点を当てています):

          <ComputeScalar>
            <DefinedValues>
              <DefinedValue>
                <ColumnReference Column="Expr1003" />
                <ScalarOperator ScalarString="[Q252661].[dbo].[Q252661_TableA].[ColA]+[Q252661].[dbo].[Q252661_TableA].[ColB]">
                  <Arithmetic Operation="ADD">
                    <ScalarOperator>
                      <Identifier>
                        <ColumnReference Database="[Q252661]" Schema="[dbo]" Table="[Q252661_TableA]" Column="ColA" />
                      </Identifier>
                    </ScalarOperator>
                    <ScalarOperator>
                      <Identifier>
                        <ColumnReference Database="[Q252661]" Schema="[dbo]" Table="[Q252661_TableA]" Column="ColB" />
                      </Identifier>
                    </ScalarOperator>
                  </Arithmetic>
                </ScalarOperator>
              </DefinedValue>
              <DefinedValue>
                <ColumnReference Column="Expr1004" />
                <ScalarOperator ScalarString="[Q252661].[dbo].[Q252661_TableA].[ColC]+[Q252661].[dbo].[Q252661_TableA].[ColD]">
                  <Arithmetic Operation="ADD">
                    <ScalarOperator>
                      <Identifier>
                        <ColumnReference Database="[Q252661]" Schema="[dbo]" Table="[Q252661_TableA]" Column="ColC" />
                      </Identifier>
                    </ScalarOperator>
                    <ScalarOperator>
                      <Identifier>
                        <ColumnReference Database="[Q252661]" Schema="[dbo]" Table="[Q252661_TableA]" Column="ColD" />
                      </Identifier>
                    </ScalarOperator>
                  </Arithmetic>
                </ScalarOperator>
              </DefinedValue>
              <DefinedValue>
                <ColumnReference Column="Expr1005" />
                <ScalarOperator ScalarString="[Q252661].[dbo].[Q252661_TableA].[ColA]+[Q252661].[dbo].[Q252661_TableA].[ColB]+[Q252661].[dbo].[Q252661_TableA].[ColC]+[Q252661].[dbo].[Q252661_TableA].[ColD]">
                  <Arithmetic Operation="ADD">
                    <ScalarOperator>
                      <Arithmetic Operation="ADD">
                        <ScalarOperator>
                          <Arithmetic Operation="ADD">
                            <ScalarOperator>
                              <Identifier>
                                <ColumnReference Database="[Q252661]" Schema="[dbo]" Table="[Q252661_TableA]" Column="ColA" />
                              </Identifier>
                            </ScalarOperator>
                            <ScalarOperator>
                              <Identifier>
                                <ColumnReference Database="[Q252661]" Schema="[dbo]" Table="[Q252661_TableA]" Column="ColB" />
                              </Identifier>
                            </ScalarOperator>
                          </Arithmetic>
                        </ScalarOperator>
                        <ScalarOperator>
                          <Identifier>
                            <ColumnReference Database="[Q252661]" Schema="[dbo]" Table="[Q252661_TableA]" Column="ColC" />
                          </Identifier>
                        </ScalarOperator>
                      </Arithmetic>
                    </ScalarOperator>
                    <ScalarOperator>
                      <Identifier>
                        <ColumnReference Database="[Q252661]" Schema="[dbo]" Table="[Q252661_TableA]" Column="ColD" />
                      </Identifier>
                    </ScalarOperator>
                  </Arithmetic>
                </ScalarOperator>
              </DefinedValue>
            </DefinedValues>
            <RelOp AvgRowSize="23" EstimateCPU="8.07E-05" EstimateIO="0.0032035" EstimateRebinds="0" EstimateRewinds="0" EstimatedExecutionMode="Row" EstimateRows="2" LogicalOp="Table Scan" NodeId="1" Parallel="false" PhysicalOp="Table Scan" EstimatedTotalSubtreeCost="0.0032842" TableCardinality="2">
              <OutputList>
                <ColumnReference Database="[Q252661]" Schema="[dbo]" Table="[Q252661_TableA]" Column="ColA" />
                <ColumnReference Database="[Q252661]" Schema="[dbo]" Table="[Q252661_TableA]" Column="ColB" />
                <ColumnReference Database="[Q252661]" Schema="[dbo]" Table="[Q252661_TableA]" Column="ColC" />
                <ColumnReference Database="[Q252661]" Schema="[dbo]" Table="[Q252661_TableA]" Column="ColD" />
              </OutputList>
              <TableScan Ordered="false" ForcedIndex="false" ForceScan="false" NoExpandHint="false" Storage="RowStore">
                <DefinedValues>
                  <DefinedValue>
                    <ColumnReference Database="[Q252661]" Schema="[dbo]" Table="[Q252661_TableA]" Column="ColA" />
                  </DefinedValue>
                  <DefinedValue>
                    <ColumnReference Database="[Q252661]" Schema="[dbo]" Table="[Q252661_TableA]" Column="ColB" />
                  </DefinedValue>
                  <DefinedValue>
                    <ColumnReference Database="[Q252661]" Schema="[dbo]" Table="[Q252661_TableA]" Column="ColC" />
                  </DefinedValue>
                  <DefinedValue>
                    <ColumnReference Database="[Q252661]" Schema="[dbo]" Table="[Q252661_TableA]" Column="ColD" />
                  </DefinedValue>
                </DefinedValues>
                <Object Database="[Q252661]" Schema="[dbo]" Table="[Q252661_TableA]" IndexKind="Heap" Storage="RowStore" />
              </TableScan>
            </RelOp>
          </ComputeScalar>

そのため、実際のテーブルから値が取得される場合は、個々の計算が繰り返し実行されます。次のXMLフラグメントは、上記の要約からの抜粋です。

                <ScalarOperator ScalarString="[Q252661].[dbo].[Q252661_TableA].[ColA]+[Q252661].[dbo].[Q252661_TableA].[ColB]">
                  <Arithmetic Operation="ADD">

実行プランには5つの<Arithmetic Operation="ADD">ステップがあります。


あなたの質問に答える

CalculationAとCalculationBはそれぞれ2回計算されますか?

はい、例のように計算が列の実際の合計である場合。最後の計算はCalculationA + CalculationBの合計になります。

または、オプティマイザは一度計算して結果を2回使用するのに十分賢いでしょうか?

計算対象によって異なります。 -この例では:はい。 -ランディの答え:いいえ。

私の仮定は、計算を2回実行することです。

あなたは特定の計算に適しています。

どちらの場合、関連する計算に応じて、派生テーブルまたはネストされたビューを使用する方が良いでしょうか?

正しい。


完了したら、データベースを再度削除できます。

USE [master]
GO
DROP DATABASE Q252661
4

質問にはすでに良い答えがあるので、私はDRYの側面に焦点を当てます(DRY =自分を繰り返さないでください)。

同じクエリで同じ計算を複数回行う必要がある場合は、CROSS APPLYを使用することに慣れました(同じ計算が何度も繰り返される傾向があるGROUP BY / WHERE / ORDER BYを忘れないでください)。

SELECT calc.ColA,
       calc.ColB,
       calc.ColA + calc.ColB AS ColC
  FROM TableA AS a
 CROSS APPLY (SELECT a.Org_A * 100 AS ColA
                   , a.Org_B / 100 AS ColB
             ) AS calc
 WHERE calc.ColB = @whatever
 ORDER BY calc.ColA

ある計算が別の計算に依存する場合、複数のCROSS APPLY呼び出しを使用して中間結果を計算しない理由はありません(WHERE/ORDER BYで最終結果を使用する必要がある場合も同じです) )

SELECT calc1.ColA,
       calc2.ColB,
       calc3.ColC
  FROM TableA AS a
 CROSS APPLY (SELECT a.Org_A    * 100        AS ColA) AS calc1
 CROSS APPLY (SELECT calc1.ColA * 100        AS ColB) AS calc2
 CROSS APPLY (SELECT calc1.ColA + calc2.ColB AS ColC) AS calc3
 WHERE calc.ColB = @whatever
 ORDER BY calc.ColA, calc3.ColC

これを行うための主なポイントは、バグを見つけた場合、または複数の発生ではなく何かを変更する必要がある場合に、コードの1行のみを変更または修正する必要があり、複数(わずかに異なるため、1つを変更するのを忘れたため) )同じ計算のバージョン。

PS:読みやすさについてCROSS APPLYは通常、特に計算で異なるソーステーブルの列を使用する場合や中間結果がある場合に、いくつかのネストされたサブ選択(またはCTE)に勝ります。

0
Thomas Franz