sum()与count()

时间:2013-02-21 09:03:17

标签: sql postgresql aggregate-functions

考虑在PostgreSQL中实现的投票系统,其中每个用户可以在“foo”上向上或向下投票。有foo表存储所有“foo信息”,votes表存储user_idfoo_idvote,其中{ {1}}为+1或-1。

要获得每个foo的投票结果,以下查询将起作用:

vote

但是,以下内容同样适用:

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)) 上有一个索引。

哪种方法更有效? (换句话说,哪个会运行得更快?) 我对PostgreSQL特定的答案和一般的SQL答案感兴趣。

修改

许多答案都考虑了votes.foo_id为空的情况。我忘了提到投票栏上有一个vote约束。

此外,许多人指出第一个更容易阅读。是的,这绝对是真的,如果一位同事写了第二篇,我会愤怒地爆发,除非有表演的必要性。从来没有,问题仍然在于两者的表现。 (从技术上讲,如果第一个查询 way 较慢,那么编写第二个查询就不会是一种犯罪行为。)

3 个答案:

答案 0 :(得分:12)

当然,第一个例子更快,更简单,更容易阅读。甚至在得到slapped with aquatic creatures之前应该是显而易见的。虽然sum()count()略贵,但更重要的是,第二个示例需要两次扫描。

但也存在实际差异sum()可以返回NULL,其中count()不会。我引用manual on aggregate functions

  

应该注意除了count之外,这些函数返回a   没有选择行时为null。特别是,没有行的总和   返回null,而不是像预期的那样为零,

由于您似乎在性能优化方面存在弱点,因此您可能会看到以下详细信息: count(*) 略快于count(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

答案 1 :(得分:2)

第一个会更快。您可以通过简单的方式尝试。

生成一些数据:

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);

BUT!在某些情况下,这个索引可能是最糟糕的,所以你应该尝试两者并运行EXPLAIN ANALYZE以查看哪一个是最好的,或者甚至创建两个并检查哪个PostgreSQL最常用(并排除另一个)。 / p>

答案 2 :(得分:1)

我希望第一个查询能够更快地工作,因为这是一个单一的查询,并且它更具可读性(如果你不得不在一段时间之后再回到这个问题,那就很方便)。

第二个查询包含两个查询。您只能获得一个结果,就像它是一个查询一样。

那就是说,为了绝对肯定哪一个对你有用,我会用两个表填充大量的伪数据并检查查询执行时间。