Maneira rápida de validar duas tabelas uma contra a outra

12

Estamos fazendo um processo ETL. Quando tudo estiver dito e feito, há várias tabelas que devem ser idênticas. Qual é a maneira mais rápida de verificar se essas tabelas (em dois servidores diferentes) são de fato idênticas. Estou falando de esquema e dados.

Posso fazer um hash na tabela que eu próprio faria em um arquivo ou grupo de arquivos individual - para comparar um com o outro. Temos dados do Red-Gate comparados, mas como as tabelas em questão contêm milhões de linhas cada, eu gostaria de algo um pouco mais de desempenho.

Uma abordagem que me intriga é esse uso criativo da declaração sindical . Mas, gostaria de explorar um pouco mais a idéia de hash, se possível.

ATUALIZAÇÃO DE RESPOSTA PÓS

Para futuros visitantes ... aqui está a abordagem exata que acabei adotando. Funcionou tão bem que estamos fazendo isso em todas as tabelas em cada banco de dados. Obrigado às respostas abaixo por me apontarem na direção certa.

CREATE PROCEDURE [dbo].[usp_DatabaseValidation]
    @TableName varchar(50)

AS
BEGIN

    SET NOCOUNT ON;

    -- parameter = if no table name was passed do them all, otherwise just check the one

    -- create a temp table that lists all tables in target database

    CREATE TABLE #ChkSumTargetTables ([fullname] varchar(250), [name] varchar(50), chksum int);
    INSERT INTO #ChkSumTargetTables ([fullname], [name], [chksum])
        SELECT DISTINCT
            '[MyDatabase].[' + S.name + '].['
            + T.name + ']' AS [fullname],
            T.name AS [name],
            0 AS [chksum]
        FROM MyDatabase.sys.tables T
            INNER JOIN MyDatabase.sys.schemas S ON T.schema_id = S.schema_id
        WHERE 
            T.name like IsNull(@TableName,'%');

    -- create a temp table that lists all tables in source database

    CREATE TABLE #ChkSumSourceTables ([fullname] varchar(250), [name] varchar(50), chksum int)
    INSERT INTO #ChkSumSourceTables ([fullname], [name], [chksum])
        SELECT DISTINCT
            '[MyLinkedServer].[MyDatabase].[' + S.name + '].['
            + T.name + ']' AS [fullname],
            T.name AS [name],
            0 AS [chksum]
        FROM [MyLinkedServer].[MyDatabase].sys.tables T
            INNER JOIN [MyLinkedServer].[MyDatabase].sys.schemas S ON 
            T.schema_id = S.schema_id
        WHERE
            T.name like IsNull(@TableName,'%');;

    -- build a dynamic sql statement to populate temp tables with the checksums of each table

    DECLARE @TargetStmt VARCHAR(MAX)
    SELECT  @TargetStmt = COALESCE(@TargetStmt + ';', '')
            + 'UPDATE #ChkSumTargetTables SET [chksum] = (SELECT CHECKSUM_AGG(BINARY_CHECKSUM(*)) FROM '
            + T.FullName + ') WHERE [name] = ''' + T.Name + ''''
    FROM    #ChkSumTargetTables T

    SELECT  @TargetStmt

    DECLARE @SourceStmt VARCHAR(MAX)
    SELECT  @SourceStmt = COALESCE(@SourceStmt + ';', '')
            + 'UPDATE #ChkSumSourceTables SET [chksum] = (SELECT CHECKSUM_AGG(BINARY_CHECKSUM(*)) FROM '
            + S.FullName + ') WHERE [name] = ''' + S.Name + ''''
    FROM    #ChkSumSourceTables S

    -- execute dynamic statements - populate temp tables with checksums

    EXEC (@TargetStmt);
    EXEC (@SourceStmt);

    --compare the two databases to find any checksums that are different

    SELECT  TT.FullName AS [TABLES WHOSE CHECKSUM DOES NOT MATCH]
    FROM #ChkSumTargetTables TT
    LEFT JOIN #ChkSumSourceTables ST ON TT.Name = ST.Name
    WHERE IsNull(ST.chksum,0) <> IsNull(TT.chksum,0)

    --drop the temp tables from the tempdb

    DROP TABLE #ChkSumTargetTables;
    DROP TABLE #ChkSumSourceTables;

END
RThomas
fonte
O SSIS é uma opção? Seria bastante fácil ler em uma tabela e pesquisar na outra.
Kevin
1
É uma opção, é o que está sendo usado para o processo ETL, mas os bigodes no andar de cima querem uma segunda opinião sobre se funcionou ou não usando o SSIS para provar que o SSIS acertou não é tão convincente quanto soltar palavras sofisticadas como CheckSum ou Hash MD5.
RThomas

Respostas:

17

Aqui está o que eu fiz antes:

(SELECT 'TableA', * FROM TableA
EXCEPT
SELECT 'TableA', * FROM TableB)
UNION ALL
(SELECT 'TableB', * FROM TableB
EXCEPT
SELECT 'TableB', * FROM TableA)

Funcionou bem o suficiente em tabelas com cerca de 1.000.000 de linhas, mas não tenho certeza de como isso funcionaria em tabelas extremamente grandes.

Adicionado:

Eu executei a consulta no meu sistema, que compara duas tabelas com 21 campos de tipos regulares em dois bancos de dados diferentes anexados ao mesmo servidor executando o SQL Server 2005. A tabela possui cerca de 3 milhões de linhas e há cerca de 25000 linhas diferentes. A chave primária na tabela é estranha, no entanto, pois é uma chave composta de 10 campos (é uma tabela de auditoria).

Os planos de execução para as consultas têm um custo total de 184.25879 para UNIONe 184.22983 para UNION ALL. O custo da árvore difere apenas na última etapa antes de retornar as linhas, a concatenação.

A execução de qualquer uma das consultas leva cerca de 42s mais cerca de 3s para realmente transmitir as linhas. O tempo entre as duas consultas é idêntico.

Segunda adição:

Na verdade, é extremamente rápido, cada um rodando contra 3 milhões de linhas em cerca de 2,5 segundos:

SELECT CHECKSUM_AGG(BINARY_CHECKSUM(*)) FROM TableA

SELECT CHECKSUM_AGG(BINARY_CHECKSUM(*)) FROM TableB

Se os resultados não corresponderem, você sabe que as tabelas são diferentes. No entanto, se os resultados do jogo, você não garantiu que os quadros sejam idênticos por causa da possibilidade [altamente improvável] de colisões de checksum.

Não tenho certeza de como as alterações no tipo de dados entre as tabelas afetariam esse cálculo. Eu executaria a consulta nos systempontos de vista ou information_schemapontos de vista.

Tentei a consulta em outra tabela com 5 milhões de linhas e essa foi executada em cerca de 5s, portanto parece ser em grande parte O (n).

Pedaços de bacon
fonte
1
Por que você está sugerindo UNION em vez de UNION ALL? Quais duplicatas você deseja eliminar?
AK
@AlexKuznetsov É justo, embora eu suspeite que o mecanismo de consulta seja inteligente o suficiente para reconhecer que estamos selecionando a chave primária inteira ou talvez não processe a distinta até que tenha o resultado definido nas EXCEPTinstruções. Na verdade, estou curioso agora, se é. No entanto, faz mais sentido lógico ser explícito no RDBMS, então eu o atualizarei.
Bacon Bits
A questão é sobre 2 servidores diferentes.
precisa saber é o seguinte
Não tenho dois servidores para testar. :)
Bacon Bits
A união em toda a abordagem de servidor vinculado me preocupou. Eu gosto da abordagem agregada e binary_checksum ... vou começar alguns testes com isso agora.
RThomas
8

Aqui estão várias idéias que podem ajudar:

  1. Experimente uma ferramenta de comparação de dados diferente - você já experimentou o conjunto de ferramentas de comparação de SQL da Idera ou o ApexSQL Data Diff . Sei que você já pagou pelo RG, mas ainda pode usá-los no modo de teste para concluir o trabalho;).

  2. Dividir e conquistar - que tal dividir tabelas em 10 tabelas menores que podem ser manipuladas por alguma ferramenta comercial de comparação de dados?

  3. Limite-se apenas a algumas colunas - você realmente precisa comparar dados em todas as colunas?

Mark Davidson
fonte
7

Acredito que você deva investigar BINARY_CHECKSUM, embora eu opte pela ferramenta Red Gate:

http://msdn.microsoft.com/en-us/library/ms173784.aspx

Algo assim:

SELECT BINARY_CHECKSUM(*) from myTable;
TelegraphOperator
fonte
Isso detectará diferenças no esquema das tabelas (diferentes nomes de colunas ou tipos de dados)?
precisa saber é o seguinte
Isso tem potencial. Vou fazer alguns testes e voltar.
RThomas
Deu a você mais 1, mas o agregado fornecido pelos pedaços de bacon era a cereja no topo. Obrigado.
RThomas
3

Se você possui uma chave primária, essa é a melhor maneira de examinar as diferenças, pois as linhas que devem ser as mesmas são mostradas juntas.

SELECT
   ID = IsNull(A.ID, B.ID),
   AValue = A.Value,
   BValue = B.Value
FROM
   dbo.TableA A
   FULL JOIN dbo.TableB B
      ON A.ID = B.ID
WHERE
   EXISTS (
      SELECT A.*
      EXCEPT SELECT B.*
   );

Veja em um sqlfiddle .

ErikE
fonte
Bom, obrigado ... boa maneira de encontrar diferenças depois que o agregado indicar que existem diferenças. Obrigado.
RThomas