Quels sont les plus performants, CTE
ou Temporary Tables
?
Quels sont les plus performants, CTE
ou Temporary Tables
?
Réponses:
Je dirais que ce sont des concepts différents mais pas trop différents pour dire «craie et fromage».
Une table temporaire est bonne pour la réutilisation ou pour effectuer plusieurs passes de traitement sur un ensemble de données.
Un CTE peut être utilisé soit pour récurer, soit simplement pour améliorer la lisibilité.
Et, comme une vue ou une fonction de table en ligne, la fonction peut également être traitée comme une macro à développer dans la requête principale
Une table temporaire est une autre table avec quelques règles autour de la portée
J'ai stocké des procs où j'utilise les deux (et des variables de table aussi)
cte vs temporary tables
donc à mon humble avis, cette réponse doit mettre en évidence les inconvénients de mieux CTE. TL; DR de la réponse liée: un CTE ne doit jamais être utilisé pour la performance. . Je suis d'accord avec cette citation car j'ai expérimenté les inconvénients de CTE.
Ça dépend.
Tout d'abord
Qu'est-ce qu'une expression de table commune?
Un CTE (non récursif) est traité de manière très similaire aux autres constructions qui peuvent également être utilisées comme expressions de table en ligne dans SQL Server. Tables dérivées, vues et fonctions de valeurs de table en ligne. Notez que tandis que BOL dit qu'un CTE "peut être considéré comme un ensemble de résultats temporaire", il s'agit d'une description purement logique. Le plus souvent, il n'est pas matérialisé à part entière.
Qu'est-ce qu'une table temporaire?
Il s'agit d'une collection de lignes stockées sur des pages de données dans tempdb. Les pages de données peuvent résider partiellement ou entièrement en mémoire. En outre, la table temporaire peut être indexée et avoir des statistiques de colonne.
Données de test
CREATE TABLE T(A INT IDENTITY PRIMARY KEY, B INT , F CHAR(8000) NULL);
INSERT INTO T(B)
SELECT TOP (1000000) 0 + CAST(NEWID() AS BINARY(4))
FROM master..spt_values v1,
master..spt_values v2;
Exemple 1
WITH CTE1 AS
(
SELECT A,
ABS(B) AS Abs_B,
F
FROM T
)
SELECT *
FROM CTE1
WHERE A = 780
Remarquez dans le plan ci-dessus qu'il n'y a aucune mention de CTE1. Il accède simplement aux tables de base directement et est traité de la même manière que
SELECT A,
ABS(B) AS Abs_B,
F
FROM T
WHERE A = 780
Réécrire ici en matérialisant le CTE en une table temporaire intermédiaire serait massivement contre-productif.
Matérialiser la définition CTE de
SELECT A,
ABS(B) AS Abs_B,
F
FROM T
Cela impliquerait de copier environ 8 Go de données dans une table temporaire, alors il y a encore la surcharge de la sélection à partir de celle-ci.
Exemple 2
WITH CTE2
AS (SELECT *,
ROW_NUMBER() OVER (ORDER BY A) AS RN
FROM T
WHERE B % 100000 = 0)
SELECT *
FROM CTE2 T1
CROSS APPLY (SELECT TOP (1) *
FROM CTE2 T2
WHERE T2.A > T1.A
ORDER BY T2.A) CA
L'exemple ci-dessus prend environ 4 minutes sur ma machine.
Seules 15 lignes des 1 000 000 de valeurs générées aléatoirement correspondent au prédicat, mais l'analyse coûteuse de la table se produit 16 fois pour les localiser.
Ce serait un bon candidat pour matérialiser le résultat intermédiaire. La réécriture de la table temporaire équivalente a pris 25 secondes.
INSERT INTO #T
SELECT *,
ROW_NUMBER() OVER (ORDER BY A) AS RN
FROM T
WHERE B % 100000 = 0
SELECT *
FROM #T T1
CROSS APPLY (SELECT TOP (1) *
FROM #T T2
WHERE T2.A > T1.A
ORDER BY T2.A) CA
La matérialisation intermédiaire d'une partie d'une requête dans une table temporaire peut parfois être utile même si elle n'est évaluée qu'une seule fois - lorsqu'elle permet de recompiler le reste de la requête en tirant parti des statistiques sur le résultat matérialisé. Un exemple de cette approche se trouve dans l'article SQL Cat When To Break Down Complex Queries .
Dans certaines circonstances, SQL Server utilisera un spool pour mettre en cache un résultat intermédiaire, par exemple un CTE, et évitera d'avoir à réévaluer cette sous-arborescence. Ceci est abordé dans l'élément Connect (migré). Fournissez un indice pour forcer la matérialisation intermédiaire des CTE ou des tables dérivées . Cependant, aucune statistique n'est créée à ce sujet et même si le nombre de lignes mises en file d'attente devait être très différent de l'estimation, il n'est pas possible pour le plan d'exécution en cours de s'adapter dynamiquement en réponse (du moins dans les versions actuelles. Des plans de requête adaptatifs peuvent devenir possibles dans l'avenir).
CTE a ses utilisations - lorsque les données dans le CTE sont petites et qu'il y a une forte amélioration de la lisibilité comme dans le cas des tables récursives. Cependant, ses performances ne sont certainement pas meilleures que celles des variables de table et lorsqu'il s'agit de tables très volumineuses, les tables temporaires surpassent considérablement le CTE. Cela est dû au fait que vous ne pouvez pas définir d'index sur un CTE et lorsque vous avez une grande quantité de données qui nécessite de se joindre à une autre table (CTE est simplement comme une macro). Si vous joignez plusieurs tables avec des millions de lignes d'enregistrements dans chacune, CTE fonctionnera nettement moins bien que les tables temporaires.
Les tables temporaires sont toujours sur le disque - donc tant que votre CTE peut être conservé en mémoire, il sera probablement plus rapide (comme une variable de table aussi).
Mais là encore, si la charge de données de votre CTE (ou variable de table temporaire) devient trop importante, elle sera également stockée sur le disque, donc il n'y a pas de gros avantage.
En général, je préfère un CTE à une table temporaire car il est parti après que je l'ai utilisé. Je n'ai pas besoin de penser à le laisser explicitement ou quoi que ce soit.
Donc, pas de réponse claire à la fin, mais personnellement, je préférerais CTE aux tables temporaires.
Ainsi, la requête qui m'a été assignée pour optimiser a été écrite avec deux CTE dans le serveur SQL. Cela prenait 28 secondes.
J'ai passé deux minutes à les convertir en tables temporaires et la requête a pris 3 secondes
J'ai ajouté un index à la table temporaire sur le champ sur lequel il était joint et je l'ai réduit à 2 secondes
Trois minutes de travail et maintenant son fonctionnement 12 fois plus rapide en supprimant CTE. Personnellement, je n'utiliserai pas les CTE car ils sont également plus difficiles à déboguer.
Ce qui est fou, c'est que les CTE n'ont été utilisés qu'une seule fois et que leur mise en place d'un index s'est avérée 50% plus rapide.
CTE ne prendra aucun espace physique. C'est juste un ensemble de résultats que nous pouvons utiliser join.
Les tables temporaires sont temporaires. Nous pouvons créer des index, des contraintes comme des tables normales pour cela nous devons définir toutes les variables.
Portée de la table temporaire uniquement dans la session. EX: ouvre deux fenêtres de requête SQL
create table #temp(empid int,empname varchar)
insert into #temp
select 101,'xxx'
select * from #temp
Exécutez cette requête dans la première fenêtre, puis exécutez la requête ci-dessous dans la deuxième fenêtre, vous pouvez trouver la différence.
select * from #temp
J'ai utilisé les deux, mais dans des procédures complexes massives, j'ai toujours trouvé les tables temporaires plus faciles à utiliser et plus méthodiques. Les CTE ont leurs utilisations mais généralement avec de petites données.
Par exemple, j'ai créé des sprocs qui reviennent avec des résultats de gros calculs en 15 secondes, mais convertissent ce code pour qu'il s'exécute dans un CTE et je l'ai vu s'exécuter plus de 8 minutes pour obtenir les mêmes résultats.
En retard à la fête, mais ...
L'environnement dans lequel je travaille est très contraint, prenant en charge certains produits de fournisseurs et fournissant des services à «valeur ajoutée» comme le reporting. En raison des limites de la politique et du contrat, je n'ai généralement pas le luxe de disposer d'un espace table / données séparé et / ou de créer du code permanent [cela s'améliore un peu, selon l'application].
IOW, je ne peux pas généralement développer une procédure stockée ou des UDF ou des tables temporaires, etc. Je dois pratiquement tout faire via l'interface de mon application (Crystal Reports - ajouter / lier des tables, définir les clauses where de w / in CR, etc. ). Une petite grâce salvatrice est que Crystal me permet d'utiliser les COMMANDES (ainsi que les expressions SQL). Certaines choses qui ne sont pas efficaces grâce à la fonctionnalité régulière d'ajout / liaison de tables peuvent être effectuées en définissant une commande SQL. J'utilise les CTE à travers cela et j'ai obtenu de très bons résultats "à distance". Les CTE aident également à la maintenance des rapports, ne nécessitant pas que le code soit développé, remis à un DBA pour compiler, crypter, transférer, installer, puis exiger des tests à plusieurs niveaux. Je peux faire des CTE via l'interface locale.
L'inconvénient de l'utilisation des CTE avec CR est que chaque rapport est séparé. Chaque CTE doit être maintenu pour chaque rapport. Là où je peux faire des SP et des UDF, je peux développer quelque chose qui peut être utilisé par plusieurs rapports, ne nécessitant qu'un lien vers le SP et en passant des paramètres comme si vous travailliez sur une table normale. CR n'est pas vraiment bon pour gérer les paramètres dans les commandes SQL, donc cet aspect de l'aspect CR / CTE peut faire défaut. Dans ces cas, j'essaie généralement de définir le CTE pour renvoyer suffisamment de données (mais pas TOUTES les données), puis j'utilise les capacités de sélection d'enregistrements dans CR pour trancher et couper cela.
Donc ... mon vote est pour les CTE (jusqu'à ce que j'obtienne mon espace de données).
Une utilisation dans laquelle j'ai trouvé les excellentes performances de CTE était celle où je devais joindre une requête relativement complexe à quelques tables de quelques millions de lignes chacune.
J'ai utilisé le CTE pour sélectionner d'abord le sous-ensemble basé sur les colonnes indexées afin de réduire d'abord ces tables à quelques milliers de lignes pertinentes chacune, puis j'ai joint le CTE à ma requête principale. Cela a réduit de façon exponentielle le temps d'exécution de ma requête.
Alors que les résultats pour le CTE ne sont pas mis en cache et que les variables de table auraient pu être un meilleur choix, je voulais vraiment les essayer et j'ai trouvé le scénario ci-dessus.
C'est une question vraiment ouverte, et tout dépend de la façon dont elle est utilisée et du type de table temporaire (variable de table ou table traditionnelle).
Une table temporaire traditionnelle stocke les données dans la base de données temporaire, ce qui ralentit les tables temporaires; mais pas les variables de table.
Je viens de tester cela - à la fois CTE et non-CTE (où la requête a été tapée pour chaque instance d'union) a pris tous les deux ~ 31 secondes. CTE a rendu le code beaucoup plus lisible - le réduire de 241 à 130 lignes, ce qui est très agréable. La table de température, d'autre part, l'a réduite à 132 lignes et a pris CINQ SECONDES pour fonctionner. Sans blague. tous ces tests ont été mis en cache - les requêtes ont toutes été exécutées plusieurs fois auparavant.
De mon expérience dans SQL Server, j'ai trouvé l'un des scénarios où CTE surperformait la table Temp
J'avais besoin d'utiliser un DataSet (~ 100000) à partir d'une requête complexe une seule fois dans ma procédure stockée.
La table temporaire provoquait une surcharge sur SQL où ma procédure s'exécutait lentement (car les tables temporaires sont de vraies tables matérialisées qui existent dans tempdb et persister pendant la durée de ma procédure actuelle)
D'autre part, avec CTE, CTE persiste uniquement jusqu'à ce que la requête suivante soit exécutée. Ainsi, CTE est une structure en mémoire pratique avec une portée limitée. Les CTE n'utilisent pas tempdb par défaut.
Il s'agit d'un scénario dans lequel les CTE peuvent vraiment vous aider à simplifier votre code et à surpasser la table temporaire. J'avais utilisé 2 CTE, quelque chose comme
WITH CTE1(ID, Name, Display)
AS (SELECT ID,Name,Display from Table1 where <Some Condition>),
CTE2(ID,Name,<col3>) AS (SELECT ID, Name,<> FROM CTE1 INNER JOIN Table2 <Some Condition>)
SELECT CTE2.ID,CTE2.<col3>
FROM CTE2
GO