web-dev-qa-db-fra.com

Caching CTES intermédiaire pour plusieurs utilisations

Je travaille sur cette énorme requête que j'ai choisi d'utiliser des CTES. En raison de la complexité de cette requête, j'ai fini par avoir à réutiliser des résultats de la requête intermédiaire plus d'une fois dans des requêtes ultérieures. Le problème que je suis confronté peut être résumé par le code de jouet suivant:

WITH cte1 AS (
    SELECT id, name
    FROM Manager)
, cte2 AS (
    SELECT id, name
    FROM Employee 
    LEFT OUTER JOIN cte1 ON Employee.id = cte1.id)
SELECT *
FROM cte1

J'ai remarqué qu'un montant inhabituel d'index cherche dans une table assez petite qui était interrogée dans l'une des premières requêtes qui, selon le plan d'exécution, prenaient environ 30% de la requête entière. Cela m'a conduit à croire que la table de base était interrogée plusieurs fois, car elle était référencée plus d'une fois dans des requêtes ultérieures.

J'avais l'impression que les résultats intermédiaires dans un CTE étaient mis en cache jusqu'à la déclaration finale. Lecture Microsofts Docs sur CTES , je n'ai pas trouvé aucune raison de penser autrement.

Les CTES intermédiaires sont-ils mis en cache pour les utilisations de Muliples dans une déclaration WITH? Sinon, existe-t-il un moyen de le mettre en cache sans réécrire la requête?

Merci!

Non, il n'y a aucun moyen de mettre en cache un CTE; Il sera généralement exécuté plusieurs fois si elle est référencée plusieurs fois. Si vous souhaitez éviter cela, vous pouvez mettre en cache les résultats à l'aide d'une table #TEpp à la place.

Dans certains cas, le plan d'exécution peut être suffisamment simple que le CTE ne soit que matérialisé une fois (cela ne signifie pas techniquement qu'il était "mis en cache") ou pour que les résultats soient réutilisés avec une bobine. Je n'ai pas d'exemples à portée de main et, même si je l'ai fait, vous ne pouviez pas compter sur cela pour toujours être le cas. Même quelque chose d'aussi simple que ceci:

CREATE TABLE dbo.OneTuple(id int PRIMARY KEY);
INSERT dbo.OneTuple(id) VALUES(1);
GO

;WITH cte AS 
(
  SELECT id FROM dbo.OneTuple
)
SELECT id FROM cte
UNION ALL
SELECT id FROM cte;

Donne deux scans de l'index identiques:

enter image description here

Voir aussi cette grande marque Martin Smith répond sur le débordement de la pile et il est discuté sur cet élément de retour .

8
Aaron Bertrand

Les CTES ne sont que des vues en ligne qui, à part la fonctionnalité fournie par des CTES récursives, ne sont rien de plus qu'une commodité syntaxique.

Si vous devez mettre en cache les résultats d'un CTE pour plusieurs utilisations ultérieures, la meilleure option consiste à rompre la requête et à charger les résultats de la requête CTE dans une table Temp, indexez-la au besoin, puis utilisez la table TEMP dans la version ultérieure. requêtes.

3
db2

y a-t-il un moyen de le mettre en cache sans réécrire la requête?

Pour cacher (ou bobine) des résultats intermédiaires, minimalement vous changeriez:

WITH cte1 AS (
    SELECT id, name
    FROM Manager)
, cte2 AS (
    SELECT id, name
    FROM Employee 
    LEFT OUTER JOIN cte1 ON Employee.id = cte1.id)
SELECT *
FROM cte2

à

SELECT id, name
into #cte1
FROM Manager;

WITH cte1 AS (
  select * from #cte1
)
, cte2 AS (
    SELECT id, name
    FROM Employee 
    LEFT OUTER JOIN cte1 ON Employee.id = cte1.id)
SELECT *
FROM cte2