Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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!

like image 210
fengye87 Avatar asked Mar 25 '15 13:03

fengye87


People also ask

Can insert be used with select?

You can use a select-statement within an INSERT statement to insert zero, one, or more rows into a table from the result table of the select-statement. The select-statement embedded in the INSERT statement is no different from the select-statement you use to retrieve data.

What does insert into select do?

The INSERT INTO SELECT statement copies data from one table and inserts it into another table.

How do I insert data into a specific row in SQL?

If you want to add data to your SQL table, then you can use the INSERT statement. Here is the basic syntax for adding rows to your SQL table: INSERT INTO table_name (column1, column2, column3,etc) VALUES (value1, value2, value3, etc); The second line of code is where you will add the values for the rows.

How do I insert data from multiple tables into one table?

To insert records from multiple tables, use INSERT INTO SELECT statement. Here, we will insert records from 2 tables.


1 Answers

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

  • Return pre-UPDATE column values using SQL only

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

like image 88
Erwin Brandstetter Avatar answered Oct 16 '22 17:10

Erwin Brandstetter