这与计算符合特定条件的记录数有关,例如invoice amount > $100。
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
i += 1
有没有人有关于特定 RDBMS 差异的具体事实?
你已经自己回答了这个问题。我有几点要补充:
在PostgreSQL(和其他支持该boolean类型的 RDBMS)中,您可以boolean直接使用测试结果。将其转换为integerand SUM():
boolean
integer
SUM()
SUM((amount > 100)::int))
NULLIF()或者在表达式中使用它and COUNT():
NULLIF()
COUNT()
COUNT(NULLIF(amount > 100, FALSE))
或者用一个简单的OR NULL:
OR NULL
COUNT(amount > 100 OR NULL)
或其他各种表达方式。性能几乎相同。COUNT()通常比 快得多SUM()。不像SUM()和像保罗已经评论过的,COUNT()永远不会返回NULL,这可能很方便。
NULL
它比上述所有方法快约 5 - 10 %:
COUNT(*) FILTER (WHERE amount > 100)
如果查询和你的测试用例一样简单,只有一个计数,没有别的,你可以重写:
SELECT count(*) FROM tbl WHERE amount > 100;
…这是真正的性能之王,即使没有索引。 使用适用的索引,速度可以提高几个数量级,尤其是使用仅索引扫描时。
db<>在这里
与下面的 Postgres 10 的结果基本相同。(我还在小提琴中添加了一个没有新并行性的测试,以比较苹果和苹果。)
我为 Postgres 10 进行了一系列新的测试,包括聚合FILTER子句和演示索引对大小计数的作用。
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 -- !!!
测试 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 -- !!!
每组中的最后一个测试使用仅索引扫描,这就是为什么它有助于计算所有行的三分之一。当涉及大约 5% 或更多的行时,普通索引或位图索引扫描无法与顺序扫描竞争。
为了验证我EXPLAIN ANALYZE对 PostgreSQL 9.1.6 中的真实表进行了快速测试。
EXPLAIN ANALYZE
184568 行中的 74208 行符合条件kat_id > 50。所有查询都返回相同的结果。我依次运行了 10 次以排除缓存效果,并将最佳结果作为注释附加:
kat_id > 50
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
性能上几乎没有任何真正的差异。
从 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 维基
另请阅读下文。
有几个技巧:
@Phil提供了一个带有CASE语句的通用替代方法(除了sum(1),这不是标准方式)。我喜欢使用更短的形式:
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可能),请进一步简化:
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
count(*)
这些查询中的任何一个(包括 Phil 的)都将total. 如果这不是可取的,请改用:
total
count(score) AS total_not_null
db<>fiddle here