我怎样才能简化这个游戏统计查询?

这个代码按预期工作,但我很长,令人毛骨悚然。

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;