Sprawdzanie, czy dwie tabele mają identyczną zawartość w PostgreSQL

28

To pytanie zostało już zadane w przypadku przepełnienia stosu , ale tylko w przypadku MySQL. Używam PostgreSQL. Niestety (i zaskakująco) PostgreSQL nie wydaje się mieć czegoś takiego CHECKSUM table.

Rozwiązanie PostgreSQL byłoby w porządku, ale ogólne byłoby lepsze. Znalazłem http://www.besttechtools.com/articles/article/sql-query-to-check-two-tables-have-identical-data , ale nie rozumiem zastosowanej logiki.

Tło: Ponownie napisałem kod generujący bazę danych, więc muszę sprawdzić, czy stary i nowy kod dają identyczne wyniki.

Faheem Mitha
źródło
3
Możesz użyć EXCEPT, sprawdź to pytanie: Skuteczny sposób porównania dwóch dużych zestawów danych w SQL
ypercubeᵀᴹ
pg_comparator wykonuje wydajne porównywanie i synchronizację zawartości tabeli
natmaka,
@natmaka Czy to powinna być osobna odpowiedź?
Faheem Mitha,

Odpowiedzi:

24

Jedną z opcji jest użycie PEŁNEGO POŁĄCZENIA ZEWNĘTRZNEGO między dwiema tabelami w następującej formie:

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 ;

Na przykład:

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 ;

Zwróci liczbę 2, podczas gdy:

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 ;

zwraca oczekiwaną liczbę 0.

W tej metodzie podoba mi się to, że musi ona czytać każdą tabelę tylko raz, a nie czytać każdej tabeli dwa razy przy użyciu EXISTS. Dodatkowo powinno to działać dla każdej bazy danych, która obsługuje pełne sprzężenia zewnętrzne (nie tylko Postgresql).

Ogólnie odradzam stosowanie klauzuli USING, ale tutaj jest jedna sytuacja, w której uważam, że jest to lepsze podejście.

Dodatek 2019-05-03:

Jeśli występuje problem z możliwymi danymi zerowymi (tj. Kolumna id nie ma wartości zerowej, ale wartość val jest), możesz spróbować wykonać następujące czynności:

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
źródło
Czy nie zawiodłoby to, gdyby val można zerować?
Amit Goldstein
@AmitGoldstein - wartości zerowe byłyby problemem. Zobacz moje uzupełnienie dla jednego możliwego rozwiązania tego problemu.
gsiems
30

Możesz użyć EXCEPToperatora. Na przykład, jeśli tabele mają identyczną strukturę, poniższe zwrócą wszystkie wiersze, które są w jednej tabeli, ale nie w drugiej (więc 0 wierszy, jeśli tabele mają identyczne dane):

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

Lub za pomocą, EXISTSaby zwrócić tylko wartość logiczną lub ciąg z jednym z 2 możliwych wyników:

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

Testowany w SQLfiddle


Również nie EXCEPTusuwa duplikatów (nie powinno to stanowić problemu, jeśli tabele mają pewne ograniczenia PRIMARY KEYlub UNIQUEograniczenia, ale może tak być, jeśli porównujesz wyniki dowolnych zapytań, które potencjalnie mogą tworzyć duplikaty wierszy).

Inną rzeczą, którą EXCEPTrobi słowo kluczowe, jest to, że traktuje NULLwartości jako identyczne, więc jeśli tabela Ama wiersz z, (1,2,NULL)a tabela Bma wiersz z (1,2,NULL), pierwsze zapytanie nie wyświetli tych wierszy, a drugie zapytanie zwróci, 'same'jeśli dwie tabele nie mają innego wiersza.

Jeśli chcesz policzyć takie wiersze jako różne, możesz użyć odmiany FULL JOINodpowiedzi gsiems , aby uzyskać wszystkie (różne) wiersze:

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

i uzyskać odpowiedź tak / nie:

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 ;

Jeśli wszystkie kolumny dwóch tabel nie mają wartości zerowej, oba podejścia dadzą identyczne odpowiedzi.

ypercubeᵀᴹ
źródło
Nie może być pewnej bardziej wydajnej metody.
ypercubeᵀᴹ
@FaheemMitha możesz użyć tego, aby porównać mniej kolumn niż wszystkie. Wystarczy użyć SELECT <column_list> FROM azamiastTABLE a
ypercubeᵀᴹ
2
EXCEPTZapytanie jest beaut!
Erwin Brandstetter
Z WYJĄTKIEM zapytanie jest słodkie!
sharadov
1

Potrzebujesz wyjątku Klauzula Coś podobnego

SELECT * FROM first_table
EXCEPT
SELECT * FROM second_table

Zwraca wszystkie wiersze z pierwszej tabeli, które nie znajdują się w drugiej tabeli

Jelen
źródło
0

Patrząc na połączony kod, którego nie rozumiesz:

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

Tajny sos używa unionw przeciwieństwie do union all. Pierwszy zachowuje tylko odrębne wiersze, podczas gdy drugi zachowuje duplikaty ( odniesienie ). Innymi słowy, zagnieżdżone zapytania mówią „daj mi wszystkie wiersze i kolumny z EmpDtl1, a ponadto te z EmpDtl2, które nie są jeszcze w EmpDtl1”. Liczba tego podzapytania będzie równa liczbie EmpDtl1 wtedy i tylko wtedy, gdy EmpDtl2 nie wnosi żadnych wyników do wyniku, tj. Dwie tabele są identyczne.

Alternatywnie, zrzuć tabele w sekwencji klawiszy do dwóch plików tekstowych i użyj wybranego narzędzia porównywania.

Michael Green
źródło
3
Nie wykryje to przypadku, gdy EmpDtl2ma mniej wierszy niż EmpDtl1i wszystkie istniejące wiersze istnieją EmpDtl1.
a_horse_w_no_name