Как получить агрегированные данные из динамического числа связанных строк в соседней таблице - PullRequest
0 голосов
/ 26 августа 2018

У меня есть таблица сыгранных матчей, примерно такая:

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 очень просты, поэтому, пожалуйста, извините за любые ошибки или неэффективные утверждения. Для полного контекста я использую Postgres 9.4 в Debian, таблица match_m - это материализованное представление с 550k строками (запрос сейчас занимает 2,5 с). Данные получены из http://aligulac.com/about/db/, Я просто отразил их, чтобы создать вышеупомянутое представление.

Ответы [ 2 ]

0 голосов
/ 26 августа 2018

Я думаю, что это то, что вы хотите.

Основная идея состоит в том, чтобы назначить «группу полос» для каждой серии побед, чтобы вы могли объединить их.Вы можете сделать это, наблюдая:

  1. Матч в выигрышной серии, очевидно, является "победой".
  2. Победную серию можно определить, посчитав количество потерь до нее -- это константа для серии.

Постгрес ввел в 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;
0 голосов
/ 26 августа 2018

Вам нужно получить все строки для наивысших полос вместо агрегированной строки.

Возвращает 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 не является моей основной СУБД, я не знаю, проще ли это с помощью массивов или оконных функций, таких как last_value(opponent_player_id) over ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...