web-dev-qa-db-fra.com

SQL Server effectue-t-il les calculs dans une liste SELECT une seule fois?

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.

29
Gravitate

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:

enter image description here

Avec l'agrégat de flux agrégeant les valeurs pour EXPR1005 & EXPR1006

enter image description here

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:

enter image description here

Et le dernier scalaire de calcul étant un simple ajout:

enter image description here

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

enter image description here

effectuer les sommes sur val & val2 puis les ajouter ensemble pour ColC:

enter image description here

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:

enter image description here

enter image description here

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.

38
Randi Vertongen

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 .

Include Actual Execution Plan

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:

Graphical Execution Plan of ADDing valuesPlan 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:

Show Execution Plan XML...

.. 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">.


Répondre à votre question

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
4
John aka hot2use

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.

0
Thomas Franz