Sweaty Imposter

Org Chart Depth With a Recursive CTE

SQLRecursive CTESeniorStaffUpdated
Given a CEO with manager_id IS NULL, return every employee in the org along with their depth (CEO = 0, direct reports = 1, etc.).

Schema

CREATE TABLE employees (
  id         BIGINT PRIMARY KEY,
  name       TEXT NOT NULL,
  manager_id BIGINT REFERENCES employees(id)
);

Drill This: Your Query First

The Correct Query

Option (b)

WITH RECURSIVE tree AS (
  SELECT id, name, 0 AS depth
  FROM employees
  WHERE manager_id IS NULL
  UNION ALL
  SELECT e.id, e.name, t.depth + 1
  FROM employees e
  JOIN tree t ON e.manager_id = t.id
)
SELECT * FROM tree;
A recursive CTE with the RECURSIVE keyword anchors on the CEO and walks down one level per iteration. (c) omits RECURSIVE, so Postgres treats 'tree' inside the second SELECT as an unknown table and the query fails. (a) caps depth at 1. (d) just counts how many managers a person has (0 or 1), which is not depth.

Going Deeper

Postgres requires the RECURSIVE keyword on the CTE even though only one branch is recursive. Always end the recursive branch with a guaranteed terminating join condition to avoid infinite loops on cyclic data.

The Options That Fail

The explanation above says why each of these breaks.

Option (a)

SELECT e.id, e.name, 0 AS depth
FROM employees e
WHERE e.manager_id IS NULL
UNION ALL
SELECT e.id, e.name, 1
FROM employees e
WHERE e.manager_id IS NOT NULL;

Option (c)

WITH tree AS (
  SELECT id, name, 0 AS depth FROM employees WHERE manager_id IS NULL
  UNION ALL
  SELECT e.id, e.name, t.depth + 1 FROM employees e JOIN tree t ON e.manager_id = t.id
)
SELECT * FROM tree;

Option (d)

SELECT e.id, e.name, COUNT(m.id) AS depth
FROM employees e
LEFT JOIN employees m ON m.id = e.manager_id
GROUP BY e.id, e.name;