Last chance! 50% off DataCamp Premium
Sale ends in
Als je al een tijdje met SQL werkt maar nog geen CTE’s gebruikt, vraag je je waarschijnlijk af hoe je het ooit zonder hebt gedaan. Ik gebruik ze bijna overal, ook in SELECT-, INSERT-, UPDATE- en DELETE-statements.
In dit artikel neem ik de basis door, waaronder hoe je een CTE maakt. Ik ga ook in op meer geavanceerde zaken, zoals het verschil tussen niet-recursieve en recursieve CTE’s, die allebei hun nut hebben.
Ben je nog niet zo vertrouwd met SQL-bewerkingen? Probeer dan onze zeer populaire cursus Introduction to SQL om te beginnen. De cursus is goed opgezet en volledig, en leert je alles wat je moet weten om data te extraheren met efficiënte queries.
Het idee achter CTE’s wordt duidelijk aan de hand van voorbeelden. Voor nu kunnen we zeggen dat een CTE, of common table expression, een tijdelijke, benoemde resultatenset in SQL is waarmee je complexe queries kunt vereenvoudigen, zodat ze makkelijker te lezen en te onderhouden zijn.
CTE’s worden vaak gebruikt wanneer je met meerdere subqueries werkt. Je herkent ze aan het kenmerkende WITH-keyword en, en zoals ik al zei, je kunt ze gebruiken in SELECT-, INSERT-, UPDATE- en DELETE-statements.
Bij het maken van een CTE gebruiken we het WITH-keyword om de CTE-definitie te starten. De algemene syntaxis van een CTE is als volgt:
WITH cte_name (column1, column2, ...) AS (
-- Query that defines the CTE
SELECT ...
FROM ...
WHERE ...
)
-- Main query
SELECT ...
FROM cte_name;
Waarbij:
Laten we naar een voorbeeld kijken. Stel, we hebben een tabel Employees en we willen werknemers selecteren die een salaris boven de $50.000 verdienen.
We beginnen met het schrijven van de basis-SELECT-query:
SELECT EmployeeID, FirstName, LastName, Salary
FROM Employees
WHERE Salary > 50000;
Gebruik het WITH-keyword om de CTE een naam te geven.
WITH HighEarningEmployees AS (
SELECT EmployeeID, FirstName, LastName, Salary
FROM Employees
WHERE Salary > 50000
)
Verwijs tot slot in een SELECT-statement naar de CTE door de hierboven gedefinieerde CTE-naam aan te roepen.
WITH HighEarningEmployees AS (
SELECT EmployeeID, FirstName, LastName, Salary
FROM Employees
WHERE Salary > 50000
)
SELECT EmployeeID, FirstName, LastName
FROM HighEarningEmployees;
Samenvattend hebben we het WITH-keyword gebruikt om de CTE HighEarningEmployees te definiëren. De binnenste query genereerde de tijdelijke dataset. De hoofdquery verwijst naar HighEarningEmployees om de gespecificeerde kolommen EmployeeID, FirstName en LastName weer te geven.
Uit het bovenstaande voorbeeld vraag je je misschien af waarom we CTE’s gebruiken als eenvoudige queries dezelfde resultaten opleveren. De redenen zijn als volgt:
CTE’s breken complexe SQL-statements op in kleinere, beter beheersbare onderdelen, waardoor de code makkelijker te lezen, te schrijven en te onderhouden is.
Stel, we hebben drie tabellen: Orders, Customers en Products. We willen de totale omzet per klant vinden voor aankopen in 2024. Zonder CTE ziet de query er rommelig uit en is hij lastig te lezen en te begrijpen.
-- Standard SQL: Hard to read nested logic
SELECT
c.CustomerName,
SUM(p.Price * o.Quantity) AS TotalRevenue
FROM Orders o
JOIN Customers c
ON o.CustomerID = c.CustomerID
JOIN Products p
ON o.ProductID = p.ProductID
WHERE EXTRACT(YEAR FROM o.OrderDate) = 2024
GROUP BY c.CustomerName
HAVING SUM(p.Price * o.Quantity) > 1000;
Met een CTE kunnen we de logica opsplitsen in een leesbaarder formaat. We isoleren eerst de stap “filteren en joinen” en voeren daarna de aggregatie uit.
-- Standard SQL: Cleaner with CTE
WITH OrderDetails AS (
SELECT
o.OrderID,
c.CustomerName,
p.Price,
o.Quantity,
o.OrderDate
FROM Orders o
JOIN Customers c
ON o.CustomerID = c.CustomerID
JOIN Products p
ON o.ProductID = p.ProductID
WHERE EXTRACT(YEAR FROM o.OrderDate) = 2024
)
-- Main query
SELECT
CustomerName,
SUM(Price * Quantity) AS TotalRevenue
FROM OrderDetails
GROUP BY CustomerName
HAVING SUM(Price * Quantity) > 1000;
CTE’s helpen duplicatie te voorkomen doordat je dezelfde resultatenset kunt hergebruiken. Als je een aggregaat (zoals een som) moet berekenen en vervolgens op basis daarvan moet filteren, is een CTE ideaal.
Stel dat we de gemiddelde en totale verkoop per productcategorie moeten berekenen. We definiëren de berekening één keer in een CTE:
WITH CategorySales AS (
SELECT
Category,
SUM(SalesAmount) AS TotalSales,
AVG(SalesAmount) AS AverageSales
FROM Products
GROUP BY Category
)
-- Select from the CTE where the pre-calculated TotalSales is high
SELECT
Category,
TotalSales,
AverageSales
FROM CategorySales
WHERE TotalSales > 5000;
Naast het vereenvoudigen van queries en codehergebruik hebben CTE’s nog andere toepassingen. Ik kan niet alle mogelijke toepassingen van CTE’s in detail behandelen. Onze cursus Data Manipulation in SQL is een goede optie als je verder wilt oefenen. Hieronder noem ik wel enkele van de andere voornaamste redenen:
CTE’s ondersteunen geavanceerde SQL-technieken, waardoor ze veelzijdig en bruikbaar zijn voor verschillende use-cases. Hieronder staan enkele geavanceerde toepassingen van CTE’s.
Je kunt meerdere CTE’s in één query definiëren, wat complexe transformaties en berekeningen mogelijk maakt. Deze methode is handig wanneer een probleem meerdere fasen van dataverwerking vereist, waarbij elke CTE een aparte fase vertegenwoordigt.
Stel dat we verkoopdata hebben in een tabel Sales en dat we de totale verkoop per product willen berekenen, producten met bovengemiddelde totale verkoop willen identificeren en deze producten willen rangschikken op basis van hun totale verkoop.
WITH ProductSales AS (
-- Step 1: Calculate total sales for each product
SELECT ProductID, SUM(SalesAmount) AS TotalSales
FROM Sales
GROUP BY ProductID
),
AverageSales AS (
-- Step 2: Calculate the average of those totals
-- Note: We can reference the previous CTE (ProductSales) here
SELECT AVG(TotalSales) AS AverageTotalSales
FROM ProductSales
),
HighSalesProducts AS (
-- Step 3: Filter products above the average
SELECT ps.ProductID, ps.TotalSales
FROM ProductSales ps
CROSS JOIN AverageSales av
WHERE ps.TotalSales > av.AverageTotalSales
)
-- Step 4: Rank the results
SELECT
ProductID,
TotalSales,
RANK() OVER (ORDER BY TotalSales DESC) AS SalesRank
FROM HighSalesProducts;
In het bovenstaande voorbeeld:
ProductSales) berekent de totale verkoop per product.AverageSales) berekent de gemiddelde totale verkoop over alle producten.HighSalesProducts) filtert producten waarvan de totale verkoop boven het gemiddelde ligt.Wanneer ze worden toegepast in UPDATE-, DELETE- en MERGE-operaties, kunnen CTE’s datamanipulatietaken vereenvoudigen, vooral bij complexe filters of hiërarchische data.
Stel dat we een Employees-tabel hebben met een kolom EmployeeSalary. We willen alle werknemers die al langer dan 5 jaar bij het bedrijf werken een loonsverhoging van 10% geven.
-- Define a CTE to find employees hired more than 5 years ago
WITH LongTermEmployees AS (
SELECT EmployeeID
FROM Employees
-- Standard SQL: Compare HireDate to 5 years before today
WHERE HireDate <= CURRENT_DATE - INTERVAL '5' YEAR
)
-- Update salaries by 10% for long-term employees identified in the CTE
UPDATE Employees
SET EmployeeSalary = EmployeeSalary * 1.10
WHERE EmployeeID IN (SELECT EmployeeID FROM LongTermEmployees);
De CTE LongTermEmployees identificeert werknemers die langer dan vijf jaar in dienst zijn. Het UPDATE-statement gebruikt deze CTE om hun salarissen selectief te verhogen.
Stel nu dat we een tabel Products hebben en alle producten willen verwijderen die in de afgelopen 2 jaar niet zijn verkocht. We kunnen een CTE gebruiken om de producten te filteren:
-- Define a CTE to identify products not sold in the last 2 years
WITH OldProducts AS (
SELECT ProductID
FROM Products
-- Standard SQL: Filter for dates older than 2 years ago
WHERE LastSoldDate < CURRENT_DATE - INTERVAL '2' YEAR
)
-- Delete products identified as old from the main table
DELETE FROM Products
WHERE ProductID IN (SELECT ProductID FROM OldProducts);
De CTE OldProducts identificeert producten die in de afgelopen twee jaar niet zijn verkocht, en vervolgens gebruikt het DELETE-statement deze CTE om die producten te verwijderen.
Het MERGE-statement in SQL maakt voorwaardelijke updates, inserts of deletions mogelijk in een doeltabel op basis van data in een brontabel. In het volgende voorbeeld combineert de CTE MergedInventory nieuwe en bestaande voorraaddata. Het MERGE-statement werkt vervolgens hoeveelheden bij voor bestaande producten of voegt nieuwe producten in op basis van de CTE-data.
-- CTE to prepare the source data for the merge
WITH MergedInventory AS (
SELECT
ni.ProductID,
ni.Quantity AS NewQuantity
FROM NewInventoryData ni
)
-- Merge the prepared data into the Inventory table
MERGE INTO Inventory AS target
USING MergedInventory AS source
ON target.ProductID = source.ProductID
-- Update existing products with new quantities
WHEN MATCHED THEN
UPDATE SET target.Quantity = source.NewQuantity
-- Insert new products if they don't exist in the inventory
WHEN NOT MATCHED THEN
INSERT (ProductID, Quantity)
VALUES (source.ProductID, source.NewQuantity);
Recursieve CTE’s zijn een speciaal type CTE dat binnen de eigen definitie naar zichzelf verwijst, waardoor de query herhaalde bewerkingen kan uitvoeren. Dit maakt ze ideaal voor het werken met hiërarchische data, zoals organogrammen.
Recursieve CTE’s zijn een speciaal type CTE dat binnen de definitie naar zichzelf verwijst, waardoor de query herhaalde bewerkingen kan uitvoeren. Dit maakt ze ideaal voor hiërarchische of boomgestructureerde data, zoals organogrammen, mappenstructuren of productassemblages. De recursieve CTE verwerkt data iteratief en geeft stap voor stap resultaten terug totdat het recursieve deel geen nieuwe rijen meer oplevert (de stopconditie).
Een recursieve CTE bestaat uit twee hoofdonderdelen:
Stel dat we een Employees-tabel hebben, waarin elke rij een EmployeeID, EmployeeName en ManagerID bevat. Als we alle directe en indirecte rapportages voor een specifieke manager willen vinden, beginnen we met het anchor-lid dat de manager op het hoogste niveau identificeert.
Let op: In standaard SQL (PostgreSQL, MySQL, SQLite) moet je het keyword RECURSIVE gebruiken.
WITH RECURSIVE EmployeeHierarchy AS (
-- Anchor member: select the top-level manager
SELECT
EmployeeID,
EmployeeName,
ManagerID,
1 AS Level
FROM Employees
WHERE EmployeeID = 1 -- Starting with the top-level manager
UNION ALL
-- Recursive member: find employees who report to the current managers
SELECT
e.EmployeeID,
e.EmployeeName,
e.ManagerID,
eh.Level + 1
FROM Employees e
INNER JOIN EmployeeHierarchy eh
ON e.ManagerID = eh.EmployeeID
)
-- Select the final result from the CTE
SELECT EmployeeID, EmployeeName, Level
FROM EmployeeHierarchy;
Hoe het werkt:
ManagerID overeenkomt met de EmployeeID uit de vorige stap.Het is belangrijk om de mogelijkheden en beperkingen van CTE’s te begrijpen om logische en leesbare queries te schrijven. Laten we enkele beperkingen en potentiële problemen bekijken bij het gebruik van CTE’s in verschillende databases.
Er zijn enkele omgevingsspecifieke beperkingen voor SQL CTE’s bij gebruik van SQL Server of Azure Synapse Analytics. Ze omvatten het volgende:
UPDATE of DELETE met CTE’s) syntaxisbeperkingen hebben ten opzichte van standaard SQL Server.Als je met SQL Server werkt, weet dan dat DataCamp veel goede bronnen heeft om je te helpen. Om te beginnen raad ik DataCamp’s cursus Introduction to SQL Server aan om de basis van SQL Server voor data-analyse onder de knie te krijgen. Je kunt ook ons SQL Server Developer-carrièrepad proberen, dat alles behandelt van transacties en foutafhandeling tot tijdreeksanalyse. Onze cursus Hierarchical and Recursive Queries in SQL Server gaat direct in op het schrijven van geavanceerde queries in SQL Server, inclusief methoden met CTE’s.
Hoewel CTE’s handig zijn om complexe queries te vereenvoudigen, zijn er enkele veelvoorkomende valkuilen waarvan je op de hoogte moet zijn. Deze omvatten het volgende:
Oneindige lussen in recursieve CTE’s: Als aan de stopconditie van een recursieve CTE niet wordt voldaan, kan dit resulteren in een oneindige lus, waardoor de query onbepaald blijft draaien. Gebruik de hint OPTION (MAXRECURSION N) om het maximale aantal recursieve iteraties te beperken, waarbij N de ingestelde limiet is, om te voorkomen dat de recursieve CTE oneindig blijft lopen.
OPTION (MAXRECURSION N) om het maximale aantal recursieve iteraties te beperken. In PostgreSQL kun je de CYCLE-clausule gebruiken om lussen automatisch te detecteren.Prestatie-overwegingen: Recursieve CTE’s kunnen veel resources verbruiken als de recursiediepte hoog is of als er grote datasets worden verwerkt. Beperk voor betere prestaties de hoeveelheid data per iteratie en zorg voor passende filtering om overmatige recursieniveaus te vermijden.
Een veelvoorkomende mythe is dat CTE’s inherent sneller zijn dan subqueries. In werkelijkheid “inlinen” de meeste moderne query-optimizers (zoals die in SQL Server en PostgreSQL) standaard CTE’s, wat betekent dat ze precies zoals subqueries worden verwerkt, zonder prestatiedifferentie.
Toch kunnen CTE’s een prestatievoordeel opleveren via materialisatie, waarbij de database het CTE-resultaat één keer berekent en in cache plaatst voor meerdere verwijzingen binnen de hoofdquery.
Ik raad aan CTE’s vooral voor de leesbaarheid te gebruiken. Prestatievoordelen zijn situationeel en hangen af van hoe jouw specifieke database caching afhandelt.
Hoewel CTE’s geschikt zijn om queries met herhaalde taken te vereenvoudigen, hebben derived tables, views en tijdelijke tabellen vergelijkbare doelen. De onderstaande tabel belicht de voor- en nadelen van elke methode en wanneer je welke inzet.
| Techniek | Voordelen | Nadelen | Geschikte use-case |
|---|---|---|---|
| CTE’s | Tijdelijke scope binnen één enkele queryGeen opslag of onderhoud vereistVerbetert de leesbaarheid door code te moduleren | Beperkt tot de query waarin ze zijn gedefinieerd | Structureren van complexe queries, tijdelijke transformaties en het opdelen van meerstapsbewerkingen |
| Derived tables | Vereenvoudigt geneste subqueriesGeen permanente opslag nodig | Moeilijker te lezen/onderhouden bij complexe queriesKan niet meerdere keren binnen één query worden hergebruikt | Snelle, eenmalige transformaties en aggregaties binnen een query |
| Views | Hergebruikbaar over meerdere queriesKan de beveiliging verbeteren door data-toegang te beperken | Vereist onderhoud en kan meerdere queries beïnvloedenComplexe views kunnen prestaties beïnvloeden | Langdurige herbruikbare logica en toegangscontrole tot data |
CTE’s beheersen kost, zoals alles, oefening: ik raad je aan om DataCamp’s Associate Data Analyst in SQL-carrièrepad te volgen om een bekwame data-analist te worden. De cursus Reporting in SQL helpt je ook om vaardig te worden in het bouwen van complexe rapporten en dashboards voor effectieve datapresentatie. Tot slot kun je de SQL Associate Certification behalen om je beheersing van SQL bij het oplossen van zakelijke vraagstukken te tonen en je te onderscheiden van andere professionals.
Een CTE (common table expression) is een tijdelijke, benoemde resultatenset die binnen een SQL-query wordt gedefinieerd met het WITH-keyword, en die wordt gebruikt om complexe queries te vereenvoudigen door ze op te splitsen in kleinere, beter beheersbare onderdelen.
CTE’s zijn tijdelijk en bestaan alleen gedurende één enkele query. Views worden in de database opgeslagen en kunnen in meerdere queries worden hergebruikt. CTE’s gebruiken geen opslagruimte, views wel.
Niet per se. CTE’s verbeteren de leesbaarheid, maar presteren niet altijd beter dan tijdelijke tabellen bij grote datasets.
Ja, CTE’s kunnen worden gebruikt in datamanipulatiestatements om het proces te vereenvoudigen, vooral wanneer filteren of joinen nodig is.
Niet-recursieve CTE’s verwijzen niet naar zichzelf en gedragen zich vergelijkbaar met een subquery of tijdelijke tabel. Niet-recursieve CTE’s vereenvoudigen complexe queries, net als subqueries of tijdelijke tabellen. Recursieve CTE’s daarentegen verwijzen binnen de querydefinitie naar zichzelf en worden gebruikt voor iteratieve dataverwerking, zoals het doorlopen van hiërarchische datastructuren. Ze zijn geschikt voor taken die herhaalde uitvoering vereisen, waarbij elke stap voortbouwt op de vorige.
Leer SQL met DataCamp
Cursus
Cursus
Cursus

blog
Adel Nehme
15 min