2017-01-05 2 views
3

У меня есть таблица транзакций с датой начала и окончания. Проблема в том, что часто эти даты транзакций перекрываются друг с другом, и я хочу сгруппировать эти сценарии вместе.Группировка строк, если их даты совпадают, и ранжирование их

Например, в случае, указанном ниже, транзакция № 1 является транзакцией «корень», а # 2- # 4 перекрываются с # 1 и/или друг с другом. Однако транзакция №5 не перекрывается ни с чем, следовательно, это новая «корневая» транзакция.

+----------------+-----------+-----------+----------------------------------+ 
| Transaction ID | StartDate | EndDate |         | 
+----------------+-----------+-----------+----------------------------------+ 
|    1 | 1/1/2017 | 1/3/2017 | root transaction     | 
|    2 | 1/2/2017 | 1/6/2017 | overlaps with #1     | 
|    3 | 1/5/2017 | 1/10/2017 | overlaps with #2     | 
|    4 | 1/3/2017 | 1/13/2017 | overlaps with #2 and #3   | 
|    5 | 1/15/2017 | 1/20/2017 | no overlap, new root transaction | 
+----------------+-----------+-----------+----------------------------------+ 

Ниже приведено, как я хочу, чтобы результат выглядел. Я хочу

  1. Определение основной сделки (столбец 4)
  2. ранжировать сделок в цепочке EndDate, так что корень всегда = 1

+----------------+-----------+-----------+------------------+------+ 
| Transaction ID | Start | End | Root Transaction | Rank | 
+----------------+-----------+-----------+------------------+------+ 
|    1 | 1/1/2017 | 1/3/2017 |    1 | 1 | 
|    2 | 1/2/2017 | 1/6/2017 |    1 | 2 | 
|    3 | 1/5/2017 | 1/10/2017 |    1 | 3 | 
|    4 | 1/3/2017 | 1/13/2017 |    1 | 4 | 
|    5 | 1/15/2017 | 1/20/2017 |    5 | 1 | 
+----------------+-----------+-----------+------------------+------+ 

Как я бы об этом подумал в SQL?

+0

Пожалуйста, отметьте свой вопрос с помощью версии SQL Server. –

ответ

3

Вот один метод, использующий OUTER APPLY

Declare @YourTable table ([Transaction ID] int,StartDate date,EndDate date) 
Insert Into @YourTable values 
(1,'1/1/2017','1/3/2017'), 
(2,'1/2/2017','1/6/2017'), 
(3,'1/5/2017','1/10/2017'), 
(4,'1/3/2017','1/13/2017'), 
(5,'1/15/2017','1/20/2017') 

Select [Transaction ID] 
     ,[Start] = StartDate 
     ,[End] = EndDate 
     ,[Root Transaction]=Grp 
     ,[Rank] = Row_Number() over (Partition By Grp Order by [Transaction ID]) 
From (
     Select A.* 
       ,Grp = max(Flag*[Transaction ID]) over (Order By [Transaction ID]) 
     From (
       Select A.*,Flag = IsNull(B.Flg,1) 
       From @YourTable A 
       Outer Apply (
           Select Top 1 Flg=0 
           From @YourTable 
           Where (StartDate between A.StartDate and A.EndDate 
            or EndDate between A.StartDate and A.EndDate) 
           and [Transaction ID]<A.[Transaction ID] 
          ) B 
      ) A 
    ) A 

Возврат

enter image description here

EDIT - Некоторые Комментарий

В OUTER APPLY, флаг будет установлен в 1 или 0. 1 Указывает новую группу.0 Указывает, что запись будет перекрываться с существующим диапазоном

Затем следующий запрос «вверх». Мы используем функцию окна для применения Grp-кода (Flag * Trans ID). Помните, что новая группа - 1, а существующая - 0.
Теперь функция окна примет максимальное значение этого продукта, поскольку оно пересекает транзакции.

Окончательный запрос просто применить ранг с помощью функции окна раздела со стороны стеклопластик заказа Транс ID

Если это помогает с визуализацией:

1-й подзапрос (внешняя применяются) genererates

enter image description here

2-й суб-запрос генерирует

enter image description here

+0

Это потрясающе! Он работает, но я не знал о ВНЕШНЕМ ПРИМЕНЕНИИ, поэтому многие из того, что вы делаете, очень новы для меня. Если у вас есть время, вы можете сломать то, что делает Flg = 0, как работает внешнее приложение, просто объясните это? Я тоже пытаюсь понять это сам. – RedVII

+0

@RedVII Конечно, я добавлю несколько комментариев в несколько –

+0

Спасибо! Очень умное решение – RedVII

1

Это пример «зазоров и островов». Для ваших данных вы можете определить «остров», определяя, где каждый начинается, то есть, когда запись не перекрывается с предыдущей записью. Затем вы можете получить ранг, используя row_number().

Итак, вот метод:

select t.*, 
     min(transactionId) over (partition by island) as start, 
     row_number() over (partition by island order by endDate) as rnk 
from (select t.*, 
      sum(startIslandFlag) over (order by startDate) as island 
     from (select t.*, 
        (case when not exists (select 1 
              from t t2 
              where t2.startdate < t.startdate and 
               t2.enddate >= t.startdate 
             ) 
         then 1 else 0 
        end) as startIslandFlag 
      from t 
      ) t 
    ) t; 

Примечания:

  • В том случае, если самая низкая сделка идентификатор не является «корень», то подстройка может потребоваться для кода для получения идентификатора транзакции с минимальной датой начала.
  • Если в коде имеются повторяющиеся даты начала, может потребоваться корректировка с суммарной суммой (с использованием явного окна range).
+0

Я немного смущен ответом, так как у меня нет вашего набора данных, и т-алиасы действительно меня отбрасывают. Может ли это быть более актуальным для моего набора данных или просто вести меня через то, что код делает в каждом подзапросе? – RedVII

+0

довольно тонкая попытка отредактировать этот ответ или добавить еще один, который повторяет это, пожалуйста, обратитесь к этому URL http://rextester.com/UTDR6041, где каждая часть этого решения запускается независимо –

1

Определить корневые сделки:

with roots as (
    select * 
    from tran as t1 
    where not exists (
     select 1 
     from tran as t2 
     where t2.Transaction_ID < t1.Transaction_ID 
     and (
      t1.StartDate between t2.StartDate and t2.EndDate 
      or 
      t1.EndDate between t2.StartDate and t2.EndDate 
      ) 
     ) 
    ) 

Создайте две корневой системы, чтобы захватить все перекрытия между ними

select t.Transaction_ID, 
    t.StartDate as [Start], 
    t.EndDate as [End], 
    r1.Transaction_ID as Root_Transaction, 
    row_number() over (partition by r1.Transaction_ID order by t.EndDate) as [Rank] 
from roots as r1 
inner join roots as r2 
on r2.Transaction_ID > r1.Transaction_ID 
inner join tran as t 
on t.Transaction_ID >= r1.Transaction_ID 
and t.Transaction_ID < r2.Transaction_ID 
where not exists (--this "not exists" makes sure r1 and r2 are consequetive roots 
    select 1 
    from roots as r3 
    where r3.Transaction_ID > r1.Transaction_ID 
    and r3.Transaction_ID < r2.Transaction_ID 
    ) 
Смежные вопросы