Requête pour déterminer les dates de début et de fin en fonction du chevauchement des heures

8

Compte tenu des données suivantes:

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

J'ai besoin de créer un ensemble de données qui respecte les règles suivantes:

  1. Les groupes sont définis en premier par user_iddonc nous devons comparer uniquement les enregistrements de la mêmeuser_id
  2. Tous les enregistrements qui ont commencé au moins dans les 15 jours suivant le début, la fermeture ou la mort de tout autre enregistrement doivent être comptés comme groupe.
  3. Pour chaque groupe, la fin doit être calculée comme le premier enregistrement fermé ou tous les enregistrements ont une valeur pour dead et nous prenons la plus grande date de la colonne dead.
  4. Si un enregistrement n'a pas commencé dans les 15 jours suivant le début ou la fin d'un autre groupe, il commence alors un nouveau regroupement.

À titre provisoire, je pense que mes données devraient ressembler à ceci:

user_id | commencé | fin
-------------------------------------------------- ----
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 | 16/04/2015 16:19:08 | 16/04/2015 16:21:06
242650 | 2015-05-21 13:09:06 | NUL
238846 | 15/05/2015 13:17:31 | NUL

Quelqu'un peut-il fournir des conseils sur la façon de créer une requête pour répondre à ces conditions?

Voici un lien vers les instructions DDL et DML pour les données présentées dans cette question.

Alternativement, nous pourrions ignorer les règles # 2 et # 4 et plus simplement déclarer que seuls les enregistrements qui se chevauchent devraient être inclus. La règle la plus importante est que dans un ensemble donné, s'il y a une date de clôture, cela devient la fin de l'ensemble et non la plus grande date morte.

Noah Goodrich
la source
Ce serait plus facile avec un changement de schéma. Pas besoin des deux colonnes, fermées et mortes. Il suffit d'avoir une colonne "terminée" et une raison pour la fin.
Andrew Brennan
Vos 3 premiers exemples peuvent être encodés comme "Si un identifiant est" fermé ", alors c'est un groupe en soi. Puisque cela ne semble pas mettre en évidence toutes vos règles, veuillez ajouter plus d'exemples.
Rick James

Réponses:

3

En raison du manque de clarté de la question, j'ai trouvé quatre solutions différentes. Les solutions diffèrent sur:

  1. Que vous soyez en "cascade" selon la réponse de Chris
  2. Lorsque vous avez une date de clôture, que vous utilisiez la date la plus ancienne pour ce groupe ou la date de début pour l'enregistrement qui est fermé.

Veuillez noter que cela se fait dans SQL Server, pas dans MySQL. À part quelques changements de syntaxe très mineurs, cela devrait fonctionner de la même manière.

Configuration commune et exemples de données pour les quatre méthodes

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 - UTILISATION de la solution CLOSED RECORD

C'est la solution que je crois que le demandeur recherche et correspond à ses résultats.

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 - UTILISATION DE LA SOLUTION D'ENREGISTREMENT FERMÉ

Début calculé par la première date de clôture lorsqu'elle est disponible, puis par la première date de début.

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 - UTILISATION DE LA SOLUTION DATE LA PLUS TÔT

Début calculé uniquement au plus tôt.

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 - UTILISATION de la solution DATE LA PLUS TÔT

Début calculé uniquement au plus tôt.

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
la source
-2

Je crains que nous n'ayons pas une idée claire de la façon dont un groupe est défini. Je dis seulement cela parce que, selon certaines conditions non déclarées, les dates ci-dessus formeront soit un seul groupe géant, soit 3 groupes où un groupe domine l'ensemble.

Conditions de regroupement manquantes?

1) Cette règle des 15 jours est-elle en cascade? Si un enregistrement Ycommence 10 jours après un autre enregistrement X, et puis il y a un autre enregistrement Zdémarré 10 jours après cela, alors cela forme-t-il un groupe de trois enregistrements X,Y,Z, ou deux groupes contenant chacun deux enregistrements X,Yet Y,Z? J'ai fait l'hypothèse que les règles de 15 jours tombent en cascade pour former des groupes plus importants.

2) Les dates sont-elles incluses? Par exemple, si un enregistrement a une date de début puis une date morte plusieurs mois plus tard, tous les jours de cette plage sont-ils fusionnés dans le groupe? Je traite les deux possibilités dans mon analyse rapide ci-dessous.

Groupements potentiels

Donc, si nous commençons par id 7714, nous voyons que la date de début est 1/27. De toute évidence, la prochaine entrée à 7882partir du 1/28 appartient à ce groupe. Notez cependant que cela 7882se termine le 5/15, donc tout ce qui commence dans les 15 jours du 5/15 doit être ajouté au groupe.

Ainsi, 19690grâce à 21210être ajouté au groupe, ce qui, via la cascade, conduit à 21918être ajouté ultérieurement au groupe. La cascade a consommé presque toutes les entrées de l'ensemble. Appelez ça GROUP A.

Si, toutefois, le regroupement inclut également la date, toutes les entrées de 13190jusqu'à 17124doivent également appartenir à GROUP A, et maintenant tous les identifiants sont dans un seul groupe.

Si les dates de GROUP Ane sont pas inclus, mais en fait respecter strictement la journée '15 après » règle avec cascade, puis à la place que vous avez un deuxième groupe composé de 13190travers 14020, et un troisième groupe avec une seule entrée, 17124.

Essentiellement, ma question est la suivante: est-ce que l'un de ces éléments correspond à votre regroupement prévu ou y a-t-il d'autres informations qui nous manquent dans la définition du groupe? Je suis désolé pour une réponse aussi longue, mais il ne semble pas que votre sortie demandée provisoire réponde à votre définition de regroupement.

Avec des clarifications, je suis sûr que nous pouvons régler ce problème.

Chris
la source
Et si je me débarrassais complètement de la règle des 15 jours? Cela simplifierait-il le problème?
Noah Goodrich
2
De plus, je pense que vous avez raté le peu de donner la priorité à la première date de clôture sur la dernière date morte. Par conséquent, pour le premier regroupement commençant le 1/27, la date de clôture du 2/2 devient la fin du groupe et non le 5/15.
Noah Goodrich
Oui, vous avez raison, j'ai mal interprété ce que vous avez dit à propos du premier fermé / dernier mort ... Désolé, je travaillais sur cette dernière nuit vers 12h30, heure du Pacifique, donc j'ai peut-être été un peu somnolent. :) De plus, le regroupement supplémentaire par données utilisateur peut aider, je pense. Je vais y réfléchir un peu plus et essayer de vous revenir.
Chris