Interrogazione per determinare le date di inizio e fine in base alla sovrapposizione temporale


8

Dati i seguenti dati:

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

Devo creare un set di dati che soddisfi le seguenti regole:

  1. I gruppi vengono definiti per primi, user_idquindi dovremmo confrontare solo i record dello stessouser_id
  2. Tutti i record che sono iniziati almeno entro 15 giorni dall'avvio, chiusura o chiusura di qualsiasi altro record devono essere conteggiati come gruppo.
  3. Di ogni gruppo, la fine dovrebbe essere calcolata come il primo record chiuso o tutti i record hanno un valore per dead e prendiamo la data più grande della colonna dead.
  4. Se un record non inizia entro 15 giorni dall'inizio o dalla fine di un altro gruppo, inizia un nuovo raggruppamento.

A titolo provvisorio, credo che i miei dati dovrebbero apparire così:

id_utente | iniziato | fine
-------------------------------------------------- ----
238846 | 27/01/2015 15:14:50 | 02-02-2015 14:14:13
259140 | 23/03/2015 14:32:16 | 24-03-2015 15:57:32
242650 | 2015-04-16 16:19:08 | 16-04-2015 16:21:06
242650 | 21/05/2015 13:09:06 | NULLO
238846 | 2015-05-15 13:17:31 | NULLO

Qualcuno può fornire alcune indicazioni su come creare una query per soddisfare queste condizioni?

Ecco un link alle istruzioni DDL e DML per i dati presentati in questa domanda.

In alternativa, potremmo saltare le regole # 2 e # 4 e più semplicemente dichiarare che dovrebbero essere inclusi solo i record che si sovrappongono. La regola più importante è che in un determinato set, se c'è una data di chiusura, questa diventa la fine del set e non la data di scadenza più grande.


Ciò sarebbe più semplice con una modifica dello schema. Non è necessario per le due colonne, chiuse e morte. Basta avere una colonna "terminata" e quindi un motivo per la fine.
Andrew Brennan,

I tuoi primi 3 esempi possono essere codificati come "Se un ID è 'chiuso', allora è un gruppo a sé. Dal momento che ciò non sembra evidenziare tutte le tue regole, ti preghiamo di aggiungere altri esempi.
Rick James,

Risposte:


3

A causa della mancanza di chiarezza nella domanda, ho trovato quattro diverse soluzioni. Le soluzioni differiscono su:

  1. Sia che tu debba "mettere in cascata" la risposta di Chris
  2. Quando si dispone di una data di chiusura, se si utilizza la prima data per quel gruppo o la data di inizio per il record che è chiuso.

Si noti che questo viene fatto in SQL Server, non in MySQL. A parte alcune modifiche di sintassi molto minori, dovrebbe funzionare allo stesso modo.

Configurazione comune e dati di esempio per tutti e quattro i metodi

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 - UTILIZZANDO la soluzione REGISTRAZIONE CHIUSA

Questa è la soluzione che ritengo che chi sta cercando e corrisponda ai suoi risultati.

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 - UTILIZZANDO la soluzione REGISTRAZIONE CHIUSA

Inizio calcolato dalla prima data di chiusura quando disponibile, quindi dalla prima data di inizio.

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: UTILIZZANDO la soluzione DATA PRIMA

Inizia calcolato solo dalla prima data.

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 - UTILIZZANDO la soluzione DATA PRIMA

Inizia calcolato solo dalla prima data.

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

Temo che potremmo non avere un quadro chiaro di come viene definito un gruppo. Lo dico solo perché, a seconda di alcune condizioni non dichiarate, le date sopra formeranno un singolo gruppo gigante o 3 gruppi in cui un gruppo domina il set.

Condizioni di raggruppamento mancanti?

1) Questa regola di 15 giorni è in cascata? Se un record Yinizia 10 giorni dopo un altro record Xe quindi esiste un altro record Ziniziato 10 giorni dopo, questo forma un gruppo di tre record X,Y,Zo due gruppi contenenti ciascuno due record X,Ye Y,Z? Ho ipotizzato che le regole dei 15 giorni precipitino in gruppi più grandi.

2) Le date sono incluse? Ad esempio, se un record ha una data di inizio e quindi una data di scadenza molti mesi dopo, tutti i giorni all'interno di quell'intervallo vengono uniti nel gruppo? Tratto entrambe le possibilità nella mia rapida analisi di seguito.

Raggruppamenti potenziali

Quindi, se iniziamo con id 7714, vediamo che la data di inizio è 1/27. Chiaramente, la voce successiva a 7882partire da 1/28 rientra in questo gruppo. Si noti tuttavia che 7882termina il 15/5, quindi tutto ciò che inizia entro 15 giorni dal 15/05 deve essere aggiunto al gruppo.

Pertanto, 19690attraverso l' 21210aggiunta al gruppo, che tramite cascata porta ad 21918essere successivamente aggiunto al gruppo. Il collegamento in cascata ha consumato quasi tutte le voci nel set. Chiama questo GROUP A.

Se, tuttavia, anche il raggruppamento include la data, anche tutte le voci 13190fino a 17124devono appartenere a GROUP A, e ora tutti gli ID sono in un singolo gruppo.

Se le date da GROUP Anon comprendono, ma in realtà strettamente aderire al '15 giorno dopo la' regola con cascata, poi invece si dispone di un secondo gruppo composto da 13190tramite 14020, ed un terzo gruppo con una sola voce, 17124.

In sostanza, la mia domanda è: qualcuna di queste corrisponde al tuo raggruppamento previsto o ci sono altre informazioni che ci mancano nella definizione del gruppo? Mi dispiace per una risposta così prolissa, ma non sembra che l'output richiesto provvisorio soddisfi la definizione di raggruppamento.

Con i chiarimenti, sono sicuro che possiamo risolvere questo problema.


E se mi liberassi del tutto dalla regola dei 15 giorni? Ciò semplificherebbe il problema?
Noah Goodrich,

2
Inoltre, penso che ti sia sfuggito un po 'di dare la precedenza alla prima data chiusa rispetto all'ultima data morta. Di conseguenza, per il primo raggruppamento che inizia il 1/27, la data di chiusura di 2/2 diventa la fine del gruppo e non il 15/5.
Noah Goodrich,

Yikes, hai ragione, ho interpretato erroneamente quello che hai detto sul primo chiuso / ultimo morto ... Mi dispiace, stavo lavorando su quest'ultima sera intorno alle 12:30 di notte all'ora del Pacifico, quindi potrei essere stato un po ' assonnato. :) Inoltre, penso che il raggruppamento aggiuntivo in base ai dati degli utenti possa aiutare. Ci penserò ancora un po ', e proverò a ricontattarti.
Chris,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.