Нахождение самой длинной серии побед

У меня есть данные в следующем формате.

match_id   team_id   won_ind
----------------------------
37          Team1    N
67          Team1    Y
98          Team1    N
109         Team1    N
158         Team1    Y
162         Team1    Y
177         Team1    Y
188         Team1    Y
198         Team1    N
207         Team1    Y
217         Team1    Y
10          Team2    N
13          Team2    N
24          Team2    N
39          Team2    Y
40          Team2    Y
51          Team2    Y
64          Team2    N
79          Team2    N
86          Team2    N
91          Team2    Y
101         Team2    N

Здесь match_id расположены в хронологическом порядке, 37 — это первый, а 217 — последний матч, сыгранный командой1. won_ind указывало, выиграла команда матч или нет.

Итак, из приведенных выше данных команда1 проиграла свой первый матч, затем выиграла матч, затем проиграла 2 матча, затем выиграла 4 матча подряд и так далее. Теперь мне интересно найти самую длинную победную серию для каждой команды.

Team_id   longest_streak
------------------------
Team1     4
Team2     3

Я знаю, как найти это в plsql, но мне было интересно, можно ли это вычислить в чистом SQL. Я пытался использовать LEAD, LAG и несколько других функций, но ничего не добился.

Я создал образец скрипта здесь.


person Noel    schedule 24.07.2013    source источник
comment
У меня нет времени повторять запись, но в этой превосходной статье обсуждается, как это сделать с помощью самообъединений и суммирования.   -  person eykanal    schedule 24.07.2013


Ответы (4)


Это должно работать, Fiddle здесь: http://sqlfiddle.com/#!4/31f95/27

SELECT   team_id, MAX(seq_length) AS longest_sequence
      FROM (SELECT   team_id, COUNT(*) AS seq_length
                 FROM (SELECT team_id, won_ind,match_id, SUM(new_group) OVER(ORDER BY match_id) AS group_no
                         FROM (SELECT   team_id, won_ind, match_id,
                                        DECODE(LAG(won_ind) OVER(ORDER BY match_id), won_ind, 0, 1) AS new_group
                                   FROM matches
                               ORDER BY team_id))
                WHERE won_ind = 'Y'
             GROUP BY team_id, group_no)
   GROUP BY team_id
   ORDER BY 2 DESC, 1;
person Vrashabh Irde    schedule 24.07.2013
comment
просто вопрос, что означают цифры в вашем заказе по пункту? - person Jafar Kofahi; 24.07.2013
comment
1 = team_id, 2 = самая длинная_последовательность, столбцы для выбора - person Vrashabh Irde; 24.07.2013
comment
@Slartibartfast, я думаю, что partition by team_id нужен как в функциях DECODE, так и в функциях SUM. Когда я запускал операторы внутреннего выбора отдельно, в расчете new_group и group_no было некоторое несоответствие. - person Noel; 26.07.2013

Используя вариант ответа, который я разместил здесь

select
    team_id,
    max(wins)
  from
    (
     select
            a.team_id,
            a.match_id amatch,
            b.match_id bmatch,
    (select count(distinct match_id) 
       from matches matches_inner
      where a.team_id = matches_inner.team_id
        and matches_inner.match_id between a.match_id and b.match_id) wins
      from
            matches a
            join matches b on a.team_id = b.team_id 
                      and b.match_id > a.match_id
     where
    not exists 
    (select 'x'
       from matches matches_inner
      where a.team_id = matches_inner.team_id
        and matches_inner.match_id between a.match_id and b.match_id
        and matches_inner.won_ind = 'N')

group by team_id
person Joe    schedule 24.07.2013
comment
Хороший. Но если самая длинная полоса равна 1, это не возвращает значение. Изменение b.match_id > a.match_id на b.match_id >= a.match_id должно решить эту проблему. - person Noel; 27.07.2013
comment
Также отсутствует закрывающая скобка в конце. - person jakejgordon; 19.10.2015

У меня была аналогичная задача на Teradata, я модифицировал ее для работы на Oracle:

SELECT
   team_id,
   MAX(cnt)
FROM
 (
   SELECT
      team_id,
      COUNT(*) AS cnt
   FROM 
    (
      SELECT
        team_id, 
        match_id,
        won_ind,
        SUM(CASE WHEN won_ind <> 'Y' THEN 1 END) 
        OVER (PARTITION BY team_id 
              ORDER BY match_id 
              ROWS UNBOUNDED PRECEDING) AS dummy
      FROM matches
    ) dt
   WHERE won_ind = 'Y'
   GROUP BY team_id, dummy
 ) dt
GROUP BY team_id;
person dnoeth    schedule 05.08.2013
comment
Хороший. Устраняет необходимость в функции LAG. - person Noel; 05.08.2013

person    schedule
comment
Отлично ... хотя это похоже на ответ Слартибартфаста, это очень легко понять. - person Noel; 26.07.2013
comment
Для совместимости с PostgreSQL и SQLite замените запрос original_data на WITH: WITH original_data (match_id, team_id, won_id) AS (VALUES (37, 'Team1', 'N'), (67, 'Team1', 'Y'), ... ) - person Clint Pachl; 04.09.2020