Wann wird der Common Table Expression (CTE) verwendet?


230

Ich habe angefangen, über Common Table Expression zu lesen, und kann mir keinen Anwendungsfall vorstellen, in dem ich sie verwenden müsste. Sie scheinen redundant zu sein, da dies auch mit abgeleiteten Tabellen möglich ist. Fehlt mir etwas oder verstehe ich es nicht gut? Kann mir jemand ein einfaches Beispiel für Einschränkungen bei regelmäßigen Auswahl-, abgeleiteten oder temporären Tabellenabfragen geben, um den Fall von CTE zu erläutern? Alle einfachen Beispiele wäre sehr dankbar.

Antworten:


197

Ein Beispiel: Wenn Sie denselben Datensatz mehrmals referenzieren / verbinden müssen, können Sie dies tun, indem Sie einen CTE definieren. Daher kann es sich um eine Form der Wiederverwendung von Code handeln.

Ein Beispiel für die Selbstreferenzierung ist die Rekursion: Rekursive Abfragen mit CTE

Für aufregende Microsoft-Definitionen aus Books Online:

Ein CTE kann verwendet werden, um:

  • Erstellen Sie eine rekursive Abfrage. Weitere Informationen finden Sie unter Rekursive Abfragen mit allgemeinen Tabellenausdrücken.

  • Eine Ansicht ersetzen, wenn die allgemeine Verwendung einer Ansicht nicht erforderlich ist; Das heißt, Sie müssen die Definition nicht in Metadaten speichern.

  • Aktivieren Sie die Gruppierung nach einer Spalte, die von einer skalaren Unterauswahl abgeleitet ist, oder nach einer Funktion, die entweder nicht deterministisch ist oder über externen Zugriff verfügt.

  • Verweisen Sie mehrmals auf die resultierende Tabelle in derselben Anweisung.


7
Ja. Sie können einer abgeleiteten Tabelle nicht selbst beitreten. Es lohnt sich darauf hinzuweisen, dass ein Selbstbeitritt zu einem CTE Ihnen dennoch zwei separate Aufrufe davon hinterlässt.
Martin Smith

@ Martin - ich bin überrascht. Können Sie diese Aussage stützen?
RichardTheKiwi


4
@cyberkiwi - Welches Stück? Dass ein Self Join zu 2 verschiedenen Anrufungen führt? Siehe das Beispiel in dieser Antwort stackoverflow.com/questions/3362043/…
Martin Smith

4
Interessante Tatsache über CTE. Ich habe mich immer gefragt, warum sich NEWID () im CTE ändert, wenn auf den CTE mehr als einmal verwiesen wird. 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

Ich verwende sie, um komplexe Abfragen aufzuteilen, insbesondere komplexe Verknüpfungen und Unterabfragen. Ich stelle fest, dass ich sie immer häufiger als "Pseudo-Ansichten" verwende, um mich mit der Absicht der Abfrage vertraut zu machen.

Meine einzige Beschwerde über sie ist, dass sie nicht wiederverwendet werden können. Zum Beispiel kann ich einen gespeicherten Prozess mit zwei Aktualisierungsanweisungen haben, die denselben CTE verwenden könnten. Der 'Umfang' des CTE ist jedoch nur die erste Abfrage.

Das Problem ist, dass "einfache Beispiele" wahrscheinlich keine CTEs benötigen!

Trotzdem sehr praktisch.


OK. Können Sie ein relativ komplexes Beispiel nennen, das mir dabei helfen kann, dieses Konzept zu verstehen?
Imak

28
"Meine einzige Beschwerde über sie ist, dass sie nicht wiederverwendet werden können" - ein CTE, den Sie wiederverwenden möchten, sollte als Kandidat für eine VIEW:) angesehen werden
am

6
@onedaywhen: Verstanden, aber das impliziert einen globalen Bereich, mit dem ich nicht immer zufrieden bin. Manchmal möchte ich im Rahmen eines Prozesses einen CTE definieren und ihn für Auswahlen und Aktualisierungen oder für die Auswahl ähnlicher Daten aus verschiedenen Tabellen verwenden.
n8wrl

5
Wenn ich denselben CTE mehrmals benötige, füge ich ihn in eine temporäre Tabelle ein und verwende die temporäre Tabelle dann so oft ich möchte.
Fandango68

43

Es gibt zwei Gründe, warum ich cte's benutze.

So verwenden Sie einen berechneten Wert in der where-Klausel Dies scheint mir ein wenig sauberer als eine abgeleitete Tabelle.

Angenommen, es gibt zwei Tabellen - Fragen und Antworten, die durch Questions.ID = Answers.Question_Id (und Quiz-ID) miteinander verbunden sind.

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

Hier ist ein weiteres Beispiel, in dem ich eine Liste mit Fragen und Antworten erhalten möchte. Ich möchte, dass die Antworten mit den Fragen in den Ergebnissen gruppiert werden.

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
Kann Ihr erstes Beispiel nicht vereinfacht werden, indem nur eine verschachtelte Abfrage anstelle des CTE verwendet wird?
Sam

2
Beide Beispiele könnten sein.
Manachi

3
Sie sollten den ersten ohne CTE hinzugefügt haben, dann ist sofort ersichtlich, warum letzterer nützlich ist.
Ufos

HAVINGist eine andere Möglichkeit, einen Filter im Spätstadium zu erstellen, der der Verwendung eines SELECT
Subfilters

21

Eines der Szenarien, die ich für die Verwendung von CTE nützlich fand, ist, wenn Sie DISTINCT-Datenzeilen basierend auf einer oder mehreren Spalten abrufen möchten, aber alle Spalten in der Tabelle zurückgeben möchten. Bei einer Standardabfrage müssen Sie möglicherweise zuerst die unterschiedlichen Werte in eine temporäre Tabelle kopieren und dann versuchen, sie wieder mit der ursprünglichen Tabelle zu verknüpfen, um den Rest der Spalten abzurufen, oder Sie schreiben eine äußerst komplexe Partitionsabfrage, in der die Ergebnisse zurückgegeben werden können Ein Lauf, aber höchstwahrscheinlich ist er nicht lesbar und führt zu Leistungsproblemen.

Aber mit CTE (wie von Tim Schmelter unter Wählen Sie die erste Instanz eines Datensatzes aus )

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

Wie Sie sehen können, ist dies viel einfacher zu lesen und zu warten. Und im Vergleich zu anderen Abfragen ist die Leistung viel besser.


16

Vielleicht ist es sinnvoller, sich einen CTE als Ersatz für eine Ansicht vorzustellen, die für eine einzelne Abfrage verwendet wird. Erfordert jedoch nicht den Overhead, die Metadaten oder die Persistenz einer formalen Ansicht. Sehr nützlich, wenn Sie:

  • Erstellen Sie eine rekursive Abfrage.
  • Verwenden Sie die Ergebnismenge des CTE mehrmals in Ihrer Abfrage.
  • Fördern Sie die Klarheit Ihrer Abfrage, indem Sie große Teile identischer Unterabfragen reduzieren.
  • Aktivieren Sie die Gruppierung nach einer Spalte, die in der Ergebnismenge des CTE abgeleitet ist

Hier ist ein Beispiel zum Ausschneiden und Einfügen zum Spielen:

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;

Genießen


7

Heute lernen wir den allgemeinen Tabellenausdruck kennen, eine neue Funktion, die in SQL Server 2005 eingeführt wurde und auch in späteren Versionen verfügbar ist.

Allgemeiner Tabellenausdruck: - Allgemeiner Tabellenausdruck kann als temporäre Ergebnismenge definiert werden oder mit anderen Worten als Ersatz für Ansichten in SQL Server. Der allgemeine Tabellenausdruck ist nur in dem Anweisungsstapel gültig, in dem er definiert wurde, und kann nicht in anderen Sitzungen verwendet werden.

Syntax zum Deklarieren von CTE (Common Table Expression): -

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

Nehmen wir ein Beispiel: -

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

Ich habe zwei Tabellen Mitarbeiter und Abteilung erstellt und 5 Zeilen in jede Tabelle eingefügt. Jetzt möchte ich diese Tabellen verbinden und eine temporäre Ergebnismenge erstellen, um sie weiter zu verwenden.

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

Nehmen wir jede Zeile der Aussage einzeln und verstehen.

Um CTE zu definieren, schreiben wir die Klausel "with", dann geben wir dem Tabellenausdruck einen Namen, hier habe ich den Namen "CTE_Example" angegeben.

Dann schreiben wir "As" und schließen unseren Code in zwei Klammern (---) ein. Wir können mehrere Tabellen in den eingeschlossenen Klammern verbinden.

In der letzten Zeile habe ich "Select * from CTE_Example" verwendet. Wir verweisen auf den Common-Tabellenausdruck in der letzten Codezeile. Wir können also sagen, dass es sich um eine Ansicht handelt, in der wir die Ansicht in einer einzigen definieren und verwenden Batch und CTE werden nicht als permanentes Objekt in der Datenbank gespeichert. Aber es verhält sich wie eine Ansicht. Wir können Lösch- und Aktualisierungsanweisungen für CTE ausführen. Dies hat direkte Auswirkungen auf die referenzierte Tabelle, die in CTE verwendet wird. Nehmen wir ein Beispiel, um diese Tatsache zu verstehen.

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

In der obigen Anweisung löschen wir eine Zeile aus CTE_Example und sie löscht die Daten aus der referenzierten Tabelle "DEPT", die im CTE verwendet wird.


Ich verstehe den Punkt immer noch nicht. Was ist der Unterschied zwischen diesem und dem Löschen aus DEPT mit genau der gleichen Bedingung? Es scheint nichts einfacher zu machen.
Holger Jakobs

Bitte korrigieren Sie mich, wenn ich falsch liege, aber der Ausführungsplan kann unterschiedlich sein, und ich denke, das ist Neerajs Argument, dass es viele Möglichkeiten gibt, dasselbe Ziel zu erreichen, aber einige haben je nach Situation Vorteile gegenüber anderen. Beispielsweise kann es unter bestimmten Umständen einfacher sein, einen CTE über eine DELETE FROM-Anweisung zu lesen, unter anderen kann auch das Gegenteil der Fall sein. Die Leistung kann sich verbessern oder verschlechtern. usw.
WonderWorker

7

Dies ist sehr nützlich, wenn Sie ein "bestelltes Update" durchführen möchten.

In MS SQL können Sie ORDER BY nicht mit UPDATE verwenden, aber mit Hilfe von CTE können Sie dies folgendermaßen tun:

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)

Weitere Informationen finden Sie hier: So aktualisieren und bestellen Sie mithilfe von ms sql


0

Ein Punkt, auf den noch nicht hingewiesen wurde, ist die Geschwindigkeit . Ich weiß, dass es eine alte beantwortete Frage ist, aber ich denke, dass dies einen direkten Kommentar / eine direkte Antwort verdient:

Sie scheinen redundant zu sein, da dies auch mit abgeleiteten Tabellen möglich ist

Als ich CTE zum ersten Mal benutzte, war ich absolut verblüfft über seine Geschwindigkeit. Es war ein Fall wie aus einem Lehrbuch, der sehr gut für CTE geeignet war, aber in allen Fällen , in denen ich jemals CTE verwendet habe, gab es einen signifikanten Geschwindigkeitsgewinn. Meine erste Abfrage war komplex mit abgeleiteten Tabellen und dauerte lange Minuten. Mit CTE hat es Sekundenbruchteile gedauert und mich schockiert, dass es überhaupt möglich ist.


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

Versuche dies

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.