SQL_Kurs

📐 SQL CTE · T-SQL Meisterklasse

Common Table Expressions (WITH-Klausel) für Microsoft SQL Server

1. Basis CTE · Lesbarkeit & Wiederverwendung

📌 Zweck: Eine temporäre Ergebnismenge innerhalb einer Abfrage definieren, um komplexe Joins oder Aggregate zu vereinfachen.
Typisch für Berichte: Zuerst filtern / aggregieren, dann mit anderen Tabellen verknüpfen.
-- Beispiel: Umsatz pro Kategorie (angenommene Tabellenstruktur) WITH SalesSummary AS ( SELECT p.CategoryID, SUM(od.Quantity * od.UnitPrice) AS TotalRevenue, COUNT(DISTINCT od.OrderID) AS NumberOfOrders FROM Sales.OrderDetails od INNER JOIN Production.Products p ON od.ProductID = p.ProductID WHERE od.OrderDate >= '2024-01-01' GROUP BY p.CategoryID ) SELECT c.CategoryName, ss.TotalRevenue, ss.NumberOfOrders, ROUND(ss.TotalRevenue / NULLIF(ss.NumberOfOrders, 0), 2) AS AvgOrderValue FROM Production.Categories c INNER JOIN SalesSummary ss ON c.CategoryID = ss.CategoryID ORDER BY ss.TotalRevenue DESC;
💡 Erklärung: Die CTE SalesSummary fasst Verkaufszahlen pro Kategorie zusammen. Die Hauptabfrage nutzt diese virtuelle Tabelle für einen JOIN mit Kategorienamen und berechnet den durchschnittlichen Bestellwert. Vorteil: Logik getrennt, lesbarer als Unterabfragen.

2. Mehrere CTEs · Kaskadierte Logik

📌 Zweck: Mehrere benannte Ergebnislisten innerhalb derselben Abfrage definieren; spätere CTEs können auf frühere verweisen.
WITH ActiveCustomers AS ( SELECT CustomerID, COUNT(OrderID) AS OrderCount FROM Sales.Orders WHERE OrderDate >= DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1) GROUP BY CustomerID ), HighValue AS ( SELECT CustomerID, OrderCount FROM ActiveCustomers WHERE OrderCount >= 5 ), AvgOrderAmount AS ( SELECT o.CustomerID, AVG(o.TotalDue) AS AvgOrderValue FROM Sales.SalesOrderHeader o WHERE o.CustomerID IN (SELECT CustomerID FROM HighValue) AND o.OrderDate >= '2024-01-01' GROUP BY o.CustomerID ) SELECT hv.CustomerID, hv.OrderCount, aoa.AvgOrderValue, c.FirstName + ' ' + c.LastName AS CustomerName FROM HighValue hv INNER JOIN Sales.Customer c ON hv.CustomerID = c.CustomerID LEFT JOIN AvgOrderAmount aoa ON hv.CustomerID = aoa.CustomerID ORDER BY hv.OrderCount DESC;
🔗 Erklärung: Drei CTEs bauen aufeinander auf – ActiveCustomersHighValueAvgOrderAmount. Beim Kompilieren werden sie wie Inline-Views behandelt. Ideale Struktur für mehrstufige Datenaufbereitung ohne reale Temp-Tabellen.

3. Rekursive CTE · Hierarchien & Graphen

📌 Zweck: Abfragen von hierarchischen Daten (Mitarbeiterstruktur, Kategoriebäume, Stücklisten). Benötigt Anchor und rekursiven Teil.
WITH OrgChart AS ( -- Anchor: top-level Manager (kein Vorgesetzter) SELECT EmployeeID, FirstName + ' ' + LastName AS EmployeeName, ManagerID, 0 AS Level, CAST(FirstName + ' ' + LastName AS VARCHAR(500)) AS Path FROM HR.Employees WHERE ManagerID IS NULL UNION ALL -- Rekursiver Teil: alle untergeordneten Mitarbeiter SELECT e.EmployeeID, e.FirstName + ' ' + e.LastName, e.ManagerID, oc.Level + 1, CAST(oc.Path + ' → ' + e.FirstName + ' ' + e.LastName AS VARCHAR(500)) FROM HR.Employees e INNER JOIN OrgChart oc ON e.ManagerID = oc.EmployeeID ) SELECT EmployeeID, EmployeeName, Level, Path, REPLICATE(' ', Level) + EmployeeName AS HierarchieDarstellung FROM OrgChart ORDER BY Path;
🧬 Erklärung: Die CTE ruft zuerst die Wurzel (CEO) ab. Im rekursiven Teil werden alle direkt oder indirekt unterstellten Mitarbeiter iterativ angehängt. Level zeigt die Tiefe. Wichtig: MAXRECURSION-Option bei tiefen Bäumen, standardmäßig 100 Iterationen (mit OPTION (MAXRECURSION 0) erweiterbar).

4. CTE mit Fensterfunktionen · ROW_NUMBER & Co.

📌 Zweck: CTEs sind ideal, um Fensterfunktionen zu verwenden und anschließend darauf zu filtern / aggregieren (z.B. Top-N pro Gruppe).
-- Beispiel: Pro Kategorie die 3 umsatzstärksten Produkte ermitteln WITH ProductSales AS ( SELECT p.ProductID, p.ProductName, p.CategoryID, SUM(od.Quantity * od.UnitPrice) AS TotalSales, ROW_NUMBER() OVER (PARTITION BY p.CategoryID ORDER BY SUM(od.Quantity * od.UnitPrice) DESC) AS RankInCategory FROM Production.Products p INNER JOIN Sales.OrderDetails od ON p.ProductID = od.ProductID GROUP BY p.ProductID, p.ProductName, p.CategoryID ) SELECT CategoryID, ProductName, TotalSales, RankInCategory FROM ProductSales WHERE RankInCategory <= 3 ORDER BY CategoryID, RankInCategory; -- Zusätzliches Beispiel mit LAG: monatliche Umsatzveränderung WITH MonthlyRevenue AS ( SELECT YEAR(OrderDate) AS Year, MONTH(OrderDate) AS Month, SUM(TotalDue) AS Revenue, LAG(SUM(TotalDue), 1) OVER (ORDER BY YEAR(OrderDate), MONTH(OrderDate)) AS PrevMonthRevenue FROM Sales.SalesOrderHeader GROUP BY YEAR(OrderDate), MONTH(OrderDate) ) SELECT Year, Month, Revenue, PrevMonthRevenue, FORMAT((Revenue - PrevMonthRevenue) / NULLIF(PrevMonthRevenue, 0), 'P2') AS GrowthRate FROM MonthlyRevenue ORDER BY Year, Month;
📊 Erklärung: Erste CTE verwendet ROW_NUMBER() OVER(PARTITION BY ...) um Produkte innerhalb jeder Kategorie zu bewerten. Danach können wir einfach auf RankInCategory filtern. Zweites Beispiel zeigt LAG() für Vorperiodenvergleich – erlaubt saubere Berechnung von Wachstumsraten ohne Self-Join.

5. CTE für DML · Aktualisieren/Löschen mit definierter Teilmenge

📌 Zweck: Eine CTE kann das Ziel einer UPDATE-, INSERT- oder DELETE-Anweisung sein, um gezielt bestimmte Datensätze zu ändern (z.B. doppelte Datensätze entfernen).
-- Beispiel 1: Duplikate in einer Tabelle löschen (basierend auf ROW_NUMBER) WITH DuplicateCheck AS ( SELECT *, ROW_NUMBER() OVER (PARTITION BY Email, CustomerName ORDER BY CreatedDate DESC) AS rn FROM Sales.Customers ) DELETE FROM DuplicateCheck WHERE rn > 1; -- Beispiel 2: Preisaktualisierung für die teuersten 10% der Produkte WITH TopProducts AS ( SELECT TOP 10 PERCENT ProductID, UnitPrice FROM Production.Products ORDER BY UnitPrice DESC ) UPDATE TopProducts SET UnitPrice = UnitPrice * 1.05; -- 5% Preiserhöhung für Luxusartikel -- Beispiel 3: Daten archivieren und löschen (mit CTE umgesetzt) WITH OldOrders AS ( SELECT OrderID, OrderDate FROM Sales.Orders WHERE OrderDate < DATEADD(YEAR, -3, GETDATE()) ) DELETE FROM OldOrders;
Erklärung: In T-SQL können CTEs direkt als Zieltabelle für UPDATE, DELETE (und INSERT mit OUTPUT) verwendet werden. Die erste Abfrage löscht alle Datensätze mit rn>1 – entfernt Duplikate effizient. Die zweite erhöht den Preis der teuersten 10% Produkte. Wichtig: Die CTE muss aktualisierbar sein (keine Aggregation, kein DISTINCT, etc.).

🧠 CTE Grundsyntax (T-SQL)

WITH cte_name (column_list) AS ( -- CTE query definition SELECT ... ) -- Hauptabfrage (SELECT, INSERT, UPDATE, DELETE) SELECT * FROM cte_name;

T-SQL Besonderheiten: Rekursion mit UNION ALL, optional OPTION (MAXRECURSION n); CTEs können geschachtelt werden; nach einer CTE nur eine einzige Data Manipulation Language (DML)-Anweisung erlaubt. Performance: häufig optimiert der Optimizer CTEs wie Subqueries, keine automatische Temp-Tabelle – für große Datenmengen ggf. #temp-Tabelle in Erwägung ziehen.

Nächster SQL Server – QUOTED_IDENTIFIER & ANSI_NULLS
Cookies user preferences
We use cookies to ensure you to get the best experience on our website. If you decline the use of cookies, this website may not function as expected.
Accept all
Decline all
Analytics
Tools used to analyze the data to measure the effectiveness of a website and to understand how it works.
Google Analytics
Advertisement
If you accept, the ads on the page will be adapted to your preferences.
Google Ad
Save