Comprobando si dos tablas tienen contenido idéntico en PostgreSQL

28

Esto ya se ha pedido en Stack Overflow , pero solo para MySQL. Estoy usando PostgreSQL. Desafortunadamente (y sorprendentemente) PostgreSQL no parece tener algo así CHECKSUM table.

Una solución PostgreSQL estaría bien, pero una solución genérica sería mejor. Encontré http://www.besttechtools.com/articles/article/sql-query-to-check-two-tables-have-identical-data , pero no entiendo la lógica utilizada.

Antecedentes: reescribí un código generador de base de datos, por lo que necesito verificar si el código antiguo y el nuevo producen resultados idénticos.

Faheem Mitha
fuente
3
Puede usar EXCEPT, verifique esta pregunta: Una forma eficiente de comparar dos grandes conjuntos de datos en SQL
ypercubeᵀᴹ
pg_comparator realiza una comparación y sincronización de contenido de tabla eficiente
natmaka
@natmaka ¿Debería ser una respuesta separada?
Faheem Mitha

Respuestas:

24

Una opción es utilizar una UNIÓN COMPLETA EXTERIOR entre las dos tablas en el siguiente formulario:

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 ejemplo:

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 ;

Devolverá una cuenta de 2, mientras 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 ;

devuelve el esperado conteo de 0.

Lo que me gusta de este método es que solo necesita leer cada tabla una vez versus leer cada tabla dos veces cuando se usa EXISTS. Además, esto debería funcionar para cualquier base de datos que admita uniones externas completas (no solo Postgresql).

Generalmente desaconsejo el uso de la cláusula USING, pero aquí hay una situación en la que creo que es el mejor enfoque.

Anexo 2019-05-03:

Si hay un problema con los posibles datos nulos (es decir, la columna de identificación no es anulable pero el valor sí lo es), puede intentar lo siguiente:

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
fuente
¿No fallaría esto si val es anulable?
Amit Goldstein
@AmitGoldstein: los nulos serían un problema. Vea mi apéndice para una posible solución a eso.
gsiems
30

Puedes usar el EXCEPToperador. Por ejemplo, si las tablas tienen una estructura idéntica, lo siguiente devolverá todas las filas que están en una tabla pero no en la otra (entonces 0 filas si las tablas tienen datos idénticos):

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

O con EXISTSpara devolver solo un valor booleano o una cadena con uno de los 2 resultados posibles:

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

Probado en SQLfiddle


Tampoco el que EXCEPTelimina duplicados (eso no debería ser una preocupación si sus tablas tienen algunas restricciones PRIMARY KEYo UNIQUErestricciones, pero puede serlo si está comparando resultados de consultas arbitrarias que potencialmente pueden producir filas duplicadas).

Otra cosa que EXCEPThace la palabra clave es que trata los NULLvalores como idénticos, por lo que si la tabla Atiene una fila con (1,2,NULL)y la tabla Btiene una fila con (1,2,NULL), la primera consulta no mostrará estas filas y la segunda consulta devolverá 'same'si las dos tablas no tienen otra fila.

Si desea contar tales filas como diferentes, puede usar una variación en la FULL JOINrespuesta de gsiems para obtener todas las filas (diferentes):

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

y para obtener una respuesta sí / no:

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 todas las columnas de las dos tablas no son anulables, los dos enfoques darán respuestas idénticas.

ypercubeᵀᴹ
fuente
Podría haber algún método más eficiente, no estoy seguro.
ypercubeᵀᴹ
@FaheemMitha puede usar esto para comparar menos columnas que todas. Solo use en SELECT <column_list> FROM alugar deTABLE a
ypercubeᵀᴹ
2
La EXCEPTconsulta es una belleza!
Erwin Brandstetter
¡EXCEPTO la consulta es dulce!
sharadov
1

Necesitas Excepto cláusula Algo así

SELECT * FROM first_table
EXCEPT
SELECT * FROM second_table

Esto devuelve todas las filas de la primera tabla que no están en la segunda tabla

Jelen
fuente
0

Mirando el código vinculado que no entiendes:

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

La salsa secreta se utiliza unionen lugar de union all. El primero conserva solo filas distintas, mientras que el segundo conserva duplicados ( referencia ). En otras palabras, las consultas anidadas dicen "dame todas las filas y columnas de EmpDtl1 y, además, las de EmpDtl2 que aún no están en EmpDtl1". El recuento de esta subconsulta será igual al recuento de EmpDtl1 si y solo si EmpDtl2 no contribuye con ninguna fila al resultado, es decir, las dos tablas son idénticas.

Alternativamente, volcar las tablas en secuencia de teclas en dos archivos de texto y usar su herramienta de comparación de elección.

Michael Green
fuente
3
Esto no detectará el caso cuando EmpDtl2tenga menos filas EmpDtl1y todas las filas existentes existan EmpDtl1.
a_horse_with_no_name