Lab 02: CTEs & Recursive Queries

Time: 40 minutes | Level: Practitioner | DB: PostgreSQL 15

Common Table Expressions (CTEs) — defined with WITH — make complex queries readable by naming sub-results. Recursive CTEs (WITH RECURSIVE) unlock hierarchical traversal: org charts, category trees, graph paths.


Step 1 — Setup: Employee Hierarchy

CREATE TABLE employees (
  id         INT PRIMARY KEY,
  name       VARCHAR(50),
  manager_id INT,
  department VARCHAR(50),
  salary     NUMERIC(10,2)
);

INSERT INTO employees VALUES
  (1,  'CEO',         NULL, 'Executive', 200000),
  (2,  'CTO',         1,    'Tech',      150000),
  (3,  'CFO',         1,    'Finance',   140000),
  (4,  'VP Eng',      2,    'Tech',      120000),
  (5,  'VP Product',  2,    'Tech',      115000),
  (6,  'Sr Dev',      4,    'Tech',       90000),
  (7,  'Jr Dev',      4,    'Tech',       70000),
  (8,  'Sr Analyst',  3,    'Finance',    85000),
  (9,  'Analyst',     3,    'Finance',    65000),
  (10, 'Designer',    5,    'Tech',       75000);

📸 Verified Output:


Step 2 — Simple CTE

💡 A CTE is scoped to the single query it belongs to. It cannot be referenced outside its WITH block.

📸 Verified Output:


Step 3 — Multiple CTEs

📸 Verified Output:


Step 4 — Recursive CTE: Org Chart Traversal

How it works:

  1. Anchor member: selects the root node(s)

  2. Recursive member: joins each employee to already-found managers

  3. PostgreSQL alternates until no new rows are found

📸 Verified Output:


Step 5 — Recursive CTE: Find All Reports Under a Manager

💡 Add WHERE level < 10 (or use CYCLE in PostgreSQL 14+) to guard against infinite loops in graphs with cycles.

📸 Verified Output:


Step 6 — CTE vs Subquery Performance

💡 In PostgreSQL, a plain CTE is an optimization fence by default — the planner cannot push predicates into it. Add MATERIALIZED or NOT MATERIALIZED explicitly to control this.


Step 7 — Materialized CTEs


Step 8 — Capstone: Category Tree with Aggregated Budgets


Summary

Concept
Syntax
Use Case

Simple CTE

WITH name AS (...)

Readable sub-query aliasing

Multiple CTEs

WITH a AS (...), b AS (...)

Chain transformations

Recursive CTE

WITH RECURSIVE + UNION ALL

Trees, graphs, sequences

Materialized

WITH MATERIALIZED name AS

Force single evaluation

Not Materialized

WITH NOT MATERIALIZED name AS

Allow predicate pushdown

Depth limiting

WHERE level < N

Prevent infinite loops

Last updated