我需要知道表中的行数来计算百分比。如果总数大于某个预定义的常量,我将使用常量值。否则,我将使用实际的行数。

我可以使用SELECT count(*) FROM表。但是,如果我的常量值是500,000,并且我的表中有5,000,000,000行,计算所有行将浪费大量时间。

是否有可能在我的常数值被超过时就停止计数?

我需要精确的行数,只要它低于给定的限制。否则,如果计数超过极限,我将使用极限值,并希望尽快得到答案。

就像这样:

SELECT text,count(*), percentual_calculus()  
FROM token  
GROUP BY text  
ORDER BY count DESC;

在Oracle中,可以使用rownum限制返回的行数。我猜类似的构造也存在于其他sql中。所以,对于你给出的例子,你可以限制返回的行数为500001,然后应用一个count(*):

SELECT (case when cnt > 500000 then 500000 else cnt end) myCnt
FROM (SELECT count(*) cnt FROM table WHERE rownum<=500001)

我曾经在postgres应用程序中运行:

EXPLAIN SELECT * FROM foo;

然后用正则表达式或类似的逻辑检查输出。对于一个简单的SELECT *,输出的第一行应该是这样的:

Seq Scan on uids  (cost=0.00..1.21 rows=8 width=75)

您可以使用rows=(\d+)值作为将返回的行数的粗略估计,然后仅在估计值小于1.5倍阈值(或您认为对应用程序有意义的任何数字)时执行实际的SELECT COUNT(*)。

根据查询的复杂程度,这个数字可能会越来越不准确。事实上,在我的应用程序中,当我们添加连接和复杂条件时,它变得非常不准确,甚至不知道在100的幂范围内我们会返回多少行,所以我们不得不放弃这种策略。

但是,如果您的查询足够简单,Pg可以在合理的误差范围内预测它将返回多少行,那么它可能适合您。


文本列有多宽?

使用GROUP BY,您无法避免数据扫描(至少是索引扫描)。

我建议:

如果可能,更改模式以删除文本数据的重复。这样,计数将发生在'many'表中的一个狭窄的外键字段上。 或者,用文本的HASH创建一个生成的列,然后GROUP BY哈希列。 同样,这是为了减少工作负载(通过窄列索引进行扫描)

编辑:

你最初的问题与你的编辑不太匹配。我不确定你是否意识到,当与GROUP BY一起使用时,COUNT将返回每个组的项目计数,而不是整个表中的项目计数。


众所周知,在PostgreSQL中,大表中的行计数很慢。MVCC模型需要活动行的完整计数以获得精确的数目。如果计数不像你的情况那样精确,有一些变通办法可以大大加快这一速度。

(请记住,在并发写负载下,即使是“精确的”计数也可能在到达时失效。)

准确的数

对于大桌子来说慢一点。 对于并发写操作,它可能在您获得它的那一刻就过时了。

SELECT count(*) AS exact_count FROM myschema.mytable;

估计

极快:

SELECT reltuples AS estimate FROM pg_class where relname = 'mytable';

通常情况下,估算值非常接近。有多接近,取决于ANALYZE或VACUUM是否运行得足够多——其中“足够”是由对表的写活动级别定义的。

安全评估

上面忽略了在一个数据库中使用相同名称的多个表的可能性——在不同的模式中。为了解释这一点:

SELECT c.reltuples::bigint AS estimate
FROM   pg_class c
JOIN   pg_namespace n ON n.oid = c.relnamespace
WHERE  c.relname = 'mytable'
AND    n.nspname = 'myschema';

转换为bigint可以很好地格式化实数,特别是对于大数。

更好地估计

SELECT reltuples::bigint AS estimate
FROM   pg_class
WHERE  oid = 'myschema.mytable'::regclass;

更快、更简单、更安全、更优雅。参见手册中的对象标识符类型。

取代“myschema。在Postgres 9.4+中使用to_regclass('myschema.mytable')来获取无效表名的异常,而不是什么都没有。看到的:

如何检查一个表是否存在于给定的模式中

更好的估计(对于很少的额外成本)

这对分区表不起作用,因为父表的relpages总是-1(而reltuples包含覆盖所有分区的实际估计值)-在Postgres 14中测试。 您必须将所有分区的估计值相加。

我们可以做Postgres计划器所做的事情。引用手册中的行估计示例:

这些数字是截至上最后一次VACUUM或ANALYZE的最新数据 表格然后,计划器获取实际的当前页数 表(这是一个廉价的操作,不需要表扫描)。如果 这与relpages和reltuples是不同的 从而得出当前的行数估计。

Postgres使用src/backend/utils/adt/plancat.c中定义的estimate_rel_size,这也涵盖了pg_class中没有数据的情况,因为关系从未被抽真空。我们可以在SQL中做类似的事情:

最小的形式

SELECT (reltuples / relpages * (pg_relation_size(oid) / 8192))::bigint
FROM   pg_class
WHERE  oid = 'mytable'::regclass;  -- your table here

安全且明确

SELECT (CASE WHEN c.reltuples < 0 THEN NULL       -- never vacuumed
             WHEN c.relpages = 0 THEN float8 '0'  -- empty table
             ELSE c.reltuples / c.relpages END
     * (pg_catalog.pg_relation_size(c.oid)
      / pg_catalog.current_setting('block_size')::int)
       )::bigint
FROM   pg_catalog.pg_class c
WHERE  c.oid = 'myschema.mytable'::regclass;      -- schema-qualified table here

不打破空桌子和桌子从未见过真空或分析。关于pg_class的手册:

如果该表从未被抽真空或分析过,则reltuples包含-1,表示行数未知。

如果此查询返回NULL,则对表运行ANALYZE或VACUUM并重复。(或者,您也可以像Postgres那样基于列类型估计行宽,但这很乏味且容易出错。)

如果这个查询返回0,表似乎是空的。但我会通过分析来确定。(也可以检查一下你的自动吸尘器设置。)

通常,block_size为8192。Current_setting ('block_size')::int覆盖罕见异常。

表和模式限定使它不受任何搜索路径和范围的影响。

无论哪种方式,对我来说,查询所花费的时间始终小于0.1 ms。

更多网络资源:

Postgres Wiki常见问题解答 计数估计和计数(*)性能的Postgres wiki页面


Postgres 9.5+中的TABLESAMPLE SYSTEM (n)

SELECT 100 * count(*) AS estimate FROM mytable TABLESAMPLE SYSTEM (1);

与@a_horse所注释的一样,如果pg_class中的统计数据由于某种原因不够及时,则为SELECT命令添加的子句可能很有用。例如:

没有自动真空运行。 紧接着在一个大的INSERT / UPDATE / DELETE之后。 临时表(不被autovacuum覆盖)。

这只查看一个随机的n %(在本例中为1)块选择,并计算其中的行数。更大的样本会增加成本,减少误差,这是你的选择。准确性取决于更多因素:

行大小的分布。如果一个给定的块刚好拥有比通常更宽的行,计数就会比通常更低等等。 死元组或FILLFACTOR占用每个块的空间。如果在表中分布不均匀,估计可能会出错。 一般舍入错误。

通常,来自pg_class的估计会更快更准确。

实际问题的答案

首先,我需要知道表中的行数,如果总行数 Count大于某个预定义的常数,

是否……

... 是否有可能此时计数通过我的常数值,它会吗 停止计数(不要等到计数结束才通知 行数更大)。

是的。你可以使用LIMIT子查询:

SELECT count(*) FROM (SELECT 1 FROM token LIMIT 500000) t;

Postgres实际上在给定限制之外停止计数,您将获得最多n行的精确和当前计数(在示例中为500000),否则为n。但是没有pg_class中的估计快。


对于SQL Server(2005或以上),一个快速可靠的方法是:

SELECT SUM (row_count)
FROM sys.dm_db_partition_stats
WHERE object_id=OBJECT_ID('MyTableName')   
AND (index_id=0 or index_id=1);

sys.详细信息。dm_db_partition_stats在MSDN中解释

该查询添加来自(可能)分区表的所有部分的行。

index_id=0是无序表(堆),index_id=1是有序表(聚集索引)

这里详细介绍了更快(但不可靠)的方法。


参考资料来自本博客。

您可以使用下面的查询来查找行数。

使用pg_class:

 SELECT reltuples::bigint AS EstimatedCount
    FROM   pg_class
    WHERE  oid = 'public.TableName'::regclass;

使用pg_stat_user_tables:

SELECT 
    schemaname
    ,relname
    ,n_live_tup AS EstimatedCount 
FROM pg_stat_user_tables 
ORDER BY n_live_tup DESC;

SELECT MAX(id) FROM <table_name>;将id更改为表的PK