【问题标题】:How to get aggregate data from a dynamic number of related rows in adjacent table如何从相邻表中的动态相关行数中获取聚合数据
【发布时间】:2018-08-26 07:48:44
【问题描述】:

我有一张比赛表,大致如下:

player_id | match_id | result | opponent_rank
----------------------------------------------
82        | 2847     |   w    |   42
82        | 3733     |   w    |  185
82        | 4348     |   l    |   10
82        | 5237     |   w    |  732
82        | 5363     |   w    |   83
82        | 7274     |   w    |    6
51        | 2347     |   w    |   39
51        | 3746     |   w    |  394
51        | 5037     |   l    |   90
...       | ...      |  ...   |  ...

要获得所有连胜记录(不仅仅是任何玩家的连胜记录),我使用以下查询:

SELECT player.tag, s.streak, match.date, s.player_id, s.match_id FROM (
    SELECT streaks.streak, streaks.player_id, streaks.match_id FROM (
        SELECT w1.player_id, max(w1.match_id) AS match_id, count(*) AS streak FROM (
            SELECT w2.player_id, w2.match_id, w2.win, w2.date, sum(w2.grp) OVER w AS grp FROM (
                SELECT m.player_id, m.match_id, m.win, m.date, (m.win = false AND LAG(m.win, 1, true) OVER w = true)::integer AS grp FROM matches_m AS m
                WHERE matches_m.opponent_position<'100'
                    WINDOW w AS (PARTITION BY m.player_id ORDER BY m.date, m.match_id)
                    ) AS w2
                    WINDOW w AS (PARTITION BY w2.player_id ORDER BY w2.date, w2.match_id)
                ) AS w1
            WHERE w1.win = true
            GROUP BY w1.player_id, w1.grp
            ORDER BY w1.player_id DESC, count(*) DESC
        ) AS streaks
    ORDER BY streaks.streak DESC
    LIMIT 100
    ) AS s
LEFT JOIN player ON player.id = s.player_id
LEFT JOIN match ON match.id = s.match_id

结果看起来像这样(注意这不是一个固定的表格/视图,因为上面的查询可以通过某些参数进行扩展,例如国籍、日期范围、玩家排名等):

player_id | match_id | streak
-------------------------------
82        | 3733     |  2
82        | 7274     |  3
51        | 3746     |  2
...       | ...      |  ...

我现在要添加的是一组汇总数据,以提供有关连胜的详细信息。首先,我想知道每一次连胜中对手的平均排名。其他数据是连胜的持续时间、第一个和最后一个日期、结束连胜的对手姓名或是否仍在进行中,等等。我尝试了各种方法——CTE、一些精心设计的连接、联合,或者将它们作为滞后函数添加到现有代码中。但我完全不知道如何解决这个问题。

从代码中可以看出,我的 SQL 技能非常基础,所以请原谅任何错误或低效的语句。对于完整的上下文,我在 Debian 上使用 Postgres 9.4,matches_m 表是一个具有 550k 行的物化视图(查询现在需要 2.5 秒)。数据来自http://aligulac.com/about/db/,我只是镜像来创建上述视图。

【问题讨论】:

  • 对不起,我有过失。但是,我确实在两个线程上都收到了可行的解决方案,因此我不会删除其中任何一个。除非模组想要从 DBA 那里“迁移”解决方案,但现在我已经在另一个线程中添加了评论。感谢您指出规则,但不会再发生。

标签: sql postgresql aggregate-functions postgresql-9.4


【解决方案1】:

我认为这可以满足您的需求。

关键思想是为每个连胜分配一个“连胜组”,以便您可以汇总它们。你可以通过观察来做到这一点:

  1. 连胜的比赛显然是“胜利”。
  2. 可以通过计算之前的失败次数来识别连续获胜 - 这对于连续来说是恒定的。

Postgres 在 9.4 中引入了filter 子句,这使得语法更简单一些:

select player_id, count(*) as streak_length,
       avg(opponent_rank) as avg_opponent_rank
from (select m.*,
             count(*) filter (where result = 'l') over (partition by player_id order by date) as streak_grp
      from matches_m m
     ) m
where result = 'w'
group by player_id, streak_grp;

【讨论】:

  • 在我收到的所有解决方案中,这让我大吃一惊——仅仅是因为简单。由于某种奇怪的原因,它比我原来的查询稍微慢;但是,它的可读性要高得多,而且时间损失可以忽略不计(在 10% 范围内)。但最重要的是,这正是我所需要的,没有任何额外的连接/子查询等,所以这是一个完美的答案!谢谢!
  • @hcac 。 . .如果您在(hcac, date, result) 上有索引,则性能可能会显着提高。
【解决方案2】:

您需要获取所有行的最高条纹,而不是汇总行。

这将返回前 100 条带详细信息的条纹(而不是返回所有超过 n 的条纹更容易)。

SELECT ....
FROM
 (
   SELECT streaks.*,
      -- used to filter the top 100 streaks
      -- (would be more efficient without by filtering streaks only in Where)
      Dense_Rank()
      Over (ORDER BY streak DESC, grp, player_id) AS topStreak
   FROM
    (
      SELECT w1.*,
         Count(*) 
         Over (PARTITION BY player_id, grp) AS streak -- count wins per streak
      FROM
       ( --  simplified assigning the group numbers to a single Cumulative Sum
         SELECT m.player_id, m.match_id, m.win, m.DATE, --additional columns needed
            -- cumulative sum over 0/1, doesn't increase for wins, i.e. a streak of wins gets the same number
            Sum(CASE WHEN win = False THEN 1 ELSE 0 end)  
            Over(PARTITION BY m.player_id
                 ORDER BY DATE, match_id
                 ROWS Unbounded Preceding) AS grp
         FROM matches_m AS m
         WHERE matches_m.opponent_position<'100' -- should be <100 if it's an INT 
       ) AS w1
      WHERE w1.win = True  -- remove the losses
    ) AS streaks
   -- restrict the number of rows processed by the DENSE_RANK
   -- (could be used instead of DENSE_RANK + WHERE topStreak <= 100)
   WHERE streak > 20 
 ) AS s
WHERE topStreak <= 100

现在您可以对这些条纹应用任何类型的分析。由于 PG 不是我的主要 DBMS,我不知道这是否更容易使用数组或窗口函数,如 last_value(opponent_player_id) over ...

【讨论】:

  • 很好的解决方案,我希望我能给你加分,因为它使它完全可读,包括评论,加上使用我所有的原始查询变量(所以我能够复制/粘贴这个来尝试它出去)。我非常喜欢这个解决方案,它似乎是最快的(特别是如果我限制了连续范围) - 但它仍然需要一些工作来“减少”到我需要的,然后计算时间再次增加。但我已将其添加为书签以备将来使用,很棒的回复!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-11-01
  • 2023-04-06
  • 1970-01-01
  • 2021-11-11
  • 1970-01-01
  • 2020-03-13
  • 1970-01-01
相关资源
最近更新 更多