如何在保留FK关系的同时将迁移数据复制到带有Identity列的新表中?


8

我想将数据从一个数据库迁移到另一个数据库。表模式完全相同:

CREATE TABLE Customers(
    [Id] INT NOT NULL PRIMARY KEY IDENTITY,
    (some other columns ......)
);

CREATE TABLE Orders(
    [Id] INT NOT NULL PRIMARY KEY IDENTITY,
    [CustomerId] INT NOT NULL,
    (some other columns ......),
    CONSTRAINT [FK_Customers_Orders] FOREIGN KEY ([CustomerId]) REFERENCES [Customers]([Id])
)

这两个数据库具有不同的数据,因此在两个数据库中,同一表的新标识密钥将有所不同。那不是问题;我的目标是将新数据添加到现有数据中,而不是完全替换整个表中的所有数据。但是,我想保留插入数据的所有父子关系。

如果我使用SSMS的“生成脚本”功能,则脚本将尝试使用相同的ID进行插入,这将与目标数据库中的现有数据发生冲突。如何仅使用数据库脚本复制数据?

我希望目标处的身份列从其上一个值正常继续。

Customers没有任何其他UNIQUE NOT NULL约束。它是确定在其他列重复数据(我使用的是CustomersOrders只是作为一个例子在这里,所以我没有解释整个故事)。问题是关于任何一对N关系。

Answers:


11

这是一种可以轻松扩展到三个相关表的方法。

使用MERGE将数据插入复制表中,以便可以将旧的和新的IDENTITY值输出到控制表中,并将它们用于相关的表映射。

实际的答案只有两个create table语句和三个合并。剩下的就是样本数据的设置和拆卸。

USE tempdb;

--## Create test tables ##--

CREATE TABLE Customers(
    [Id] INT NOT NULL PRIMARY KEY IdENTITY,
    [Name] NVARCHAR(200) NOT NULL
);

CREATE TABLE Orders(
    [Id] INT NOT NULL PRIMARY KEY IdENTITY,
    [CustomerId] INT NOT NULL,
    [OrderDate] DATE NOT NULL,
    CONSTRAINT [FK_Customers_Orders] FOREIGN KEY ([CustomerId]) REFERENCES [Customers]([Id])
);

CREATE TABLE OrderItems(
    [Id] INT NOT NULL PRIMARY KEY IdENTITY,
    [OrderId] INT NOT NULL,
    [ItemId] INT NOT NULL,
    CONSTRAINT [FK_Orders_OrderItems] FOREIGN KEY ([OrderId]) REFERENCES [Orders]([Id])
);

CREATE TABLE Customers2(
    [Id] INT NOT NULL PRIMARY KEY IdENTITY,
    [Name] NVARCHAR(200) NOT NULL
);

CREATE TABLE Orders2(
    [Id] INT NOT NULL PRIMARY KEY IdENTITY,
    [CustomerId] INT NOT NULL,
    [OrderDate] DATE NOT NULL,
    CONSTRAINT [FK_Customers2_Orders2] FOREIGN KEY ([CustomerId]) REFERENCES [Customers2]([Id])
);

CREATE TABLE OrderItems2(
    [Id] INT NOT NULL PRIMARY KEY IdENTITY,
    [OrderId] INT NOT NULL,
    [ItemId] INT NOT NULL,
    CONSTRAINT [FK_Orders2_OrderItems2] FOREIGN KEY ([OrderId]) REFERENCES [Orders2]([Id])
);

--== Populate some dummy data ==--

INSERT Customers(Name)
VALUES('Aaberg'),('Aalst'),('Aara'),('Aaren'),('Aarika'),('Aaron'),('Aaronson'),('Ab'),('Aba'),('Abad');

INSERT Orders(CustomerId, OrderDate)
SELECT Id, Id+GETDATE()
FROM Customers;

INSERT OrderItems(OrderId, ItemId)
SELECT Id, Id*1000
FROM Orders;

INSERT Customers2(Name)
VALUES('Zysk'),('Zwiebel'),('Zwick'),('Zweig'),('Zwart'),('Zuzana'),('Zusman'),('Zurn'),('Zurkow'),('ZurheIde');

INSERT Orders2(CustomerId, OrderDate)
SELECT Id, Id+GETDATE()+20
FROM Customers2;

INSERT OrderItems2(OrderId, ItemId)
SELECT Id, Id*1000+10000
FROM Orders2;

SELECT * FROM Customers JOIN Orders ON Orders.CustomerId = Customers.Id JOIN OrderItems ON OrderItems.OrderId = Orders.Id;

SELECT * FROM Customers2 JOIN Orders2 ON Orders2.CustomerId = Customers2.Id JOIN OrderItems2 ON OrderItems2.OrderId = Orders2.Id;

--== ** START ACTUAL ANSWER ** ==--

--== Create Linkage tables ==--

CREATE TABLE CustomerLinkage(old INT NOT NULL PRIMARY KEY, new INT NOT NULL);
CREATE TABLE OrderLinkage(old INT NOT NULL PRIMARY KEY, new INT NOT NULL);

--== Copy Header (Customers) rows and record the new key ==--

MERGE Customers2
USING Customers
ON 1=0 -- we just want an insert, so this forces every row as unmatched
WHEN NOT MATCHED THEN
INSERT (Name) VALUES(Customers.Name)
OUTPUT Customers.Id, INSERTED.Id INTO CustomerLinkage;

--== Copy Detail (Orders) rows using the new key from CustomerLinkage and record the new Order key ==--

MERGE Orders2
USING (SELECT Orders.Id, CustomerLinkage.new, Orders.OrderDate
FROM Orders 
JOIN CustomerLinkage
ON CustomerLinkage.old = Orders.CustomerId) AS Orders
ON 1=0 -- we just want an insert, so this forces every row as unmatched
WHEN NOT MATCHED THEN
INSERT (CustomerId, OrderDate) VALUES(Orders.new, Orders.OrderDate)
OUTPUT Orders.Id, INSERTED.Id INTO OrderLinkage;

--== Copy Detail (OrderItems) rows using the new key from OrderLinkage ==--

MERGE OrderItems2
USING (SELECT OrderItems.Id, OrderLinkage.new, OrderItems.ItemId
FROM OrderItems 
JOIN OrderLinkage
ON OrderLinkage.old = OrderItems.OrderId) AS OrderItems
ON 1=0 -- we just want an insert, so this forces every row as unmatched
WHEN NOT MATCHED THEN
INSERT (OrderId, ItemId) VALUES(OrderItems.new, OrderItems.ItemId);

--== ** END ACTUAL ANSWER ** ==--

--== Display the results ==--

SELECT * FROM Customers2 JOIN Orders2 ON Orders2.CustomerId = Customers2.Id JOIN OrderItems2 ON OrderItems2.OrderId = Orders2.Id;

--== Drop test tables ==--

DROP TABLE OrderItems;
DROP TABLE OrderItems2;
DROP TABLE Orders;
DROP TABLE Orders2;
DROP TABLE Customers;
DROP TABLE Customers2;
DROP TABLE CustomerLinkage;
DROP TABLE OrderLinkage;

天哪,您救了我的命。能否请添加像一些过滤器“仅复制到数据库时1 ORDERS2有2名以上的项目”
映湾

2

过去做完这些后,我做了类似的事情:

  • 备份两个数据库。

  • 将要从第一个DB移动到第二个DB的行复制到新表中,而没有IDENTITY列。

  • 将这些行的所有子行复制到新表中,而不必将任何外键复制到父表中。

注意:我们将以上表称为“临时表”;但是,我强烈建议您将它们存储在自己的数据库中,并在完成后进行备份。

  • 确定第二个数据库中第一个数据库中的行需要多少个ID值。
  • 使用DBCC CHECKIDENT交班IDENTITY价值为目标表1超出您所需要的移动。这将留下一个X IDENTITY值的开放块,您可以将其分配给从第一个数据库移出的行。
  • 设置一个映射表,标识出IDENTITY第一个数据库中行的旧值,以及它们将在第二个数据库中使用的新值。
  • 示例:您将需要一个新IDENTITY值的473行从第一个数据库移动到第二个数据库。Per DBCC CHECKIDENT,第二个数据库中该表的下一个标识值现在为1128。使用DBCC CHECKIDENT将该值重新设置为1601。然后,您将使用IDENTITY父表中列的当前值作为旧值填充映射表,并使用该ROW_NUMBER()函数将数字1128到1600分配为新值。

  • 使用映射表,更新IDENTITY临时父表中通常列中的值。

  • 使用映射表,在子表的所有副本中,更新通常是到父表的外键的值。
  • 使用SET IDENTITY_INSERT <parent> ON,将来自临时父表的更新后的父行插入第二个DB。
  • 将来自临时子表的更新后的子行插入第二个DB。

注意:如果某些子表具有IDENTITY自己的值,这将变得非常复杂。我的实际脚本(部分由供应商开发,所以我不能真正共享它们)处理了数十个表和主键列,其中包括一些不是自动递增数值的表。但是,这些是基本步骤。

迁移后,我保留了映射表,其好处是允许我们根据旧ID查找“新”记录。

它不是为微弱的心脏,和必须,必须,必须进行(理想情况下测试多个倍)在测试环境中。

更新:我也应该说,即使这样,我也不必担心“浪费” ID值。实际上,我在第二个数据库中将ID块设置为比我需要的值大2-3个值,以确保不会意外碰撞现有值。

我当然知道我不希望在此过程中跳过成千上万个潜在的有效ID,尤其是如果将重复该过程(在30个月的时间内,我的最终运行了大约20次)。就是说,通常来说,不能依靠自动增量ID值来保证连续无间断。创建一行并回滚时,该行的自动增量值消失了。添加的下一行将具有下一个值,而回滚的行将被跳过。


谢谢。我的想法是,基本上是预先分配一个IDENTITY值块,然后手动更改一组临时表中的值,直到它们与目标匹配,然后再插入。不过对于我的情况,子表确实具有其IDENTITY列(我实际上必须移动三个表,它们之间具有两个1-N关系)。这使其变得相当复杂,但是我对此表示赞赏。
凯文

1
子表是其他表的父母吗?那是事情变得复杂的时候。
RDFozz '17

想像Customer-Order-OrderItemCountry-State-City。将三个表组合在一起时,它们是独立的。
凯文(Kevin)

0

我正在使用WideWorldImporters数据库中的表,该表是Microsoft的新示例数据库。这样,您可以按原样运行我的脚本。您可以从此处下载该数据库的备份。

源表(存在于带有数据的样本中)。

USE [WideWorldImporters]
GO


SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [Warehouse].[VehicleTemperatures]
(
    [VehicleTemperatureID] [bigint] IDENTITY(1,1) NOT NULL,
    [VehicleRegistration] [nvarchar](20) COLLATE Latin1_General_CI_AS NOT NULL,
    [ChillerSensorNumber] [int] NOT NULL,
    [RecordedWhen] [datetime2](7) NOT NULL,
    [Temperature] [decimal](10, 2) NOT NULL,
    [FullSensorData] [nvarchar](1000) COLLATE Latin1_General_CI_AS NULL,
    [IsCompressed] [bit] NOT NULL,
    [CompressedSensorData] [varbinary](max) NULL,

 CONSTRAINT [PK_Warehouse_VehicleTemperatures]  PRIMARY KEY NONCLUSTERED 
(
    [VehicleTemperatureID] ASC
)
)
GO

目标表:

USE [WideWorldImporters]
GO

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [Warehouse].[VehicleTemperatures_dest]
(
    [VehicleTemperatureID] [bigint] IDENTITY(1,1) NOT NULL,
    [VehicleRegistration] [nvarchar](20) COLLATE Latin1_General_CI_AS NOT NULL,
    [ChillerSensorNumber] [int] NOT NULL,
    [RecordedWhen] [datetime2](7) NOT NULL,
    [Temperature] [decimal](10, 2) NOT NULL,
    [FullSensorData] [nvarchar](1000) COLLATE Latin1_General_CI_AS NULL,
    [IsCompressed] [bit] NOT NULL,
    [CompressedSensorData] [varbinary](max) NULL,

 CONSTRAINT [PK_Warehouse_VehicleTemperatures_dest]  PRIMARY KEY NONCLUSTERED 
(
    [VehicleTemperatureID] ASC
)
)
GO

现在执行不带标识列值的导出。注意,我没有插入“身份”列VehicleTemperatureID,也没有从中进行选择。

INSERT INTO [Warehouse].[vehicletemperatures_dest] 
            (
             [vehicleregistration], 
             [chillersensornumber], 
             [recordedwhen], 
             [temperature], 
             [fullsensordata], 
             [iscompressed], 
             [compressedsensordata]) 
SELECT  
       [vehicleregistration], 
       [chillersensornumber], 
       [recordedwhen], 
       [temperature], 
       [fullsensordata], 
       [iscompressed] [bit], 
       [compressedsensordata] 
FROM   [Warehouse].[vehicletemperatures] 

要回答有关FK约束的第二个问题,请参阅这篇文章。特别是下面的部分。

您应该做的是保存向导创建的SSIS包,然后在BIDS / SSDT中对其进行编辑。编辑包时,您将能够控制处理表的顺序,因此您可以处理父表,然后在完成所有父表后再处理子表。


这只会将数据插入一个表中。它没有解决在运行前未知新PK时如何保留FK关系的问题。
凯文(Kevin)

1
问题中已经有两个表格,它们之间有关系。是的,我正在从两个表中导出。(没有违法行为,但不确定您如何错过……
凯文(Kevin

@SqlWorldWide这个问题似乎有些相关但不完全相同。您指的是哪个答案作为此处问题的解决方案?
ypercubeᵀᴹ
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.