First, a remark: your query could be written simpler as SELECT count(DISTINCT assembly_id) FROM assembly_prods; Also, your statistics query is wrong, because `n_distict` can also be negative. You should query: ``` SELECT CASE WHEN s.n_distinct < 0 THEN - s.n_distinct * t.reltuples ELSE s.n_distinct END AS n_distinct FROM pg_class t JOIN pg_namespace n ON n.oid = t.relnamespace JOIN pg_stats s ON t.relname = s.tablename AND n.nspname = s.schemaname WHERE s.schemaname = 'public' AND s.tablename = 'assembly_prods' AND s.attname = 'assembly_id'; ``` For a simple query like that, the statistics should contain a good estimate. If the estimates are off, try to `ANALYZE` the table. That will also fix the results for a newly `TRUNCATE`d table. `TRUNCATE` does not cause PostgreSQL to autoanalyze the table (there may be room for improvement here). If that improves the results, see that the table is analyzed more often by configuring ALTER TABLE assembly_prods SET (autovacuum_analyze_scale_factor = 0.05); It is also possible to set `autovacuum_analyze_scale_factor` to 0 and raise `autovacuum_analyze_threshold` to the daily change rate for the table. If `ANALYZE` alone does not improve the estimate, increase the size of the sample: ALTER TABLE assembly_prods ALTER assembly_id SET STATISTICS 1000; A new `ANALYZE` should now produce better estimates. Getting good `n_distinct` estimates for more complicated queries becomes increasingly more difficult. Sometimes [extended statistics][1] will improve the estimate considerably. As far as I know, PostgreSQL v12 does not bring any improvements in this area. [1]: https://www.postgresql.org/docs/current/sql-createstatistics.html