Quando utilizzare Common Table Expression (CTE)


230

Ho iniziato a leggere su Common Table Expression e non riesco a pensare a un caso d'uso in cui avrei bisogno di usarli. Sembrerebbero ridondanti poiché lo stesso si può fare con le tabelle derivate. C'è qualcosa che mi manca o che non capisco bene? Qualcuno può darmi un semplice esempio di limitazioni con regolari query di selezione, derivate o temporanee per rendere il caso di CTE? Qualsiasi semplice esempio sarebbe molto apprezzato.

Risposte:


197

Un esempio, se è necessario fare riferimento / unire lo stesso set di dati più volte, è possibile farlo definendo un CTE. Pertanto, può essere una forma di riutilizzo del codice.

Un esempio di autoreferenziazione è la ricorsione: query ricorsive tramite CTE

Per le entusiasmanti definizioni Microsoft tratte dalla documentazione online:

Un CTE può essere utilizzato per:

  • Crea una query ricorsiva. Per ulteriori informazioni, vedere Query ricorsive che utilizzano espressioni di tabella comuni.

  • Sostituire una vista quando non è richiesto l'uso generale di una vista; cioè, non è necessario archiviare la definizione in metadati.

  • Abilitare il raggruppamento per colonna derivata da una sottoselezione scalare o una funzione non deterministica o con accesso esterno.

  • Fare riferimento alla tabella risultante più volte nella stessa istruzione.


7
Sì. Non è possibile unirsi autonomamente a una tabella derivata. Vale la pena sottolineare che un self-join su un CTE ti lascerà comunque con 2 invocazioni separate di esso.
Martin Smith,

@Martin - Sono sorpreso. Puoi sostenere questa affermazione?
RichardTheKiwi,

@John Grazie, sto trovando 4guysfromrolla.com/webtech/071906-1.shtml anche abbastanza utile
imak

4
@cyberkiwi - Quale bit? Che un self-join porterà a 2 invocazioni diverse? Vedi l'esempio in questa risposta stackoverflow.com/questions/3362043/…
Martin Smith,

4
Fatto interessante su CTE. Mi sono sempre chiesto perché NEWID () nel CTE cambi quando si fa riferimento al CTE più di una volta. 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 il

50

Li uso per interrompere query complesse, in particolare join complessi e query secondarie. Trovo che li sto usando sempre più come "pseudo-viste" per aiutarmi a capire meglio l'intento della query.

La mia unica lamentela è che non possono essere riutilizzati. Ad esempio, potrei avere un proc memorizzato con due istruzioni di aggiornamento che potrebbero usare lo stesso CTE. Ma l'ambito del CTE è solo la prima query.

Il problema è che "semplici esempi" probabilmente non hanno davvero bisogno dei CTE!

Comunque, molto utile.


ok. Puoi fare un caso con qualche esempio relativamente complesso che può aiutare la mia testa attorno a questo concetto?
imak,

28
"La mia unica lamentela è che non possono essere riutilizzati" - un CTE che si desidera riutilizzare dovrebbe essere considerato un candidato per un VIEW:)
onedayquando il

6
@onedaywhen: Capito, ma questo implica un ambito globale con cui non sono sempre a mio agio. A volte nell'ambito di un proc vorrei definire un CTE e usarlo per selezioni e aggiornamenti, o selezioni di dati simili da tabelle diverse.
n8wrl,

5
Quando ho bisogno dello stesso CTE più di una volta, lo inserisco in una tabella temporanea e quindi uso la tabella temporanea quanto voglio.
Fandango68,

43

Ci sono due ragioni per cui vedo usare cte's.

Per utilizzare un valore calcolato nella clausola where. Questo mi sembra un po 'più pulito di una tabella derivata.

Supponiamo che ci siano due tabelle: domande e risposte unite da Questions.ID = Answers.Question_Id (e ID quiz)

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

Ecco un altro esempio in cui desidero ottenere un elenco di domande e risposte. Voglio che le risposte siano raggruppate con le domande nei risultati.

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

10
Il tuo primo esempio non può essere semplificato per utilizzare solo una query nidificata anziché CTE?
Sam,

2
Entrambi gli esempi potrebbero essere.
Manachi,

3
Avresti dovuto aggiungere il primo senza CTE, quindi è immediatamente chiaro perché è utile quest'ultimo.
Ufo

HAVINGè un altro modo di fare un filtro in fase avanzata che può essere simile all'utilizzo di un subSELECT
William Entriken il

21

Uno degli scenari che ho trovato utile per utilizzare CTE è quando si desidera ottenere righe di dati DISTINCT basate su una o più colonne ma restituire tutte le colonne nella tabella. Con una query standard potresti prima dover scaricare i valori distinti in una tabella temporanea e quindi provare a ricollegarli alla tabella originale per recuperare il resto delle colonne oppure potresti scrivere una query di partizione estremamente complessa che può restituire i risultati in una corsa, ma molto probabilmente, sarà illeggibile e causerà problemi di prestazioni.

Ma usando CTE (come ha risposto Tim Schmelter su Seleziona la prima istanza di un record )

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

Come puoi vedere, questo è molto più facile da leggere e mantenere. E rispetto ad altre query, è molto meglio in termini di prestazioni.


16

Forse è più significativo pensare a un CTE come sostituto di una vista utilizzata per una singola query. Ma non richiede l'overhead, i metadati o la persistenza di una visione formale. Molto utile quando è necessario:

  • Crea una query ricorsiva.
  • Utilizzare il set di risultati del CTE più di una volta nella query.
  • Promuovi la chiarezza nella tua query riducendo grossi blocchi di sottoquery identiche.
  • Abilita il raggruppamento per colonna derivata nel set di risultati del CTE

Ecco un esempio di copia e incolla con cui giocare:

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;

Godere


7

Oggi impareremo l'espressione di tabella comune che è una nuova funzionalità che è stata introdotta in SQL Server 2005 e disponibile anche nelle versioni successive.

Espressione di tabella comune: - L'espressione di tabella comune può essere definita come un set di risultati temporaneo o in altre parole è un sostituto delle visualizzazioni in SQL Server. L'espressione di tabella comune è valida solo nel batch di istruzioni in cui è stata definita e non può essere utilizzata in altre sessioni.

Sintassi di dichiarazione CTE (espressione di tabella comune): -

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

Facciamo un esempio: -

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')

Ho creato due tabelle impiegate e Dept e ho inserito 5 righe in ogni tabella. Ora vorrei unirmi a queste tabelle e creare un set di risultati temporaneo per utilizzarlo ulteriormente.

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

Consente di prendere ciascuna riga dell'istruzione una per una e capire.

Per definire CTE scriviamo la clausola "with", quindi diamo un nome all'espressione della tabella, qui ho dato il nome come "CTE_Example"

Quindi scriviamo "As" e racchiudiamo il nostro codice tra due parentesi (---), possiamo unire più tabelle tra parentesi quadre.

Nell'ultima riga, ho usato "Seleziona * da CTE_Example", ci riferiamo all'espressione di tabella comune nell'ultima riga di codice, quindi possiamo dire che è come una vista, in cui stiamo definendo e usando la vista in un singolo batch e CTE non sono archiviati nel database come oggetto permanente. Ma si comporta come una vista. possiamo eseguire istruzioni di eliminazione e aggiornamento su CTE e ciò avrà un impatto diretto sulla tabella di riferimento che vengono utilizzate in CTE. Facciamo un esempio per capire questo fatto.

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

Nell'istruzione precedente stiamo eliminando una riga da CTE_Example e eliminerà i dati dalla tabella di riferimento "DEPT" che viene utilizzata nel CTE.


Ancora non capisco il punto. Qual è la differenza tra questa e la semplice eliminazione da DEPT con esattamente le stesse condizioni? Non sembra facilitare nulla.
Holger Jakobs

Per favore, correggimi se sbaglio, ma il piano di esecuzione potrebbe essere diverso e penso che questo sia il punto di Neeraj, che ci sono molti modi per raggiungere lo stesso obiettivo, ma alcuni avranno vantaggi rispetto ad altri a seconda della situazione. Ad esempio, in alcune circostanze potrebbe essere più semplice leggere un CTE su un'istruzione DELETE FROM, mentre in altri potrebbe essere vero il contrario. Le prestazioni potrebbero migliorare o peggiorare. ecc.
WonderWorker,

7

È molto utile quando si desidera eseguire un "aggiornamento ordinato".

MS SQL non ti consente di utilizzare ORDER BY con UPDATE, ma con l'aiuto di CTE puoi farlo in questo modo:

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)

Guarda qui per maggiori informazioni: Come aggiornare e ordinare usando ms sql


0

Un punto non ancora sottolineato, è la velocità . So che è una vecchia domanda con risposta, ma penso che questo meriti un commento / risposta diretta:

Sembrerebbero ridondanti poiché lo stesso si può fare con le tabelle derivate

Quando ho usato CTE per la prima volta, sono rimasto assolutamente sbalordito dalla sua velocità. Era un caso come da un libro di testo, molto adatto per CTE, ma in tutte le occasioni in cui ho mai usato CTE, c'era un significativo aumento di velocità. La mia prima query è stata complessa con le tabelle derivate, impiegando lunghi minuti per l'esecuzione. Con CTE ci sono voluti pochi secondi e mi ha lasciato scioccato, che è persino possibile.


-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;

prova questo

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.