Bagaimana cara menyalin data migrasi ke tabel baru dengan kolom identitas, sambil menjaga hubungan FK?

8

Saya ingin memigrasikan data dari satu database ke yang lain. Skema tabel persis sama:

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

Dua database memiliki data yang berbeda, jadi kunci identitas baru untuk tabel yang sama akan berbeda di kedua database. Itu bukan masalah; tujuan saya adalah menambahkan data baru ke yang sudah ada, tidak lengkap mengganti semua data dari seluruh tabel. Namun saya ingin menjaga semua hubungan orangtua-anak dari data yang dimasukkan.

Jika saya menggunakan fitur "Buat Skrip" dari SSMS, skrip akan berusaha memasukkan menggunakan ID yang sama, yang akan bertentangan dengan data yang ada di database tujuan. Bagaimana saya bisa menyalin data hanya menggunakan skrip database?

Saya ingin kolom identitas di tujuan untuk melanjutkan secara normal dari nilai terakhirnya.

Customerstidak memiliki UNIQUE NOT NULLkendala lain . Tidak apa-apa untuk memiliki data duplikat di kolom lain (saya menggunakan Customersdan Ordershanya sebagai contoh di sini, jadi saya tidak perlu menjelaskan keseluruhan cerita). Pertanyaannya adalah tentang hubungan satu-ke-N.

kevin
sumber

Jawaban:

11

Berikut adalah cara yang mudah menskala ke tiga tabel terkait.

Gunakan MERGE untuk menyisipkan data ke dalam tabel salinan sehingga Anda bisa OUTPUT nilai IDENTITAS lama dan baru ke dalam tabel kontrol dan menggunakannya untuk pemetaan tabel terkait.

Jawaban sebenarnya adalah hanya dua membuat pernyataan tabel dan tiga gabungan. Sisanya adalah contoh pengaturan data dan runtuhkan.

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;
Tuan Magoo
sumber
OMG kau menyelamatkan hidupku. Bisa tambahkan beberapa filter lagi seperti 'salin saja ke Database 1 ketika Orders2 memiliki lebih dari 2 item'
Anh Bảy
2

Ketika saya telah melakukan ini di masa lalu, saya melakukannya seperti ini:

  • Cadangkan kedua database.

  • Salin baris yang ingin Anda pindahkan dari DB pertama ke yang kedua ke tabel baru, tanpa IDENTITYkolom.

  • Salin semua baris anak dari baris itu ke tabel baru tanpa kunci asing ke tabel induk.

Catatan: Kami akan merujuk ke set tabel di atas sebagai "sementara"; Namun, saya sangat menyarankan Anda menyimpannya di database mereka sendiri, dan mendukungnya juga ketika Anda selesai.

  • Tentukan berapa banyak nilai ID yang Anda butuhkan dari basis data kedua untuk baris dari basis data pertama.
  • Gunakan DBCC CHECKIDENTuntuk menggeser nilai berikutnya IDENTITYuntuk tabel target ke 1 di luar apa yang Anda butuhkan untuk bergerak. Ini akan meninggalkan blok terbuka IDENTITYnilai X yang dapat Anda tetapkan untuk baris yang dibawa dari database pertama.
  • Menyiapkan tabel pemetaan, mengidentifikasi nilai lama IDENTITYuntuk baris membentuk DB pertama, dan nilai baru yang akan mereka gunakan dalam DB kedua.
  • Contoh: Anda memindahkan 473 baris yang akan membutuhkan IDENTITYnilai baru dari database pertama ke yang kedua. Per DBCC CHECKIDENT, nilai identitas berikutnya untuk tabel itu di database kedua adalah 1128 sekarang. Gunakan DBCC CHECKIDENTuntuk mengembalikan nilai ke 1601. Kemudian Anda akan mengisi tabel pemetaan Anda dengan nilai saat ini untuk IDENTITYkolom dari tabel induk Anda sebagai nilai lama, dan menggunakan ROW_NUMBER()fungsi untuk menetapkan angka 1128 hingga 1600 sebagai nilai baru.

  • Menggunakan tabel pemetaan, perbarui nilai dalam apa yang biasanya IDENTITYkolom di tabel induk sementara.

  • Menggunakan tabel pemetaan, perbarui nilai-nilai yang biasanya kunci asing ke tabel induk, di semua salinan tabel anak.
  • Dengan menggunakan SET IDENTITY_INSERT <parent> ON, masukkan baris induk yang diperbarui dari tabel induk sementara ke dalam DB kedua.
  • Masukkan baris anak yang diperbarui dari tabel anak sementara ke dalam DB kedua.

CATATAN: Jika beberapa tabel anak memiliki IDENTITYnilai sendiri, ini menjadi cukup rumit. Skrip aktual saya (sebagian dikembangkan oleh vendor, jadi saya tidak bisa membaginya) berurusan dengan lusinan tabel dan kolom kunci utama, termasuk beberapa yang bukan nilai numerik kenaikan otomatis. Namun, ini adalah langkah dasar.

Saya mempertahankan tabel pemetaan, pasca migrasi, yang memiliki manfaat memungkinkan kami menemukan catatan "baru" berdasarkan ID lama.

Ini bukan untuk menjadi lemah hati, dan harus, harus, harus diuji (idealnya beberapa kali) di lingkungan pengujian.

UPDATE: Saya juga harus mengatakan bahwa, bahkan dengan ini, saya tidak terlalu khawatir tentang "membuang-buang" nilai ID. Saya benar-benar mengatur blok ID saya di database kedua menjadi 2-3 nilai lebih besar dari yang saya butuhkan, untuk memastikan saya tidak akan secara tidak sengaja bertabrakan dengan nilai yang ada.

Saya tentu mengerti tidak ingin melewatkan ratusan ribu ID potensial yang potensial selama proses ini, terutama jika proses itu akan diulangi (tambang saya akhirnya berjalan total sekitar 20 kali selama 30 bulan). Yang mengatakan, secara umum, seseorang tidak bisa mengandalkan nilai ID kenaikan otomatis untuk berurutan tanpa celah. Ketika sebuah baris dibuat dan digulung kembali, nilai kenaikan otomatis untuk baris itu hilang; baris berikutnya yang ditambahkan akan memiliki nilai berikutnya , dan yang dari baris belakang yang digulirkan akan dilewati.

RDFozz
sumber
Terima kasih. Saya mendapatkan ide, pada dasarnya pra-alokasi blok nilai IDENTITAS, kemudian secara manual mengubah nilai-nilai dalam seperangkat tabel temp sampai mereka cocok dengan tujuan, lalu masukkan. Namun untuk skenario saya, tabel anak memang memiliki kolom IDENTITAS (saya benar-benar harus memindahkan tiga tabel, dengan dua hubungan 1-N di antara mereka). Ini membuatnya cukup rumit, tetapi saya menghargai gagasan itu.
kevin
1
Apakah meja anak orang tua ke meja lainnya? Saat itulah segalanya menjadi rumit.
RDFozz
Berpikir seperti Customer-Order-OrderItematau Country-State-City. Tiga tabel, ketika dikelompokkan bersama, mandiri.
kevin
0

Saya menggunakan tabel dari WideWorldImportersbasis data yang merupakan basis data sampel baru dari Microsoft. Dengan cara itu Anda dapat menjalankan skrip saya apa adanya. Anda dapat mengunduh cadangan dari basis data ini dari sini .

Tabel sumber (ini ada dalam sampel dengan data).

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

Tabel tujuan:

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

Sekarang melakukan ekspor tanpa nilai kolom identitas. Perhatikan saya tidak memasukkan ke dalam kolom identitas VehicleTemperatureIDdan juga tidak memilih dari yang sama.

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

Untuk menjawab pertanyaan kedua tentang batasan FK, silakan lihat posting ini . Terutama bagian di bawah ini.

Yang harus Anda lakukan adalah menyimpan paket SSIS yang dibuat oleh wizard, kemudian mengeditnya di BIDS / SSDT. Ketika Anda mengedit paket Anda akan dapat mengontrol urutan tabel diproses sehingga Anda dapat memproses tabel induk kemudian memproses tabel anak ketika semua tabel induk selesai.

SqlWorldWide
sumber
Ini hanya memasukkan data ke dalam satu tabel. Itu tidak membahas pertanyaan tentang bagaimana menjaga hubungan FK ketika PK baru tidak diketahui sebelum runtime.
kevin
1
Sudah ada dua tabel dalam pertanyaan, dengan suatu hubungan. Dan ya, saya mengekspor dari kedua tabel. (Jangan tersinggung, tetapi tidak yakin bagaimana Anda melewatkannya ... )
kevin
@SqlWorldWide pertanyaan itu tampaknya agak terkait tetapi tidak identik. Manakah dari jawaban yang Anda rujuk sebagai solusi untuk masalah ini di sini?
ypercubeᵀᴹ