Найдите самую длинную серию отличных результатов на игрока - PullRequest
2 голосов
/ 13 июня 2019

У меня есть следующий результат запроса SELECT с ORDER BY player_id ASC, time ASC в базе данных PostgreSQL:

player_id  points  time

395        0       2018-06-01 17:55:23.982413-04
395        100     2018-06-30 11:05:21.8679-04
395        0       2018-07-15 21:56:25.420837-04
395        100     2018-07-28 19:47:13.84652-04
395        0       2018-11-27 17:09:59.384-05
395        100     2018-12-02 08:56:06.83033-05
399        0       2018-05-15 15:28:22.782945-04
399        100     2018-06-10 12:11:18.041521-04
454        0       2018-07-10 18:53:24.236363-04
675        0       2018-08-07 20:59:15.510936-04
696        0       2018-08-07 19:09:07.126876-04
756        100     2018-08-15 08:21:11.300871-04
756        100     2018-08-15 16:43:08.698862-04
756        0       2018-08-15 17:22:49.755721-04
756        100     2018-10-07 15:30:49.27374-04
756        0       2018-10-07 15:35:00.975252-04
756        0       2018-11-27 19:04:06.456982-05
756        100     2018-12-02 19:24:20.880022-05
756        100     2018-12-04 19:57:48.961111-05

Я пытаюсь найти самую длинную полосу каждого игрока, где points = 100, с тай-брейкомбыть какой-либо серии началось совсем недавно.Мне также нужно определить время, когда началась самая длинная полоса этого игрока.Ожидаемый результат будет:

player_id  longest_streak  time_began

395        1               2018-12-02 08:56:06.83033-05
399        1               2018-06-10 12:11:18.041521-04
756        2               2018-12-02 19:24:20.880022-05

Ответы [ 3 ]

1 голос
/ 13 июня 2019

A действительно проблема.

Предполагая:

  • "Полосы" не прерываются строками издругие игроки.
  • Все столбцы определены NOT NULL.(В противном случае вы должны сделать больше.)

Это должно быть самым простым и быстрым, так как для этого нужны только две быстрые row_number() оконные функции :

SELECT DISTINCT ON (player_id)
       player_id, count(*) AS seq_len, min(ts) AS time_began
FROM  (
   SELECT player_id, points, ts
        , row_number() OVER (PARTITION BY player_id ORDER BY ts) 
        - row_number() OVER (PARTITION BY player_id, points ORDER BY ts) AS grp
   FROM   tbl
   ) sub
WHERE  points = 100
GROUP  BY player_id, grp  -- omit "points" after WHERE points = 100
ORDER  BY player_id, seq_len DESC, time_began DESC;

db <> fiddle здесь

Использование имени столбца ts вместо time, которое является стандартным зарезервированным словом в стандартеSQL.Это разрешено в Postgres, но с ограничениями, и все же плохая идея использовать его в качестве идентификатора.

«Хитрость» заключается в том, чтобы вычесть номера строк так, чтобы последовательные строки попадали в одну группу (grp) в(player_id, points). Затем отфильтруйте те, у кого 100 очков, сгруппируйте их по группам и верните только самый длинный, самый последний результат на игрока.
Базовое объяснение техники:

Мы можем использовать GROUP BY и DISTINCT ON в том же SELECT, GROUP BY применяется до DISTINCT ON.Рассмотрим последовательность событий в запросе SELECT:

О DISTINCT ON:

1 голос
/ 13 июня 2019

Это проблема с пропуском и островом, вы можете попытаться использовать функцию с усечением условия SUM с оконной функцией, получив номер пропуска.

, затем снова использовать MAX и COUNT оконную функцию.

Запрос 1 :

WITH CTE AS (
    SELECT *,
           SUM(CASE WHEN points = 100 THEN 1 END) OVER(PARTITION BY player_id ORDER BY time) - 
           SUM(1) OVER(ORDER BY time) RN
    FROM T
)
SELECT player_id,
       MAX(longest_streak) longest_streak,
       MAX(cnt) longest_streak 
FROM (
  SELECT player_id,
         MAX(time) OVER(PARTITION BY rn,player_id) longest_streak, 
         COUNT(*) OVER(PARTITION BY rn,player_id)  cnt
  FROM CTE 
  WHERE points > 0
) t1
GROUP BY player_id

Результаты :

| player_id |              longest_streak | longest_streak |
|-----------|-----------------------------|----------------|
|       756 | 2018-12-04T19:57:48.961111Z |              2 |
|       399 | 2018-06-10T12:11:18.041521Z |              1 |
|       395 |  2018-12-02T08:56:06.83033Z |              1 |
0 голосов
/ 13 июня 2019

Один из способов сделать это - посмотреть, сколько строк между предыдущим и следующим не 100 результатами.Чтобы получить длины полос:

with s as (
      select s.*,
             row_number() over (partition by player_id order by time) as seqnum,
             count(*) over (partition by player_id) as cnt          
      from scores s
     )
select s.*,
       coalesce(next_seqnum, cnt + 1) - coalesce(prev_seqnum, 0) - 1 as length
from (select s.*,
             max(seqnum) filter (where score <> 100) over (partition by player_id order by time) as prev_seqnum,
             max(seqnum) filter (where score <> 100) over (partition by player_id order by time) as next_seqnum
      from s
     ) s
where score = 100;

Затем вы можете включить другие условия:

with s as (
      select s.*,
             row_number() over (partition by player_id order by time) as seqnum,
             count(*) over (partition by player_id) as cnt          
      from scores s
     ),
     streaks as (
      select s.*,
             coalesce(next_seqnum - prev_seqnum) over (partition by player_id) as length,
             max(next_seqnum - prev_seqnum) over (partition by player_id) as max_length,
             max(next_seqnum) over (partition by player_id) as max_next_seqnum
      from (select s.*,
                   coalesce(max(seqnum) filter (where score <> 100) over (partition by player_id order by time), 0) as prev_seqnum,
                   coalesce(max(seqnum) filter (where score <> 100) over (partition by player_id order by time), cnt + 1) as next_seqnum
            from s
           ) s
      where score = 100
     )
select s.*
from streaks s
where length = max_length and
      next_seqnum = max_next_seqnum;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...