Champ calculé SQL dans les clauses SELECT et GROUP BY

11

Souvent, en interrogeant mes bases de données MS SQL Server, je dois créer un champ calculé, tel que celui-ci

(CASE WHEN A.type = 'Workover' THEN 'Workover' 
      ELSE (CASE WHEN substring(C.category, 2, 1) = 'D' THEN 'Drilling' 
                 WHEN substring(C.category, 2, 1) = 'C' THEN 'Completion' 
                 WHEN substring(C.category, 2, 1) = 'W' THEN 'Workover' 
                 ELSE 'Other' 
            END)
END)

puis je dois regrouper mes résultats par ce champ calculé (entre autres). Par conséquent, j'ai le même calcul dans les clauses SELECT et GROUP BY. Le serveur SQL effectue-t-il réellement ces calculs deux fois ou est-il suffisamment intelligent pour ne le faire qu'une seule fois?

Dr. Drew
la source

Réponses:

13

J'ai le même calcul dans les clauses SELECT et GROUP BY. Le serveur SQL effectue-t-il réellement ces calculs deux fois ou est-il suffisamment intelligent pour ne le faire qu'une seule fois?

La réponse simple est que SQL Server ne donne aucune garantie générale quant au moment et au nombre de fois où une expression scalaire sera évaluée au moment de l'exécution.

Il existe toutes sortes de comportements compliqués (et non documentés) dans l'optimiseur et le moteur d'exécution concernant le placement, l'exécution et la mise en cache des expressions scalaires. Books Online n'a pas grand-chose à dire à ce sujet, mais ce qu'il dit est le suivant:

Description du calcul scalaire

Cela décrit l'un des comportements auxquels j'ai fait allusion auparavant, l'exécution différée des expressions. J'ai écrit sur certains des autres comportements actuels (qui pourraient changer à tout moment) dans ce billet de blog .

Une autre considération est que le modèle de coût utilisé par l'optimiseur de requêtes ne fait actuellement pas grand-chose en termes d'estimation des coûts pour les expressions scalaires. Sans un cadre de calcul des coûts robuste, les résultats actuels sont basés sur une large heuristique ou le pur hasard.

Pour les expressions très simples, cela ne fait probablement pas beaucoup de différence que l'expression soit évaluée une ou plusieurs fois dans la plupart des cas. Cela dit, j'ai rencontré de grandes requêtes où les performances ont été affectées négativement lorsque l'expression est évaluée de manière redondante un très grand nombre de fois, ou l'évaluation se produit sur un seul thread où il aurait été avantageux d'évaluer dans une branche parallèle de l'exécution plan.

En résumé, le comportement actuel n'est pas défini, et il n'y a pas grand-chose dans les plans d'exécution pour vous aider à comprendre ce qui s'est passé (et il ne sera pas toujours pratique d'attacher un débogueur pour examiner les comportements détaillés du moteur, comme dans le billet de blog).

Si vous rencontrez des cas où les problèmes d'évaluation scalaire sont importants pour les performances, soulevez le problème auprès du support technique Microsoft. Il s'agit de la meilleure façon de fournir des commentaires afin d'améliorer les futures versions du produit.

Paul White 9
la source
3

Comme l'indique le commentaire sur votre question, la réponse est (au moins d'après mon expérience) "oui". SQL Server est généralement suffisamment intelligent pour éviter le recalcul. Vous pouvez probablement vérifier cela en affichant le plan d'exécution à partir de SQL Server Management Studio. Chaque champ calculé est désigné Exprxxxxx(où xxxxx est un nombre). Si vous savez quoi rechercher, vous devriez pouvoir vérifier qu'il utilise la même expression.

Pour ajouter à la discussion, votre autre option esthétique est une expression de table courante :

with [cte] as
(
    select
        (case when a.type = 'workover' then 'workover' else 
        (case when substring(c.category, 2, 1) = 'd' then 'drilling'
              when substring(c.category, 2, 1) = 'c' then 'completion'
              when substring(c.category, 2, 1) = 'w' then 'workover'
              else 'other' end)
         end)) as [group_key],
         *
    from
        [some_table]
)
select
    [group_key],
    count(*) as [count]
from
    [cte]
group by
    [group_key]

Réponse courte, ils sont fonctionnellement identiques à une vue, mais ne sont valables que pour une utilisation dans l'instruction très suivante. Je les vois comme une alternative plus lisible aux tables dérivées car cela évite l'imbrication.

Bien qu'ils ne soient pas pertinents pour cette question, ils peuvent se référencer et être utilisés de cette manière pour construire des requêtes récursives.

Quick Joe Smith
la source
@Quick Joe Smith: Je pense que vous avez raison au sujet de l'Exprxxxxx, car j'ai également vu cela. Cependant, si je donne un nom à l'expression manuellement (case ... end) en tant que OpType, puis que j'utilise le champ OpType dans la clause GROUP BY, j'obtiens une erreur indiquant qu'il s'agit d'un nom de colonne non valide.
Dr Drew
Malheureusement, souvent, votre seul moyen de ne pas spécifier l'expression deux fois est d'utiliser l'une des méthodes ci-dessus: un CTE, une vue ou une requête imbriquée.
Quick Joe Smith
2
À moins que vous ne connaissiez également CROSS APPLY .
Andriy M
L'utilisation cross applydans ce cas est un peu exagérée, et elle nuirait très probablement aux performances en introduisant une auto-jointure inutile.
Quick Joe Smith
2
Je ne pense pas que vous ayez "reçu" la suggestion. Le CROSS APPLYdéfinit simplement l'alias des colonnes de la même ligne. Pas besoin de rejoindre. par exempleSELECT COUNT(*), hilo FROM master..spt_values CROSS APPLY (VALUES(high + low)) V(hilo) GROUP BY hilo
Martin Smith
1

La performance n'est qu'un aspect. L'autre est la maintenabilité.

Personnellement, j'ai tendance à faire ce qui suit:

SELECT T.GroupingKey, SUM(T.value)
FROM
(
    SELECT 
        A.*
        (CASE WHEN A.type = 'Workover' THEN 'Workover' ELSE 
        (CASE WHEN substring(C.category, 2, 1) = 'D' THEN 'Drilling' WHEN substring(C.category, 2, 1) = 'C' THEN 'Completion' WHEN substring(C.category, 2, 1) = 'W' THEN 'Workover' ELSE 'Other' END)
        END) AS GroupingKey
    FROM Table AS A
) AS T

GROUP BY T.GroupingKey

METTRE À JOUR:

Si vous n'aimez pas faire de l'imbrication, vous pouvez créer VIEW pour chaque table où vous devez utiliser des expressions complexes.

CREATE VIEW TableExtended
AS 
SELECT 
    A.*
    (CASE WHEN A.type = 'Workover' THEN 'Workover' ELSE 
    (CASE WHEN substring(C.category, 2, 1) = 'D' THEN 'Drilling' WHEN substring(C.category, 2, 1) = 'C' THEN 'Completion' WHEN substring(C.category, 2, 1) = 'W' THEN 'Workover' ELSE 'Other' END)
    END) AS GroupingKey
FROM Table AS A

Ensuite, vous pouvez faire une sélection sans faire d'imbrication supplémentaire;

SELECT GroupingKey, SUM(value)
FROM TableExtended
GROUP BY GroupingKey
Kaspars Ozols
la source