FK関係を維持しながら、ID列を含む新しいテーブルにデータを移行する方法


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])
)

2つのデータベースには異なるデータがあるため、同じテーブルの新しいIDキーは2つのデータベースで異なります。それは問題ではない; 私の目標は、既存のデータに新しいデータを追加することであり、テーブル全体のすべてのデータを完全に置き換えることではありません。ただし、挿入されたデータのすべての親子関係を維持したいと思います。

SSMSの「スクリプトの生成」機能を使用すると、スクリプトは同じIDを使用して挿入を試み、宛先データベースの既存のデータと競合します。データベーススクリプトのみを使用してデータをコピーするにはどうすればよいですか?

宛先のID列を最後の値から通常どおりに継続させたい。

Customers他のUNIQUE NOT NULL制約はありません。他の列に重複するデータがあっても問題ありません(ここでは例として使用CustomersOrdersているので、全体を説明する必要はありません)。問題は、1対Nの関係についてです。

回答:


11

これは、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.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;

OMGあなたは私の命を救った。「唯一Orders2が2つの以上の項目がある場合、データベース1にコピー」のようないくつかのより多くのフィルタを追加してください可能性
アンベイ

2

私が過去にこれを行ったとき、私は次のようなことをしました:

  • 両方のデータベースをバックアップします。

  • 最初のDBから2番目のDBに移動する行を、列なしで新しいテーブルにコピーしIDENTITYます。

  • これらの行のすべての子行を、親テーブルへの外部キーなしで新しいテーブルにコピーします。

注:上記の表のセットを「一時」と呼びます。ただし、それらを独自のデータベースに保存し、完了したらバックアップすることを強くお勧めします。

  • 最初のデータベースからの行に2番目のデータベースから必要なID値の数を決定します。
  • ターゲットテーブルのDBCC CHECKIDENT次のIDENTITY値を、移動に必要な値を超えて1 にシフトするために使用します。これIDENTITYにより、最初のデータベースから持ち込まれた行に割り当てることができるX 値のオープンブロックが残ります。
  • マッピングテーブルをセットアップし、IDENTITY最初のDBを形成する行の古い値と、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に挿入します。
  • 更新された子行を一時的な子テーブルから2番目のDBに挿入します。

注:子テーブルのいくつかにIDENTITY独自の値がある場合、これは非常に複雑になります。実際のスクリプト(一部はベンダーによって開発されたため、実際には共有できません)は、自動インクリメントされていない数値を含む数十のテーブルと主キー列を処理します。ただし、これらは基本的な手順です。

移行後にマッピングテーブルを保持しましたが、これには古いIDに基づいて「新しい」レコードを見つけることができるという利点がありました。

心臓の弱い人のためではなく、テスト環境で(理想的には複数回)テストする必要あります。

更新:私はまた、これを使用しても、ID値の「無駄」についてあまり心配していなかったとも言うべきです。既存の値と誤って衝突しないようにするために、実際には2番目のデータベースのIDブロックを必要な値よりも2〜3大きい値に設定しました。

特にプロセスが繰り返される場合(特に、鉱山は30か月で合計で約20回実行された場合)、このプロセス中に数十万の潜在的な有効なIDをスキップしたくないことは確かに理解できます。とはいえ、一般的に、ギャップのない自動インクリメントID値をシーケンシャルにすることはできません。行が作成されてロールバックされると、その行の自動インクリメント値はなくなります。追加された次の行には次の値があり、ロールバックされた行の1つはスキップされます。


ありがとう。基本的にIDENTITY値のブロックを事前に割り当ててから、一時テーブルのセットの値を宛先と一致するまで手動で変更し、挿入するというアイデアを得ました。ただし、私のシナリオでは、子テーブルにIDENTITY列があります(実際には、3つのテーブルを移動する必要があり、テーブル間に2つの1-N関係があります)。これはかなり複雑になりますが、私は考えに感謝します。
ケビン

1
子テーブルは他のテーブルの親ですか?それは物事が複雑になるときです。
RDFozz

Customer-Order-OrderItemまたはのように考えCountry-State-Cityます。3つのテーブルをグループ化すると、自己完結型になります。
ケビン

0

WideWorldImportersMicrosoftの新しいサンプルデータベースであるデータベースのテーブルを使用しています。このようにして、スクリプトをそのまま実行できます。このデータベースのバックアップは、こちらからダウンロードできます。

ソーステーブル(これはデータのサンプルに存在します)。

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番目の質問に回答するには、この投稿を参照しください。特に以下のセクション。

ウィザードが作成したSSISパッケージを保存してから、BIDS / SSDTで編集する必要があります。パッケージを編集すると、テーブルが処理される順序を制御できるため、親テーブルを処理し、すべての親テーブルが完了したときに子テーブルを処理できます。


これは、1つのテーブルにデータを挿入するだけです。実行前に新しいPKが不明な場合にFK関係を保持する方法の問題については触れていません。
ケビン

1
問題にはすでに2つのテーブルがあり、関係があります。はい、両方のテーブルからエクスポートしています。(違反はありませんが、どのようにそれを逃したかはわかりません...
ケビン

@SqlWorldWideその質問は多少関連しているようですが、同一ではありません。ここで問題の解決策としてどの回答を参照していますか?
ypercubeᵀᴹ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.