Migração de dados com troca de FK
Esta semana concluí uma migração de dados de uma base para outra. Seria um trabalho simples de INSERT/SELECT, não fosse um fato bem "insignificante": na migração, os valores das chaves primárias das tabelas de origem teriam um valor diferente na tabela de destino. Com isso, todas as chaves estrangeiras referenciadas teriam que ser atualizadas para o novo valor.
Depois de um levantamento completo da estrutura dos BDs envolvidos, a definição da ordem de execução das tabelas, atualizando sempre as tabelas PAI de relacionamentos antes das respectivas tabelas FILHO do relacionamento, e o mapeamento de/para das colunas, resolvi o problema da conversão das FKs de um jeito simples.
Uma informação importante: todas as tabelas dos BDs origem e destino foram criadas com IDENTITY.
Segue o passo a passo da migração.
Script 1
Criar uma coluna para cada PK e FK na tabela de destino. Para facilitar, o nome da nova coluna era formado pelo nome da coluna original acrescido do sufixo "_Origem". Por exemplo: IdtCliente_Origem.
Criar um índice para cada coluna PK e FK na tabela de destino.
Script 2
Execução de INSERT/SELECT para cada tabela envolvida na migração, respeitando a ordem de prioridade de tabelas PAI de relacionamentos antes das tabelas FILHA dos relacionamentos.
No comando INSERT/SELECT, eu preenchi as colunas "*_Origem" com o respectivo valor da tabela de origem. Por exemplo:
INSERT Cliente (Nome, CPF, DtNascimento, IdCliente_Origem) SELECT O.NomeCliente, O.CPF, O.DtNascimento, O.IdCliente FROM BD_Origem.dbo.Clientes
A chave primária da tabela Clientes (IdCliente) foi preenchida automaticamente pelo SQL Server, pois trata-se de uma coluna IDENTITY.
Na migração de dados de uma tabela filha da tabela Clientes, eu usei um JOIN para pegar o novo valor de identificação do cliente.
INSERT Endereco (IdCliente, Endereco, Bairro, Cidade, UF, CEP, IdEndereco_Origem, IdCliente_Origem) SELECT C.IdCliente, E.Endereco, E.Bairro, E.Cidade, E.UF, E.CEP, E.IdEndereco, E.IdCliente FROM BD_Origem.dbo.Endereco AS E INNER JOIN BD_Novo.dbo.Cliente AS C ON E.IdCliente = C.IdCliente_Origem
Este JOIN, ligando o código do cliente da tabela Endereço com o código do cliente na tabela Cliente no BD novo, mas usando o código do cliente na tabela origem, é que permite localizar o novo código do cliente, criado no BD novo, mantendo a integridade da informação.
Script 3
Com a migração de dados concluída, executei um script que grava os valores das colunas PK e FK do BD Origem e do BD Destino, guardando o De/Para gerado na migração. Neste caso, montei uma tabela simples, sem me preocupar muito com normalização. Afinal, essa tabela é apenas para mim. Este é o script da tabela:
CREATE TABLE [Oxxy].[TB_Migracao]( [IdMigracao] [int] IDENTITY(1,1) NOT NULL, [NmTabela] [varchar](128) NULL, [NmPK] [varchar](128) NULL, [IdPkTabela] [int] NULL, [IdPkTabela_Origem] [int] NULL, [NmColuna_Origem_01] [varchar](128) NULL, [VlrColuna_Origem_01] [int] NULL, [VlrColuna_Destino_01] [int] NULL, [NmColuna_Origem_02] [varchar](128) NULL, [VlrColuna_Origem_02] [int] NULL, [VlrColuna_Destino_02] [int] NULL, [NmColuna_Origem_03] [varchar](128) NULL, [VlrColuna_Origem_03] [int] NULL, [VlrColuna_Destino_03] [int] NULL, [NmColuna_Origem_04] [varchar](128) NULL, [VlrColuna_Origem_04] [int] NULL, [VlrColuna_Destino_04] [int] NULL, [NmColuna_Origem_05] [varchar](128) NULL, [VlrColuna_Origem_05] [int] NULL, [VlrColuna_Destino_05] [int] NULL, [NmColuna_Origem_06] [varchar](128) NULL, [VlrColuna_Origem_06] [int] NULL, [VlrColuna_Destino_06] [int] NULL ) ON [PRIMARY] GO
Criei até 6 colunas origem/destino, porque foi o máximo que precisei tratar.
A cópia das chaves para a tabela TB_Migracao seguiu este formato:
INSERT TB_Migracao (NmTabela, NmPK, IdPkTabela, IdPkTabela_Origem) SELECT 'Cliente' AS NmTabela, 'IdCliente' AS NmPK, IdCliente AS IdPkTabela, IdCliente_Origem AS IdPkTabela_Origem FROM Cliente WHERE IdCliente_Origem IS NOT NULL
INSERT TB_Migracao (NmTabela, NmPK, IdPkTabela, IdPkTabela_Origem, NmColuna_Origem_01, VlrColuna_Origem_01, VlrColuna_Destino_01) SELECT 'Endereco' AS NmTabela, 'IdEndereco' AS NmPK, IdEndereco AS IdPkTabela, IdEndereco_Origem AS IdPkTabela_Origem_,
'IdCliente' AS NmColuna_Origem_01, IdCliente_Origem AS VlrColuna_Origem_01, IdCliente AS VlrColuna_Destino_01 FROM Endereco WHERE IdEndereco_Origem IS NOT NULL OR IdCliente_Origem IS NOT NULL
Script 4
Exclusão de todos os índices das colunas _Origem.
Exclusão de todas as colunas _Origem.
O Bote salva-vidas
A tabela TB_Migracao acabou me salvando. Dentre as colunas migradas, teve uma que o desenvolvedor me informou errado. Foi necessário atualizar os dados. Como as colunas _Origem foram excluídas da tabela, se eu não tivesse a TB_Migracao para identificar os registros e valores tratados, seria muito mais complicado corrigir o valor desta FK.
Para atualizar a tabela, eu:
Copiei os registros com problema para uma tabela temporária.
Excluí os registros criados na primeira importação, ligando a tabela com problema à TB_Migracao.
Atualizei a tabela TB_Migracao, marcando os registros que foram excluídos.
Extrai, dos scripts acima, os comandos que envolviam a tabela com problema.
Fiz as correções necessárias.
Executei novamente.
Sucesso!
Concluindo, fica aqui minha sugestão para vocês. Numa migração de dados entre tabelas ou Bancos de Dados, onde os valores de PK e FK são modificados, guarde o de/para numa tabela separada. Você nunca sabe quando vai precisar.