12

考虑一个在 PostgreSQL 中实现的投票系统,每个用户都可以对“foo”进行投票。有一个foo存储所有“foo 信息”的votes表,以及一个存储user_idfoo_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 答案都感兴趣。

编辑

很多答案都考虑到了votenull 的情况。我忘了提到NOT NULL投票列有一个限制。

此外,许多人指出第一个更容易阅读。是的,这绝对是真的,如果一个同事写了第二个,我会气得要爆炸,除非有表演的必要性。无论如何,问题仍然在于两人的表现。(从技术上讲,如果第一个查询慢得多,那么编写第二个查询就不是犯罪了。)

4

3 回答 3

13

当然,第一个例子更快、更简单、更容易阅读。甚至在被水生生物打耳光之前就应该很明显。虽然sum()比 略贵count(),但更重要的是第二个示例需要两次扫描。

但也有一个实际的区别sum()可以返回不返回的NULL地方。count()我引用了关于聚合函数的手册

需要注意的是,除了count之外,这些函数在没有选择行时返回一个空值。特别是,没有行的总和返回空值,而不是人们可能期望的零,

由于您似乎有一个性能优化的弱点,这里有一个您可能喜欢的细节: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;

通常类似的性能。细节:

但是,在从 table读取大部分或全部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

于 2013-02-21T12:24:45.783 回答
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);

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

于 2013-02-21T12:45:10.607 回答
1

我希望第一个查询能够更快地工作,因为这是一个单一的查询并且它更具可读性(方便您在一段时间后必须回到这个查询)。

第二个查询由两个查询组成。您只会得到一个结果,就好像它是一个查询一样。

也就是说,为了绝对确定其中哪一个更适合您,我将使用大量虚拟数据填充两个表并检查查询执行时间。

于 2013-02-21T09:42:50.910 回答