Aqui está uma maneira de escalar facilmente para três tabelas relacionadas.
Use MERGE para inserir os dados nas tabelas de cópia, para que você possa OUTPUT os valores antigos e novos de IDENTITY em uma tabela de controle e use-os para o mapeamento de tabelas relacionadas.
A resposta real é apenas duas instruções de criação de tabela e três mesclagens. O restante é exemplo de configuração de dados e desmontagem.
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;
Quando eu fiz isso no passado, fiz algo assim:
Faça backup dos dois bancos de dados.
Copie as linhas que você deseja mover do primeiro banco de dados para o segundo em uma nova tabela, sem uma
IDENTITY
coluna.Nota: Vamos nos referir ao conjunto de tabelas acima como "temporário"; no entanto, eu recomendo que você os armazene em seu próprio banco de dados e faça o backup também quando terminar.
DBCC CHECKIDENT
para mudar o próximoIDENTITY
valor da tabela de destino para 1 além do necessário para a movimentação. Isso deixará um bloco aberto deIDENTITY
valores X que você pode atribuir às linhas que estão sendo trazidas do primeiro banco de dados.IDENTITY
valor antigo para as linhas do primeiro banco de dados e o novo valor que eles usarão no segundo banco de dados.Exemplo: você está movendo 473 linhas que precisarão de um novo
IDENTITY
valor do primeiro banco de dados para o segundo. PorDBCC CHECKIDENT
, o próximo valor de identidade para essa tabela no segundo banco de dados é 1128 no momento. UseDBCC CHECKIDENT
para redefinir o valor para 1601. Você preencherá sua tabela de mapeamento com os valores atuais daIDENTITY
coluna da tabela pai como valores antigos e use aROW_NUMBER()
função para atribuir os números 1128 a 1600 como os novos valores.Usando a tabela de mapeamento, atualize os valores que normalmente são a
IDENTITY
coluna na tabela pai temporária.SET IDENTITY_INSERT <parent> ON
, insira as linhas pai atualizadas da tabela pai temporária no segundo banco de dados.NOTA: Se algumas das tabelas filho tiverem
IDENTITY
valores próprios, isso ficará bastante complicado. Meus scripts reais (parcialmente desenvolvidos por um fornecedor, portanto não posso compartilhá-los) lidam com dezenas de tabelas e colunas de chave primária, incluindo algumas que não eram valores numéricos de incremento automático. No entanto, estas são as etapas básicas.Eu mantive as tabelas de mapeamento, pós-migração, que tinham o benefício de nos permitir encontrar um "novo" registro com base em um ID antigo.
Não é para os fracos de coração, e deve, deve, deve ser testado (idealmente várias vezes) em um ambiente de teste.
UPDATE: Também devo dizer que, mesmo com isso, não me preocupei demais com "desperdiçar" valores de ID. Na verdade, configurei meus blocos de ID no segundo banco de dados para serem 2-3 valores maiores que o necessário, para tentar garantir que não colidisse acidentalmente com os valores existentes.
Eu certamente entendo que não quero pular centenas de milhares de possíveis IDs válidos durante esse processo, especialmente se o processo for repetido (o meu acabou sendo executado um total geral de cerca de 20 vezes ao longo de 30 meses). Dito isto, em geral, não se pode confiar nos valores de ID de auto incremento para serem sequenciais sem lacunas. Quando uma linha é criada e revertida, o valor de incremento automático dessa linha desaparece; a próxima linha adicionada terá o próximo valor e a da linha revertida será ignorada.
fonte
Customer-Order-OrderItem
ouCountry-State-City
. As três tabelas, quando agrupadas, são independentes.Estou usando uma tabela do
WideWorldImporters
banco de dados que é o novo banco de dados de exemplo da Microsoft. Dessa forma, você pode executar meu script como está. Você pode baixar um backup desse banco de dados aqui .Tabela de origem (isso existe na amostra com dados).
Tabela de destino:
Agora, faça a exportação sem o valor da coluna de identidade. Observe que não estou inserindo na coluna de identidade
VehicleTemperatureID
e também não selecionando a mesma.Para responder à segunda pergunta sobre restrições de FK, consulte este post. Especialmente seção abaixo.
fonte