Avantages de Common Table Expression (CTE)?

21

De msdn :

Contrairement à une table dérivée, un CTE peut être auto-référencé et peut être référencé plusieurs fois dans la même requête.

J'utilise beaucoup les CTE, mais je n'ai jamais vraiment réfléchi aux avantages de les utiliser.

Si je référence plusieurs fois un CTE dans la même requête:

  • Y a-t-il un avantage en termes de performances?
  • Si je fais une auto-jointure, SQL Server analysera-t-il les tables cibles deux fois?
Royi Namir
la source
2
Le profileur devrait vous dire s'il scanne deux fois. À mon humble avis, les CTE sont géniaux pour la récursivité.
Dan Andrews
3
Il n'y a pas de réponse difficile lorsque l'optimiseur de requête est en cours de lecture. Certaines requêtes verront des avantages en termes de performances, d'autres non. Parfois, l'utilisation d'une table temporaire au lieu d'un CTE sera plus rapide, parfois non.

Réponses:

25

En règle générale, un CTE n'améliorera JAMAIS les performances .

Un CTE est essentiellement une vue jetable. Il n'y a pas de statistiques supplémentaires stockées, pas d'index, etc. Il fonctionne comme un raccourci pour une sous-requête.

À mon avis, ils peuvent être FACILEMENT surutilisés (je vois beaucoup de surutilisation dans le code dans mon travail). Vous trouverez ici de bonnes réponses, mais si vous devez faire référence à quelque chose plusieurs fois, ou s'il s'agit de plus de quelques centaines de milliers de lignes, placez-le dans un #temptableau à la place et indexez-le.

JNK
la source
3
Se mettre d'accord. À l'exception des CTE récursifs, ils facilitent simplement la lisibilité
gbn
Que se passe-t-il si le CTE ne renvoie que quelques lignes (afin qu'elles puissent être conservées en mémoire) qui sont coûteuses à calculer (agrégation sur une grande table) et que le résultat est utilisé plusieurs fois? Cela devrait améliorer les performances, non? (du moins c'est mon expérience avec PostgreSQL et Oracle où la table temporaire est utilisée très rarement)
a_horse_with_no_name
2
@a_horse_with_no_name - ce serait équivalent à en faire simplement une sous-requête. Si le résultat est utilisé plusieurs fois dans une même requête, il sera réutilisé et non recalculé. S'il est utilisé dans plusieurs requêtes, alors a CTEest un mauvais choix car les résultats sont ignorés après la première requête.
JNK
@JNK: merci. Il semble que SQL Server se comporte différemment ici.
a_horse_with_no_name
Certaines personnes trouvent que CTE est plus lisible dans certaines circonstances FWIW stackoverflow.com/a/11170918/32453
rogerdpack
14

Outre la récursivité, je trouve que les CTE sont extrêmement utiles lors de la création de requêtes de rapports complexes. J'utilise une série de CTE pour obtenir des morceaux des données dont j'ai besoin, puis je les combine dans la sélection finale. Je trouve qu'ils sont plus faciles à maintenir que de faire la même chose avec beaucoup de tables dérivées ou 20 jointures et je trouve que je peux être plus sûr qu'il renvoie les données correctes sans effet de plusieurs enregistrements en raison des relations un-plusieurs dans toutes les différentes jointures. Permettez-moi de donner un exemple rapide:

;WITH Conferences (Conference_id)
AS 
(select  m.Conference_id
FROM mydb.dbo.Conference m 
WHERE client_id = 10
    and Conference_id in 
            (select Conference_id from mydb.dbo.Expense 
            where amount <>0
            and amount is not null)
     )
--select * from Conferences
,MealEaters(NumberMealEaters, Conference_id, AttendeeType)
AS
(Select count(*) as NumberMealEaters, m.Conference_id,  AttendeeType 
from mydb.dbo.attendance ma 
join Conferences m on m.Conference_id = ma.Conference_id
where (ma.meals_consumed>0 or meals_consumed is null)and attended = 1
group by m.Conference_id)
--select * from MealEaters

,Expenses (Conference_id,expense_date, expenseDescription,  RecordIdentifier,amount)
AS
(select Conference_id,max(expense_date) as Expense_date, expenseDescription,  RecordIdentifier,sum(amount) as amount
    FROM
        (SELECT Conference_id,expense_date,  amount, RecordIdentifier
        FROM mydb.dbo.Expense
        WHERE  amount <> 0 
            and Conference_id IN 
            (SELECT  Conference_id
            FROM mydb.dbo.Conferences ) 
        group by Conference_id, RecordIdentifier) a
)
--select * from Expenses
Select m.Conference_id,me.NumberMealEaters, me.AttendeeType, e.expense_date,         e.RecordIdentifier,amount
from Conferences m
join mealeaters me on m.Conference_id = me.Conference_id
join expenses e on e.Conference_id = m.Conference_id

Donc, en séparant les différents morceaux d'informations que vous souhaitez, vous pouvez vérifier chaque partie individuellement (en utilisant les sélections commentées, en décommentant chacune individuellement et en ne l'exécutant que dans la mesure sélectionnée) et si vous avez besoin de modifier les dépenses calcul (dans cet exemple), il est plus facile à trouver que lorsqu'ils sont tous mélangés en une seule requête massive. Bien sûr, les requêtes de rapport réelles pour lesquelles j'utilise ceci sont généralement beaucoup plus compliquées que l'exemple.

HLGEM
la source
1
Juste pour signaler des requêtes? Les systèmes sur lesquels je travaille tous les jours ont des requêtes de transaction qui sont si compliquées. Curieusement, nos requêtes de rapports sont souvent parmi nos plus simples. (À l'exclusion des requêtes CRUD sans jointures triviales bien sûr).
Kevin Cathcart
J'ai utilisé cela comme exemple parce que ceux-ci sont généralement les plus compliqués ici
HLGEM
+1 Parfois, une requête plus logique (lisible par l'homme) est préférable à une requête potentiellement plus performante.
onedaywhen
Oui. Étant donné qu'un CTE produira généralement le même plan résultant, je ne vois aucune raison de créer des monstruosités multi-sous-requêtes horriblement imbriquées - alors que nous pourrions plutôt présenter chaque composant visuellement dans l'ordre dont ils ont besoin. J'importe des fichiers XML et je fais diverses acrobaties pour obtenir les données sous la bonne forme, ce qui serait insupportable à écrire / lire sans CTE. (Certains de mes anciens codes contiennent probablement d'horribles sous-requêtes!)
underscore_d
0

Comme toujours, cela dépend mais il y a des cas où les performances sont grandement améliorées. Je le vois avec les instructions INSERT INTO SELECT où vous utilisez un CTE pour la sélection, puis utilisez-le dans INSERT INTO. Cela peut être dû au fait que RCSI est activé pour la base de données, mais pour les moments où très peu est sélectionné, cela peut aider un peu.

Ron S
la source