Abfrage zum Ermitteln von Start- und Enddaten basierend auf Zeitüberschneidungen

8

Angesichts der folgenden Daten:

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

Ich muss einen Datensatz erstellen, der die folgenden Regeln erfüllt:

  1. Gruppen werden zuerst durch definiert, user_iddaher sollten wir nur Datensätze derselben vergleichenuser_id
  2. Alle Datensätze, die mindestens innerhalb von 15 Tagen nach dem Start, dem Schließen oder dem Tod eines anderen Datensatzes gestartet wurden, sollten als Gruppe gezählt werden.
  3. Von jeder Gruppe sollte das Ende entweder als der erste geschlossene Datensatz berechnet werden oder alle Datensätze haben einen Wert für tot und wir nehmen das größte Datum der toten Spalte.
  4. Wenn ein Datensatz nicht innerhalb von 15 Tagen nach Beginn oder Ende einer anderen Gruppe gestartet wurde, beginnt eine neue Gruppierung.

Ich bin vorläufig der Meinung, dass meine Daten folgendermaßen aussehen sollten:

user_id | gestartet | Ende
-------------------------------------------------- ----
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

Kann jemand eine Anleitung zum Erstellen einer Abfrage geben, um diese Bedingungen zu erfüllen?

Hier ist ein Link zu den DDL- und DML-Anweisungen für die in dieser Frage dargestellten Daten.

Alternativ könnten wir die Regeln 2 und 4 überspringen und einfacher angeben, dass nur Datensätze enthalten sein sollten, die sich überlappen. Die wichtigere Regel ist, dass in einem bestimmten Satz, wenn es ein geschlossenes Datum gibt, dies das Ende des Satzes und nicht das größte Totdatum wird.

Noah Goodrich
quelle
Dies wäre mit einer Schemaänderung einfacher. Die beiden Säulen, geschlossen und tot, sind nicht erforderlich. Haben Sie einfach eine "beendete" Spalte und dann einen Grund für das Ende.
Andrew Brennan
Ihre ersten drei Beispiele können wie folgt codiert werden: "Wenn eine ID" geschlossen "ist, handelt es sich um eine Gruppe für sich. Da dies nicht alle Ihre Regeln hervorzuheben scheint, fügen Sie bitte weitere Beispiele hinzu.
Rick James

Antworten:

3

Aufgrund der Unklarheit der Frage habe ich vier verschiedene Lösungen gefunden. Die Lösungen unterscheiden sich in:

  1. Ob Sie nach Chris 'Antwort "kaskadieren" sollen
  2. Wenn Sie ein geschlossenes Datum haben, geben Sie an, ob Sie das früheste Datum für diese Gruppe oder das Startdatum für den geschlossenen Datensatz verwenden.

Bitte beachten Sie, dass dies in SQL Server und nicht in MySQL erfolgt. Abgesehen von einigen sehr geringfügigen Syntaxänderungen sollte es genauso funktionieren.

Gemeinsame Setup- und Beispieldaten für alle vier Methoden

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. CASCADING - VERWENDUNG DER CLOSED RECORD-Lösung

Dies ist die Lösung, nach der der Fragesteller meiner Meinung nach sucht und die zu seinen Ergebnissen passt.

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. NON-CASCADING - VERWENDUNG DER CLOSED RECORD-Lösung

Der Start wird nach dem ersten Abschlussdatum berechnet, sofern verfügbar, und dann nach dem frühesten Startdatum.

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. NON-CASCADING - VERWENDUNG DER FRÜHESTEN DATUM-Lösung

Start nur nach frühestem Datum berechnet.

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. CASCADING - VERWENDUNG DER FRÜHESTEN DATUM-Lösung

Start nur nach frühestem Datum berechnet.

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
Anthony Genovese
quelle
-2

Ich mache mir Sorgen, dass wir möglicherweise kein klares Bild davon haben, wie eine Gruppe definiert ist. Ich sage dies nur, weil die oben genannten Daten abhängig von einigen nicht angegebenen Bedingungen entweder eine riesige Einzelgruppe oder drei Gruppen bilden, in denen eine Gruppe die Menge dominiert.

Fehlende Gruppierungsbedingungen?

1) Kaskadiert diese 15-Tage-Regel? Wenn ein Datensatz Y10 Tage nach einem anderen Datensatz beginnt und 10 Tage danach ein Xweiterer Datensatz Zgestartet wird, bildet dieser dann eine Gruppe von drei Datensätzen X,Y,Zoder zwei Gruppen mit jeweils zwei Datensätzen X,Yund Y,Z? Ich ging davon aus, dass die 15-Tage-Regeln zu größeren Gruppen zusammenfallen.

2) Sind die Daten inklusive? Wenn beispielsweise ein Datensatz viele Monate später ein Startdatum und dann ein Totdatum hat, werden dann alle Tage in diesem Bereich in der Gruppe zusammengeführt? Ich behandle beide Möglichkeiten in meiner kurzen Analyse unten.

Mögliche Gruppierungen

Wenn wir also mit id beginnen 7714, sehen wir, dass das Startdatum 1/27 ist. Der nächste Eintrag 7882ab dem 28. Januar fällt eindeutig in diese Gruppe. Beachten Sie jedoch, dass dies 7882am 15. Mai endet. Daher muss alles, was innerhalb von 15 Tagen nach dem 15. Mai beginnt, zur Gruppe hinzugefügt werden.

Somit wird 19690durch 21210Hinzufügen zur Gruppe hinzugefügt, was durch Kaskadierung dazu führt, 21918dass anschließend zur Gruppe hinzugefügt wird. Die Kaskadierung hat fast alle Einträge im Satz verbraucht. Nennen Sie das GROUP A.

Wenn die Gruppierung jedoch auch datumsbezogen ist 13190, 17124müssen auch alle Einträge von bis bis zu gehören GROUP A, und jetzt befinden sich alle IDs in einer einzigen Gruppe.

Wenn die Daten von GROUP Anicht inklusive sind, sich aber strikt an die Regel '15 Tage danach 'mit Kaskadierung halten, haben Sie stattdessen eine zweite Gruppe bestehend aus 13190durch 14020und eine dritte Gruppe mit einem einzigen Eintrag 17124.

Meine Frage lautet im Wesentlichen: Entspricht eine dieser Angaben Ihrer beabsichtigten Gruppierung, oder fehlen in der Gruppendefinition andere Informationen? Es tut mir leid für eine so langwierige Antwort, aber es scheint nicht, dass Ihre vorläufig angeforderte Ausgabe Ihrer Gruppierungsdefinition entspricht.

Ich bin mir sicher, dass wir dieses Problem mit Klarstellungen lösen können.

Chris
quelle
Was ist, wenn ich die 15-Tage-Regel insgesamt losgeworden bin? Würde das das Problem vereinfachen?
Noah Goodrich
2
Ich denke auch, dass Sie es verpasst haben, dem ersten geschlossenen Datum Vorrang vor dem letzten toten Datum einzuräumen. Infolgedessen wird für die erste Gruppierung, die am 27. Januar beginnt, das Abschlussdatum 2/2 zum Ende der Gruppe und nicht zum 15. Mai.
Noah Goodrich
Huch, du hast recht, ich habe falsch interpretiert, was du über den ersten geschlossenen / letzten Toten gesagt hast ... Entschuldigung, ich habe an dieser letzten Nacht gegen 12:30 Uhr in der pazifischen Nacht gearbeitet, also war ich vielleicht ein bisschen schläfrig. :) Auch die zusätzliche Gruppierung nach Benutzerdaten kann helfen, denke ich. Ich werde noch ein bisschen darüber nachdenken und versuchen, mich bei Ihnen zu melden.
Chris