[英]sum() vs. count()
考虑在PostgreSQL中实现的投票系统,其中每个用户可以在“foo”上向上或向下投票。 有一个foo
表,用于存储所有的“富信息”,以及votes
存储表user_id
, foo_id
,并vote
,其中vote
是+1或-1。
要获得每个foo的投票结果,以下查询将起作用:
SELECT sum(vote) FROM votes WHERE foo.foo_id = votes.foo_id;
但是,以下内容也可以正常工作:
(SELECT count(vote) FROM votes
WHERE foo.foo_id = votes.foo_id
AND votes.vote = 1)
- (SELECT count(vote) FROM votes
WHERE foo.foo_id = votes.foo_id
AND votes.vote = (-1))
我目前在votes.foo_id
上有一个索引。
哪种方法更有效? (换句话说,哪个会运行得更快?)我对PostgreSQL特定的答案和一般的SQL答案感兴趣。
编辑
很多答案都考虑到vote
为空的情况。 我忘了提到投票列上有一个NOT NULL
约束。
此外,许多人指出,第一个更容易阅读。 是的,这绝对是真的,如果一位同事写了第二篇,我会愤怒地爆发,除非有表演的必要性。 从来没有,问题仍然在于两者的表现。 (从技术上来说,如果第一个查询方法要慢,它不会是这种罪行写入第二个查询。)
当然,第一个例子更快,更简单,更容易阅读。 甚至在被水生生物拍打之前应该是显而易见的。 虽然sum()
比count()
略贵,但更重要的是,第二个例子需要两次扫描。
但是也有一个实际的区别 : sum()
可以返回NULL
,而count()
则不会。 我引用了关于聚合函数的手册 :
应该注意,除了count之外,这些函数在没有选择行时返回空值。 特别是,没有行的总和返回null,而不是像人们预期的那样为零,
由于您似乎在性能优化方面存在弱点,因此这里有一个您可能会喜欢的细节: count(*)
略快于count(vote)
。 如果vote为NOT NULL
则仅等效。 使用EXPLAIN ANALYZE
测试性能。
这两个查询都是语法上的废话,独自站立。 只有从较大查询的SELECT
列表中复制它们才有意义:
SELECT *, (SELECT sum(vote) FROM votes WHERE votes.foo_id = foo.foo_id)
FROM foo;
这里重要的一点是相关子查询 - 如果您只在查询中阅读一小部分 votes
,这可能没问题。 我们会看到其他WHERE
条件,您应该有匹配的索引。
在Postgres 9.3或更高版本中,替代的,更清洁,100%等效的解决方案将使用LEFT JOIN LATERAL ... ON true
:
SELECT *
FROM foo f
LEFT JOIN LATERAL (
SELECT sum(vote) FROM votes WHERE foo_id = f.foo_id
) v ON true;
通常类似的表现。 细节:
但是 ,在从表格votes
读取大部分或全部内容时 ,这将(更快)更快:
SELECT f.*, v.score
FROM foo f
JOIN (
SELECT foo_id, sum(vote) AS score
FROM votes
GROUP BY 1
) v USING (foo_id);
首先在子查询中聚合值,然后加入到结果中。
关于USING
:
第一个会更快。 您可以通过简单的方式尝试。
生成一些数据:
CREATE TABLE votes(foo_id integer, vote integer);
-- Insert 1000000 rows into 100 foos (1 to 100)
INSERT INTO votes SELECT round(random()*99)+1, CASE round(random()) WHEN 0 THEN -1 ELSE 1 END FROM generate_series(1, 1000000);
CREATE INDEX idx_votes_id ON votes (foo_id);
检查两个
EXPLAIN ANALYZE SELECT SUM(vote) FROM votes WHERE foo_id = 5;
EXPLAIN ANALYZE SELECT (SELECT COUNT(*) AS count FROM votes WHERE foo_id=5 AND vote=1) - (SELECT COUNT(*)*-1 AS count FROM votes WHERE foo_id=5 AND vote=-1);
但事实是,它们并不等同,为了确保第一个作为第二个,你需要对待null
案例:
SELECT COALESCE(SUM(vote), 0) FROM votes WHERE foo_id = 5;
还有一件事。 如果您使用的是PostgreSQL 9.2,则可以使用其中的两列创建索引,这样您就有可能使用仅索引扫描:
CREATE INDEX idx_votes_id ON votes (foo_id, vote);
但! 在某些情况下,这个索引可能是最差的,所以你应该尝试使用两个并运行EXPLAIN ANALYZE
以查看哪个是最好的,或者甚至创建两个并检查哪个PostgreSQL使用最多(并排除另一个)。
我希望第一个查询能够更快地工作,因为这是一个单一的查询,并且它更具可读性(如果你不得不在一段时间之后再回到这个问题,那就很方便了)。
第二个查询包含两个查询。 您只能获得一个结果,就像它是一个查询一样。
也就是说,为了绝对确定哪些更适合你,我会用两个表填充大量的伪数据并检查查询执行时间。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.