这个组+总和查询可以更好地编写,以便它可以更快地执行吗?

时间:2017-05-12 13:21:18

标签: postgresql

这是数据:

me  friend  game    status  count
1   2       gem     done    10
2   1       gem     done    5
1   3       gem     done    4
3   1       gem     done    6

这是我的疑问:

WITH
  -- outgoing for all
    outgoing_for_all AS
  (SELECT
     me,
     sum(count) AS sum
   FROM game_totals
   WHERE status IN ('pending', 'done')
   GROUP BY me),

  -- incoming for all
    incoming_for_all AS
  (SELECT
     friend,
     sum(count) AS sum
   FROM game_totals
   WHERE status IN ('pending', 'done')
   GROUP BY friend)

SELECT
  me,
  outgoing_for_all.sum AS outgoing,
  incoming_for_all.sum AS incoming,
  outgoing_for_all.sum - incoming_for_all.sum AS score
FROM outgoing_for_all
  FULL OUTER JOIN incoming_for_all ON outgoing_for_all.me = incoming_for_all.friend

结果如下:

me  outgoing    incoming    score
1   14          11          3
2   5           10          -5
3   6           4           2

上面的查询是否可以写入以便更快地执行? 我认为可能只用一个SELECT进行求和。问题是,我不知道如何正确GROUP BY所以我可以将count从两行加成一行。

谢谢。

2 个答案:

答案 0 :(得分:2)

是的,您可以使用window functions在一个查询中获得两者的总和。

SELECT
 me,
 sum(count) AS sum over(partition by me) AS outgoing,
 sum(count) AS sum over(partition by friend) AS incoming
FROM game_totals
WHERE status IN ('pending', 'done')

答案 1 :(得分:1)

  

问题是,我不知道如何正确GROUP BY所以我可以将count从两行加成一行。

您猜对了:因为您希望将一行(count值)计算两次(一个用于me的{​​{1}},一个用于outgoing' s friend),您需要将所有行加倍。此外,这些加倍的行将需要按不同的列进行分组。传统的方法通常是incoming

UNION

或者,因为我们确切地知道每行必须计算两次,所以您也可以使用SELECT me, SUM(count) FILTER (WHERE mul = 1) outgoing, SUM(count) FILTER (WHERE mul = -1) incoming, SUM(mul * count) score FROM ( SELECT me, 1 mul, count FROM game_totals WHERE status IN ('pending', 'done') UNION ALL SELECT friend, -1, count FROM game_totals WHERE status IN ('pending', 'done') ) t GROUP BY me;

CROSS JOIN

但是:这些更具可读性。它们实际上比你的变体慢。 (我担心,简单的窗口功能对你来说也无济于事。)我认为你已经找到了最快的解决方案。但是,您应该考虑使用索引(可能更多,或其他索引,如果您已经使用它们)。 F.ex.这个指数可以帮到你很多:

SELECT   CASE mul WHEN 1 THEN me ELSE friend END me,
         SUM(count) FILTER (WHERE mul = 1) outgoing,
         SUM(count) FILTER (WHERE mul = -1) incoming,
         SUM(mul * count) score
FROM     game_totals, (VALUES (1), (-1)) m(mul)
WHERE    status IN ('pending', 'done')
GROUP BY CASE mul WHEN 1 THEN me ELSE friend END

http://rextester.com/NGAHW3672