sqlpostgresqlsql-insertsql-returning

INSERT INTO ... FROM SELECT ... RETURNING id mappings


I'm using PostgreSQL 9.3.

I want to duplicate some of the db records. Since I'm using an auto-increment pk id for the table, I want to get back the id mappings from the generated ids of duplicated records to the original ones. For example, say I have a table posts with 2 records in it:

 [{'id': 1, 'title': 'first'}
, {'id': 2. 'title': 'second'}]

With SQL:

INSERT INTO posts (title) SELECT title FROM posts RETURNING id, ??

I expect to see mappings like:

 [{'id': 3, 'from_id': 1}
, {'id': 4, 'from_id': 2}]

Any idea on how to fill in the question marks above to make it work? Thanks a lot!


Solution

  • This would be simpler for UPDATE, where additional rows joined into the update are visible to the RETURNING clause:

    The same is currently not possible for INSERT. The manual:

    The expression can use any column names of the table named by table_name

    table_name being the target of the INSERT command.

    You can use (data-modifying) CTEs to get this to work.
    Assuming title to be unique per query, else you need to do more:

    WITH sel AS (
       SELECT id, title
       FROM   posts
       WHERE  id IN (1,2)   -- select rows to copy
       )
    , ins AS (
       INSERT INTO posts (title)
       SELECT title FROM sel
       RETURNING id, title
     )
    SELECT ins.id, sel.id AS from_id
    FROM   ins
    JOIN   sel USING (title);
    

    If title is not unique per query (but at least id is unique per table):

    WITH sel AS (
       SELECT id, title, row_number() OVER (ORDER BY id) AS rn
       FROM   posts
       WHERE  id IN (1,2)   -- select rows to copy
       ORDER  BY id
       )
    , ins AS (
       INSERT INTO posts (title)
       SELECT title FROM sel ORDER  BY id  -- ORDER redundant to be sure
       RETURNING id
     )
    SELECT i.id, s.id AS from_id
    FROM  (SELECT id, row_number() OVER (ORDER BY id) AS rn FROM ins) i
    JOIN   sel s USING (rn);
    

    This second query relies on the undocumented implementation detail that rows are inserted in the order provided. It works in all current versions of Postgres and is probably not going to break.

    db<>fiddle here
    Old sqlfiddle