Quand utiliser Common Table Expression (CTE)

230

J'ai commencé à lire sur Common Table Expression et je ne peux pas penser à un cas d'utilisation où j'aurais besoin de les utiliser. Ils sembleraient redondants car on peut faire de même avec les tables dérivées. Y a-t-il quelque chose qui me manque ou que je ne comprends pas bien? Quelqu'un peut-il me donner un exemple simple de limitations avec des requêtes régulières de table de sélection, dérivée ou temporaire pour faire le cas de CTE? Tout exemple simple serait très apprécié.

imak
la source

Réponses:

197

Par exemple, si vous devez référencer / joindre plusieurs fois le même ensemble de données, vous pouvez le faire en définissant un CTE. Par conséquent, il peut s'agir d'une forme de réutilisation de code.

Un exemple d'auto-référencement est la récursivité: requêtes récursives utilisant CTE

Pour des définitions Microsoft intéressantes tirées de la documentation en ligne:

Un CTE peut être utilisé pour:

  • Créez une requête récursive. Pour plus d'informations, consultez Requêtes récursives utilisant des expressions de table communes.

  • Remplacer une vue lorsque l'utilisation générale d'une vue n'est pas requise; c'est-à-dire que vous n'avez pas besoin de stocker la définition dans des métadonnées.

  • Activez le regroupement par une colonne dérivée d'une sous-sélection scalaire ou une fonction non déterministe ou disposant d'un accès externe.

  • Référencez la table résultante plusieurs fois dans la même instruction.

John Sansom
la source
7
Oui. Vous ne pouvez pas vous joindre à une table dérivée. Il vaut la peine de souligner qu'une auto-jointure sur un CTE vous laissera quand même 2 invocations distinctes.
Martin Smith
@Martin - Je suis surpris. Pouvez-vous sauvegarder cette déclaration?
RichardTheKiwi
@John Merci, je trouve 4guysfromrolla.com/webtech/071906-1.shtml très utile aussi
imak
4
@cyberkiwi - Quel morceau? Qu'une auto-jointure conduira à 2 invocations différentes? Voir l'exemple dans cette réponse stackoverflow.com/questions/3362043/…
Martin Smith
4
Fait intéressant sur CTE. Je me suis toujours demandé pourquoi NEWID () dans le CTE change lorsque le CTE est référencé plus d'une fois. select top 100 * into #tmp from master..spt_values order by 1,2,3,4 select A.number, COUNT(*) from #tmp A inner join #tmp B ON A.number = B.number+1 group by A.numbervswith CTE AS (select top 100 * from master..spt_values order by 1,2,3,4) select A.number, COUNT(*) from CTE A inner join CTE B ON A.number = B.number+1 group by A.number
RichardTheKiwi
50

Je les utilise pour décomposer des requêtes complexes, en particulier des jointures et des sous-requêtes complexes. Je trouve que je les utilise de plus en plus comme des «pseudo-vues» pour m'aider à comprendre l'objectif de la requête.

Ma seule plainte à leur sujet est qu'ils ne peuvent pas être réutilisés. Par exemple, je peux avoir un proc stocké avec deux instructions de mise à jour qui pourraient utiliser le même CTE. Mais la «portée» du CTE n'est que la première requête.

Le problème est que les «exemples simples» n'ont probablement pas vraiment besoin de CTE!

Pourtant, très pratique.

n8wrl
la source
D'accord. Pouvez-vous justifier un exemple relativement complexe qui peut m'aider à comprendre ce concept?
imak
28
"Ma seule plainte à leur sujet est qu'ils ne peuvent pas être réutilisés" - un CTE que vous souhaitez réutiliser devrait être considéré comme un candidat pour un VIEW:)
quand le
6
@onedaywhen: Compris, mais cela implique une portée mondiale avec laquelle je ne suis pas toujours à l'aise. Parfois, dans le cadre d'un processus, je voudrais définir un CTE et l'utiliser pour des sélections et des mises à jour, ou des sélections de données similaires de différentes tables.
n8wrl
5
Lorsque j'ai besoin du même CTE plus d'une fois, je l'introduis dans une table temporaire puis j'utilise la table temporaire autant que je veux.
Fandango68
43

Je vois deux raisons d'utiliser les cte.

Pour utiliser une valeur calculée dans la clause where. Cela me semble un peu plus propre qu'une table dérivée.

Supposons qu'il existe deux tableaux - Questions et réponses jointes par Questions.ID = Answers.Question_Id (et quiz id)

WITH CTE AS
(
    Select Question_Text,
           (SELECT Count(*) FROM Answers A WHERE A.Question_ID = Q.ID) AS Number_Of_Answers
    FROM Questions Q
)
SELECT * FROM CTE
WHERE Number_Of_Answers > 0

Voici un autre exemple où je veux obtenir une liste de questions et réponses. Je veux que les réponses soient regroupées avec les questions dans les résultats.

WITH cte AS
(
    SELECT [Quiz_ID] 
      ,[ID] AS Question_Id
      ,null AS Answer_Id
          ,[Question_Text]
          ,null AS Answer
          ,1 AS Is_Question
    FROM [Questions]

    UNION ALL

    SELECT Q.[Quiz_ID]
      ,[Question_ID]
      ,A.[ID] AS  Answer_Id
      ,Q.Question_Text
          ,[Answer]
          ,0 AS Is_Question
        FROM [Answers] A INNER JOIN [Questions] Q ON Q.Quiz_ID = A.Quiz_ID AND Q.Id = A.Question_Id
)
SELECT 
    Quiz_Id,
    Question_Id,
    Is_Question,
    (CASE WHEN Answer IS NULL THEN Question_Text ELSE Answer END) as Name
FROM cte    
GROUP BY Quiz_Id, Question_Id, Answer_id, Question_Text, Answer, Is_Question 
order by Quiz_Id, Question_Id, Is_Question Desc, Name
BrianK
la source
10
Votre premier exemple ne peut-il pas être simplifié pour utiliser simplement une requête imbriquée au lieu du CTE?
Sam
2
Les deux exemples pourraient l'être.
Manachi
3
Vous auriez dû ajouter le premier sans le CTE, puis il est immédiatement évident pourquoi ce dernier est utile.
Ufos
HAVINGest une autre façon de faire un filtre à un stade avancé qui peut être similaire à l'utilisation d'un sous-SELECT
William Entriken
21

L'un des scénarios que j'ai trouvé utile d'utiliser CTE est lorsque vous souhaitez obtenir des lignes de données DISTINCT basées sur une ou plusieurs colonnes mais renvoyer toutes les colonnes de la table. Avec une requête standard, vous devrez peut-être d'abord vider les valeurs distinctes dans une table temporaire, puis essayer de les joindre à la table d'origine pour récupérer le reste des colonnes ou vous pouvez écrire une requête de partition extrêmement complexe qui peut renvoyer les résultats dans une exécution mais, selon toute probabilité, elle sera illisible et entraînera un problème de performances.

Mais en utilisant CTE (comme l'a répondu Tim Schmelter sur Sélectionner la première instance d'un enregistrement )

WITH CTE AS(
    SELECT myTable.*
    , RN = ROW_NUMBER()OVER(PARTITION BY patientID ORDER BY ID)
    FROM myTable 
)
SELECT * FROM CTE
WHERE RN = 1

Comme vous pouvez le voir, c'est beaucoup plus facile à lire et à entretenir. Et par rapport à d'autres requêtes, les performances sont bien meilleures.

TheDanMan
la source
16

Il est peut-être plus judicieux de penser à un CTE comme substitut à une vue utilisée pour une seule requête. Mais ne nécessite pas la surcharge, les métadonnées ou la persistance d'une vue formelle. Très utile lorsque vous devez:

  • Créez une requête récursive.
  • Utilisez l'ensemble de résultats du CTE plus d'une fois dans votre requête.
  • Améliorez la clarté de votre requête en réduisant les gros morceaux de sous-requêtes identiques.
  • Activer le regroupement par une colonne dérivée du jeu de résultats du CTE

Voici un exemple de copier-coller pour jouer avec:

WITH [cte_example] AS (
SELECT 1 AS [myNum], 'a num' as [label]
UNION ALL
SELECT [myNum]+1,[label]
FROM [cte_example]
WHERE [myNum] <=  10
)
SELECT * FROM [cte_example]
UNION
SELECT SUM([myNum]), 'sum_all' FROM [cte_example]
UNION
SELECT SUM([myNum]), 'sum_odd' FROM [cte_example] WHERE [myNum] % 2 = 1
UNION
SELECT SUM([myNum]), 'sum_even' FROM [cte_example] WHERE [myNum] % 2 = 0;

Prendre plaisir

Vic
la source
7

Aujourd'hui, nous allons en apprendre davantage sur l'expression de table commune qui est une nouvelle fonctionnalité introduite dans SQL Server 2005 et également disponible dans les versions ultérieures.

Expression de table commune: - L'expression de table commune peut être définie comme un jeu de résultats temporaire ou en d'autres termes, c'est un substitut des vues dans SQL Server. L'expression de table commune n'est valide que dans le lot d'instructions où elle a été définie et ne peut pas être utilisée dans d'autres sessions.

Syntaxe de déclaration de CTE (expression de table commune): -

with [Name of CTE]
as
(
Body of common table expression
)

Prenons un exemple: -

CREATE TABLE Employee([EID] [int] IDENTITY(10,5) NOT NULL,[Name] [varchar](50) NULL)

insert into Employee(Name) values('Neeraj')
insert into Employee(Name) values('dheeraj')
insert into Employee(Name) values('shayam')
insert into Employee(Name) values('vikas')
insert into Employee(Name) values('raj')

CREATE TABLE DEPT(EID INT,DEPTNAME VARCHAR(100))
insert into dept values(10,'IT')
insert into dept values(15,'Finance')
insert into dept values(20,'Admin')
insert into dept values(25,'HR')
insert into dept values(10,'Payroll')

J'ai créé deux tables employé et département et inséré 5 lignes dans chaque table. Maintenant, je voudrais rejoindre ces tables et créer un jeu de résultats temporaire pour l'utiliser davantage.

With CTE_Example(EID,Name,DeptName)
as
(
select Employee.EID,Name,DeptName from Employee 
inner join DEPT on Employee.EID =DEPT.EID
)
select * from CTE_Example

Permet de prendre chaque ligne de l'énoncé un par un et de comprendre.

Pour définir CTE, nous écrivons la clause "with", puis nous donnons un nom à l'expression de table, ici j'ai donné le nom "CTE_Example"

Ensuite, nous écrivons "As" et mettons notre code entre deux crochets (---), nous pouvons joindre plusieurs tables entre crochets.

Dans la dernière ligne, j'ai utilisé "Select * from CTE_Example", nous faisons référence à l'expression de la table Common dans la dernière ligne de code, nous pouvons donc dire que c'est comme une vue, où nous définissons et utilisons la vue en une seule batch et CTE n'est pas stocké dans la base de données en tant qu'objet permanent. Mais il se comporte comme une vue. nous pouvons effectuer une instruction de suppression et de mise à jour sur CTE et cela aura un impact direct sur la table référencée qui est utilisée dans CTE. Prenons un exemple pour comprendre ce fait.

With CTE_Example(EID,DeptName)
as
(
select EID,DeptName from DEPT 
)
delete from CTE_Example where EID=10 and DeptName ='Payroll'

Dans l'instruction ci-dessus, nous supprimons une ligne de CTE_Example et cela supprimera les données de la table référencée "DEPT" qui est utilisée dans le CTE.

Neeraj Kumar Yadav
la source
Je ne comprends toujours pas le point. Quelle est la différence entre cela et la suppression de DEPT avec exactement la même condition? Cela ne semble rien faciliter.
Holger Jakobs
Veuillez me corriger si je me trompe, mais le plan d'exécution peut être différent, et je pense que c'est le point de Neeraj, qu'il existe de nombreuses façons d'atteindre le même objectif, mais certains auront des avantages par rapport à d'autres en fonction de la situation. Par exemple, il peut être plus facile de lire un CTE sur une instruction DELETE FROM dans certaines circonstances, l'inverse peut également être vrai dans d'autres. Les performances peuvent s'améliorer ou s'aggraver. etc.
WonderWorker
7

Il est très utile lorsque vous souhaitez effectuer une "mise à jour commandée".

MS SQL ne vous permet pas d'utiliser ORDER BY avec UPDATE, mais avec l'aide de CTE, vous pouvez le faire de cette façon:

WITH cte AS
(
    SELECT TOP(5000) message_compressed, message, exception_compressed, exception
    FROM logs
    WHERE Id >= 5519694 
    ORDER BY Id
)
UPDATE  cte
SET     message_compressed = COMPRESS(message), exception_compressed = COMPRESS(exception)

Regardez ici pour plus d'informations: Comment mettre à jour et commander en utilisant ms sql

bside
la source
0

Un point pas encore souligné, c'est la vitesse . Je sais que c'est une vieille question à réponse, mais je pense que cela mérite un commentaire / réponse directe:

Ils sembleraient redondants car on peut faire de même avec les tables dérivées

Lorsque j'ai utilisé CTE la toute première fois, j'ai été absolument stupéfait par sa vitesse. C'était un cas comme dans un manuel, très adapté au CTE, mais dans toutes les situations où j'ai jamais utilisé le CTE, il y avait un gain de vitesse significatif. Ma première requête était complexe avec des tables dérivées, prenant de longues minutes à exécuter. Avec CTE, cela a pris des fractions de secondes et m'a choqué, c'est même possible.

Oak_3260548
la source
-4
 ;with cte as
  (
  Select Department, Max(salary) as MaxSalary
  from test
  group by department
  )  
  select t.* from test t join cte c on c.department=t.department 
  where t.salary=c.MaxSalary;

essaye ça

Sudhir Panda
la source