Memeriksa apakah dua tabel memiliki konten yang identik di PostgreSQL

28

Ini sudah ditanyakan pada Stack Overflow , tetapi hanya untuk MySQL. Saya menggunakan PostgreSQL. Sayangnya (dan yang mengejutkan) PostgreSQL sepertinya tidak memiliki sesuatu seperti CHECKSUM table.

Solusi PostgreSQL akan baik-baik saja, tetapi yang umum akan lebih baik. Saya menemukan http://www.besttechtools.com/articles/article/sql-query-to-check-two-tables-have-identical-data , tapi saya tidak mengerti logika yang digunakan.

Latar Belakang: Saya menulis ulang beberapa kode penghasil basis data, jadi saya perlu memeriksa apakah kode lama dan baru menghasilkan hasil yang identik.

Faheem Mitha
sumber
3
Anda dapat menggunakan EXCEPT, periksa pertanyaan ini: Cara efisien untuk membandingkan dua set data besar dalam SQL
ypercubeᵀᴹ
pg_comparator melakukan perbandingan dan sinkronisasi konten tabel efisien
natmaka
@natmaka Apakah ini jawaban yang terpisah?
Faheem Mitha

Jawaban:

24

Salah satu opsi adalah menggunakan FULL OUTER JOIN antara dua tabel dalam bentuk berikut:

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 ;

Sebagai contoh:

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 ;

Akan mengembalikan hitungan 2, sedangkan:

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 ;

mengembalikan harapan untuk hitungan 0.

Hal yang saya sukai dari metode ini adalah ia hanya perlu membaca setiap tabel satu kali vs membaca setiap tabel dua kali saat menggunakan EXISTS. Selain itu, ini harus berfungsi untuk basis data apa pun yang mendukung gabungan luar penuh (bukan hanya Postgresql).

Saya umumnya mengecilkan penggunaan klausa PENGGUNAAN tetapi di sini adalah satu situasi di mana saya percaya itu adalah pendekatan yang lebih baik.

Tambahan 2019-05-03:

Jika ada masalah dengan kemungkinan data nol, (yaitu kolom id tidak dapat dibatalkan tetapi nilai valnya), maka Anda dapat mencoba yang berikut ini:

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
sumber
Bukankah ini akan gagal jika val dibatalkan?
Amit Goldstein
@AmitGoldstein - nulls akan menjadi masalah. Lihat tambahan saya untuk satu solusi yang memungkinkan untuk itu.
gsiems
30

Anda dapat menggunakan EXCEPToperator. Misalnya, jika tabel memiliki struktur yang identik, berikut ini akan mengembalikan semua baris yang ada dalam satu tabel tetapi tidak yang lain (jadi 0 baris jika tabel memiliki data yang identik):

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

Atau dengan EXISTSmengembalikan hanya nilai boolean atau string dengan salah satu dari 2 hasil yang mungkin:

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

Diuji di SQLfiddle


Juga bukan yang EXCEPTmenghapus duplikat (yang seharusnya tidak khawatir jika tabel Anda memiliki beberapa PRIMARY KEYatau UNIQUEkendala tetapi mungkin jika Anda membandingkan hasil kueri sewenang-wenang yang berpotensi menghasilkan baris duplikat).

Hal lain yang dilakukan oleh EXCEPTkata kunci adalah memperlakukan NULLnilai sebagai identik, jadi jika tabel Amemiliki baris dengan (1,2,NULL)dan tabel Bmemiliki baris dengan (1,2,NULL), kueri pertama tidak akan menampilkan baris ini dan kueri kedua akan kembali 'same'jika kedua tabel tidak memiliki baris lain.

Jika Anda ingin menghitung baris yang berbeda, Anda bisa menggunakan variasi pada FULL JOINjawaban gsiem , untuk mendapatkan semua baris (berbeda):

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

dan untuk mendapatkan jawaban ya / tidak:

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 ;

Jika semua kolom dari dua tabel tidak dapat dibatalkan, kedua pendekatan akan memberikan jawaban yang identik.

ypercubeᵀᴹ
sumber
Mungkin ada beberapa metode yang lebih efisien, tidak yakin.
ypercubeᵀᴹ
@FaheemMitha Anda dapat menggunakan ini untuk membandingkan lebih sedikit kolom dari semua. Cukup gunakan SELECT <column_list> FROM asebagai gantiTABLE a
ypercubeᵀᴹ
2
The EXCEPTquery benar-benar hebat!
Erwin Brandstetter
KECUALI permintaan manis!
sharadov
1

Anda perlu Kecuali klausa Sesuatu seperti

SELECT * FROM first_table
EXCEPT
SELECT * FROM second_table

Ini mengembalikan semua baris dari tabel pertama yang tidak ada di tabel kedua

Jelen
sumber
0

Melihat kode tertaut yang tidak Anda mengerti:

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

Saus rahasia digunakan unionsebagai lawan union all. Yang pertama hanya mempertahankan baris yang berbeda sedangkan yang kedua menyimpan duplikat ( referensi ). Dengan kata lain query yang bersarang mengatakan "beri saya semua baris dan kolom dari EmpDtl1 dan selain itu dari EmpDtl2 yang belum ada di EmpDtl1". Hitungan subquery ini akan sama dengan jumlah EmpDtl1 jika dan hanya jika EmpDtl2 tidak berkontribusi setiap baris ke hasil yaitu dua tabel identik.

Atau, buang tabel dalam urutan tombol ke dua file teks dan gunakan alat perbandingan pilihan Anda.

Michael Green
sumber
3
Ini tidak akan mendeteksi kasus ketika EmpDtl2memiliki lebih sedikit baris daripada EmpDtl1dan semua baris yang ada memang ada di EmpDtl1.
a_horse_with_no_name