2017-02-01 6 views
3

Рассмотрим таблицу нижеУзнайте порядковый номер шаблона

Таблица

ActivityId  Flag  Type 
----------  -----  ----- 
1    N        
2    N 
3    Y   EXT 
4    Y 
5    Y   
6    N 
7    Y   INT 
8    Y  
9    N 
10    N 
11    N 
12    Y   EXT 
13    N    
14    N 
15    N 
16    Y   EXT 
17    Y   
18    Y   INT 
19    Y   
20    Y   EXT 
21    Y 
22    N 
23    N  

Первая запись всегда Flag = N и тогда любая последовательность Flag = Y или Flag = N может существовать для последующих записей. Каждый раз, когда флаг изменяется от N до Y, поле Type является либо EXT, либо INT. Следующие Y записей (до следующего N) могут иметь Type = EXT или INT или NULL, и это не важно.

Я хочу рассчитать Cycle No для этой последовательности N/Y. Первый цикл начинается, когда Flag = N (всегда первая запись имеет flag = N) и заканчивается концом, когда флаг изменяется на Y и Type = EXT. Затем следующий цикл начинается, когда флаг изменяется на N и заканчивается, когда флаг становится Y и type = EXT. это повторяется до тех пор, пока не будут обработаны все записи. Результат для выше таблицы:

Результат

ActivityId  Flag  Type  Cycle No 
----------  -----  -----  -------- 
1    N      1  
2    N      1 
3    Y   EXT   1 
4    Y 
5    Y   
6    N      2 
7    Y   INT   2 
8    Y      2 
9    N      2 
10    N      2 
11    N      2 
12    Y   EXT   2 
13    N      3 
14    N      3 
15    N      3 
16    Y   EXT   3 
17    Y   
18    Y   INT 
19    Y   
20    Y   EXT 
21    Y 
22    N      4 
23    N      4 

Я использую SQL Server 2008 R2 (не LAG/LEAD). Не могли бы вы помочь мне найти SQL-запрос для вычисления Cycle No?

+1

, что вы пробовали до сих пор –

+0

попробуйте это, выберите * из соблазнительного соединения соблазнительное b на a.activityid + 1 = b.activityid –

+0

ваша проблема такая же, как http://stackoverflow.com/questions/3410687/sql-group-by-on-consecutive -records ... –

ответ

3

У меня есть решение, его не очень, но через пошагового уточнения я получаю ваш результат:

Раствор находится в трех шагах

  1. Изолировать ActivityID для всех возможных цикла запуска и конец строк цикла.
  2. Отфильтровывать все манатки запуска события
  3. Число циклов, и найти интервал ActivityID для каждого цикла

Первый я выбрать из всех запуска и событий окончания цикла:

with tab as 
(select * from (values 
(1,'N',''),(2,'N',''),(3,'Y','EXT'),(4,'Y','') 
,(5,'Y',''),(6,'N',''),(7,'Y','INT'),(8,'Y','') 
,(9,'N',''),(10,'N',''),(11,'N',''),(12,'Y','EXT') 
,(13,'N',''),(14,'N',''),(15,'N',''),(16,'Y','EXT') 
,(17,'Y',''),(18,'Y','INT'),(19,'Y',''),(20,'Y','EXT') 
,(21,'Y',''),(22,'N',''),(23,'N','')) a(ActivityId,Flag,[Type])) 

,CTE1 as 
(select 
    ROW_NUMBER() over (order by t1.ActivityId) rn 
    ,t1.ActivityId 
    ,case when t1.Flag='N' then 'Start' else 'End' end Cycle 
from tab t1 
where t1.Flag='N' or (t1.Flag='Y' and t1.[Type]='Ext') 
) 
select * from cte1 

Это возвращает

rn ActivityId Cycle 
1 1 Start 
2 2 Start 
3 3 End 
4 6 Start 
5 9 Start 
6 10 Start 
7 11 Start 
8 12 End 
9 13 Start 
10 14 Start 
11 15 Start 
12 16 End 
13 20 End 
14 22 Start 
15 23 Start 

Проблема в том, что, хотя w e уверены в завершении цикла, то есть когда флаг равен N, а Type - Ext, мы не уверены, когда начинается цикл. Строки 1 и 2 обозначают возможное событие начала. Но, к счастью, мы можем видеть, что нужно учитывать только начальное событие после события End. Так как мы не имеем отставание или свинца, мы должны присоединиться к КТР с собой:

,CTE2 as 
(
select ROW_NUMBER() over (order by a1.activityid) rn 
     ,a1.ActivityId 
     ,a1.Cycle 
     ,a2.Cycle PrevCycle 
from CTE1 a1 left join CTE1 a2 on a1.rn=a2.rn+1 
where 

    a2.Cycle is null -- First Cycle 
    or 
    (a2.Cycle is not null 
    and 
    ( 
     (a1.Cycle='End' and a2.Cycle='Start') -- End of cycle 
     or 
     (a1.Cycle='Start' 
     and a2.Cycle='End') -- next cycles 
    ) 
    ) 
    ) 
select * from cte2 

Это возвращает

rn ActivityId Cycle PrevCycle 
1 1 Start NULL 
2 3 End Start 
3 6 Start End 
4 12 End Start 
5 13 Start End 
6 16 End Start 
7 22 Start End 

I Выберите первое начальное событие - так как мы всегда начинаем с одной, а затем сохранить События END, которые следуют за началом события. Наконец, мы сохраняем остальную часть начальных событий, если предыдущим событием было событие End.

Теперь мы можем найти начало и конец каждого цикла, и пронумеровать их:

,cte3 as 
(
select ROW_NUMBER() over (order by b1.ActivityId) CycleNumber 
    ,b1.ActivityId StartId,b2.ActivityId EndId 
from cte2 b1 left join cte2 b2 
on b1.rn=b2.rn-1 
where b1.Cycle='Start' 
) 
select * from cte3 

Что дает нам то, что нам нужно:

CycleNumber StartId EndId 
1 1 3 
2 6 12 
3 13 16 
4 22 NULL 

Теперь нам нужно просто присоединиться к этому обратно наш стол:

select 
a.ActivityId,a.Flag,a.[Type],CycleNumber 
from tab a 
left join cte3 b on a.ActivityId between b.StartId and isnull(b.EndId,a.ActivityId) 

Это дает результат, который вы искали.

Это просто быстрое и грязное решение, возможно, с небольшим количеством ТСХ вы можете справиться с этим и уменьшить количество шагов.

Полное решение здесь:

with tab as 
(select * from (values 
(1,'N',''),(2,'N',''),(3,'Y','EXT'),(4,'Y','') 
,(5,'Y',''),(6,'N',''),(7,'Y','INT'),(8,'Y','') 
,(9,'N',''),(10,'N',''),(11,'N',''),(12,'Y','EXT') 
,(13,'N',''),(14,'N',''),(15,'N',''),(16,'Y','EXT') 
,(17,'Y',''),(18,'Y','INT'),(19,'Y',''),(20,'Y','EXT') 
,(21,'Y',''),(22,'N',''),(23,'N','')) a(ActivityId,Flag,[Type])) 

,CTE1 as 
(select 
    ROW_NUMBER() over (order by t1.ActivityId) rn 
    ,t1.ActivityId 
    ,case when t1.Flag='N' then 'Start' else 'End' end Cycle 
from tab t1 
where t1.Flag='N' or (t1.Flag='Y' and t1.[Type]='Ext') 
) 
,CTE2 as 
(
select ROW_NUMBER() over (order by a1.activityid) rn 
     ,a1.ActivityId 
     ,a1.Cycle 
     ,a2.Cycle PrevCycle 
from CTE1 a1 left join CTE1 a2 on a1.rn=a2.rn+1 
where 

    a2.Cycle is null -- First Cycle 
    or 
    (a2.Cycle is not null 
    and 
    ( 
     a1.Cycle='End' -- End of cycle 
     or 
     (a1.Cycle='Start' 
     and a2.Cycle='End') -- next cycles 
    ) 
    ) 
    ) 

,cte3 as 
(
select ROW_NUMBER() over (order by b1.ActivityId) CycleNumber 
    ,b1.ActivityId StartId,b2.ActivityId EndId 
from cte2 b1 left join cte2 b2 
on b1.rn=b2.rn-1 
where b1.Cycle='Start' 
) 

select 
a.ActivityId,a.Flag,a.[Type],CycleNumber 
from tab a 
left join cte3 b on a.ActivityId between b.StartId and isnull(b.EndId,a.ActivityId) 
+0

Спасибо, но запрос исключает любую запись между началом цикла и запуском следующего цикла. Он также исключает любую запись, что их флаг = Y, а Type - INT. Я не хочу исключать любую запись из таблицы, ища результат, который я показал в вопросе. – Bob

+0

@Bob Я думаю, что вы внедрили этот запрос неправильно. Я только что протестировал его и получил точный результат по вашему запросу. – iamdave

+0

Вы были правы. Я снова реализовал и получил точный результат, который я искал. Спасибо Сорен за такой подробный ответ на мой вопрос. – Bob

1

Если вы счастливы с рекурсией, это может быть достигнуто, а просто с немного логики сравнения в предыдущей строке, когда с предписанием ActivityId:

declare @t table(ActivityId int,Flag nvarchar(1),TypeValue nvarchar(3)); 
insert into @t values(1 ,'N',null),(2 ,'N',null),(3 ,'Y','EXT'),(4 ,'Y',null),(5 ,'Y',null),(6 ,'N',null),(7 ,'Y','INT'),(8 ,'Y',null),(9 ,'N',null),(10,'N',null),(11,'N',null),(12,'Y','EXT'),(13,'N',null),(14,'N',null),(15,'N',null),(16,'Y','EXT'),(17,'Y',null),(18,'Y','INT'),(19,'Y',null),(20,'Y','EXT'),(21,'Y',null),(22,'N',null),(23,'N',null); 

with rn as -- Derived table purely to guarantee incremental row number. If you can guarantee your ActivityId values are incremental start to finish, this isn't required. 
( select row_number() over (order by ActivityId) as rn 
      ,ActivityId 
      ,Flag 
      ,TypeValue 
    from @t 
),d as 
( select rn    -- Recursive CTE that compares the current row to the one previous. 
      ,ActivityId 
      ,Flag 
      ,TypeValue 
      ,cast(1 as decimal(10,5)) as CycleNo 
    from rn 
    where rn = 1 

    union all 

    select rn.rn 
      ,rn.ActivityId 
      ,rn.Flag 
      ,rn.TypeValue 
      ,cast(
       case when d.Flag = 'Y' and d.TypeValue = 'EXT' and d.CycleNo >= 1 
         then case when rn.Flag = 'N' 
            then d.CycleNo + 1 
            else (d.CycleNo + 1) * 0.0001 -- This part keeps track of the cycle number in fractional values, which can be removed by converting the final result to INT. 
            end 
         else case when rn.Flag = 'N' and d.CycleNo < 1 
            then d.CycleNo * 10000 
            else d.CycleNo 
            end 
         end 
      as decimal(10,5)) as CycleNo 
    from rn 
     inner join d 
      on d.rn = rn.rn - 1 
) 
select ActivityId 
    ,Flag 
    ,TypeValue 
    ,cast(CycleNo as int) as CycleNo 
from d 
order by ActivityId; 

Выход:

+------------+------+-----------+---------+ 
| ActivityId | Flag | TypeValue | CycleNo | 
+------------+------+-----------+---------+ 
|   1 | N | NULL  |  1 | 
|   2 | N | NULL  |  1 | 
|   3 | Y | EXT  |  1 | 
|   4 | Y | NULL  |  0 | 
|   5 | Y | NULL  |  0 | 
|   6 | N | NULL  |  2 | 
|   7 | Y | INT  |  2 | 
|   8 | Y | NULL  |  2 | 
|   9 | N | NULL  |  2 | 
|   10 | N | NULL  |  2 | 
|   11 | N | NULL  |  2 | 
|   12 | Y | EXT  |  2 | 
|   13 | N | NULL  |  3 | 
|   14 | N | NULL  |  3 | 
|   15 | N | NULL  |  3 | 
|   16 | Y | EXT  |  3 | 
|   17 | Y | NULL  |  0 | 
|   18 | Y | INT  |  0 | 
|   19 | Y | NULL  |  0 | 
|   20 | Y | EXT  |  0 | 
|   21 | Y | NULL  |  0 | 
|   22 | N | NULL  |  4 | 
|   23 | N | NULL  |  4 | 
+------------+------+-----------+---------+ 
+0

Ваш запрос работает, но, пожалуйста, продолжайте читать мой комментарий. каждый набор идентификаторов активности относится к одному конкретному рабочему элементу. Для простоты я исключил это поле (WorkId). Ваш запрос мгновен для таблицы выше, но он становится чрезвычайно медленным (почти никакого прогресса), когда я тестировал около 100 идентификаторов работы. Реальные данные имеют около 1 миллиона идентификаторов работы, в которых каждый имеет несколько идентификаторов активности. – Bob

+0

@Bob Это важная контекстная информация, которая должна была быть включена в ваш вопрос. Обновите свой вопрос, чтобы лучше отражать ваши фактические требования. – iamdave

Смежные вопросы