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 ;
EXCEPT
, verifique esta pregunta: Una forma eficiente de comparar dos grandes conjuntos de datos en SQL