【问题标题】:Copy datasets with n:m-relation复制具有 n:m 关系的数据集
【发布时间】:2012-04-17 11:33:32
【问题描述】:

我想使用单个 SQL 语句

insert into T (...) select ... from T where ...

复制大量数据集。我的问题是从表 T 到其他表有 N:M 关系,这些也必须复制。如果我不知道哪个原始数据集属于哪个复制数据集,我该怎么做?让我举例说明。

之前的数据库内容:

T:

ID  | COL1 | COL2    
-----------------
1   | A    | B
2   | C    | D

N:M-table 从表 T 引用表 U(表 U 未显示):

T   | U              
---------
1   | 100
1   | 101
2   | 100
2   | 102

我的复制操作,其中 [???] 是我不知道的部分:

insert into T (COL1, COL2) select COL1, COL2 from T
insert into NM (T, U) select [???]

之后的数据库内容:

T:

ID  | COL1 | COL2
-----------------
1   | A    | B
2   | C    | D
3   | A    | B
4   | C    | D

N:M 表:

T   | U
---------
1   | 100
1   | 101
2   | 100
2   | 102
3   | 100
3   | 101
4   | 100
4   | 102

注意:

  • 我有数千个数据集(不仅仅是两个)
  • 我想使用“插入...选择”来获得更好的性能

【问题讨论】:

  • 我不明白这个问题,尤其是最后一句话。只需制作您想要的任何select - 根据需要连接多个表,这将生成一个结果表,并将插入该表。就是这样!
  • @Tomas,对不起,我误解了这个问题:(
  • @Tomas:我想从一个表复制到同一个表中。
  • 这应该不是问题——我有一个similar question,应该不是问题。所以准备你的select子查询,就像你通常做的那样,然后试着把它放在插入语句下,不要担心。
  • 我添加了一个小例子,在您的“非常相似的问题”中您进行了更新。我想做一个插入,但不知道插入的 id。

标签: mysql sql postgresql insert many-to-many


【解决方案1】:

如果您有幸运行当前的 PostgreSQL 9.1,则可以使用新的data-modifying CTEs 使用单个命令 提供优雅而快速的解决方案。

不支持 Common Table Expressions (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 已经提供的相同想法,但都在一个查询中。
  • 如果当前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;

有一个有点similar question recently,我提供了一个有点相似的答案。加上没有 CTE 和窗口函数的版本 8.3 的替代品。

【讨论】:

    【解决方案2】:

    步骤 1. 锁定(两个)表或确保只有此脚本在运行。禁用 FK 检查。

    第 2 步。按以下顺序使用这两个 INSERT 语句:

    INSERT INTO NM 
        (T, U) 
      SELECT 
          T + maxID, U
      FROM 
          NM
        CROSS JOIN
          ( SELECT MAX(ID) AS maxID 
            FROM T
          ) AS m
    
    INSERT INTO T 
        (ID, COL1, COL2) 
      SELECT 
          ID+maxID, COL1, COL2 
      FROM 
          T
        CROSS JOIN
          ( SELECT MAX(ID) AS maxID 
            FROM T
          ) AS m
    

    第 3 步。重新启用 FK。

    【讨论】:

    • 之后不要忘记重置序列列的序列。
    猜你喜欢
    • 2012-09-10
    • 1970-01-01
    • 2014-09-29
    • 1970-01-01
    • 1970-01-01
    • 2020-12-07
    • 1970-01-01
    • 1970-01-01
    • 2017-10-14
    相关资源
    最近更新 更多