我怎样才能简化这个游戏统计查询?
这个代码按预期工作,但我很长,令人毛骨悚然。
select p.name, p.played, w.won, l.lost from (select users.name, count(games.name) as played from users inner join games on games.player_1_id = users.id where games.winner_id > 0 group by users.name union select users.name, count(games.name) as played from users inner join games on games.player_2_id = users.id where games.winner_id > 0 group by users.name) as p inner join (select users.name, count(games.name) as won from users inner join games on games.player_1_id = users.id where games.winner_id = users.id group by users.name union select users.name, count(games.name) as won from users inner join games on games.player_2_id = users.id where games.winner_id = users.id group by users.name) as w on p.name = w.name inner join (select users.name, count(games.name) as lost from users inner join games on games.player_1_id = users.id where games.winner_id != users.id group by users.name union select users.name, count(games.name) as lost from users inner join games on games.player_2_id = users.id where games.winner_id != users.id group by users.name) as l on l.name = p.name
如您所见,它由3个重复部分组成,用于检索:
- 玩家名字和他们玩的游戏数量
- 球员名字和他们赢得的比赛数量
- 玩家名字和他们输掉的游戏数量
而每一个也包括2个部分:
- 玩家名称以及作为玩家1参与的游戏数量
- 玩家名称和他们作为玩家参与的游戏数量_2
这怎么可以简化?
结果如下所示:
name | played | won | lost ---------------------------+--------+-----+------ player_a | 5 | 2 | 3 player_b | 3 | 2 | 1 player_c | 2 | 1 | 1
由于这是对“长而令人毛骨悚然”的追求,所以查询可以短得多。 即使在第9.3页(或实际上任何版本):
SELECT u.name , count(g.winner_id > 0 OR NULL) AS played , count(g.winner_id = u.id OR NULL) AS won , count(g.winner_id <> u.id OR NULL) AS lost FROM games g JOIN users u ON u.id IN (g.player_1_id, g.player_2_id) GROUP BY u.name;
更多解释:
- 对于绝对性能,SUM是更快还是COUNT?
在第9章中,这可以使用新的Aggregate FILTER子句更清晰(就像@Joe已经提到的那样)。
SELECT u.name , count(*) FILTER (WHERE g.winner_id > 0) AS played , count(*) FILTER (WHERE g.winner_id = u.id) AS won , count(*) FILTER (WHERE g.winner_id <> u.id) AS lost FROM games g JOIN users u ON u.id IN (g.player_1_id, g.player_2_id) GROUP BY u.name;
- 手册
- Postgres Wiki
- 德佩斯的博客文章
这种情况下,相关的子查询可能会简化逻辑:
select u.*, (played - won) as lost from (select u.*, (select count(*) from games g where g.player_1_id = u.id or g.player_2_id = u.id ) as played, (select count(*) from games g where g.winner_id = u.id ) as won from users u ) u;
这假定没有关系。
select users.name, count(case when games.winner_id > 0 then games.name else null end) as played, count(case when games.winner_id = users.id then games.name else null end) as won, count(case when games.winner_id != users.id then games.name else null end) as lost from users inner join games on games.player_1_id = users.id or games.player_2_id = users.id group by users.name;