简体   繁体   English

在单个查询中将行插入多个表,从涉及的表中进行选择

[英]INSERT rows into multiple tables in a single query, selecting from an involved table

I have two tables of the following form (ie, every foo is linked to exactly one bar). 我有两个以下表格的表格(即每个foo都链接到一个条形图)。

CREATE TABLE foo (
    id INTEGER PRIMARY KEY,
    x INTEGER NOT NULL,
    y INTEGER NOT NULL,
    ...,
    bar_id INTEGER UNIQUE NOT NULL,
    FOREIGN key (bar_id) REFERENCES bar(id)
);

CREATE TABLE bar (
    id INTEGER PRIMARY KEY,
    z INTEGER NOT NULL,
    ...
);

It's easy to copy rows in foo which meet a particular condition using a nested query: 使用嵌套查询复制满足特定条件的foo中的行很容易:

INSERT INTO foo (...) (SELECT ... FROM foo WHERE ...)

But I can't figure out how to make a copy of the associated row in bar for each row in foo and insert the id of bar into the new foo row. 但我无法弄清楚如何使相关行的拷贝在bar中的每一行foo和插入的ID bar到新foo行。 Is there any way of doing this in a single query? 有没有办法在单个查询中执行此操作?

Concrete example of desired result: 期望结果的具体例子:

-- Before query:

foo(id=1,x=3,y=4,bar_id=100)  .....  bar(id=100,z=7)
foo(id=2,x=9,y=6,bar_id=101)  .....  bar(id=101,z=16)
foo(id=3,x=18,y=0,bar_id=102) .....  bar(id=102,z=21)


-- Query copies all pairs of foo/bar rows for which x>3:

-- Originals
foo(id=1,x=3,y=4,bar_id=101)  .....  bar(id=101,z=7)
foo(id=2,x=9,y=6,bar_id=102)  .....  bar(id=102,z=16)
foo(id=3,x=18,y=0,bar_id=103) .....  bar(id=103,z=21)

-- "Copies" of foo(id=2,...) and foo(id=3,...), with matching copies of
-- bar(id=102,...) and bar(id=103,...)
foo(id=4,x=9,y=6,bar_id=104)  .....  bar(id=104,z=16)
foo(id=5,x=18,y=0,bar_id=105) .....  bar(id=105,z=21)

Final version 最终版本

... after some more info from OP. ...来自OP的更多信息之后。 Consider this demo: 考虑这个演示:

-- DROP TABLE foo; DROP TABLE bar;

CREATE TEMP TABLE bar (
 id serial PRIMARY KEY  -- using a serial column!
,z  integer NOT NULL
);

CREATE TEMP TABLE foo (
 id     serial PRIMARY KEY  -- using a serial column!
,x      integer NOT NULL
,y      integer NOT NULL
,bar_id integer UNIQUE NOT NULL REFERENCES bar(id)
);

Insert values - bar first. 插入值 - 先bar
It would be very helpful if you provided test data in your question like this! 如果你在这样的问题中提供测试数据会非常有用

INSERT INTO bar (id,z) VALUES
 (100, 7)
,(101,16)
,(102,21);

INSERT INTO foo (id, x, y, bar_id) VALUES
 (1, 3,4,100)
,(2, 9,6,101)
,(3,18,0,102);

Set sequences to current values or we get duplicate key violations: 将序列设置为当前值或我们得到重复的密钥违规:

SELECT setval('foo_id_seq', 3);
SELECT setval('bar_id_seq', 102);

Checks: 检查:

-- SELECT nextval('foo_id_seq')
-- SELECT nextval('bar_id_seq')
-- SELECT * from bar;
-- SELECT * from foo;

Query: 查询:

WITH a AS (
    SELECT f.x, f.y, bar_id, b.z
    FROM   foo f
    JOIN   bar b ON b.id = f.bar_id
    WHERE  x > 3
    ),b AS (
    INSERT INTO bar (z)
    SELECT z
    FROM   a
    RETURNING z, id AS bar_id
    )
INSERT INTO foo (x, y, bar_id)
SELECT a.x, a.y, b.bar_id
FROM   a
JOIN   b USING (z);

This should do what your last update describes. 这应该是您上次更新描述的内容。

The query assumes that z is UNIQUE . 该查询假定zUNIQUE If z is not unique, it gets more complex. 如果z不是唯一的,它会变得更复杂。 Refer to Query 2 in this related answer for a ready solution using the window function row_number() in this case. 在这种情况下,请参考此相关答案中的查询2,以获得使用窗口函数row_number()的现成解决方案。

Also, consider replacing the 1:1 relation between foo and bar with a single united table. 另外,考虑用单个联合表替换foobar之间的1:1关系


Data modifying CTE 数据修改CTE

Second answer after more info. 更多信息后的第二个答案。

If you want to add rows to foo and bar in a single query, you can use a data modifying CTE since PostgreSQL 9.1 : 如果要在单个查询中向foo bar添加行,则可以使用自PostgreSQL 9.1以来修改CTE数据

WITH x AS (
    INSERT INTO bar (col1, col2)
    SELECT f.col1, f.col2
    FROM   foo f
    WHERE  f.id BETWEEN 12 AND 23 -- some filter
    RETURNING col1, col2, bar_id  -- assuming bar_id is a serial column
    )
INSERT INTO foo (col1, col2, bar_id)
SELECT col1, col2, bar_id
FROM   x;

I draw values from foo , insert them in bar , have them returned together with an auto-generated bar_id and insert that into foo . 我从foo绘制值,将它们插入bar ,让它们与自动生成的bar_id一起返回并将插入foo You can use any other data, too. 您也可以使用任何其他数据。

Here is a working demo to play with on sqlfiddle . 这是一个在sqlfiddle上玩工作演示


Basics 基本

Original answer with basic information before clarifications. 在澄清之前提供基本信息的原始答案。
The basic form is: 基本形式是:

INSERT INTO foo (...)
SELECT ... FROM foo WHERE ...

No parenthesis needed. 不需要括号。 You can do the same with any table 你可以对任何表做同样的事情

INSERT INTO foo (...)
SELECT ... FROM bar WHERE ...

And you can join to the table you insert into in the SELECT: 您可以加入到SELECT中插入的表:

INSERT INTO foo (...)
SELECT f.col1, f.col2, .. , b.bar_id
FROM   foo f
JOIN   bar b USING (foo_id);  -- present in foo and bar

It's just a SELECT like any other - that can include the table you are inserting into. 它只是一个SELECT,就像其他任何一样 - 可以包括你插入的表。 The rows are first read, and then inserted. 首先读取行,然后插入。

if id of bar is serial and have default value nextval('bar_id_seq'::regclass) you can manually call this function to get new ids in cte 如果bar id是serial并且具有默认值nextval('bar_id_seq'::regclass)您可以手动调用此函数以获取nextval('bar_id_seq'::regclass)新ID

with
s_bar as (
  SELECT id, z, nextval('bar_id_seq'::regclass) new_id
  FROM   bar
  WHERE  ...
),
s_foo as (
  SELECT x, y, bar_id
  FROM   foo
  WHERE  ...
),
i_bar as (
  INSERT INTO bar (id, z)
  SELECT new_id, z
  FROM   s_bar
),
i_foo as (
  INSERT INTO foo (x, y, bar_id)
  SELECT f.x, f.y, b.new_id
  FROM   s_foo f
  JOIN   s_bar b on b.id = f.bar_id
)
SELECT 1

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM