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

时间:2012-05-06 15:30:19

标签: sql postgresql insert common-table-expression

我有两个以下表格的表格(即每个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,
    ...
);

使用嵌套查询复制满足特定条件的foo中的行很容易:

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

但我无法弄清楚如何在bar中为foo中的每一行制作关联行的副本,并将bar的ID插入新foo行。有没有办法在单个查询中执行此操作?

期望结果的具体例子:

-- 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)

2 个答案:

答案 0 :(得分:29)

最终版

...来自OP的更多信息之后。考虑一下这个演示:

-- 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)
);

首先插入值 - bar 如果你在这样的问题中提供测试数据,那将是非常有用

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);

将序列设置为当前值,否则会出现重复的密钥违规行为:

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

检查:

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

查询:

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);

这应该是您上一次更新所描述的内容。

该查询假设z UNIQUE 。如果z不唯一,则会变得更复杂。在这种情况下,请使用窗口函数row_number()参考Query 2 in this related answer以获得现成的解决方案。

另外,请考虑使用单个联合表替换foobar之间的 1:1关系


修改CTE的数据

更多信息后的第二个答案。

如果要在单个查询中向foo bar添加行,则可以使用data modifying CTE,因为PostgreSQL 9.1

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;

我从foo中提取值,将其插入bar,将它们与自动生成的bar_id一起返回,然后将 插入{{1} }}。您也可以使用任何其他数据。

这是working demo to play with on sqlfiddle


基本

在澄清之前提供基本信息的原始答案 基本形式是:

foo

不需要括号。 你可以对任何表格做同样的事情

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

您可以在SELECT:

中加入您插入的表格
INSERT INTO foo (...)
SELECT ... FROM bar WHERE ...

它只是一个像其他任何一样的SELECT - 可以包括你插入的表。首先读取行,然后插入。

答案 1 :(得分:0)

如果id bar是序列号且默认值为nextval('bar_id_seq'::regclass),您可以手动调用此函数在cte中获取新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