Are SELECT type queries the only type that can be nested?

Basic answer

There are CTEs (Common Table Expressions) in Postgres – like in any major modern RDBMS, That includes MySQL since version 8.0.
Since version 9.1 Postgres also features data-modifying CTEs, which can be “nested”.

Unlike subqueries CTEs pose as optimization barriers. The query planner cannot inline trivial commands into the main command or reorder joins among main query and CTEs. The same is possible with subqueries. May be (very) good or (very) bad for performance, it depends.
Either way, CTEs require a bit more overhead than subqueries.

Update: Postgres 12 can finally inline plain CTEs in the main query.

Details you did not ask for

Your question is very basic, the above is probably enough to answer. But I’ll add a bit for advanced users (and a code example to show the syntax).

All CTEs of a query are based off the same snapshot of the database. The next CTE can reuse the output of previous CTEs (internal temporary tables), but effects on underlying tables are invisible for other CTEs. The sequence of multiple CTEs is arbitrary unless something is returned with the RETURNING clause for INSERT, UPDATE, DELETE – irrelevant for SELECT, since it does not change anything and just reads from the snapshot.

That can have subtle effects with multiple updates that would be affecting the same row. Only one update can affect each row. Which one is influenced by the sequence of CTEs.

Try to predict the outcome:

CREATE TEMP TABLE t (t_id int, txt text);
INSERT INTO t VALUES (1, 'foo'), (2, 'bar'), (3, 'baz');

WITH sel AS (SELECT * FROM t)
   , up1 AS (UPDATE t SET txt = txt || '1' WHERE t_id = 1 RETURNING *)
   , up2 AS (UPDATE t SET txt = t.txt || '2'
             FROM   up1
             WHERE  up1.t_id = t.t_id
             RETURNING t.*)
   , ins AS (INSERT INTO t VALUES (4, 'bamm'))
   , up3 AS (UPDATE t SET txt = txt || '3' RETURNING *)
SELECT 'sel' AS source, * FROM sel
UNION ALL
SELECT 'up1' AS source, * FROM up1
UNION ALL
SELECT 'up2' AS source, * FROM up2
UNION ALL
SELECT 'up3' AS source, * FROM up3
UNION ALL
SELECT 't'   AS source, * FROM t;

db<>fiddle here
Old sqlfiddle

Don’t be disappointed, I doubt there are many here who could have done it. 🙂
The gist of it: avoid conflicting commands in CTEs.

Leave a Comment