sexta-feira, 21 de agosto de 2009

Transferindo dados entre bancos de dados, considerando a integridade referêncial

Ontem no meu trabalho, foi solicitado que eu importe os dados de um banco de dados local, para um banco de dados remoto. A princípio, a tarefa parecia bastante fácil, usando o DTS Import/Export do SQL Server acessando através do Management Studio.

Porém, devido a uma agradável surpresa (o banco estava com todas as chaves primárias e estrangeiras configuradas, o que é muito bom),este procedimento ficou um pouco mais difícil. Com a integridade referêncial configurada corretamente, para inserir os dados nas tabelas, é necessário obedecer as precedências, e inserir os dados hierarquicamente inserindo primeiro os dados nas tabelas básicas que são referenciadas por outras e por último as tabelas mais "dependentes".

Pesquisando um pouco na internet, encontrei um artigo no blog Gustavo Maia relatando uma situação similar a que estava me encontrando.

A primeira grande idéia foi de criar uma função para retornar o nome do objeto através de seu ID.


CREATE FUNCTION dbo.RetornaNomeObjeto (@ID INT)
RETURNS SYSNAME
AS
BEGIN
DECLARE @Nome SYSNAME
SELECT @Nome = SCHEMA_NAME(SCHEMA_ID) + '.' + NAME
FROM SYS.OBJECTS WHERE OBJECT_ID = @ID
RETURN @Nome
END


Não vou republicar o texto na íntegra, mas a seguir, o Gustavo implementa uma query recursiva, classificando as tabelas em níveis, onde as tabelas com o maior número de referências a sua chave primária e um menor número de chaves estrangeiras, tem as maiores "notas" e as tabelas com o maior números de chaves estrangeiras e poucas referências a sua chave primária são pontuadas com as menores notas.


WITH Relacoes (Filho_ID, Pai_ID)
AS (
SELECT Parent_Object_ID, Referenced_Object_ID
FROM SYS.FOREIGN_KEYS
WHERE Parent_Object_ID != Referenced_Object_ID),

Dependencias (Filho_ID, TabelaFilho, Pai_ID, TabelaPai, Nivel)
AS (

SELECT Filho_ID, dbo.RetornaNomeObjeto(Filho_ID),
Pai_ID, dbo.RetornaNomeObjeto(Pai_ID), 1 AS Nivel
FROM Relacoes

UNION ALL

SELECT REL.Filho_ID, TabelaFilho, DEP.Pai_ID, TabelaPai, Nivel + 1
FROM Relacoes AS REL
INNER JOIN Dependencias AS DEP ON REL.Pai_ID = DEP.Filho_ID),

Lista (Tabela, Niveis)
AS (

SELECT TabelaPai, COUNT(Nivel) FROM Dependencias
GROUP BY TabelaPai

UNION ALL

SELECT dbo.RetornaNomeObjeto(OBJECT_ID), 0 FROM sys.tables AS T
WHERE NOT EXISTS (
SELECT Pai_ID FROM Dependencias
WHERE Dependencias.Pai_ID = T.OBJECT_ID))

SELECT Tabela,niveis FROM Lista
ORDER BY Niveis DESC


Este exemplo funciona apenas no Sql 2005 ou superior, devido a algumas referências a tabelas de sistema implementadas a partir da versão 2005 como SYS.FOREIGN_KEYS. A solução foi muito boa e definitivamente, resolveu o meu problema!

Até a próxima!

2 comentários:

  1. mas como "resolveu" seu problema? Aí neste comando apenas são trazidas as notas das tabelas, ok. Mas como vc fez para copiar os dados dessas tabelas (conforme ordem retornada pela query) e jogar na nova base, respeitando as chaves?

    Digo isso por causa das chaves autonumericas, que , ao serem inseridas na nova tabela de destino, seguem outra numeração e perde-se a referencia.

    ResponderExcluir
  2. Olá Rodrigo,

    Fico feliz que um artigo meu o tenha ajudado. Confesso que depois de um tempo encontrei algumas falhas e melhorias no artigo e devo "atualizá-lo" em breve.

    [ ]s,

    Gustavo Maia Aguiar
    http://gustavomaiaaguiar.spaces.live.com

    ResponderExcluir

Leave your comment here!