Làm cách nào để sao chép di chuyển dữ liệu sang các bảng mới bằng cột nhận dạng, trong khi vẫn giữ mối quan hệ FK?


8

Tôi muốn di chuyển dữ liệu từ cơ sở dữ liệu này sang cơ sở dữ liệu khác. Các lược đồ bảng hoàn toàn giống nhau:

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

Hai cơ sở dữ liệu có dữ liệu khác nhau, vì vậy khóa nhận dạng mới cho cùng một bảng sẽ khác nhau ở hai cơ sở dữ liệu. Đó không phải là vấn đề; Mục tiêu của tôi là nối thêm dữ liệu mới vào dữ liệu hiện có, không hoàn thành thay thế tất cả dữ liệu của toàn bộ bảng. Tuy nhiên, tôi muốn duy trì tất cả mối quan hệ cha-con của dữ liệu được chèn.

Nếu tôi sử dụng tính năng "Tạo tập lệnh" của SSMS, tập lệnh sẽ cố gắng chèn bằng cùng một ID, điều này sẽ xung đột dữ liệu hiện có trong cơ sở dữ liệu đích. Làm thế nào tôi có thể sao chép dữ liệu chỉ bằng cách sử dụng tập lệnh cơ sở dữ liệu?

Tôi muốn cột danh tính ở đích tiếp tục bình thường từ giá trị cuối cùng của nó.

Customerskhông có bất kỳ UNIQUE NOT NULLràng buộc nào khác . Có thể có dữ liệu trùng lặp trong các cột khác (tôi đang sử dụng CustomersOrderschỉ là một ví dụ ở đây, vì vậy tôi không phải giải thích toàn bộ câu chuyện). Câu hỏi là về bất kỳ mối quan hệ một-N.

Câu trả lời:


11

Đây là một cách dễ dàng chia tỷ lệ thành ba bảng liên quan.

Sử dụng MERGE để chèn dữ liệu vào các bảng sao chép để bạn có thể ĐẦU RA các giá trị IDENTITY cũ và mới vào bảng điều khiển và sử dụng chúng cho ánh xạ bảng liên quan.

Câu trả lời thực tế chỉ là hai câu lệnh tạo bảng và ba phép hợp nhất. Phần còn lại là thiết lập dữ liệu mẫu và phá bỏ.

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 bạn đã cứu cuộc sống của tôi. Có thể vui lòng thêm một số bộ lọc khác như 'chỉ sao chép vào Cơ sở dữ liệu 1 khi Đơn hàng 2 có nhiều hơn 2 mục'
Anh Lưu

2

Khi tôi đã làm điều này trong quá khứ, tôi đã làm nó như thế này:

  • Sao lưu cả cơ sở dữ liệu.

  • Sao chép các hàng bạn muốn di chuyển từ DB đầu tiên sang thứ hai vào một bảng mới, không có IDENTITYcột.

  • Sao chép tất cả các hàng con của các hàng đó vào các bảng mới mà không có khóa ngoại vào bảng cha.

Lưu ý: Chúng tôi sẽ đề cập đến bộ bảng ở trên là "tạm thời"; tuy nhiên, tôi thực sự khuyên bạn nên lưu trữ chúng trong cơ sở dữ liệu của riêng họ và sao lưu nó khi bạn hoàn thành.

  • Xác định có bao nhiêu giá trị ID bạn cần từ cơ sở dữ liệu thứ hai cho các hàng từ cơ sở dữ liệu đầu tiên.
  • Sử dụng DBCC CHECKIDENTđể chuyển IDENTITYgiá trị tiếp theo cho bảng mục tiêu sang 1 ngoài những gì bạn cần cho việc di chuyển. Điều này sẽ để lại một khối X mởIDENTITY giá trị mà bạn có thể gán cho các hàng được mang từ cơ sở dữ liệu đầu tiên.
  • Thiết lập bảng ánh xạ, xác định IDENTITYgiá trị cũ cho các hàng tạo thành DB đầu tiên và giá trị mới mà chúng sẽ sử dụng trong DB thứ hai.
  • Ví dụ: Bạn đang di chuyển 473 hàng sẽ cần một IDENTITYgiá trị mới từ cơ sở dữ liệu thứ nhất sang cơ sở dữ liệu thứ hai. Per DBCC CHECKIDENT, giá trị nhận dạng tiếp theo cho bảng đó trong cơ sở dữ liệu thứ hai là 1128 ngay bây giờ. Sử dụng DBCC CHECKIDENTđể định lại giá trị thành 1601. Sau đó, bạn sẽ điền vào bảng ánh xạ của mình với các giá trị hiện tại cho IDENTITYcột từ bảng cha của bạn làm giá trị cũ và sử dụng ROW_NUMBER()hàm để gán các số từ 1128 đến 1600 làm giá trị mới.

  • Sử dụng bảng ánh xạ, cập nhật các giá trị trong IDENTITYcột thường là gì trong bảng cha tạm thời.

  • Sử dụng bảng ánh xạ, cập nhật các giá trị thường là khóa ngoại vào bảng cha, trong tất cả các bản sao của bảng con.
  • Sử dụng SET IDENTITY_INSERT <parent> ON, chèn các hàng cha được cập nhật từ bảng cha tạm thời vào DB thứ hai.
  • Chèn các hàng con được cập nhật tạo thành các bảng con tạm thời vào DB thứ hai.

LƯU Ý: Nếu một số bảng con có IDENTITYcác giá trị riêng, điều này trở nên khá phức tạp. Các tập lệnh thực tế của tôi (được phát triển một phần bởi một nhà cung cấp, vì vậy tôi thực sự không thể chia sẻ chúng) đối phó với hàng tá bảng và cột khóa chính, bao gồm một số bảng không tự động tăng giá trị số. Tuy nhiên, đây là những bước cơ bản.

Tôi đã giữ lại các bảng ánh xạ, di chuyển bài, có lợi ích cho phép chúng tôi tìm bản ghi "mới" dựa trên ID cũ.

Nó không dành cho người yếu tim, và phải, phải, phải được kiểm tra (lý tưởng nhiều lần) trong môi trường thử nghiệm.

CẬP NHẬT: Tôi cũng nên nói rằng, ngay cả với điều này, tôi đã không lo lắng về việc "lãng phí" các giá trị ID. Tôi thực sự đã thiết lập các khối ID của mình trong cơ sở dữ liệu thứ hai lớn hơn 2-3 giá trị mà tôi cần, để cố gắng đảm bảo tôi sẽ không vô tình va chạm với các giá trị hiện có.

Tôi chắc chắn hiểu rằng không muốn bỏ qua hàng trăm ngàn ID hợp lệ tiềm năng trong quá trình này, đặc biệt là nếu quy trình sẽ được lặp lại (cuối cùng tôi đã chạy tổng cộng khoảng 20 lần trong suốt 30 tháng). Điều đó nói rằng, nói chung, người ta không thể dựa vào các giá trị ID tăng tự động để tuần tự mà không có khoảng trống. Khi một hàng được tạo và cuộn lại, giá trị tăng tự động cho hàng đó sẽ biến mất; hàng tiếp theo được thêm vào sẽ có giá trị tiếp theo và hàng từ hàng được cuộn lại sẽ bị bỏ qua.


Cảm ơn. Tôi có ý tưởng, về cơ bản phân bổ trước một khối các giá trị IDENTITY, sau đó thay đổi thủ công các giá trị trong một tập hợp các bảng tạm thời cho đến khi chúng khớp với đích, sau đó chèn. Đối với kịch bản của tôi, bảng con có cột IDENTITY (tôi thực sự phải di chuyển ba bảng, với hai mối quan hệ 1-N giữa chúng). Điều này làm cho nó khá phức tạp, nhưng tôi đánh giá cao ý tưởng.
kevin

1
Các bảng con có cha mẹ để các bảng khác? Đó là khi mọi thứ trở nên phức tạp.
RDFozz

Nghĩ như thế Customer-Order-OrderItemhay Country-State-City. Ba bảng, khi được nhóm lại với nhau, là khép kín.
kevin

0

Tôi đang sử dụng một bảng từ WideWorldImporterscơ sở dữ liệu là cơ sở dữ liệu mẫu mới của Microsoft. Theo cách đó bạn có thể chạy kịch bản của tôi như vậy. Bạn có thể tải về bản sao lưu của cơ sở dữ liệu này từ đây .

Bảng nguồn (cái này tồn tại trong mẫu với dữ liệu).

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

Bảng đích:

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

Bây giờ thực hiện xuất mà không có giá trị cột danh tính. Lưu ý rằng tôi không chèn vào cột danh tính VehicleTemperatureIDvà cũng không chọn từ cùng.

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

Để trả lời câu hỏi thứ hai về các ràng buộc FK, vui lòng xem bài đăng này . Đặc biệt là phần dưới đây.

Điều bạn nên làm là lưu gói SSIS mà trình hướng dẫn tạo, sau đó chỉnh sửa nó trong BIDS / SSDT. Khi bạn chỉnh sửa gói, bạn sẽ có thể kiểm soát thứ tự các bảng được xử lý để bạn có thể xử lý các bảng cha sau đó xử lý các bảng con khi tất cả các bảng cha được thực hiện.


Điều này chỉ chèn dữ liệu vào một bảng. Nó không giải quyết câu hỏi làm thế nào để duy trì mối quan hệ FK khi PK mới không được biết trước khi chạy.
kevin

1
Đã có hai bảng trong câu hỏi, với một mối quan hệ. Và vâng, tôi đang xuất từ ​​cả hai bảng. (Không xúc phạm, nhưng không chắc bạn đã bỏ lỡ nó như thế nào ... )
kevin

@SqlWorldWide câu hỏi đó có vẻ hơi liên quan nhưng không giống nhau. Câu trả lời nào bạn muốn đề cập đến như một giải pháp cho vấn đề ở đây?
ypercubeᵀᴹ
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.