Database
 sql >> Datenbank >  >> RDS >> Database

Alles, was Sie über SQL CTE wissen müssen, an einem Ort

Das erste Mal, dass Karl von SQL Server CTE hörte, war, als er nach etwas suchte, um seinen SQL-Code optisch ansprechender zu gestalten. Es ist eine Art Kopfzerbrechen, wenn man es ansieht. Anton, sein besorgter Kollege, fragte ihn nach CTE. Karl dachte, Anton beziehe sich auf seine Kopfschmerzen. Vielleicht hat er alles falsch verstanden, also antwortete er:„Natürlich nicht.“ Das Lustige ist, dass er sich auf die chronisch traumatische Enzephalopathie bezog, auch eine CTE – eine neurodegenerative Erkrankung, die durch wiederholte Kopfverletzungen verursacht wird. Aber aufgrund von Karls Antwort wusste Anton mit Sicherheit, dass sein Kollege keine Ahnung hatte, was er sagte.

Was für eine verrückte Art, CTEs einzuführen! Also, bevor Sie in dasselbe Boot steigen, lassen Sie uns klären, was ist SQL CTE oder Common Table Expressions in der SQL-Welt?

Die Grundlagen können Sie hier nachlesen. In der Zwischenzeit erfahren wir etwas mehr darüber, was in dieser ungewöhnlichen Geschichte passiert ist.

4 Grundlegendes über CTE in SQL Server

„Ein SQL-CTE hat einen Namen“

Anton begann mit der Idee, dass SQL-CTEs vorübergehend benannte Ergebnismengen sind. Da es sich um vorübergehende Mittel handelt, ist CTE in seinem Umfang begrenzt.

„Also, es ist wie eine Unterabfrage?“ fragte Karl.

„In gewisser Weise ja. Aber Sie können keine Unterabfrage benennen“, sagte Anton. „Ein CTE hat einen Namen, ähnlich wie eine Tabelle mit einem Namen. Anstelle von CREATE verwenden Sie jedoch WITH, um es zu erstellen. Dann schrieb er die Syntax auf Papier:

WITH <cte_name>(<column list>)
AS
(
<inner query defining the CTE>
)
<outer query against CTE>

„CTE ist weg, wenn das SELECT abgeschlossen ist“

Anton fuhr fort, indem er den Umfang von SQL CTE erläuterte.

„Eine temporäre Tabelle kann innerhalb des Geltungsbereichs der Prozedur oder global existieren. Aber CTE ist weg, wenn das SELECT fertig ist“, sagte er mit einem Reim. „Das Gleiche gilt, wenn Sie es für INSERT, UPDATE oder DELETE verwenden“, fuhr er fort.

"Sie können es nicht wiederverwenden"

„Im Gegensatz zu einer Ansicht oder einer temporären Tabelle können Sie SQL CTE nicht wiederverwenden. Der Name ist vorhanden, sodass Sie in der inneren und äußeren Abfrage darauf verweisen können. Aber das ist alles“, sagte Anton.

„Was ist also das Besondere an SQL-CTEs?“ fragte Karl.

"Sie können Ihren Code besser lesbar machen"

"Die große Sache?" Anton erwiderte die Frage. „Es ist so, dass Sie Ihren Code leicht lesbar machen können. Ist es nicht das, wonach Sie suchen?“

„Das stimmt“, gab Karl zu.

Also, was ist der nächste logische Schritt für Karl?

Zusätzliches über CTE in SQL

Am nächsten Tag setzte Karl seine Suche nach SQL CTE fort. Abgesehen von dem oben Gesagten hat er Folgendes gefunden:

  • SQL CTE kann nicht-rekursiv oder rekursiv sein.
  • Nicht nur SQL Server, sondern auch MySQL und Oracle unterstützen die Idee. Es ist tatsächlich ein Teil der SQL-99-Spezifikationen.
  • Obwohl es zur Vereinfachung des SQL-Codes verwendet wird, verbessert es nicht die Leistung.
  • Und es ersetzt auch keine Unterabfragen und temporären Tabellen. Jeder hat seinen Platz und seine Verwendung.

Kurz gesagt, es ist eine andere Art, eine Suchanfrage auszudrücken .

Aber Karl war hungrig nach mehr Details, also suchte er weiter danach, was funktionieren würde, was nicht und wie es im Vergleich zu Unterabfragen und temporären Tabellen funktionieren würde.

Was funktioniert in SQL Server CTE?

Um mehr über CTE zu erfahren, listete Karl unten auf, was SQL Server akzeptieren würde. Werfen Sie auch einen Blick auf seine Studien.

Inline- oder externe Spaltenaliase zuweisen

SQL-CTEs unterstützen zwei Formen der Zuweisung von Spaltenaliasen. Das erste ist das Inline-Formular, wie im Beispiel unten:

-- Use an Inline column alias

USE AdventureWorks
GO;

WITH Sales_CTE
AS  
(  
	SELECT SalesPersonID, COUNT(*) AS NumberOfOrders
	FROM Sales.SalesOrderHeader  
	WHERE SalesPersonID IS NOT NULL  
	GROUP BY SalesPersonID  
)
SELECT
 a.SalesPersonID
,a.NumberOfOrders
FROM Sales_CTE a

Der obige Code verwendet den Spaltenalias innerhalb der CTE-Definition, wenn er innerhalb der SELECT-Anweisung zugewiesen wird. Haben Sie COUNT(*) AS NumberOfOrders bemerkt? ? Das ist das Inline-Formular.

Nun, ein weiteres Beispiel ist die externe Form:

-- Use an external column alias

USE AdventureWorks
GO;

WITH Sales_CTE(SalesPersonID, NumberOfOrders) 
AS  
(  
	SELECT SalesPersonID, COUNT(*)
	FROM Sales.SalesOrderHeader  
	WHERE SalesPersonID IS NOT NULL  
	GROUP BY SalesPersonID  
)
SELECT
 a.SalesPersonID
,a.NumberOfOrders
FROM Sales_CTE a

Die Spalten können auch nach dem Setzen des CTE-Namens in Klammern definiert werden. Beachten Sie den WITH Sales_CTE (SalesPersonID, NumberOfOrders) .

CTE in SQL geht einem SELECT, INSERT, UPDATE oder DELETE voran

Im nächsten Punkt geht es um den Verbrauch des CTE. Das erste und übliche Beispiel ist, wenn es einer SELECT-Anweisung vorangeht.

-- List down all Salespersons with their all-time number of orders
USE AdventureWorks
GO;

WITH Sales_CTE (SalesPersonID, NumberOfOrders)  
AS  
(  
	SELECT SalesPersonID, COUNT(*)  
	FROM Sales.SalesOrderHeader  
	WHERE SalesPersonID IS NOT NULL  
	GROUP BY SalesPersonID  
)
SELECT
 a.SalesPersonID
,CONCAT(P.LastName,', ',P.FirstName,' ',P.MiddleName) AS SalesPerson
,a.NumberOfOrders
FROM Sales_CTE a
INNER JOIN Person.Person p ON a.SalesPersonID = p.BusinessEntityID

Was zeigt dieses Beispiel?

  • Sales_CTE – Name des CTE.
  • (SalesPersonID, NumberOfOrders) – die Definition von CTE-Spalten.
  • SELECT SalesPersonID, COUNT(*) FROM Sales.SalesOrderHeader WHERE SalesPersonID NICHT NULL GROUP BY SalesPersonID – das innere SELECT, das den CTE definiert.
  • SELECT a.SalesPersonID, CONCAT(P.LastName,’, ‘,P.FirstName,’ ‘,P.MiddleName) AS SalesPerson – die äußere Abfrage, die den CTE verbraucht. In diesem Beispiel wird ein SELECT verwendet, um den CTE zu verwenden.
  • VON Sales_CTE a – die Referenz der äußeren Abfrage auf den CTE.

Neben einem SELECT funktioniert es auch mit INSERT, UPDATE und DELETE. Hier ist ein Beispiel für die Verwendung von INSERT:

-- add a 10% increase to Employee 16 after 1 year from the previous increase.
USE AdventureWorks
GO;

WITH LatestEmployeePay
AS
(
    SELECT TOP 1
     eph.BusinessEntityID
    ,eph.RateChangeDate
    ,eph.Rate
    ,eph.PayFrequency
    FROM HumanResources.EmployeePayHistory eph 
    WHERE eph.BusinessEntityID = 16
    ORDER BY eph.RateChangeDate DESC
)
INSERT INTO HumanResources.EmployeePayHistory
SELECT
 BusinessEntityID
,DATEADD(d,365,RateChangeDate)
,(Rate * 0.1) + Rate
,PayFrequency
,GETDATE()
FROM LatestEmployeePay

In der obigen Auflistung ruft der CTE den letzten Lohn für Mitarbeiter 16 ab. Die Ergebnismenge des CTE wird dann verwendet, um einen neuen Datensatz in EmployeePayHistory einzufügen . Karl hielt seine Erkenntnisse elegant fest. Außerdem verwendete er passende Beispiele.

Mehrere CTEs in 1 Abfrage definieren

Stimmt. Karl fand heraus, dass mehrere CTEs in einer Abfrage möglich sind. Hier ist ein Beispiel:

-- Get the present and previous rate of employee 16
USE AdventureWorks
GO;

WITH LatestEmployeePay
AS
(
    SELECT TOP 1
     eph.BusinessEntityID
    ,eph.RateChangeDate
    ,eph.Rate
    FROM HumanResources.EmployeePayHistory eph 
    WHERE eph.BusinessEntityID = 16
    ORDER BY eph.RateChangeDate DESC
),
PreviousEmployeePay AS
(
    SELECT TOP 1
     eph.BusinessEntityID
    ,eph.RateChangeDate
    ,eph.Rate
    FROM HumanResources.EmployeePayHistory eph
    INNER JOIN LatestEmployeePay lep 
      ON eph.BusinessEntityID = lep.BusinessEntityID
    WHERE eph.BusinessEntityID = 16
      AND eph.RateChangeDate < lep.RateChangeDate
    ORDER BY eph.RateChangeDate DESC
)
SELECT
 a.BusinessEntityID
,a.Rate
,a.RateChangeDate
,b.Rate AS PreviousRate
FROM LatestEmployeePay a
INNER JOIN PreviousEmployeePay b 
    ON a.BusinessEntityID = b.BusinessEntityID

Der obige Code verwendet 2 CTEs in einer Abfrage, nämlich LatestEmployeePay und PreviousEmployeePay .

Mehrmals auf einen CTE verweisen

Zum vorherigen Beispiel gehört noch mehr. Beachten Sie auch, dass Sie den ersten CTE mit dem zweiten CTE INNER JOIN können. Schließlich kann die äußere Abfrage beide 2 CTEs verbinden. Das LatestEmployeePay wurde zweimal erwähnt.

Argumente an einen SQL-CTE übergeben

Argumente, wie z. B. Variablen, können zusammen mit einem CTE übergeben werden:

DECLARE @SalesPersonID INT = 275;

WITH Sales_CTE
AS  
(  
	SELECT SalesPersonID, COUNT(*) AS NumberOfOrders
	FROM Sales.SalesOrderHeader 
	WHERE SalesPersonID = @SalesPersonID  
	GROUP BY SalesPersonID  
)  
SELECT SalesPersonID, NumberOfOrders
FROM Sales_CTE

Der obige Code beginnt mit dem Deklarieren und Setzen einer Variablen @SalesPersonID . Der Wert wird dann an den CTE übergeben, um das Ergebnis zu filtern.

In einem CURSOR verwenden

Ein SQL-Cursor kann eine SELECT-Anweisung verwenden und die Ergebnisse durchlaufen. Auch ein SQL CTE kann damit verwendet werden:

DECLARE @SalesPersonID INT
DECLARE @NumberofOrders INT

DECLARE sales_cursor CURSOR FOR
    WITH Sales_CTE (SalesPersonID, NumberOfOrders)  
	AS  
	(  
		SELECT SalesPersonID, COUNT(*)  
		FROM Sales.SalesOrderHeader  
		WHERE SalesPersonID IS NOT NULL  
		GROUP BY SalesPersonID  
	)  
	SELECT salespersonid, numberoforders
	FROM Sales_CTE; 
OPEN sales_cursor
FETCH NEXT FROM sales_cursor INTO @SalesPersonID, @NumberofOrders
WHILE @@FETCH_STATUS = 0  
BEGIN
	PRINT 'SalesPersonID: ' + CAST(@SalesPersonID AS VARCHAR)
	PRINT '# of Orders: ' + CAST(@NumberofOrders AS VARCHAR)
	FETCH NEXT FROM sales_cursor  INTO @SalesPersonID, @NumberofOrders
END
CLOSE sales_cursor
DEALLOCATE sales_cursor;

Verwenden Sie eine temporäre Tabelle in einem rekursiven CTE

Rekursives CTE verwendet ein Ankerelement und ein rekursives Element innerhalb der CTE-Definition. Es hilft, Hierarchien innerhalb einer Tabelle zu erhalten. SQL CTE kann zu diesem Zweck auch eine temporäre Tabelle verwenden. Siehe ein Beispiel unten:

-- Create a Crew table.  
CREATE TABLE #EnterpriseDSeniorOfficers  
(  
CrewID SMALLINT NOT NULL,  
FirstName NVARCHAR(30)  NOT NULL,  
LastName  NVARCHAR(40) NOT NULL,  
CrewRank NVARCHAR(50) NOT NULL,  
HigherRankID INT NULL,  
 CONSTRAINT PK_CrewID PRIMARY KEY CLUSTERED (CrewID ASC)   
);  
-- Populate the table with values.  
INSERT INTO #EnterpriseDSeniorOfficers VALUES   
 (1, N'Jean-Luc', N'Picard', N'Captain',NULL)  
,(2, N'William', N'Riker', N'First Officer',1)  
,(3, N'Data', N'', N'Second Officer',1)  
,(4, N'Worf', N'', N'Chief of Security',1)  
,(5, N'Deanna', N'Troi', N'Ship Counselor',1)  
,(6, N'Beveryly', N'Crusher', N'Chief Medical Officer',1)  
,(7, N'Geordi', N'LaForge', N'Chief Engineer',1);  

WITH DirectReports(HigherRankID, CrewID, Title, CrewLevel) AS   
(  
    SELECT HigherRankID, CrewID, CrewRank, 0 as CrewLevel
    FROM #EnterpriseDSeniorOfficers
    WHERE HigherRankID IS NULL  
    UNION ALL  
    SELECT e.HigherRankID, e.CrewID, e.CrewRank, CrewLevel + 1  
    FROM #EnterpriseDSeniorOfficers AS e  
        INNER JOIN DirectReports AS d  
        ON e.HigherRankID = d.CrewID   
)  
SELECT HigherRankID, CrewID, Title, CrewLevel   
FROM DirectReports  
OPTION (MAXRECURSION 2)
ORDER BY HigherRankID;  

DROP TABLE #EnterpriseDSeniorOfficers

Karl erklärte, indem er diesen CTE sezierte. So geht's.

Das Ankerelement ist die erste SELECT-Anweisung mit dem Crew-Level null (0):

SELECT HigherRankID, CrewID, CrewRank, 0 as CrewLevel
 FROM #EnterpriseDSeniorOfficers
 WHERE HigherRankID IS NULL

Dieses Ankerelement erhält den Wurzelknoten der Hierarchie. Die WHERE-Klausel gibt an, dass die Stammebene (HigherRankID IS NULL ).

Das rekursive Element, das die untergeordneten Knoten erhält, wird unten extrahiert:

SELECT e.HigherRankID, e.CrewID, e.CrewRank, CrewLevel + 1  
FROM #EnterpriseDSeniorOfficers AS e  
INNER JOIN DirectReports AS d  
        ON e.HigherRankID = d.CrewID

Es gibt auch eine OPTION (MAXRECURSION 2) in der äußeren Abfrage verwendet. Rekursive CTEs können problematisch werden, wenn aus der rekursiven Abfrage eine Endlosschleife resultiert. MAXRECURSION 2 vermeidet dieses Durcheinander – es beschränkt die Schleife auf nur 2 Rekursionen.

Damit endet Karls Liste dessen, was funktionieren wird. Allerdings funktioniert nicht alles, woran wir denken. Im nächsten Abschnitt werden Karls Erkenntnisse dazu erörtert.

Was funktioniert nicht in SQL CTE?

Hier haben wir eine Liste von Dingen, die bei der Verwendung von SQL CTE einen Fehler erzeugen.

Kein Semikolon vor dem SQL CTE

Wenn vor dem CTE eine Anweisung steht, muss diese Anweisung mit einem Semikolon abgeschlossen werden. Die WITH-Klausel kann für andere Zwecke wie in einem Tabellenhinweis verwendet werden, daher beseitigt das Semikolon Mehrdeutigkeiten. Die unten angegebene Anweisung verursacht einen Fehler:

DECLARE @SalesPersonID INT

SET @SalesPersonID = 275

WITH Sales_CTE
AS  
(  
	SELECT SalesPersonID, COUNT(*) AS NumberOfOrders
	FROM Sales.SalesOrderHeader 
	WHERE SalesPersonID = @SalesPersonID  
	GROUP BY SalesPersonID  
)  
SELECT SalesPersonID, NumberOfOrders
FROM Sales_CTE

Anfänger, die Anweisungen früher nicht mit einem Semikolon abgeschlossen haben, stoßen auf diesen Fehler:

Unbenannte Spalten

„Sie haben vergessen, einen Spaltenalias anzugeben? Dann ist Ihnen ein weiterer Fehler unterlaufen.“ Karl sagte dies in seinem Artikel und stellte auch einen Beispielcode zur Verfügung, den ich unten teile:

DECLARE @SalesPersonID INT

SET @SalesPersonID = 275;

WITH Sales_CTE
AS  
(  
	SELECT SalesPersonID, COUNT(*)
	FROM Sales.SalesOrderHeader 
	WHERE SalesPersonID = @SalesPersonID  
	GROUP BY SalesPersonID  
)  
SELECT SalesPersonID, NumberOfOrders
FROM Sales_CTE

Sehen Sie sich dann die Fehlermeldung an:

Doppelte Spaltennamen

Ein weiterer Fehler im Zusammenhang mit Nr. 2 oben ist die Verwendung desselben Spaltennamens innerhalb des CTE. Mit einer normalen SELECT-Anweisung kommen Sie damit durch, aber nicht mit einem CTE. Karl hatte noch ein Beispiel:

WITH Sales_CTE
AS  
(  
	SELECT SalesPersonID AS col1, COUNT(*) AS col1
	FROM Sales.SalesOrderHeader 
	GROUP BY SalesPersonID  
)  
SELECT *
FROM Sales_CTE

ORDER BY-Klausel ohne TOP oder OFFSET-FETCH

Standard-SQL erlaubt kein ORDER BY in Tabellenausdrücken, wenn wir es zum Sortieren der Ergebnismengen verwenden. Wenn jedoch TOP oder OFFSET-FETCH verwendet werden, wird ORDER BY zu einer Filterhilfe.

Hier ist Karls Beispiel mit einem ORDER BY:

WITH LatestEmployeePay
AS
(
    SELECT
     eph.BusinessEntityID
    ,eph.RateChangeDate
    ,eph.Rate
    ,eph.PayFrequency
    FROM HumanResources.EmployeePayHistory eph 
    WHERE eph.BusinessEntityID = 16
    ORDER BY eph.RateChangeDate DESC
)
INSERT INTO HumanResources.EmployeePayHistory
SELECT
 BusinessEntityID
,DATEADD(d,365,RateChangeDate)
,(Rate * 0.1) + Rate
,PayFrequency
,GETDATE()
FROM LatestEmployeePay

Beachten Sie, dass es dasselbe Beispiel ist, das wir zuvor hatten, aber dieses Mal ist TOP nicht angegeben. Überprüfen Sie den Fehler:

Die Anzahl der Spalten stimmt nicht mit der Definition der Spaltenliste überein

Karl verwendete das gleiche rekursive CTE-Beispiel, aber er entfernte eine Spalte im Ankerelement:

WITH DirectReports(HigherRankID, CrewID, Title, CrewLevel) AS   
(  
    SELECT HigherRankID, CrewID
    FROM #EnterpriseDSeniorOfficers
    WHERE HigherRankID IS NULL  
    UNION ALL  
    SELECT e.HigherRankID, e.CrewID, e.CrewRank, CrewLevel + 1  
    FROM #EnterpriseDSeniorOfficers AS e  
        INNER JOIN DirectReports AS d  
        ON e.HigherRankID = d.CrewID   
)  
SELECT HigherRankID, CrewID, Title, CrewLevel   
FROM DirectReports  
ORDER BY HigherRankID;

Der obige Code verwendet eine dreispaltige Liste mit einem externen Formular, aber das Ankerelement hatte nur 2 Spalten. Es ist nicht erlaubt. Ein Fehler wie dieser löst einen Fehler aus:

Andere Dinge, die in einem CTE nicht erlaubt sind

Abgesehen von der obigen Liste sind hier noch ein paar weitere Erkenntnisse von Karl, die Fehler auslösen, wenn Sie es versehentlich in einem SQL-CTE verwenden.

  • Verwendung von SELECT INTO, OPTION-Klausel mit Abfragehinweisen und Verwendung von FOR BROWSE.
  • Unterschiedliche Daten und Typen in den Ankerelementspalten im Vergleich zu den rekursiven Elementspalten.
  • Die folgenden Schlüsselwörter in einem rekursiven Member eines rekursiven CTE haben:
    • TOP
    • OUTER JOIN (aber INNER JOIN ist erlaubt)
    • GRUPPE NACH und HAVING
    • Unterabfragen
    • WÄHLEN SIE EINZIGARTIG AUS
  • Skalare Aggregation verwenden.
  • Unterabfragen in einem rekursiven Mitglied verwenden.
  • Verschachtelte SQL-CTEs.

SQL CTE vs. Temporäre Tabellen vs. Unterabfragen

Manchmal können Sie einen SQL-CTE mithilfe einer Unterabfrage umschreiben. Außerdem können Sie aus Leistungsgründen gelegentlich einen SQL-CTE mithilfe von temporären Tabellen aufschlüsseln. Wie bei jeder anderen Abfrage müssen Sie den Actual Execution Plan und STATISTICS IO überprüfen, um zu wissen, welche Option zu wählen ist. SQL CTE mag freundlich für die Augen sein, aber wenn Sie auf eine Leistungsgrenze stoßen, verwenden Sie eine andere Option . Keine Option ist schneller als die andere.

Untersuchen wir drei Abfragen aus Karls Papieren, die zu denselben Ergebnissen führen. Einer verwendet einen SQL-CTE, der andere eine Unterabfrage und der dritte eine temporäre Tabelle. Der Einfachheit halber hat Karl eine kleine Ergebnismenge verwendet.

Der Code und die Ergebnismenge

Es begann mit der Verwendung mehrerer SQL-CTEs.

WITH LatestEmployeePay
AS
(
    SELECT TOP 1
     eph.BusinessEntityID
    ,eph.RateChangeDate
    ,eph.Rate
    FROM HumanResources.EmployeePayHistory eph 
    WHERE eph.BusinessEntityID = 16
    ORDER BY eph.RateChangeDate DESC
),
PreviousEmployeePay AS
(
    SELECT TOP 1
     eph.BusinessEntityID
    ,eph.RateChangeDate
    ,eph.Rate
    FROM HumanResources.EmployeePayHistory eph
    INNER JOIN LatestEmployeePay lep 
        ON eph.BusinessEntityID = lep.BusinessEntityID
    WHERE eph.BusinessEntityID = 16
        AND eph.RateChangeDate < lep.RateChangeDate
    ORDER BY eph.RateChangeDate DESC
)
SELECT
 a.BusinessEntityID
,a.Rate
,a.RateChangeDate
,b.Rate AS PreviousRate
FROM LatestEmployeePay a
INNER JOIN PreviousEmployeePay b 
    ON a.BusinessEntityID = b.BusinessEntityID

Die nächste ist eine Unterabfrage. Wie Sie sehen, sieht der CTE modular und lesbar aus, aber die folgende Unterabfrage ist kürzer:

SELECT TOP 1
 eph.BusinessEntityID
,eph.Rate
,eph.RateChangeDate
,(SELECT TOP 1 eph1.Rate FROM HumanResources.EmployeePayHistory eph1
  WHERE eph1.BusinessEntityID=16
    AND eph1.RateChangeDate < eph.RateChangeDate
  ORDER BY eph1.RateChangeDate DESC) AS PreviousRate
FROM HumanResources.EmployeePayHistory eph
WHERE eph.BusinessEntityID = 16
ORDER BY eph.RateChangeDate DESC;

Karl hat auch versucht, es in kleine Codestücke zu unterteilen und die Ergebnisse dann mit temporären Tabellen zu kombinieren.

SELECT TOP 1
 eph.BusinessEntityID
,eph.RateChangeDate
,eph.Rate
INTO #LatestPay
FROM HumanResources.EmployeePayHistory eph 
WHERE eph.BusinessEntityID = 16
ORDER BY eph.RateChangeDate DESC

SELECT TOP 1
 eph.BusinessEntityID
,eph.RateChangeDate
,eph.Rate
INTO #PreviousPay
FROM HumanResources.EmployeePayHistory eph
INNER JOIN #LatestPay lep 
    ON eph.BusinessEntityID = lep.BusinessEntityID
WHERE eph.BusinessEntityID = 16
    AND eph.RateChangeDate < lep.RateChangeDate
ORDER BY eph.RateChangeDate DESC

SELECT
 a.BusinessEntityID
,a.Rate
,a.RateChangeDate
,b.Rate AS PreviousRate
FROM #LatestPay a
INNER JOIN #PreviousPay b 
    ON a.BusinessEntityID = b.BusinessEntityID

DROP TABLE #LatestPay
DROP TABLE #PreviousPay

Sehen Sie sich diese drei Möglichkeiten an, um die aktuellen und früheren Gehälter für Mitarbeiter 16 zu erhalten. Sie sind gleich:

Die logischen Lesevorgänge

Was verbraucht die meisten SQL Server-Ressourcen? Schauen wir uns das STATISTICS IO an. Karl hat statisticsparser.com verwendet, um die Ergebnisse schön zu formatieren – gut für uns.

Abbildung 7 zeigt logische Lesevorgänge bei der Verwendung eines CTE im Vergleich zur Verwendung einer Unterabfrage:

Sehen Sie sich als Nächstes die gesamten logischen Lesevorgänge an, wenn Sie den Code mithilfe temporärer Tabellen in kleine Teile aufteilen:

Was verbraucht also mehr Ressourcen? Karl hat sie der Übersichtlichkeit halber in eine Rangfolge gebracht.

  1. Unterabfrage – 4 logische Lesevorgänge (GEWINNER!).
  2. SQL CTE – 6 logische Lesevorgänge.
  3. Temporäre Tabellen – 8 logische Lesevorgänge.

In diesem Beispiel ist die Unterabfrage am schnellsten.

Der tatsächliche Ausführungsplan

Um die logischen Reads zu verstehen, die wir vom STATISTICS IO erhalten haben, überprüfte Karl auch den Actual Execution Plan. Er startete vom CTE:

Wir können ein paar Dinge aus diesem Plan beobachten:

  • Neueste Mitarbeitervergütung CTE wurde zweimal ausgewertet, wenn es in der äußeren Abfrage verwendet wurde und wenn es mit PreviousEmployeePay verbunden wurde . Wir sehen also 2 TOP-Knoten dafür.
  • Wir sehen PreviousEmployeePay einmal ausgewertet.

Sehen Sie sich dann den tatsächlichen Ausführungsplan der Abfrage mit einer Unterabfrage an:

Hier gibt es einige offensichtliche Dinge:

  • Der Plan ist einfacher.
  • Es ist einfacher, weil die Unterabfrage zum Abrufen der letzten Bezahlung nur einmal ausgewertet wird.
  • Kein Wunder, dass die logischen Lesevorgänge im Vergleich zu den logischen Lesevorgängen der Abfrage mit einem CTE geringer sind.

Schließlich ist hier der tatsächliche Ausführungsplan, als Karl temporäre Tabellen verwendete:

Da es sich um einen Stapel mit drei Anweisungen handelt, sehen wir auch drei Diagramme im Plan. Alle drei sind einfach, aber der kollektive Plan ist nicht so einfach wie der Plan der Abfrage mit der Unterabfrage.

Die Zeitstatistik

Mit der dbForge Studio for SQL Server-Lösung können Sie die Zeitstatistiken im Query Profiler vergleichen. Halten Sie dazu die STRG-Taste gedrückt und klicken Sie auf die Ergebnisnamen jeder Abfrage im Ausführungsverlauf:

Die Zeitstatistiken stimmen mit den logischen Lesevorgängen und dem tatsächlichen Ausführungsplan überein. Die Unterabfrage lief am schnellsten (88 ms). Darauf folgt der CTE (199ms). Der letzte ist die Verwendung temporärer Tabellen (536 ms).

Also, was haben wir von Karl gelernt?

In diesem speziellen Beispiel haben wir gesehen, dass die Verwendung einer Unterabfrage viel besser ist, wenn wir die schnellste Option wünschen. Es kann jedoch eine andere Geschichte sein, wenn die Anforderungen nicht so sind.

Überprüfen Sie immer die STATISTIK IO und die tatsächlichen Ausführungspläne, um zu wissen, welche Technik zu verwenden ist.

Imbiss

Ich hoffe, Sie haben sich nicht den Kopf an die Wand gestoßen, um zu verstehen, was ein CTE (Common Table Expressions) ist. Andernfalls könnten Sie eine CTE (chronisch-traumatische Enzephalopathie) bekommen. Spaß beiseite, was haben wir verraten?

  • Allgemeine Tabellenausdrücke sind temporär benannte Ergebnismengen. In Verhalten und Umfang ähnelt sie eher einer Unterabfrage, ist aber klarer und modularer. Es hat auch einen Namen.
  • SQL CTE dient zum Vereinfachen von Code, nicht zum Beschleunigen Ihrer Abfrage.
  • Sieben Dinge, die wir gelernt haben, funktionieren auf SQL CTE.
  • Fünf Dinge lösen einen Fehler aus.
  • Wir haben auch noch einmal bestätigt, dass die STATISTICS IO und der Actual Execution Plan Ihnen immer ein besseres Urteilsvermögen geben werden. Es ist wahr, wenn Sie einen CTE mit einer Unterabfrage und einem Stapel vergleichen, der temporäre Tabellen verwendet.

Genossen es? Dann warten die Social-Media-Buttons darauf, gedrückt zu werden. Wählen Sie Ihren Favoriten aus und teilen Sie die Liebe!

Lesen Sie auch

Wie CTE beim Schreiben komplexer, leistungsstarker Abfragen helfen kann:Eine Leistungsperspektive