次の例を見てください。
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
この場合、計算が一度だけ実行されることを望みますか?
誰かが私の仮定を確認または反論できますか?または、このようなものを自分でテストする方法を教えてください。
ありがとう。
必要な情報のほとんどは、実行計画(および計画XML)に含まれます。
次のクエリを使用します。
_SELECT COUNT(val) As ColA,
COUNT(val2) As ColB,
COUNT(val) + COUNT(val2) As ColC
FROM dbo.TableA;
_
実行計画( sentryone plan Explorer )で開かれると、実行された手順が表示されます。
EXPR1005&EXPR1006の値を集約するストリーム集約を使用
これらが何かを知りたい場合は、クエリプランXMLからこれらの式に関する正確な情報を取得できます。
_<ColumnReference Column="Expr1005" />
<ScalarOperator ScalarString="COUNT([Database].[dbo].[TableA].[val])">
<Aggregate AggType="COUNT_BIG" Distinct="false">
_
最初の計算スカラー計算_ColA & ColB
_の場合:
そして、最後の計算スカラーは単純な追加です:
これは、データが流れるときに読み取ることです。理論的には、論理的な実行を超える場合は、左から右に読み取る必要があります。
その場合、_EXPR1004
_は他の式_EXPR1002
_&_EXPR1003
_を呼び出します。次に、これらは_EXPR1005
_&_EXPR1006
_を呼び出します。
CalculationAとCalculationBはそれぞれ2回計算されますか?または、オプティマイザは一度計算して結果を2回使用するのに十分賢いでしょうか?
以前のテストでは、この場合ColC
は、ColA
&ColB
として定義されている計算の追加として簡略化されています。
その結果、ColA
&ColB
は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の異なる値に集約
valとval2で合計を実行し、それらをColCに追加します。
一意ではない値を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行です。
もちろん、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回。
計算の最初の部分が関数ではなく実際の計算(Col1 + Col2
)である場合、個々の計算は「計算」ステップごとに実行されます。
SELECT <CalculationA> As ColA, <CalculationB> As ColB, <CalculationA> + <CalculationB> As ColC FROM TableA
ステートメントの<CalculationA>
を、テーブルのColA
とColB
を使用した有効な計算で置き換え、これを後続の<CalculationB>,...
ステップごとに繰り返すと、実際のタスク結果の計算は、ステップごとに個別に実行されます。
ステートメントを再現するには、次のコードスニペットをSQL Server Management Studioに貼り付けて実行します。オプション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
クエリが実行され、次のようなグラフィカルな実行プランが生成されます。
ランディの回答と同様に、Compute Scalar演算子に焦点を当てます。
SSMSでクエリ実行プランをクリックして右クリックすると、実際のプランが表示されます。
..次の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
質問にはすでに良い答えがあるので、私は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)に勝ります。