DEV Community

umzzil nng
umzzil nng

Posted on Originally published at oraerror.com

PostgreSQL 42P19 Error: Causes and Solutions Complete Guide

PostgreSQL Error 42P19: invalid recursion — Causes, Fixes & Prevention

What Is Error 42P19?

PostgreSQL error code 42P19 (invalid_recursion) is raised when the database engine detects a structurally invalid recursive query during the parse and analysis phase. It most commonly occurs with WITH RECURSIVE CTEs when the recursive reference appears in a disallowed position, or when forbidden clauses like GROUP BY, DISTINCT, or aggregate functions are used inside the recursive term. Because this error is caught before execution begins, no data is ever processed — it's purely a query structure problem.


Top 3 Causes

1. Recursive Reference Placed in the Non-Recursive (Base) Term

The WITH RECURSIVE syntax requires a strict structure: a non-recursive base term, then UNION ALL, then a recursive term. Placing the self-reference in the wrong half immediately triggers 42P19.

-- WRONG: recursive reference in the base (non-recursive) term
WITH RECURSIVE emp_tree AS (
    SELECT e.id, e.name, e.manager_id
    FROM employees e
    JOIN emp_tree et ON e.id = et.manager_id  -- ERROR: self-ref in base term!

    UNION ALL

    SELECT e.id, e.name, e.manager_id
    FROM employees e
    WHERE e.manager_id IS NULL
)
SELECT * FROM emp_tree;

-- CORRECT: base term first, recursive term second
WITH RECURSIVE emp_tree AS (
    -- Base case: root nodes
    SELECT id, name, manager_id, 1 AS depth
    FROM employees
    WHERE manager_id IS NULL

    UNION ALL

    -- Recursive term: expand children
    SELECT e.id, e.name, e.manager_id, et.depth + 1
    FROM employees e
    JOIN emp_tree et ON e.manager_id = et.id  -- self-ref only here
)
SELECT * FROM emp_tree ORDER BY depth;
Enter fullscreen mode Exit fullscreen mode

2. Aggregate Functions, DISTINCT, or GROUP BY Inside the Recursive Term

PostgreSQL strictly forbids COUNT, SUM, DISTINCT, GROUP BY, HAVING, LIMIT, and OFFSET inside the recursive term of a CTE. These constructs prevent the engine from correctly evaluating termination conditions.

-- WRONG: GROUP BY inside the recursive term
WITH RECURSIVE category_path AS (
    SELECT id, name, parent_id, 1 AS level
    FROM categories
    WHERE parent_id IS NULL

    UNION ALL

    SELECT c.id, c.name, c.parent_id, cp.level + 1
    FROM categories c
    JOIN category_path cp ON c.parent_id = cp.id
    GROUP BY c.id, c.name, c.parent_id, cp.level  -- ERROR: forbidden here!
)
SELECT * FROM category_path;

-- CORRECT: move aggregation outside the CTE
WITH RECURSIVE category_path AS (
    SELECT id, name, parent_id, 1 AS level
    FROM categories
    WHERE parent_id IS NULL

    UNION ALL

    SELECT c.id, c.name, c.parent_id, cp.level + 1
    FROM categories c
    JOIN category_path cp ON c.parent_id = cp.id
)
-- Aggregate in the outer query only
SELECT level, COUNT(*) AS total_nodes
FROM category_path
GROUP BY level
ORDER BY level;
Enter fullscreen mode Exit fullscreen mode

3. Improper Self-Reference in Recursive Views

Creating a regular view that references itself without using CREATE RECURSIVE VIEW — or creating circular view dependencies — also raises 42P19.

-- WRONG: standard CREATE VIEW with self-reference
-- CREATE VIEW org_chart AS
--   SELECT id, name, manager_id FROM employees
--   UNION ALL
--   SELECT e.id, e.name, e.manager_id
--   FROM employees e JOIN org_chart oc ON e.manager_id = oc.id;
-- → ERROR 42P19!

-- CORRECT: use CREATE RECURSIVE VIEW
CREATE RECURSIVE VIEW org_chart (id, name, manager_id, depth) AS (
    SELECT id, name, manager_id, 0 AS depth
    FROM employees
    WHERE manager_id IS NULL

    UNION ALL

    SELECT e.id, e.name, e.manager_id, oc.depth + 1
    FROM employees e
    JOIN org_chart oc ON e.manager_id = oc.id
);

SELECT * FROM org_chart ORDER BY depth, name;
Enter fullscreen mode Exit fullscreen mode

Quick Fix Checklist

When you hit 42P19, run through this checklist:

-- Safe recursive CTE template
WITH RECURSIVE cte AS (
    -- [1] Base term: NO self-reference allowed here
    SELECT col1, col2, 1 AS depth
    FROM my_table
    WHERE <termination_condition>

    UNION ALL   -- prefer UNION ALL over UNION for performance

    -- [2] Recursive term: NO aggregates, DISTINCT, GROUP BY, LIMIT
    SELECT t.col1, t.col2, c.depth + 1
    FROM my_table t
    JOIN cte c ON <join_condition>
    WHERE c.depth < 100  -- [3] Always include a depth guard!
)
SELECT * FROM cte;
Enter fullscreen mode Exit fullscreen mode

Prevention Tips

1. Always add a depth guard and cycle detection. Even if 42P19 is resolved, a logically infinite loop will exhaust server resources at runtime. Use a depth counter and optionally track visited IDs.

WITH RECURSIVE safe_cte AS (
    SELECT id, parent_id, 1 AS depth, ARRAY[id] AS path
    FROM nodes WHERE parent_id IS NULL

    UNION ALL

    SELECT n.id, n.parent_id, s.depth + 1, s.path || n.id
    FROM nodes n
    JOIN safe_cte s ON n.parent_id = s.id
    WHERE s.depth < 50                    -- depth limit
      AND NOT (n.id = ANY(s.path))        -- cycle guard
)
SELECT * FROM safe_cte;
Enter fullscreen mode Exit fullscreen mode

2. Set statement_timeout in development. Since 42P19 is caught at parse time, logical infinite loops that slip through will run forever. Protect your server:

-- In development / testing sessions
SET statement_timeout = '10s';

-- Verify recursive query structure with EXPLAIN before running on production
EXPLAIN WITH RECURSIVE my_cte AS ( ... ) SELECT * FROM my_cte;
Enter fullscreen mode Exit fullscreen mode

Related Errors

Code Name Notes
42P20 windowing_error Invalid window function usage, can co-occur with recursive CTEs
42601 syntax_error Missing WITH RECURSIVE keyword or UNION/UNION ALL
57014 query_canceled Runtime cancel when a logical infinite loop isn't caught at parse time
54001 statement_too_complex Triggered when recursion depth or nesting becomes excessive

📖 Want a more detailed guide?
Check out the full in-depth version (Korean) on oraerror.com — includes detailed analysis, additional SQL examples, and prevention tips.

Top comments (0)