如何解决对于绝对性能,SUM 更快还是 COUNT 更快?
你已经自己回答了这个问题。我有几点要补充:
在(和其他支持该boolean
类型的 RDBMS)中,您可以boolean
直接使用测试结果。将其转换为integer
and SUM()
:
SUM((amount > 100)::int))
NULLIF()
或者在表达式中使用它and COUNT()
:
COUNT(NULLIF(amount > 100, FALSE))
或者用一个简单的OR NULL
:
COUNT(amount > 100 OR NULL)
或其他各种表达方式。。COUNT()
通常比 快得多SUM()
。不像SUM()
和像保罗已经评论过的,COUNT()
永远不会返回NULL
,这可能很方便。
它比上述所有方法约 5 - 10 %:
COUNT(*) FILTER (WHERE amount > 100)
查询和你的测试用例一样简单,只有一个计数,没有别的,你可以重写:
SELECT count(*) FROM tbl WHERE amount > 100;
…这是真正的性能之王,即使没有索引。 使用适用的索引,速度可以提高几个数量级,尤其是使用仅索引扫描时。
基准
Postgres 13
db<>在这里
与下面的 Postgres 10 的结果基本相同。(我还在小提琴中添加了一个没有新并行性的测试,以比较苹果和苹果。)
Postgres 10
我为 Postgres 10 进行了一系列新的测试,包括聚合FILTER
子句和演示索引对大小计数的作用。
简单设置:
CREATE TABLE tbl (
tbl_id int
, amount int NOT NULL
);
INSERT INTO tbl
SELECT g, (random() * 150)::int
FROM generate_series (1, 1000000) g;
-- only relevant for the last test
CREATE INDEX ON tbl (amount);
由于背景噪音和测试台的具体情况,实际时间会有很大差异。从一组更大的测试中显示典型的最佳时间。这两种情况应该抓住本质:
计数〜所有行的 1%
SELECT COUNT(NULLIF(amount > 148, FALSE)) FROM tbl; -- 140 ms
SELECT SUM((amount > 148)::int) FROM tbl; -- 136 ms
SELECT SUM(CASE WHEN amount > 148 THEN 1 ELSE 0 END) FROM tbl; -- 133 ms
SELECT COUNT(CASE WHEN amount > 148 THEN 1 END) FROM tbl; -- 130 ms
SELECT COUNT((amount > 148) OR NULL) FROM tbl; -- 130 ms
SELECT COUNT(*) FILTER (WHERE amount > 148) FROM tbl; -- 118 ms -- !
SELECT count(*) FROM tbl WHERE amount > 148; -- without index -- 75 ms -- !!
SELECT count(*) FROM tbl WHERE amount > 148; -- with index -- 1.4 ms -- !!!
db<>在这里
计数〜所有行的 33%
SELECT COUNT(NULLIF(amount > 100, FALSE)) FROM tbl; -- 140 ms
SELECT SUM((amount > 100)::int) FROM tbl; -- 138 ms
SELECT SUM(CASE WHEN amount > 100 THEN 1 ELSE 0 END) FROM tbl; -- 139 ms
SELECT COUNT(CASE WHEN amount > 100 THEN 1 END) FROM tbl; -- 138 ms
SELECT COUNT(amount > 100 OR NULL) FROM tbl; -- 137 ms
SELECT COUNT(*) FILTER (WHERE amount > 100) FROM tbl; -- 132 ms -- !
SELECT count(*) FROM tbl WHERE amount > 100; -- without index -- 102 ms -- !!
SELECT count(*) FROM tbl WHERE amount > 100; -- with index -- 55 ms -- !!!
db<>在这里
每组中的最后一个测试使用扫描,这就是为什么它有助于计算所有行的三分之一。当涉及大约 5% 或更多的行时,普通索引或位图索引扫描无法与顺序扫描竞争。
Postgres 9.1 的旧测试
为了验证我EXPLAIN ANALYZE
对 PostgreSQL 9.1.6 中的真实表进行了快速测试。
184568 行中的 74208 行符合条件kat_id > 50
。所有查询都返回相同的结果。我依次运行了 10 次以排除缓存效果,并将最佳结果作为注释附加:
SELECT SUM((kat_id > 50)::int) FROM log_kat; -- 438 ms
SELECT COUNT(NULLIF(kat_id > 50, FALSE)) FROM log_kat; -- 437 ms
SELECT COUNT(CASE WHEN kat_id > 50 THEN 1 END) FROM log_kat; -- 437 ms
SELECT COUNT((kat_id > 50) OR NULL) FROM log_kat; -- 436 ms
SELECT SUM(CASE WHEN kat_id > 50 THEN 1 ELSE 0 END) FROM log_kat; -- 432 ms
性能上几乎没有任何真正的差异。
FILTER
Postgres 9.4+ 中的聚合子句
从 Postgres 9.4 开始,有一种干净快速(SQL 标准)的方式:
SELECT count(*) FILTER (WHERE score BETWEEN 0 AND 3) AS low
, count(*) FILTER (WHERE score BETWEEN 4 AND 7) AS mid
, count(*) FILTER (WHERE score BETWEEN 8 AND 10) AS high
, count(*) AS total
FROM foo;
total`加起来和`low`,除非涉及 NULL 或其他值。`mid``high
链接:
另请阅读下文。
Postgres 9.3-
有几个技巧:
@Phil提供了一个带有CASE
语句的通用替代方法(除了sum(1)
,这不是标准方式)。我喜欢使用更短的形式:
SELECT count(score BETWEEN 0 AND 3 OR NULL) AS low
, count(score BETWEEN 4 AND 6 OR NULL) AS mid
, count(score BETWEEN 7 AND 10 OR NULL) AS high
, count(*) AS total
FROM foo;
如果您的值与您的问题中定义的一样(仅0
-10
可能),请进一步简化:
SELECT count(score < 4 OR NULL) AS low
, count(score BETWEEN 4 AND 6 OR NULL) AS mid
, count(score > 6 OR NULL) AS high
, count(*) AS total
FROM foo;
稍微短一点,快一点。
- 最重要的是,根据文档:
需要注意的是,除了 之外
count
,这些函数在没有选择行时返回空值。特别是,sum
of no rows 返回 null,而不是预期的零,…
-
count(*)
是标准方式,比sum(1)
. 同样,null vs. 0 适用。
这些查询中的任何一个(包括 Phil 的)都将total
. 如果这不是可取的,请改用:
count(score) AS total_not_null
db<>fiddle here
解决方法
这与计算符合特定条件的记录数有关,例如invoice amount > $100
。
我倾向于更喜欢
COUNT(CASE WHEN invoice_amount > 100 THEN 1 END)
但是,这同样有效
SUM(CASE WHEN invoice_amount > 100 THEN 1 ELSE 0 END)
我会认为 COUNT 更可取,原因有两个:
- 传达意图,即
COUNT
COUNT
可能在某处涉及一个简单的i += 1
操作,而 SUM 不能指望它的表达式是一个简单的整数值。
有没有人有关于特定 RDBMS 差异的具体事实?
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。