Simple and correct
First of all, you can simplify your test case with a VALUES
expression instead of the more verbose UNION ALL SELECT
.
You'd need explicit type casts in the first row, if data types are not the default integer
and text
..
Second, a FULL OUTER JOIN
is utterly pointless. All it does is make your query slower. And if any row has more than one match in the other table, it gets multiplied in the count.
WITH t1(col1, col2) AS (VALUES (1, 1), (2, 2), (3, 3))
,t2(col1, col2) AS (VALUES (1, 'A'), (2, 'B'), (2, 'C')) -- 2nd row for "2"
SELECT count(t1.*), count(t2.*)
FROM t1
FULL OUTER JOIN t2 USING (col1);
Yields:
4 3
which is wrong.
WITH t1(col2) AS (VALUES (1), (2), ( 3))
,t2(col2) AS (VALUES ('A'), ('B'), ('C'))
SELECT (SELECT count(*) FROM t1) AS t1_ct
,(SELECT count(*) FROM t1) AS t2_ct;
Yields:
3 3
which is correct, besides being simpler and faster.
Admittedly, with row_number()
freshly applied, there can be no dupes. But it's just a waste of time.
Performance
Counting is relatively slow for big tables. If you don't need an exact count but can live with an estimate, you can get this extremely fast:
SELECT reltuples::bigint AS estimate
FROM pg_class
WHERE oid = 'myschema.mytable'::regclass;
I quote the manual here:
It is updated by VACUUM, ANALYZE, and a few DDL commands such as
CREATE INDEX.
More details in this related answer.