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

后端 未结 1 1043
粉色の甜心
粉色の甜心 2020-11-30 16:06

Is it possible to have an non-select query (update, insert, delete) embedded into another query?

Something like (an insert inside a select)

A single          


        
相关标签:
1条回答
  • 2020-11-30 16:25

    Basic answer

    There are CTEs (Common Table Expressions) in Postgres (like in any major modern RDBMS except MySQL). Since version 9.1 that includes data-modifying CTEs. Those can be "nested".
    Update: MySQL 8.0 finally adds CTEs.

    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 (performance cost) 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;
    

    SQL Fiddle

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

    0 讨论(0)
提交回复
热议问题