如果您有幸运行当前的PostgreSQL 9.1,那么有一个优雅而快速的解决方案,使用新的数据修改 CTE的单个命令。
不支持通用表表达式 (CTE)的MySQL就没有这样的运气,更不用说数据修改 CTE。
假设(col1, col2)
最初是唯一的:
查询 1
- 在这种情况下,您可以轻松地从表中选择任意切片。
t.id
不会浪费任何序列号。
WITH s AS (
SELECT id, col1, col2
FROM t
-- WHERE some condition
)
,i AS (
INSERT INTO t (col1, col2)
SELECT col1, col2 -- I gather from comments that id is a serial column
FROM s
RETURNING id, col1, col2
)
INSERT INTO tu (t, u)
SELECT i.id, tu.u
FROM tu
JOIN s ON tu.t = s.id
JOIN i USING (col1, col2);
如果(col1, col2)
不是唯一的,我看到了另外两种方式:
查询 2
WITH s AS (
SELECT id, col1, col2
, row_number() OVER (PARTITION BY col1, col2) AS rn
FROM t
-- WHERE some condition
)
,i AS (
INSERT INTO t (col1, col2)
SELECT col1, col2
FROM s
RETURNING id, col1, col2
)
,r AS (
SELECT *
, row_number() OVER (PARTITION BY col1, col2) AS rn
FROM i
)
INSERT INTO tu (t, u)
SELECT r.id, tu.u
FROM r
JOIN s USING (col1, col2, rn) -- match exactly one id per row
JOIN tu ON tu.t = s.id;
查询 3
- 这是基于@ypercube 已经提供的相同想法,但都在一个查询中。
- 如果 current 的数字空间中存在漏洞,
t.id
则将相应地为新行烧录序列号。
- 不要忘记将您的序列重置为超出新的最大值,否则您将在从序列中提取
t
默认值的新插入中得到重复的键错误。id
我将此作为最后一步集成到命令中。这种方式最快最安全。
WITH s AS (
SELECT max(id) AS max_id
FROM t
)
,i AS (
INSERT INTO t (id, col1, col2)
SELECT id + s.max_id, col1, col2
FROM t, s
)
,j AS (
INSERT INTO tu (t, u)
SELECT tu.t + s.max_id, tu.u
FROM tu, s
)
SELECT setval('t_id_seq', s.max_id + s.max_id)
FROM s;
手册中有关setval()的详细信息。
测试设置
进行快速测试。
CREATE TEMP TABLE t (id serial primary key, col1 text, col2 text);
INSERT INTO t (col1, col2) VALUES
('A', 'B')
,('C', 'D');
CREATE TEMP TABLE tu (t int, u int);
INSERT INTO tu VALUES
(1, 100)
,(1, 101)
,(2, 100)
,(2, 102);
SELECT * FROM t;
SELECT * FROM tu;
最近有一个类似的问题,我提供了一个类似的答案。加上不带 CTE 和窗口函数的8.3版的替代方案。