2013-07-24 2 views
9

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

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 s в хронологическом порядке, 37 является первым и 217 последний матч играет TEAM1. won_ind указал, выиграла ли команда в матче или нет.

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

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

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

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

+1

У меня нет времени для тиражирования записи, но [эта отличная статья] (http://www.sqlteam.com/article/detecting-runs-or-streaks-in-your-data) обсуждает, как выполнить это использование самосоединений и сумм. – eykanal

ответ

5
with original_data as (
    select 37 match_id, 'Team1' team_id, 'N' won_id from dual union all 
    select 67 match_id, 'Team1' team_id, 'Y' won_id from dual union all 
    select 98 match_id, 'Team1' team_id, 'N' won_id from dual union all 
    select 109 match_id, 'Team1' team_id, 'N' won_id from dual union all 
    select 158 match_id, 'Team1' team_id, 'Y' won_id from dual union all 
    select 162 match_id, 'Team1' team_id, 'Y' won_id from dual union all 
    select 177 match_id, 'Team1' team_id, 'Y' won_id from dual union all 
    select 188 match_id, 'Team1' team_id, 'Y' won_id from dual union all 
    select 198 match_id, 'Team1' team_id, 'N' won_id from dual union all 
    select 207 match_id, 'Team1' team_id, 'Y' won_id from dual union all 
    select 217 match_id, 'Team1' team_id, 'Y' won_id from dual union all 
    select 10 match_id, 'Team2' team_id, 'N' won_id from dual union all 
    select 13 match_id, 'Team2' team_id, 'N' won_id from dual union all 
    select 24 match_id, 'Team2' team_id, 'N' won_id from dual union all 
    select 39 match_id, 'Team2' team_id, 'Y' won_id from dual union all 
    select 40 match_id, 'Team2' team_id, 'Y' won_id from dual union all 
    select 51 match_id, 'Team2' team_id, 'Y' won_id from dual union all 
    select 64 match_id, 'Team2' team_id, 'N' won_id from dual union all 
    select 79 match_id, 'Team2' team_id, 'N' won_id from dual union all 
    select 86 match_id, 'Team2' team_id, 'N' won_id from dual union all 
    select 91 match_id, 'Team2' team_id, 'Y' won_id from dual union all 
    select 101 match_id, 'Team2' team_id, 'N' won_id from dual 
), 
---------------------------------------------------------------------- 
new_streaks as (
-- 
-- Identifying new streaks. 
-- ------------------------ 
-- 
    select 
     match_id, 
     team_id, 
     won_id, 
-- 
-- A new streak is identfied if 
-- 
    case when 
-- 
-- a) won_id = 'Y' and 
-- 
     won_id = 'Y' and 
-- 
-- b) the previous won_id = 'N': 
--  
     lag(won_id) over (partition by team_id order by match_id) = 'N' 
-- 
--  
     then 1 
-- 
-- All other cases: no new streak: 
     else 0 
-- 
    end new_streak 
    from 
     original_data 
), 
------------------------------- 
streak_no as (
-- 
-- Assigning a unique number to each streak. 
-- ----------------------------------------- 
-- 
select 
-- 
    match_id, 
    team_id, 
-- 
-- In order to be able to count the number of records 
-- of a streak, we first need to assign a unique number 
-- to each streak: 
-- 
    sum(new_streak) over (partition by team_id order by match_id) streak_no 
-- 
from 
    new_streaks 
where 
-- We're only interested in «winning streaks»: 
    won_id = 'Y' 
), 
----------------------------------------------- 
-- 
-- Counting the elements per streak 
-- -------------------------------- 
-- 
records_per_streak as (
select 
    count(*) counter, 
    team_id, 
    streak_no 
from 
    streak_no 
group by 
    team_id, 
    streak_no 
) 
------------------------------------------------ 
-- 
-- Finally: we can find the «longest streak» 
-- per team: 
-- 
select 
    max(counter) longest_streak, 
    team_id 
from 
    records_per_streak 
group by team_id 
; 
+0

Отлично .. хотя это похоже на ответ Slartibartfast, это очень легко понять. – Noel

7

Это должно работать, 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; 
+0

просто вопрос, что означают цифры в вашем заказе по предложению? –

+0

1 = team_id, 2 = longest_sequence, столбцы для выбора – Slartibartfast

+0

@Slartibartfast Я думаю, что 'partition by team_id' необходим как в' DECODE', так и 'SUM'. Когда я запускал операторы внутреннего выбора отдельно, было некоторое несоответствие в вычислении new_group и group_no – Noel

2

Используя вариант ответа я отправил here

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 
+1

Ницца. Но если самая длинная полоса равна 1, это не возвращает значение. Изменение 'b.match_id> a.match_id' на' b.match_id> = a.match_id' должно решить это. – Noel

+0

В конце также отсутствует закрывающая скобка. – jakejgordon

1

У меня была похожая задача на 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; 
+0

Nice. Устраняет необходимость в функции LAG. – Noel