Recursive CTEs for Hierarchical Data
11 min read
Recursive CTEs for Hierarchical Data
A recursive CTE has two parts separated by UNION ALL: the base case (the starting rows) and the recursive case (how to find children). PostgreSQL alternates between the two until the recursive case produces no new rows.
Org Chart Traversal
sql
CREATE TABLE employees (
id int PRIMARY KEY,
name text NOT NULL,
manager_id int REFERENCES employees(id)
);
-- Find all reports under employee 5, any depth
WITH RECURSIVE reports AS (
-- Base case: the root employee
SELECT id, name, manager_id, 0 AS depth
FROM employees
WHERE id = 5
UNION ALL
-- Recursive case: direct reports of already-found employees
SELECT e.id, e.name, e.manager_id, r.depth + 1
FROM employees e
JOIN reports r ON r.id = e.manager_id
)
SELECT depth, repeat(' ', depth) || name AS indented_name
FROM reports
ORDER BY depth, name;Computing Paths and Cycle Detection
sql
-- Build a breadcrumb path and detect cycles with an array of visited IDs
WITH RECURSIVE category_path AS (
SELECT id, name, parent_id,
ARRAY[id] AS visited,
name::text AS path
FROM categories
WHERE parent_id IS NULL
UNION ALL
SELECT c.id, c.name, c.parent_id,
cp.visited || c.id,
cp.path || ' > ' || c.name
FROM categories c
JOIN category_path cp ON cp.id = c.parent_id
WHERE NOT c.id = ANY(cp.visited) -- cycle guard
)
SELECT path FROM category_path ORDER BY path;Always include a cycle guard (WHERE NOT id = ANY(visited)) or use PostgreSQL 14+'s built-in CYCLE id SET is_cycle USING path clause. Without it, a circular reference will cause an infinite loop and an out-of-memory error.
Generating Series with Recursive CTEs
sql
-- Generate a calendar of dates (also doable with generate_series)
WITH RECURSIVE cal AS (
SELECT '2024-01-01'::date AS d
UNION ALL
SELECT d + 1 FROM cal WHERE d < '2024-01-31'
)
SELECT d FROM cal;For date series, generate_series('2024-01-01'::date, '2024-01-31'::date, '1 day') is more idiomatic in PostgreSQL. Recursive CTEs shine for graph traversal where the depth is unknown.
Ready to test yourself?
Practice PostgreSQL— quiz & coding exercises