Интересный, возможно, сложный SQL-запрос для определения количества последовательных месяцев

#sql #sql-server-2005 #sql-server-2008

#sql #sql-server-2005 #sql-server-2008

Вопрос:

Прежде всего спасибо всем, кто помогает мне ответить на этот вопрос! Я погуглил и попытался узнать как можно больше о рекурсивных CTE и других расширенных функциях MS SQL 05-08, но я в тупике.

У меня есть таблица, которая выглядит следующим образом:

 AgentID, GoalReached, MonthEndDate
360123, 1, 1/22/2011
360123, Null, 2/22/2011
360123, 1, 3/22/2011
360123, 1, 4/22/2011
360123, 1, 5/22/2011
360123, 2, 6/22/2011
360124, 2, 1/22/2011
360124, 2, 2/22/2011
360124, 1, 3/22/2011
360124, 2, 4/22/2011
360124, 2, 5/22/2011
360124, 3, 6/22/2011
 

Как мне создать таблицу, которая группируется по идентификатору агента и показывает мне только идентификаторы агентов, которые достигли уровня для 4 из любых 6 месяцев подряд? А затем покажите, за какой 6-месячный период была достигнута эта цель?

Кроме того, если агент достиг уровня 2 или выше, он также достиг предыдущего уровня 1. Итак, если у агента есть три уровня 2 и три уровня 1 за 6-месячный период, тогда агент достиг только уровня 1.

Конечным результатом будет:

 AgentID, LevelReached, StartDate, EndDate
360123,1,3/22/2011,6/22/2011
360124,2,2/22/2011,6/22/2011
 

Спасибо за любую информацию, которую вы можете предоставить.

Ответ №1:

Я придумал этот, который выглядит так, как будто он работает:

 declare @t table ( AgentID int, GoalReached int, MonthEndDate date)
insert @t (AgentID , GoalReached , MonthEndDate )
values 
(360123, Null, '2/22/2011'),
(360123, 1, '1/22/2011'),
(360123, 1, '3/22/2011'),
(360123, 1, '4/22/2011'),
(360123, 1, '5/22/2011'),
(360123, 2, '6/22/2011'),
(360124, 1, '3/22/2011'),
(360124, 2, '1/22/2011'),
(360124, 2, '2/22/2011'),
(360124, 2, '4/22/2011'),
(360124, 2, '5/22/2011'),
(360124, 3, '6/22/2011')

select  t1.AgentID, t1.GoalReached, t1.MonthEndDate StartDate, max(t2.MonthEndDate) Enddate
from    @t t1
        inner join @t t2 
            on t1.AgentID = t2.AgentID
            and t1.GoalReached = t2.GoalReached
            and datediff(m, t1.MonthEndDate, t2.MonthEndDate) between 0 and 6  -- find any other rows for same agent within 6 months
group by t1.AgentID, t1.GoalReached, t1.MonthEndDate
having count(*) >= 4
 

Однако я извлекаю эти строки:

 360123  1   2011-01-22  2011-05-22
360124  2   2011-01-22  2011-05-22
 

Но я думаю, что приведенный вами пример вывода может быть отключен. (Конечная дата 6/22 для агента 360123 относится к уровню 2, а не 1. Для 360124 эта дата относится к уровню 3 вместо 2.

Дайте мне знать, если это поможет. Вы не указали, как обрабатывать случаи, когда, например, существует последовательный период в 9 месяцев, когда цель достигается каждый месяц. Например, этот запрос учитывает перекрывающиеся периоды как два, поэтому дайте мне знать, если, например, строка может быть только в 1 группе.

С уважением, GJ

Редактировать

Привет, ok сделал это, см. Ниже.

Чтобы удалить перекрывающиеся последовательности, я использовал рекурсивный CTE. Я думаю, что это единственный способ для группы «узнать», есть ли определенная строка уже в предыдущей группе. Было бы интересно увидеть другие подходы.

Лично я бы подумал об использовании более «процедурного» способа сделать это с помощью курсора или чего-то, что фактически обновляет строки, чтобы разделить их на группы. Этот запрос может быть трудным для понимания, может стать проблемой для будущего сопровождения кода.

Итак, с этим покончено, наслаждайтесь! И спасибо за это, милый маленький jawbreaker 😉

Rgds GJ

 declare @t table (AgentID int, GoalReached int, MonthEndDate date)
insert @t (AgentID , GoalReached , MonthEndDate )
values 
(360123, Null, '2/22/2011'),
(360123, 1, '1/22/2011'),
(360123, 1, '3/22/2011'),
(360123, 1, '4/22/2011'),
(360123, 1, '5/22/2011'),
(360123, 2, '6/22/2011'),
(360124, 1, '3/22/2011'),
(360124, 2, '1/22/2011'),
(360124, 2, '2/22/2011'),
(360124, 2, '4/22/2011'),
(360124, 2, '5/22/2011'),
(360124, 3, '6/22/2011'),

(100, 1, '1/1/2010'),
(100, 1, '2/1/2010'),
(100, 2, '3/1/2010'),
(100, 1, '4/1/2010'),
(100, 1, '5/1/2010'),
(100, 1, '6/1/2010'),
(100, 1, '7/1/2010'),
(100, 1, '8/1/2010'),
(100, 1, '9/1/2010')
;


with 
--1: find groups of rows that are within 6 months of eachother and number the rows
step1 as (
select  t1.AgentID, t1.MonthEndDate StartDate, t2.GoalReached, t2.MonthEndDate EndDate,  ROW_NUMBER() over (partition by t1.agentid, t1.MonthEndDate order by t2.monthenddate) RowRank
from    @t t1
        inner join @t t2 
            on t1.AgentID = t2.AgentID
            --and t1.GoalReached = t2.GoalReached
            and datediff(m, t1.MonthEndDate, t2.MonthEndDate) between 0 and 6  -- find any other rows for same agent within 6 months
)
--select * from step1
-- cut sequences off to no longer than 4 rows and get rid of shorter sequences
,step2 as (
    select  * 
    from    step1 t1
    where   exists (
        select *
        from    step1 t2
        where   t1.AgentID = t2.AgentID
                and t1.StartDate = t2.StartDate
                --t1.id1 = t2.id1       -- same group(same start row)
                and t2.RowRank  = 4 -- group has to have at least 4 rows 
                and t1.RowRank <= 4 -- get rid of rows that are beyond 4
    )
)
--select * from step2 
-- collapse groups to a single row (makes next step easier)
,grps as (
    select  AgentID,            
            MAX(GoalReached) MaxGoal,
            MIN(StartDate) StartDate,
            MAX(EndDate) EndDate,
            dense_rank() over (partition by AgentId order by StartDate) GrpRank
    from    step2
    group by agentid,
            StartDate
)
--select * from sub1
-- use common table expression to remove overlap (only way I could figure out how)
,cte as (
    -- anchor to first sequence of 4 rows for each agent
    select  AgentID,
            StartDate,
            EndDate,
            MaxGoal
    from    grps
    where   GrpRank = 1

    union all 

    -- repeat to find following sequences
    select  AgentID,
            StartDate,
            EndDate,
            MaxGoal     
    from    (
                select  g.*, row_number() over (partition by g.AgentId order by g.StartDate) grp_rank
                from    grps g              
                inner join cte c 
                        on  g.AgentID = c.AgentID   -- same agent                       
                        and g.StartDate > c.EndDate -- group must start after previous group has ended (here we remove the overlap)

            )s
    where   s.grp_rank = 1  -- only add 1 group per agent for each iteration of the CTE     
)
select  *
from    cte
order by AgentID, StartDate
 

Комментарии:

1. Это выглядит великолепно! Большое спасибо за вашу помощь, возможно ли свести его к строке, которая может быть только в 1 группе? Кроме того, если бы был период в 9 месяцев, в течение которого были достигнуты цели, можно ли было бы выбрать самую высокую цель?

2. Привет, извините, вчера было время спать, и я на работе, где у меня есть только Oracle. Я попробую сегодня вечером.

3. ok обновлено для удаления перекрывающихся групп и достижения максимальной цели для каждой группы.