intermediate
CTE
Use common table expressions for readability, recursive queries, and staging complex operations while checking plan behavior.
Common Table Expressions (`WITH`) improve readability and enable recursion. In PostgreSQL 12+, non-recursive CTEs are often inlined; older versions materialized them by default, which could help or hurt plans.
-- Readable staging
WITH recent AS (
SELECT * FROM orders
WHERE created_at >= now() - interval '30 days'
),
totals AS (
SELECT customer_id, sum(amount) AS total
FROM recent
GROUP BY customer_id
)
SELECT c.name, t.total
FROM totals t
JOIN customers c ON c.id = t.customer_id
ORDER BY t.total DESC
LIMIT 20;
-- Recursive hierarchy
WITH RECURSIVE tree AS (
SELECT id, parent_id, name, 1 AS depth
FROM categories WHERE parent_id IS NULL
UNION ALL
SELECT c.id, c.parent_id, c.name, t.depth + 1
FROM categories c
JOIN tree t ON c.parent_id = t.id
)
SELECT * FROM tree;
Always check `EXPLAIN`: a CTE that filters early may inline well; one that scans a huge table may still be expensive when materialized.
On interviews: contrast readability CTEs with subqueries, explain recursive CTE use cases (org charts, graphs), and note that "CTEs are always slow" is outdated advice for modern PostgreSQL.
Common pitfalls: assuming materialization without checking the plan; recursive CTEs without cycle guards; duplicating heavy scans across multiple CTE branches.
The trade-off is clarity versus planner control — CTEs organize complex SQL but still need plan verification under real data volumes.
Checklist:
- Use CTEs to name intermediate steps.
- Guard recursive queries against cycles and runaway depth.
- Compare EXPLAIN with and without CTE rewrite.
- Prefer one well-filtered base CTE over repeated subqueries.