13

假设我有表 A 和表 B。表 B 引用表 A。我想深复制表 A 和表 B 中的一组行。我希望所有新表 B 行都引用新表 A 行。

请注意,我没有将行复制到任何其他表中。表 A 中的行将复制到表 A 中,表 B 中的行将复制到表 B 中。

如何确保将外键引用作为副本的一部分进行重新调整?

为了澄清,我试图找到一种通用的方法来做到这一点。我给出的示例涉及两个表,但实际上依赖关系图可能要复杂得多。即使是动态生成 SQL 来完成工作的通用方法也可以。

更新:

人们在问为什么这是必要的,所以我将提供一些背景信息。这可能太多了,但这里有:

我正在使用已移至客户端-服务器模型的旧桌面应用程序。但是,该应用程序仍然使用基本的内部二进制文件格式来存储其表的数据。数据文件只是一个标题,后面跟着一系列行,每一行只是二进制序列化的字段值,其顺序由模式文本文件确定。唯一的好处是它非常快。其他方面都很糟糕。我正在将应用程序移动到 SQL Server 并尽量不降低性能。

这是一种调度应用程序;数据对任何人都不重要,也不需要审计跟踪等。这不是海量数据,如果数据库变得太大,我们不一定需要保留非常旧的数据。

他们习惯的一项功能是能够复制整个时间表以创建他们可以处理的“假设”场景。任何用户都可以根据需要多次执行此操作。在旧数据库中,每个计划的数据文件都存储在它们自己的数据文件夹中,按名称标识。因此,复制计划就像复制数据文件夹并重命名一样简单。

必须能够使用 SQL Server 有效地做同样的事情,否则迁移将无法进行。也许您认为我只能复制实际更改的数据以避免冗余;但老实说,这听起来太复杂了,不可行。

为了将另一个扳手投入其中,可以有一个时间表数据文件夹的层次结构。因此,一个数据文件夹可能包含一个数据文件夹,其中可能包含一个数据文件夹。并且复制可以发生在任何级别。

在 SQL Server 中,我正在实现一个嵌套集层次结构来模仿这一点。我有一个这样的 DATA_SET 表:

CREATE TABLE dbo.DATA_SET
(
    DATA_SET_ID UNIQUEIDENTIFIER PRIMARY KEY,
    NAME NVARCHAR(128) NOT NULL,
    LFT INT NOT NULL,
    RGT INT NOT NULL
)

因此,存在数据集的树形结构。每个数据集代表一个时间表,并且可能包含子数据集。每个表中的每一行都有一个 DATA_SET_ID FK 引用,指示它属于哪个数据集。每当我复制数据集时,我都会将表中该数据集的所有行以及所有其他数据集复制到同一个表中,但会引用新的数据集。

因此,这是一个简单的具体示例:

CREATE TABLE FOO
(
    FOO_ID BIGINT PRIMARY KEY,
    DATA_SET_ID BIGINT FOREIGN KEY REFERENCES DATA_SET(DATA_SET_ID) NOT NULL
)


CREATE TABLE BAR
(
    BAR_ID BIGINT PRIMARY KEY,
    DATA_SET_ID BIGINT FOREIGN KEY REFERENCES DATA_SET(DATA_SET_ID) NOT NULL,
    FOO_ID UNIQUEIDENTIFIER PRIMARY KEY
)

INSERT INTO FOO
SELECT 1, 1 UNION ALL
SELECT 2, 1 UNION ALL
SELECT 3, 1 UNION ALL

INSERT INTO BAR
SELECT 1, 1, 1
SELECT 2, 1, 2
SELECT 3, 1, 3

因此,假设我将数据集 1 复制到 ID 为 2 的新数据集。复制后,表将如下所示:

FOO
FOO_ID, DATA_SET_ID
1    1
2    1
3    1
4    2
5    2
6    2

BAR
BAR_ID, DATA_SET_ID, FOO_ID
1    1    1
2    1    2
3    1    3
4    2    4
5    2    5
6    2    6

如您所见,新的 BAR 行正在引用新的 FOO 行。这不是我要询问的 DATA_SET_ID 的重新布线。我问的是一般重新布线外键。

所以,这肯定是太多的信息,但你去了。

我敢肯定有很多关于像这样批量复制数据的想法的性能问题。桌子不会很​​大。我不希望任何表中的记录超过 1000 条,而且大多数表会比这小得多。可以直接删除旧数据集而不会产生任何影响。

谢谢,泰德兹

4

2 回答 2

2

这是一个包含三个表的示例,可能可以帮助您入门。

数据库模式

CREATE TABLE users
    (user_id int auto_increment PRIMARY KEY, 
     user_name varchar(32));
CREATE TABLE agenda
    (agenda_id int auto_increment PRIMARY KEY, 
     `user_id` int, `agenda_name` varchar(7));
CREATE TABLE events
    (event_id int auto_increment PRIMARY KEY, 
     `agenda_id` int, 
     `event_name` varchar(8));

SP 用他的议程和事件记录克隆用户

DELIMITER $$
CREATE PROCEDURE clone_user(IN uid INT)
BEGIN
    DECLARE last_user_id INT DEFAULT 0;

    INSERT INTO users (user_name)
    SELECT user_name
      FROM users
     WHERE user_id = uid;

    SET last_user_id = LAST_INSERT_ID();

    INSERT INTO agenda (user_id, agenda_name)
    SELECT last_user_id, agenda_name
      FROM agenda
     WHERE user_id = uid;

    INSERT INTO events (agenda_id, event_name)
    SELECT a3.agenda_id_new, e.event_name
      FROM events e JOIN
    (SELECT a1.agenda_id agenda_id_old, 
           a2.agenda_id agenda_id_new
      FROM
    (SELECT agenda_id, @n := @n + 1 n 
       FROM agenda, (SELECT @n := 0) n 
      WHERE user_id = uid 
      ORDER BY agenda_id) a1 JOIN
    (SELECT agenda_id, @m := @m + 1 m 
       FROM agenda, (SELECT @m := 0) m 
      WHERE user_id = last_user_id 
      ORDER BY agenda_id) a2 ON a1.n = a2.m) a3 
         ON e.agenda_id = a3.agenda_id_old;
END$$
DELIMITER ;

克隆用户

CALL clone_user(3);

这是SQLFiddle演示。

于 2013-06-05T05:14:36.607 回答
1

我最近发现自己需要解决类似的问题;也就是说,我需要复制表(表 A)中的一组行以及相关表中具有指向表 A 主键的外键的所有行。我使用的是 Postgres,因此确切的查询可能会有所不同,但总体方法是相同的。这种方法最大的好处是可以递归使用,可以无限深

TLDR:方法看起来像这样

1) find all the related table/columns of Table A
2) copy the necessary data into temporary tables
3) create a trigger and function to propagate primary key column 
   updates to related foreign keys columns in the temporary tables
4) update the primary key column in the temporary tables to the next 
   value in the auto increment sequence
5) Re-insert the data back into the source tables, and drop the 
   temporary tables/triggers/function

1) 第一步是查询信息模式以查找所有引用表 A 的表和列。在 Postgres 中,这可能如下所示:

SELECT tc.table_name, kcu.column_name
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name
JOIN information_schema.constraint_column_usage ccu
ON ccu.constraint_name = tc.constraint_name
WHERE constraint_type = 'FOREIGN KEY'
AND ccu.table_name='<Table A>'
AND ccu.column_name='<Primary Key>'

2)接下来,我们需要从表 A 以及引用表 A 的任何其他表中复制数据 - 假设有一个称为表 B。要开始此过程,让我们为这些表中的每一个创建一个临时表,然后我们将填充它与我们需要复制的数据。这可能如下所示:

CREATE TEMP TABLE temp_table_a AS (
    SELECT * FROM <Table A> WHERE ...
)

CREATE TEMP TABLE temp_table_b AS (
    SELECT * FROM <Table B> WHERE <Foreign Key> IN (
        SELECT <Primary Key> FROM temp_table_a
    )
)

3)我们现在可以定义一个函数,它将主键列更新级联到相关的外键列,并在主键列更改时执行触发器。例如:

CREATE OR REPLACE FUNCTION cascade_temp_table_a_pk()
RETURNS trigger AS
$$
BEGIN
   UPDATE <Temp Table B> SET <Foreign Key> = NEW.<Primary Key>
   WHERE <Foreign Key> = OLD.<Primary Key>;

   RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trigger_temp_table_a
AFTER UPDATE
ON <Temp Table A>
FOR EACH ROW
WHEN (OLD.<Primary Key> != NEW.<Primary Key>)
EXECUTE PROCEDURE cascade_temp_table_a_pk();

4)现在我们只是将主键列更新为源表序列的下一个值()。这将激活触发器,更新将级联到 . 在 Postgres 中,您可以执行以下操作:

UPDATE <Temp Table A>
SET <Primary Key> = nextval(pg_get_serial_sequence('<Table A>', '<Primary Key>'))

5) 将临时表中的数据插回源表中。然后删除临时表、触发器和函数。

INSERT INTO <Table A> (SELECT * FROM <Temp Table A>)
INSERT INTO <Table B> (SELECT * FROM <Temp Table B>)
DROP TRIGGER trigger_temp_table_a
DROP cascade_temp_table_a_pk()

可以采用这种通用方法并将其转换为可以递归调用的脚本,以便无限深入。我最终使用 python 做到了这一点(我们的应用程序使用的是 django,所以我能够使用 django ORM 来简化其中的一些操作)

于 2018-01-24T21:50:18.533 回答