一尘不染

对于绝对性能,SUM 更快还是 COUNT 更快?

mysql

这与计算符合特定条件的记录数有关,例如invoice amount > $100

我倾向于更喜欢

COUNT(CASE WHEN invoice_amount > 100 THEN 1 END)

但是,这同样有效

SUM(CASE WHEN invoice_amount > 100 THEN 1 ELSE 0 END)

我会认为 COUNT 更可取,原因有两个:

  1. 传达意图,即COUNT
  2. COUNT 可能在某处涉及一个简单的i += 1操作,而 SUM 不能指望它的表达式是一个简单的整数值。

有没有人有关于特定 RDBMS 差异的具体事实?


阅读 102

收藏
2022-10-18

共2个答案

一尘不染

你已经自己回答了这个问题。我有几点要补充:

PostgreSQL(和其他支持该boolean类型的 RDBMS)中,您可以boolean直接使用测试结果。将其转换为integerand 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计数〜所有行的 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<>在这里

测试 2计数〜所有行的 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

性能上几乎没有任何真正的差异。

2022-10-18
一尘不染

FILTERPostgres 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,这些函数在没有选择行时返回空值。特别是,sumof no rows 返回 null,而不是预期的零,…

  • count(*) 标准方式,比sum(1). 同样,null vs. 0 适用。

这些查询中的任何一个(包括 Phil 的)都将total. 如果这不是可取的,请改用:

count(score) AS total_not_null

db<>fiddle here

2022-10-18