Prenons l'exemple suivant:
SELECT <CalculationA> As ColA,
<CalculationB> As ColB,
<CalculationA> + <CalculationB> As ColC
FROM TableA
Est-ce que CalculationA et CalculationB, chacun serait calculé deux fois?
Ou l'optimiseur serait-il assez intelligent pour les calculer une fois et utiliser le résultat deux fois?
Je voudrais effectuer un test pour voir le résultat par moi-même, cependant, je ne sais pas comment je pourrais vérifier quelque chose comme ça.
Mon hypothèse est qu'il effectuerait le calcul deux fois.
Dans quel cas, selon les calculs impliqués, serait-il préférable d'utiliser une table dérivée ou une vue imbriquée? Considérer ce qui suit:
SELECT TableB.ColA,
TableB.ColB,
TableB.ColA + TableB.ColB AS ColC,
FROM(
SELECT <CalculationA> As ColA,
<CalculationB> As ColB
FROM TableA
) As TableB
Dans ce cas, j'espère que les calculs ne seront effectués qu'une seule fois?
S'il vous plaît quelqu'un peut-il confirmer ou réfuter mes hypothèses? Ou me dire comment tester moi-même quelque chose comme ça?
Merci.
La plupart des informations dont vous aurez besoin se trouveront dans le plan d'exécution (et le plan XML).
Prenez cette requête:
SELECT COUNT(val) As ColA,
COUNT(val2) As ColB,
COUNT(val) + COUNT(val2) As ColC
FROM dbo.TableA;
Le plan d'exécution (ouvert avec Sentryone Plan Explorer ) montre les étapes qu'il a suivies:
Avec l'agrégat de flux agrégeant les valeurs pour EXPR1005 & EXPR1006
Si nous voulons savoir de quoi il s'agit, nous pourrions obtenir les informations exactes sur ces expressions à partir du plan de requête XML:
<ColumnReference Column="Expr1005" />
<ScalarOperator ScalarString="COUNT([Database].[dbo].[TableA].[val])">
<Aggregate AggType="COUNT_BIG" Distinct="false">
Avec le premier calcul scalaire ColA & ColB
:
Et le dernier scalaire de calcul étant un simple ajout:
Il s'agit de le lire au fur et à mesure que les données circulent, en théorie, vous devriez le lire de gauche à droite si vous passez par l'exécution logique.
Dans ce cas, EXPR1004
Appelle les autres expressions, EXPR1002
& EXPR1003
. Ces derniers appellent à leur tour EXPR1005
& EXPR1006
.
Est-ce que CalculationA et CalculationB, chacun serait calculé deux fois? Ou l'optimiseur serait-il assez intelligent pour les calculer une fois et utiliser le résultat deux fois?
Les tests précédents montrent que dans ce cas ColC
est simplifié en tant qu'ajout des calculs définis comme ColA
& ColB
.
Par conséquent, ColA
& ColB
ne sont calculés qu'une seule fois.
Regroupement par 200 valeurs distinctes
Si nous regroupons par 200 valeurs distinctes (val3), la même chose est affichée:
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;
Agrégation jusqu'à ces 200 valeurs distinctes dans val3
effectuer les sommes sur val & val2 puis les ajouter ensemble pour ColC:
Même si nous regroupons toutes les valeurs non uniques sauf une, le même ajout devrait être observé pour le scalaire de calcul.
Ajout d'une fonction à ColA et ColB
Même si nous changeons la requête en ceci:
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
Les agrégations ne seront toujours pas calculées deux fois, nous ajoutons simplement la fonction ABS()
au jeu de résultats de l'agrégation, qui est une ligne:
Bien entendu, l'exécution de SUM(ABS(ColA)
& SUM(ABS(ColB))
rendra l'optimiseur incapable d'utiliser la même expression pour calculer ColC
.
Si vous voulez approfondir quand cela se produit, je vous pousserais vers Query Optimizer Deep Dive - Partie 1 (jusqu'à la partie 4) par Paul White.
Une autre façon d'approfondir les phases d'exécution des requêtes consiste à ajouter ces conseils:
OPTION
(
RECOMPILE,
QUERYTRACEON 3604,
QUERYTRACEON 8605
);
Cela exposera l'arborescence d'entrée créée par l'optimiseur.
L'ajout des deux précédentes valeurs calculées pour obtenir ColC
est alors traduit en:
AncOp_PrjEl COL: Expr1004
ScaOp_Arithmetic x_aopAdd
ScaOp_Identifier COL: Expr1002
ScaOp_Identifier COL: Expr1003
Ces informations sont déjà présentes dans l'arborescence d'entrée , avant même la phase de simplification, montrant que l'optimiseur sait immédiatement qu'il n'a pas à effectuer le même calcul deux fois.
Si la première partie de votre calcul est un calcul réel (Col1 + Col2
) Et non une fonction, alors les calculs individuels sont effectués pour chaque étape de "calcul".
SELECT <CalculationA> As ColA, <CalculationB> As ColB, <CalculationA> + <CalculationB> As ColC FROM TableA
Si nous remplaçons <CalculationA>
De votre relevé par un calcul valide utilisant ColA
et ColB
à partir d'un tableau, et répétons cela pour chaque étape <CalculationB>,...
Suivante, puis le la tâche réelle de calcul du résultat sera effectuée pour chaque étape individuellement.
Pour reproduire mon instruction, collez les extraits de code suivants dans SQL Server Management Studio et exécutez. Assurez-vous d'avoir activé l'option Inclure le plan d'exécution réel .
Il crée une base de données, une table, remplit la table et effectue un calcul qui produit un plan d'exécution.
CRÉER UNE BASE DE DONNÉES Q252661 ALLER UTILISER Q252661 ALLER CRÉER UN TABLEAU dbo.Q252661_TableA ( ColA INT , ColB INT, ColC INT, ColD INT) GO INSÉRER DANS Q252661_TableA ( ColA, ColB, ColC, ColD ) VALEURS ( 1, 2, 3, 4 ), ( 2, 4, 8, 16 ) ALLER SÉLECTIONNER ColA + ColB AS ColA, ColC + ColD AS ColB, ColA + ColB + ColC + ColD AS ColC FROM Q252661_TableA GO
La requête s'exécutera et produira un plan d'exécution graphique semblable au suivant:
Plan d'exécution graphique des valeurs ADDing
Comme dans la réponse de Randi, nous nous concentrerons sur l'opérateur Compute Scalar .
Si vous cliquez sur le plan d'exécution des requêtes dans SSMS et cliquez avec le bouton droit pour afficher le plan réel:
.. vous trouverez le XML suivant (en vous concentrant sur la partie 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>
Ainsi, chaque calcul individuel est effectué encore et encore dans le cas où les valeurs sont extraites d'une table réelle. Le fragment XML suivant provient du résumé ci-dessus:
<ScalarOperator ScalarString="[Q252661].[dbo].[Q252661_TableA].[ColA]+[Q252661].[dbo].[Q252661_TableA].[ColB]">
<Arithmetic Operation="ADD">
Le plan d'exécution comporte cinq étapes <Arithmetic Operation="ADD">
.
Est-ce que CalculationA et CalculationB, chacun serait calculé deux fois?
Oui, si les calculs sont des sommes réelles de colonnes selon l'exemple. Le dernier calcul serait la somme de CalculationA + CalculationB
.
Ou l'optimiseur serait-il assez intelligent pour les calculer une fois et utiliser le résultat deux fois?
Cela dépend de ce que vous calculez. - Dans cet exemple: oui. - Dans la réponse de Randi: non.
Mon hypothèse est qu'il effectuerait le calcul deux fois.
Vous avez raison pour certains calculs.
Dans quel cas, selon les calculs impliqués, serait-il préférable d'utiliser une table dérivée ou une vue imbriquée?
Correct.
Une fois que vous avez terminé, vous pouvez à nouveau déposer votre base de données:
USE [master]
GO
DROP DATABASE Q252661
Puisqu'il y a déjà de bonnes réponses à la question, je me concentrerai sur l'aspect SEC (SEC = ne vous répétez pas).
Je me suis habitué à utiliser CROSS APPLY
, si je dois faire plusieurs fois les mêmes calculs dans la même requête (n'oubliez pas GROUP BY / WHERE / ORDER BY
, où les mêmes calculs ont tendance à se répéter encore et encore).
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
Lorsqu'un calcul dépend d'un autre, il n'y a aucune raison de ne pas utiliser plusieurs CROSS APPLY
appelle pour calculer les résultats intermédiaires (idem, lorsque vous devez utiliser le résultat final dans le 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
Le point principal pour ce faire est que vous devez modifier/corriger une seule ligne de code lorsque vous trouvez un bogue ou devez changer quelque chose au lieu de plusieurs occurrences et ne risquez pas d'en avoir plusieurs (légèrement différent, car vous avez oublié d'en changer une). ) versions du même calcul.
PS: concernant la lisibilité CROSS APPLY
gagnera généralement par rapport à plusieurs sous-sélections imbriquées (ou CTE), en particulier lorsque les calculs utilisent des colonnes de différentes tables source ou que vous avez des résultats intermédiaires.