Запрос для определения даты начала и окончания на основе перекрытия времени

8

Учитывая следующие данные:

id      |   user_id |   started             |   closed              |   dead
-------------------------------------------------------------------------------------------
7714    |   238846  |   2015-01-27 15:14:50 |   2015-02-02 14:14:13 |   NULL
7882    |   238846  |   2015-01-28 13:25:58 |   NULL                |   2015-05-15 12:16:07
13190   |   259140  |   2015-03-17 10:11:44 |   NULL                |   2015-03-18 07:31:57
13192   |   259140  |   2015-03-17 10:12:17 |   NULL                |   2015-03-18 11:46:46
13194   |   259140  |   2015-03-17 10:12:53 |   NULL                |   2015-03-18 11:46:36
14020   |   259140  |   2015-03-23 14:32:16 |   2015-03-24 15:57:32 |   NULL
17124   |   242650  |   2015-04-16 16:19:08 |   2015-04-16 16:21:06 |   NULL
19690   |   238846  |   2015-05-15 13:17:31 |   NULL                |   2015-05-27 13:56:43
20038   |   242650  |   2015-05-19 15:38:17 |   NULL                |   NULL
20040   |   242650  |   2015-05-19 15:39:58 |   NULL                |   2015-05-21 12:01:02
20302   |   242650  |   2015-05-21 13:09:06 |   NULL                |   NULL
20304   |   242650  |   2015-05-21 13:09:54 |   NULL                |   NULL
20306   |   242650  |   2015-05-21 13:10:19 |   NULL                |   NULL
20308   |   242650  |   2015-05-21 13:12:20 |   NULL                |   NULL
21202   |   238846  |   2015-05-29 16:47:29 |   NULL                |   NULL
21204   |   238846  |   2015-05-29 16:47:56 |   NULL                |   NULL
21208   |   238846  |   2015-05-29 17:05:15 |   NULL                |   NULL
21210   |   238846  |   2015-05-29 17:05:55 |   NULL                |   NULL
21918   |   242650  |   2015-06-04 17:04:29 |   NULL                |   2015-06-12 15:47:23

Мне нужно создать набор данных, который соответствует следующим правилам:

  1. Группы определяются в первую очередь, user_idпоэтому мы должны сравнивать только записи из того жеuser_id
  2. Все записи, которые начались по крайней мере в течение 15 дней после начала, закрытия или прекращения действия любой другой записи, должны учитываться как группа.
  3. Конец каждой группы должен рассчитываться как первая закрытая запись или все записи имеют значение для мертвых, и мы берем наибольшую дату мертвого столбца.
  4. Если запись не началась в течение 15 дней с начала или конца другой группы, то начинается новая группировка.

Ориентировочно, я считаю, что мои данные должны выглядеть так:

user_id | начал | конец
-------------------------------------------------- ----
238846 | 2015-01-27 15:14:50 | 2015-02-02 14:14:13
259140 | 2015-03-23 ​​14:32:16 | 2015-03-24 15:57:32
242650 | 2015-04-16 16:19:08 | 2015-04-16 16:21:06
242650 | 2015-05-21 13:09:06 | ЗНАЧЕНИЕ NULL
238846 | 2015-05-15 13:17:31 | ЗНАЧЕНИЕ NULL

Может ли кто-нибудь дать несколько советов о том, как построить запрос для удовлетворения этих условий?

Вот ссылка на операторы DDL и DML для данных, представленных в этом вопросе.

В качестве альтернативы, мы могли бы пропустить правила № 2 и № 4 и более просто заявить, что должны быть включены только записи, которые перекрывают друг друга. Более важное правило заключается в том, что в данном наборе, если есть закрытая дата, она становится концом набора, а не самой большой мертвой датой.

Ной Гудрич
источник
Это было бы проще с изменением схемы. Там нет необходимости для двух колонн, закрытых и мертвых. Просто есть столбец «закончилась», а затем причина окончания.
Эндрю Бреннан
Ваши первые 3 примера могут быть закодированы как «Если идентификатор« закрыт », то это группа сама по себе. Так как это, кажется, не выделяет все ваши правила, пожалуйста, добавьте больше примеров.
Рик Джеймс

Ответы:

3

Из-за отсутствия ясности в вопросе я предложил четыре различных решения. Решения различаются по:

  1. Будь ты "каскадом" за ответ Криса
  2. Если у вас есть закрытая дата, используете ли вы самую раннюю дату для этой группы или начальную дату для закрытой записи.

Обратите внимание, что это делается в SQL Server, а не в MySQL. Помимо некоторых незначительных изменений синтаксиса, он должен работать так же.

Общая настройка и пример данных для всех четырех методов

CREATE TABLE #example 
(
    id int NOT NULL DEFAULT '0',
    borrower_id int NOT NULL,
    started datetime NULL DEFAULT NULL,
    closed datetime NULL DEFAULT NULL,
    dead datetime NULL DEFAULT '0000-00-00 00:00:00'
);

CREATE TABLE #result 
(   
    borrower_id int NOT NULL DEFAULT '0',    
    started datetime NULL DEFAULT NULL,    
    ended datetime NULL DEFAULT NULL 
);    

INSERT INTO #example 
    (id, borrower_id, started, closed, dead) 
VALUES 
    (7714,238846,'2015-01-27 15:14:50','2015-02-02 14:14:13',NULL), 
    (7882,238846,'2015-01-28 13:25:58',NULL,'2015-05-15 12:16:07'), 
    (13190,259140,'2015-03-17 10:11:44',NULL,'2015-03-18 07:31:57'), 
    (13192,259140,'2015-03-17 10:12:17',NULL,'2015-03-18 11:46:46'), 
    (13194,259140,'2015-03-17 10:12:53',NULL,'2015-03-18 11:46:36'), 
    (14020,259140,'2015-03-23 14:32:16','2015-03-24 15:57:32',NULL), 
    (17124,242650,'2015-04-16 16:19:08','2015-04-16 16:21:06',NULL), 
    (19690,238846,'2015-05-15 13:17:31',NULL,'2015-05-27 13:56:43'), 
    (20038,242650,'2015-05-19 15:38:17',NULL,NULL), 
    (20040,242650,'2015-05-19 15:39:58',NULL,'2015-05-21 12:01:02'), 
    (20302,242650,'2015-05-21 13:09:06',NULL,NULL), 
    (20304,242650,'2015-05-21 13:09:54',NULL,NULL), 
    (20306,242650,'2015-05-21 13:10:19',NULL,NULL), 
    (20308,242650,'2015-05-21 13:12:20',NULL,NULL), 
    (21202,238846,'2015-05-29 16:47:29',NULL,NULL), 
    (21204,238846,'2015-05-29 16:47:56',NULL,NULL), 
    (21208,238846,'2015-05-29 17:05:15',NULL,NULL), 
    (21210,238846,'2015-05-29 17:05:55',NULL,NULL), 
    (21918,242650,'2015-06-04 17:04:29',NULL,'2015-06-12 15:47:23'); 

1. Каскадирование - использование решения ЗАКРЫТОЙ ЗАПИСИ

Это решение, которое, я считаю, ищущий ищет и соответствует своим результатам.

select *
into #temp1
from #example

while (select count(1) from #temp1)>0
begin
    --Grab only one user's records and place into a temp table to work with
    declare @curUser int
    set @curUser=(select min(borrower_id) from #temp1)

    select * 
    into #temp2
    from #temp1 t1
    where t1.borrower_id=@curUser

    while(select count(1) from #temp2)>0
    begin
        --Grab earliest start date and use as basis for 15 day window (#2 rule)
        --Use the record as basis for rules 3 and 4
        declare @minTime datetime
        set @minTime=(select min(started) from #temp2)

        declare @maxTime datetime
        set @maxTime=@minTime

        declare @curId int
        set @curId=(select min(id) from #temp2 where started=@minTime)

        select * 
        into #temp3
        from #temp2 t2
        where t2.id=@curId

        --Remove earliest record from pool of potential records to check rules against
        delete 
        from #temp2 
        where id=@curId

        --Insert all records within 15 days of start date, then remove record from pool
        while (select count(1) 
                from #temp2 t2 
                where t2.started<=DATEADD(day,15,@maxTime) 
                    or t2.closed<=DATEADD(day,15,@maxTime) 
                    or t2.dead<=DATEADD(day,15,@maxTime)  )>0
        begin
            insert into #temp3
            select *
            from #temp2 t2
            where t2.started<=DATEADD(day,15,@maxTime)  or t2.closed<=DATEADD(day,15,@maxTime)  or t2.dead<=DATEADD(day,15,@maxTime) 

            delete
            from #temp2
            where started<=DATEADD(day,15,@maxTime)  or closed<=DATEADD(day,15,@maxTime)  or dead<=DATEADD(day,15,@maxTime) 

            --set new max time from any column
            if (select max(started) from #temp3)>@maxTime
                set @maxTime=(select max(started) from #temp3)
            if (select max(closed) from #temp3)>@maxTime
                set @maxTime=(select max(started) from #temp3)
            if (select max(dead) from #temp3)>@maxTime
                set @maxTime=(select max(started) from #temp3)

        end

        --Calculate end time according to rule #3
        declare @end datetime 
        set @end = null
        set @end=(select min(closed) from #temp3)

        if @end is not null
        begin
            set @minTime=(select started from #temp3 where closed=@end)
        end

        if @end is null
        begin
            if(select count(1) from #temp3 where dead is null)=0
            set @end= (select max(dead) from #temp3)
        end

        insert into #result (borrower_id,started,ended)
        values (@curUser,@minTime,@end)

        drop table #temp3
    end

    --Done with the one user, remove him from temp table and iterate thru to the next user
    delete  
    from #temp1 
    where borrower_id=@curUser    

    drop table #temp2

end

drop table #temp1

drop table #example

select * from #result order by started

drop table #result

2. НЕ КАСКАДИНГ - ИСПОЛЬЗОВАНИЕ ЗАКРЫТОГО ЗАПИСИ

Начало рассчитывается по первой закрытой дате, когда она доступна, затем по самой ранней дате начала.

select *
into #temp1
from #example

while (select count(1) from #temp1)>0
begin
    --Grab only one user's records and place into a temp table to work with
    declare @curUser int
    set @curUser=(select min(borrower_id) from #temp1)

    select * 
    into #temp2
    from #temp1 t1
    where t1.borrower_id=@curUser

    while(select count(1) from #temp2)>0
    begin
        --Grab earliest start date and use as basis for 15 day window (#2 rule)
        --Use the record as basis for rules 3 and 4
        declare @minTime datetime
        set @minTime=(select min(started) from #temp2)

        declare @curId int
        set @curId=(select min(id) from #temp2 where started=@minTime)

        select * 
        into #temp3
        from #temp2 t2
        where t2.id=@curId

        --Remove earliest record from pool of potential records to check rules against
        delete 
        from #temp2 
        where id=@curId

        --Insert all records within 15 days of start date, then remove record from pool
        insert into #temp3
        select *
        from #temp2 t2
        where t2.started<=DATEADD(day,15,@minTime)

        delete
        from #temp2
        where started<=DATEADD(day,15,@minTime)

        --Insert all records within 15 days of closed, then remove record from pool
        insert into #temp3
        select *
        from #temp2 t2
        where t2.closed<=DATEADD(day,15,@minTime)

        delete
        from #temp2
        where closed<=DATEADD(day,15,@minTime)

        --Insert all records within 15 days of dead, then remove record from pool
        insert into #temp3
        select *
        from #temp2 t2
        where t2.dead<=DATEADD(day,15,@minTime)

        delete
        from #temp2
        where dead<=DATEADD(day,15,@minTime)

        --Calculate end time according to rule #3
        declare @end datetime 
        set @end = null
        set @end=(select min(closed) from #temp3)

        if @end is not null
        begin
            set @minTime=(select started from #temp3 where closed=@end)
        end

        if @end is null
        begin
            if(select count(1) from #temp3 where dead is null)=0
            set @end= (select max(dead) from #temp3)
        end

        insert into #result (borrower_id,started,ended)
        values (@curUser,@minTime,@end)

        drop table #temp3
    end

    --Done with the one user, remove him from temp table and iterate thru to the next user
    delete  
    from #temp1 
    where borrower_id=@curUser


    drop table #temp2

end

drop table #temp1

drop table #example

select * from #result

drop table #result

3. НЕ каскадирование - использование решения с самой ранней датой

Начало рассчитывается только по самой ранней дате.

select *
into #temp1
from #example

while (select count(1) from #temp1)>0
begin
    --Grab only one user's records and place into a temp table to work with
    declare @curUser int
    set @curUser=(select min(borrower_id) from #temp1)

    select * 
    into #temp2
    from #temp1 t1
    where t1.borrower_id=@curUser

    while(select count(1) from #temp2)>0
    begin
        --Grab earliest start date and use as basis for 15 day window (#2 rule)
        --Use the record as basis for rules 3 and 4
        declare @minTime datetime
        set @minTime=(select min(started) from #temp2)

        declare @curId int
        set @curId=(select min(id) from #temp2 where started=@minTime)

        select * 
        into #temp3
        from #temp2 t2
        where t2.id=@curId

        --Remove earliest record from pool of potential records to check rules against
        delete 
        from #temp2 
        where id=@curId

        --Insert all records within 15 days of start date, then remove record from pool
        insert into #temp3
        select *
        from #temp2 t2
        where t2.started<=DATEADD(day,15,@minTime) or t2.closed<=DATEADD(day,15,@minTime) or t2.dead<=DATEADD(day,15,@minTime)

        delete
        from #temp2
        where started<=DATEADD(day,15,@minTime) or closed<=DATEADD(day,15,@minTime) or dead<=DATEADD(day,15,@minTime)

        --Calculate end time according to rule #3
        declare @end datetime 
        set @end = null

        set @end=(select min(closed) from #temp3)

        if @end is null
        begin
            if(select count(1) from #temp3 where dead is null)=0
            set @end= (select max(dead) from #temp3)
        end

        insert into #result (borrower_id,started,ended)
        values (@curUser,@minTime,@end)

        drop table #temp3
    end

    --Done with the one user, remove him from temp table and itterate thru to the next user
    delete  
    from #temp1 
    where borrower_id=@curUser    

    drop table #temp2

end

drop table #temp1

drop table #example

select * from #result

drop table #result

4. Каскадирование - использование решения с самой ранней датой

Начало рассчитывается только по самой ранней дате.

select *
into #temp1
from #example

while (select count(1) from #temp1)>0
begin
--Grab only one user's records and place into a temp table to work with
declare @curUser int
set @curUser=(select min(borrower_id) from #temp1)

select * 
into #temp2
from #temp1 t1
where t1.borrower_id=@curUser

while(select count(1) from #temp2)>0
begin
    --Grab earliest start date and use as basis for 15 day window (#2 rule)
    --Use the record as basis for rules 3 and 4
        declare @minTime datetime
    set @minTime=(select min(started) from #temp2)


    declare @maxTime datetime
    set @maxTime=@minTime

    declare @curId int
    set @curId=(select min(id) from #temp2 where started=@minTime)

    select * 
    into #temp3
    from #temp2 t2
    where t2.id=@curId

    --Remove earliest record from pool of potential records to check rules against
    delete 
    from #temp2 
    where id=@curId

    --Insert all records within 15 days of start date, then remove record from pool
    while (select count(1) 
            from #temp2 t2 
            where t2.started<=DATEADD(day,15,@maxTime) 
                or t2.closed<=DATEADD(day,15,@maxTime) 
                or t2.dead<=DATEADD(day,15,@maxTime)  )>0
    begin
        insert into #temp3
        select *
        from #temp2 t2
        where t2.started<=DATEADD(day,15,@maxTime)  or t2.closed<=DATEADD(day,15,@maxTime)  or t2.dead<=DATEADD(day,15,@maxTime) 

        delete
        from #temp2
        where started<=DATEADD(day,15,@maxTime)  or closed<=DATEADD(day,15,@maxTime)  or dead<=DATEADD(day,15,@maxTime) 

        --set new max time from any column
        if (select max(started) from #temp3)>@maxTime
            set @maxTime=(select max(started) from #temp3)
        if (select max(closed) from #temp3)>@maxTime
            set @maxTime=(select max(started) from #temp3)
        if (select max(dead) from #temp3)>@maxTime
            set @maxTime=(select max(started) from #temp3)

    end

    --Calculate end time according to rule #3
    declare @end datetime 
    set @end = null

    set @end=(select min(closed) from #temp3)

    if @end is null
    begin
        if(select count(1) from #temp3 where dead is null)=0
        set @end= (select max(dead) from #temp3)
    end

    insert into #result (borrower_id,started,ended)
    values (@curUser,@minTime,@end)

    drop table #temp3
end

--Done with the one user, remove him from temp table and iterate thru to the next user
delete  
from #temp1 
where borrower_id=@curUser

drop table #temp2

end

drop table #temp1

drop table #example

select * from #result order by started

drop table #result
Энтони Дженовезе
источник
-2

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

Отсутствуют условия группировки?

1) Правильно ли это правило 15 дней? Если запись Yначинается через 10 дней после другой записи X, а затем через 10 дней начинается другая запись , то Zобразует ли это одну группу из трех записей X,Y,Zили две группы, каждая из которых содержит две записи, X,Yи Y,Z? Я сделал предположение, что правила 15 дней каскадно образуют большие группы.

2) Являются ли даты включительно? Например, если одна запись имеет дату начала, а затем мертвую дату спустя много месяцев, все ли дни в этом диапазоне объединяются в группу? Я рассматриваю обе возможности в своем кратком анализе ниже.

Потенциальные группировки

Итак, если мы начнем с id 7714, мы увидим, что начальная дата 1/27. Очевидно, что следующая запись, 7882начинающаяся с 1/28, попадает в эту группу. Однако обратите внимание, что это 7882заканчивается 5/15, поэтому все, что начинается в течение 15 дней с 5/15, должно быть добавлено в группу.

Таким образом, 19690через 21210get добавляются в группу, что посредством каскадирования приводит к 21918последующему добавлению в группу. Каскадирование поглотило почти все записи в наборе. Назови это GROUP A.

Однако, если группировка также включает в себя дату, все записи от 13190до 17124должны также принадлежать GROUP A, и теперь все идентификаторы находятся в одной группе.

Если даты из GROUP Aне включены, но на самом деле строго придерживаться '15 дня после»правил с каскадом, то вместо этого вы бы иметь вторую группу , состоящую из 13190через 14020, и третью группу с одним входом, 17124.

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

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

Крис
источник
Что если я вообще избавлюсь от правила 15 дней? Это упростит проблему?
Ноа Гудрич
2
Кроме того, я думаю, что вы упустили момент о приоритете первой закрытой даты по сравнению с последней мертвой датой. В результате для первой группировки, начинающейся 1/27, дата закрытия 2/2 становится концом группы, а не 5/15.
Ноа Гудрич
Да, вы правы, я неверно истолковал то, что вы сказали о первых закрытых / последних мертвых ... Извините, я работал над этим прошлым вечером около 12:30 по тихоокеанскому времени, так что, возможно, я немного сонный. :) Также может помочь дополнительная группировка по пользовательским данным. Я еще немного подумаю и попытаюсь вернуться к вам.
Крис