📐 SQL CTE · T-SQL Meisterklasse
1. Basis CTE · Lesbarkeit & Wiederverwendung
NICHT REKURSIVTypisch 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;
2. Mehrere CTEs · Kaskadierte Logik
MEHRFACH CTE
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;
3. Rekursive CTE · Hierarchien & Graphen
REKURSIV (UNION ALL)
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;
4. CTE mit Fensterfunktionen · ROW_NUMBER & Co.
ANALYTISCH
-- 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;
5. CTE für DML · Aktualisieren/Löschen mit definierter Teilmenge
UPDATE, DELETE
-- 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;
🧠 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.