-- Planning time grows as N^3 with the number of subqueries that share a base name -- (PostgreSQL 19 / master, since commit 8c49a484e8e). Self-contained; run with -- psql -X -f repro.sql. Nothing is executed: EXPLAIN without ANALYZE only plans. -- 1. The names. Each uncorrelated scalar sublink becomes an InitPlan named by -- choose_plan_name(glob, "expr", true): expr_1, expr_2, expr_3. EXPLAIN (COSTS OFF) SELECT ARRAY[(SELECT 1 WHERE random() >= 0), (SELECT 2 WHERE random() >= 0), (SELECT 3 WHERE random() >= 0)]; -- Planning time of a statement, from EXPLAIN (SUMMARY), without printing the plan. CREATE FUNCTION pg_temp.planning_ms(stmt text) RETURNS numeric LANGUAGE plpgsql AS $$ DECLARE plan json; BEGIN EXECUTE 'EXPLAIN (SUMMARY, FORMAT JSON) ' || stmt INTO plan; RETURN round((plan -> 0 ->> 'Planning Time')::numeric, 1); END $$; -- 2. The cost: N sublinks (names expr_1 .. expr_N) and an N-arm UNION (every arm -- planned as its own subquery, names setop_1 .. setop_N). Doubling N multiplies -- planning time by about 8. SELECT n, pg_temp.planning_ms((SELECT 'SELECT ARRAY[' || string_agg(format('(SELECT %s WHERE random() >= 0)', i), ', ') || ']' FROM generate_series(1, n) AS i)) AS sublinks_ms, pg_temp.planning_ms((SELECT string_agg(format('SELECT %s', i), ' UNION ') FROM generate_series(1, n) AS i)) AS union_ms FROM (VALUES (250), (500), (1000), (2000)) AS v(n);