【问题标题】:INSERT rows into multiple tables in a single query, selecting from an involved table在单个查询中将行插入到多个表中,从涉及的表中进行选择
【发布时间】:2012-05-06 15:30:19
【问题描述】:

我有两个如下形式的表格(即每个 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 ...)

但我不知道如何为foo 中的每一行复制bar 中的关联行并将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)

【问题讨论】:

  • 您是否查看过带有插入规则的可更新视图? archives.postgresql.org/pgsql-admin/2006-05/msg00290.php
  • bar.z 的值是否唯一?
  • 嗯,谢谢你的额外回答。我想不可能在单个查询中以任何一般方式执行此操作。
  • 为什么这么说?我提供了相反的证据。
  • 嗯,有一个简单的解释。我在您添加答案的链接之前说过,它并不假定“z”是唯一的:) 但是,似乎唯一实用的解决方案是合并这两个表,但是,正如您所建议的那样。

标签: sql postgresql insert common-table-expression


【解决方案1】:

最终版本

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

这应该和你上次更新描述的一样。

查询假定zUNIQUE。如果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 一起返回,然后将那个插入foo。您也可以使用任何其他数据。

这是working demo to play with on sqlfiddle


基础知识

在澄清之前包含基本信息的原始答案。
基本形式是:

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

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

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

您可以加入到您在 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

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

【讨论】:

  • 但是这个查询不会复制bar 行,对吧?新的foo 行将链接到旧的bar 行,而不是它们的副本。
  • @foldl:我没有关注。此查询将INSERTSELECT 返回到表foo 中。 foo 中的新行与任何其他行一样。 “链接”是什么意思?
  • 我的意思是查询不会向bar 添加任何新行。我想为foo 中插入的每个新行在bar 中插入一个新行。
  • @foldl:查询将行添加到 foo,仅此而已。您只能在 PostgreSQL 中的单个 INSERT 语句中向 one 表添加行。您可以将多个 INSERT 与新的数据修改 CTE 链接在一起。如果您在您的问题中阐明您想要实现的目标,将在我的回答中添加一个示例。请编辑最后一段。
  • 是的,我知道单个INSERT语句只能向一个表添加行;这就是为什么我首先提出这个问题。我不确定如何修改问题。我只想复制foo 中行的子集,并制作bar 中行的相应副本(以便foo 中的新行通过bar_id 适当链接到bar 中的新行)。
【解决方案2】:

如果barid 是串行的并且具有默认值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

【讨论】:

    猜你喜欢
    • 2013-11-15
    • 2014-05-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-14
    • 2018-07-27
    • 2021-05-12
    • 1970-01-01
    相关资源
    最近更新 更多