web-dev-qa-db-fra.com

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

Cela a déjà été demandé sur Stack Overflow , mais uniquement pour MySQL. J'utilise PostgreSQL. Malheureusement (et de façon surprenante) PostgreSQL ne semble pas avoir quelque chose comme 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.

29
Faheem Mitha

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 lors de l'utilisation d'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 ;
24
gsiems

Vous pouvez utiliser l'opérateur EXCEPT. 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 EXISTS pour 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é à SQLfiddle


Pas non plus que EXCEPT supprime les doublons (cela ne devrait pas vous inquiéter si vos tables ont des PRIMARY KEY ou UNIQUE contrainte mais cela peut être le cas si vous comparez les résultats de requêtes arbitraires qui peuvent potentiellement produire des lignes en double).

Une autre chose que le mot clé EXCEPT fait est qu'il traite les valeurs de NULL comme identiques, donc si la table A a une ligne avec (1,2,NULL) et la table B a une ligne avec (1,2,NULL), la première requête n'affichera pas ces lignes et la deuxième requête renverra 'same' si les deux tables n'ont pas d'autre ligne.

Si vous souhaitez compter ces lignes comme différentes, vous pouvez utiliser une variante du gsiems 'FULL JOIN réponse, pour obtenir toutes les (différentes) lignes:

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.

31
ypercubeᵀᴹ

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

1
Jelen

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 union par 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.

0
Michael Green