Vérifier si deux tables ont un contenu identique dans PostgreSQL


28

Cela a déjà été demandé sur Stack Overflow , mais uniquement pour MySQL. J'utilise PostgreSQL. Malheureusement (et de manière surprenante) PostgreSQL ne semble pas avoir quelque chose comme ça CHECKSUM table.

Une solution PostgreSQL serait bien, mais une solution générique serait mieux. J'ai trouvé http://www.besttechtools.com/articles/article/sql-query-to-check-two-tables-have-identical-data , mais je ne comprends pas la logique utilisée.

Contexte: J'ai réécrit du code de génération de base de données, je dois donc vérifier si l'ancien et le nouveau code produisent des résultats identiques.


3
Vous pouvez utiliser EXCEPT, vérifiez cette question: Un moyen efficace de comparer deux grands ensembles de données dans SQL
ypercubeᵀᴹ

pg_comparator effectue une comparaison et une synchronisation efficaces du contenu des tables
natmaka

@natmaka Devrait-il s'agir d'une réponse distincte?
Faheem Mitha

Réponses:


24

Une option consiste à utiliser une FULL OUTER JOIN entre les deux tables sous la forme suivante:

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 ;

Par exemple:

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 ;

Renvoie un nombre de 2, alors 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 ;

renvoie le nombre espéré de 0.

Ce que j'aime dans cette méthode, c'est qu'elle n'a besoin de lire chaque table qu'une seule fois que de lire chaque table deux fois lorsque vous utilisez EXISTS. De plus, cela devrait fonctionner pour toute base de données qui prend en charge les jointures externes complètes (pas seulement Postgresql).

Je décourage généralement l'utilisation de la clause USING, mais voici une situation où je pense que c'est la meilleure approche.

Addendum 2019-05-03:

S'il y a un problème avec des données nulles possibles (c'est-à-dire que la colonne id n'est pas nullable mais que la valeur l'est), vous pouvez essayer ce qui suit:

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 ;

Cela n'échouerait-il pas si val est nullable?
Amit Goldstein

@AmitGoldstein - nulls serait un problème. Voir mon addendum pour une solution possible à cela.
gsiems

30

Vous pouvez utiliser l' EXCEPTopérateur. Par exemple, si les tables ont une structure identique, ce qui suit renverra toutes les lignes qui se trouvent dans une table mais pas l'autre (donc 0 ligne si les tables ont des données identiques):

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

Ou avec EXISTSpour ne renvoyer qu'une valeur booléenne ou une chaîne avec l'un des 2 résultats possibles:

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

Testé chez SQLfiddle


Ce n'est pas non plus le qui EXCEPTsupprime les doublons (cela ne devrait pas être un problème si vos tables en ont PRIMARY KEYou une UNIQUEcontrainte, mais cela peut être si vous comparez les résultats de requêtes arbitraires qui peuvent potentiellement produire des lignes en double).

Une autre chose que fait le EXCEPTmot - clé est qu'il traite les NULLvaleurs comme identiques, donc si la table Aa une ligne avec (1,2,NULL)et la table Ba une ligne avec (1,2,NULL), la première requête n'affichera pas ces lignes et la deuxième requête retournera 'same'si les deux tables n'ont pas d'autre ligne.

Si vous voulez compter ces lignes comme différentes, vous pouvez utiliser une variation de la FULL JOINréponse de gsiems , pour obtenir toutes les lignes (différentes):

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

et pour obtenir une réponse oui / non:

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 ;

Si toutes les colonnes des deux tables ne peuvent pas être annulées, les deux approches donneront des réponses identiques.


Il pourrait y avoir une méthode plus efficace, je ne sais pas.
ypercubeᵀᴹ

@FaheemMitha, vous pouvez l'utiliser pour comparer moins de colonnes que toutes. Il suffit d'utiliser à la SELECT <column_list> FROM aplace deTABLE a
ypercubeᵀᴹ

2
La EXCEPTrequête est magnifique!
Erwin Brandstetter

SAUF la requête est douce!
sharadov

1

Vous avez besoin de la clause Except Quelque chose comme

SELECT * FROM first_table
EXCEPT
SELECT * FROM second_table

Cela retourne toutes les lignes de la première table qui ne sont pas dans la deuxième table


0

En regardant le code lié que vous ne comprenez pas:

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

La sauce secrète utilise unionpar opposition à union all. La première ne conserve que des lignes distinctes tandis que la seconde conserve des doublons ( référence ). En d'autres termes, les requêtes imbriquées indiquent "donnez-moi toutes les lignes et colonnes d'EmpDtl1 et en plus celles d'EmpDtl2 qui ne sont pas déjà dans EmpDtl1". Le nombre de cette sous-requête sera égal au nombre d'EmpDtl1 si et seulement si EmpDtl2 ne contribue aucune ligne au résultat, c'est-à-dire que les deux tables sont identiques.

Vous pouvez également vider les tableaux dans l'ordre des touches dans deux fichiers texte et utiliser l'outil de comparaison de votre choix.


3
Cela ne détectera pas le cas lorsque le nombre de lignes EmpDtl2est inférieur à EmpDtl1et que toutes les lignes existantes existent dans EmpDtl1.
a_horse_with_no_name
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.