Verificando se duas tabelas têm conteúdo idêntico no PostgreSQL

28

Isso já foi solicitado no Stack Overflow , mas apenas no MySQL. Estou usando o PostgreSQL. Infelizmente (e surpreendentemente) o PostgreSQL não parece ter algo parecido CHECKSUM table.

Uma solução PostgreSQL seria boa, mas uma solução genérica seria melhor. Encontrei http://www.besttechtools.com/articles/article/sql-query-to-check-two-tables-have-identical-data , mas não entendo a lógica usada.

Antecedentes: reescrevi algum código de geração de banco de dados, portanto, preciso verificar se o código antigo e o novo produzem resultados idênticos.

Faheem Mitha
fonte
3
Você pode usar EXCEPT, verifique esta pergunta: Uma maneira eficiente de comparar dois grandes conjuntos de dados no SQL
ypercubeᵀᴹ
pg_comparator faz comparação eficiente conteúdo da tabela e sincronização
natmaka
@natmaka Deve ser uma resposta separada?
Faheem Mitha

Respostas:

24

Uma opção é usar uma junção externa completa entre as duas tabelas no seguinte formato:

SELECT count (1)
    FROM table_a a
    FULL OUTER JOIN table_b b 
        USING (<list of columns to compare>)
    WHERE a.id IS NULL
        OR b.id IS NULL ;

Por exemplo:

CREATE TABLE a (id int, val text);
INSERT INTO a VALUES (1, 'foo'), (2, 'bar');

CREATE TABLE b (id int, val text);
INSERT INTO b VALUES (1, 'foo'), (3, 'bar');

SELECT count (1)
    FROM a
    FULL OUTER JOIN b 
        USING (id, val)
    WHERE a.id IS NULL
        OR b.id IS NULL ;

Retornará uma contagem de 2, enquanto que:

CREATE TABLE a (id int, val text);
INSERT INTO a VALUES (1, 'foo'), (2, 'bar');

CREATE TABLE b (id int, val text);
INSERT INTO b VALUES (1, 'foo'), (2, 'bar');

SELECT count (1)
    FROM a
    FULL OUTER JOIN b 
        USING (id, val)
    WHERE a.id IS NULL
        OR b.id IS NULL ;

retorna a contagem esperada de 0.

O que eu mais gosto nesse método é que ele só precisa ler cada tabela uma vez, em vez de ler cada tabela duas vezes ao usar EXISTS. Além disso, isso deve funcionar para qualquer banco de dados que suporte junções externas completas (não apenas o Postgresql).

Geralmente desencorajo o uso da cláusula USING, mas aqui há uma situação em que acredito que seja a melhor abordagem.

Adendo 2019-05-03:

Se houver um problema com possíveis dados nulos, (ou seja, a coluna id não pode ser anulada, mas a val é), você pode tentar o seguinte:

SELECT count (1)
    FROM a
    FULL OUTER JOIN b
        ON ( a.id = b.id
            AND a.val IS NOT DISTINCT FROM b.val )
    WHERE a.id IS NULL
        OR b.id IS NULL ;
gsiems
fonte
Isso não falharia se val for anulável?
Amit Goldstein
@AmitGoldstein - nulos seria um problema. Veja no meu adendo uma solução possível para isso.
gsiems
30

Você pode usar o EXCEPToperador. Por exemplo, se as tabelas tiverem estrutura idêntica, o seguinte retornará todas as linhas que estão em uma tabela, mas não na outra (portanto, 0 linhas se as tabelas tiverem dados idênticos):

(TABLE a EXCEPT TABLE b)
UNION ALL
(TABLE b EXCEPT TABLE a) ;

Ou com EXISTSpara retornar apenas um valor booleano ou uma string com um dos 2 resultados possíveis:

SELECT CASE WHEN EXISTS (TABLE a EXCEPT TABLE b)
              OR EXISTS (TABLE b EXCEPT TABLE a)
            THEN 'different'
            ELSE 'same'
       END AS result ;

Testado no SQLfiddle


Também não é o que EXCEPTremove duplicatas (isso não deve ser uma preocupação, se suas tabelas tiverem algumas PRIMARY KEYou UNIQUErestrições, mas pode ser se você estiver comparando resultados de consultas arbitrárias que podem potencialmente produzir linhas duplicadas).

Outra coisa que a EXCEPTpalavra-chave faz é tratar os NULLvalores como idênticos; portanto, se a tabela Ativer uma linha com (1,2,NULL)e a tabela Bcom (1,2,NULL), a primeira consulta não mostrará essas linhas e a segunda consulta retornará 'same'se as duas tabelas não tiverem outra linha.

Se você quiser contar essas linhas como diferentes, poderá usar uma variação na FULL JOINresposta dos gsiems para obter todas as linhas (diferentes):

SELECT *
FROM a NATURAL FULL JOIN b
WHERE a.some_not_null_column IS NULL 
   OR b.some_not_null_column IS NULL ;

e para obter uma resposta sim / não:

SELECT CASE WHEN EXISTS
            ( SELECT *
              FROM a NATURAL FULL JOIN b
              WHERE a.some_not_null_column IS NULL 
                 OR b.some_not_null_column IS NULL
            )
            THEN 'different'
            ELSE 'same'
       END AS result ;

Se todas as colunas das duas tabelas não forem anuláveis, as duas abordagens fornecerão respostas idênticas.

ypercubeᵀᴹ
fonte
Poderia haver algum método mais eficiente, não tenho certeza.
ypercubeᵀᴹ
@FaheemMitha, você pode usar isso para comparar menos colunas que todas. Basta usar em SELECT <column_list> FROM avez deTABLE a
ypercubeᵀᴹ
2
A EXCEPTconsulta é uma beleza!
Erwin Brandstetter
EXCETO consulta é doce!
sharadov 20/09/18
1

Você precisa da cláusula Exceto Algo como

SELECT * FROM first_table
EXCEPT
SELECT * FROM second_table

Isso retorna todas as linhas da primeira tabela que não estão na segunda tabela

Jelen
fonte
0

Olhando para o código vinculado, você não entende:

select count(*) from
(
select * From EmpDtl1
union
select * From EmpDtl2
)

O molho secreto está usando unionem oposição a union all. O primeiro mantém apenas linhas distintas, enquanto o último mantém duplicatas ( referência ). Em outras palavras, as consultas aninhadas dizem "me dê todas as linhas e colunas do EmpDtl1 e, além disso, as do EmpDtl2 que ainda não estão no EmpDtl1". A contagem dessa subconsulta será igual à contagem de EmpDtl1 se e somente se EmpDtl2 não contribuir com nenhuma linha para o resultado, ou seja, as duas tabelas são idênticas.

Como alternativa, despeje as tabelas na sequência de chaves em dois arquivos de texto e use sua ferramenta de comparação de sua escolha.

Michael Green
fonte
3
Isso não detectará o caso quando EmpDtl2houver menos linhas do que EmpDtl1todas as linhas existentes EmpDtl1.
A_horse_with_no_name