考虑在PostgreSQL中实现的投票系统,其中每个用户可以在“foo”上向上或向下投票。有foo
表存储所有“foo信息”,votes
表存储user_id
,foo_id
和vote
,其中{ {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 较慢,那么编写第二个查询就不会是一种犯罪行为。)
答案 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)
我希望第一个查询能够更快地工作,因为这是一个单一的查询,并且它更具可读性(如果你不得不在一段时间之后再回到这个问题,那就很方便)。
第二个查询包含两个查询。您只能获得一个结果,就像它是一个查询一样。
那就是说,为了绝对肯定哪一个对你有用,我会用两个表填充大量的伪数据并检查查询执行时间。