データベース間でデータを移行したい。テーブルスキーマはまったく同じです。
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])
)
2つのデータベースには異なるデータがあるため、同じテーブルの新しいIDキーは2つのデータベースで異なります。それは問題ではない;私の目標は、既存のデータに新しいデータを追加することであり、テーブル全体のすべてのデータを完全に置き換えることではありません。ただし、挿入されたデータのすべての親子関係を維持したいと思います。
SSMSの「スクリプトの生成」機能を使用すると、スクリプトは同じIDを使用して挿入しようとし、宛先データベースの既存のデータと競合します。データベーススクリプトのみを使用してデータをコピーするにはどうすればよいですか?
宛先のID列を最後の値から通常どおりに継続させたい。
Customers
には他のUNIQUE NOT NULL
制約はありません。他の列にデータが重複していても問題ありません(ここでは例としてCustomers
とOrders
を使用しているので、全体を説明する必要はありません)。問題は、1対Nの関係についてです。
これは、3つの関連するテーブルに簡単にスケーリングする方法です。
MERGEを使用してデータをコピーテーブルに挿入し、古いIDENTITY値と新しいIDENTITY値をコントロールテーブルに出力して、それらを関連テーブルのマッピングに使用できるようにします。
実際の答えは、2つのcreate tableステートメントと3つのマージだけです。残りはサンプルデータのセットアップと破棄です。
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.Id) 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.Id) 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;
私が過去にこれを行ったとき、私は次のようなことをしました:
両方のデータベースをバックアップします。
最初のDBから2番目のDBに移動する行を、IDENTITY
列なしで新しいテーブルにコピーします。
注:上記の表のセットを「一時的」と呼びます。ただし、それらを独自のデータベースに保存し、完了したらバックアップすることを強くお勧めします。
DBCC CHECKIDENT
を使用して、ターゲットテーブルの次のIDENTITY
値を、移動に必要な値を超えて1にシフトします。これにより、X IDENTITY
値のオープンブロックが残り、最初のデータベースから持ち込まれる行に割り当てることができます。IDENTITY
値と、2番目のDBで使用する新しい値を特定します。例:新しいIDENTITY
値を必要とする473行を最初のデータベースから2番目のデータベースに移動しています。 DBCC CHECKIDENT
によると、2番目のデータベースのそのテーブルの次のID値は現在1128です。 DBCC CHECKIDENT
を使用して、値を1601に再シードします。次に、マッピングテーブルに親テーブルのIDENTITY
列の現在の値を古い値として入力し、ROW_NUMBER()
を使用します新しい値として1128から1600までの数値を割り当てる関数。
マッピングテーブルを使用して、一時的な親テーブルの通常IDENTITY
列の値を更新します。
SET IDENTITY_INSERT <parent> ON
を使用して、更新された親行を一時親テーブルから2番目のDBに挿入します。注:子テーブルの一部に独自のIDENTITY
値がある場合、これは非常に複雑になります。実際のスクリプト(一部はベンダーによって開発されたため、実際には共有できません)は、自動インクリメントされていない数値を含む数十のテーブルと主キー列を処理します。ただし、これらは基本的な手順です。
移行後にマッピングテーブルを保持しましたが、これには古いIDに基づいて「新しい」レコードを見つけることができるという利点がありました。
気の弱い人のためではなく、must、must、mustがテスト環境でテストされます(理想的にはmultiple回)。
更新:私はまた、これを使用しても、ID値の「浪費」についてあまり心配していなかったとも言うべきです。既存の値と誤って衝突しないようにするために、2番目のデータベースのIDブロックを実際に必要な値よりも2〜3大きい値に設定しました。
特にプロセスが繰り返される場合(特に、鉱山は30か月で合計で約20回実行された場合)、このプロセス中に数十万の潜在的な有効なIDをスキップしたくないことは確かに理解できます。とはいえ、一般的に、ギャップのない自動インクリメントID値をシーケンシャルにすることはできません。行が作成されてロールバックされると、その行の自動インクリメント値はなくなります。追加された次の行にはnext値が含まれ、ロールバックされた行からの行はスキップされます。
Microsoftの新しいサンプルデータベースであるWideWorldImporters
データベースのテーブルを使用しています。このようにして、スクリプトをそのまま実行できます。このデータベースのバックアップは here からダウンロードできます。
ソーステーブル(これはデータのサンプルに存在します)。
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
ID列の値なしでエクスポートを実行しています。 ID列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制約に関する2番目の質問に答えるには、 this の投稿を参照してください。特に以下のセクション。
ウィザードが作成したSSISパッケージを保存してから、BIDS/SSDTで編集する必要があります。パッケージを編集すると、テーブルが処理される順序を制御できるため、親テーブルを処理し、すべての親テーブルが完了したときに子テーブルを処理できます。