2011-01-06 5 views
4

J'ai une situation où j'ai besoin de trouver des intervalles de temps entre les changements de valeur. J'ai essayé une simple clause group by, mais elle élimine les changements qui se chevauchent. Prenons l'exemple suivant:Comment éviter le chevauchement des plages de dates lors de l'utilisation d'une clause de regroupement?

create table #items (
     code varchar(4) 
    , class varchar(4) 
    , txdate datetime 
) 

insert into #items (code, class, txdate) values ('A', 'C', '2010-01-01'); 
insert into #items (code, class, txdate) values ('A', 'C', '2010-01-02'); 
insert into #items (code, class, txdate) values ('A', 'C', '2010-01-03'); 
insert into #items (code, class, txdate) values ('A', 'D', '2010-01-04'); 
insert into #items (code, class, txdate) values ('A', 'D', '2010-01-05'); 
insert into #items (code, class, txdate) values ('A', 'C', '2010-01-06'); 
insert into #items (code, class, txdate) values ('A', 'C', '2010-01-07'); 
insert into #items (code, class, txdate) values ('A', 'D', '2010-01-08'); 
insert into #items (code, class, txdate) values ('A', 'D', '2010-01-09'); 

select code 
, class 
, min(txdate) mindate 
, max(txdate) maxdate 
from #items 
group by code, class 

Cela renvoie les résultats suivants (remarquez les plages de dates qui se chevauchent):

|code|class|mindate |maxdate | 
---------------------------------- 
|A |C |2010-01-01|2010-01-07| 
|A |D |2010-01-04|2010-01-09| 

Je voudrais avoir la requête renvoie les éléments suivants:

|code|class|mindate |maxdate | 
---------------------------------- 
|A |C |2010-01-01|2010-01-03| 
|A |D |2010-01-04|2010-01-05| 
|A |C |2010-01-06|2010-01-07| 
|A |D |2010-01-08|2010-01-09| 

Des idées et des suggestions?

+0

Vos dates d'entrée ont toutes le mois de janvier pour le mois, mais certains de vos résultats ont avril. Avez-vous transposé des mois et des jours dans vos résultats en les mettant ici? – CanSpice

+0

@CanSpice: Mes dates utilisent le format aaaa-mm-jj. J'ai attrapé l'erreur et l'ai corrigée. Thx –

+0

Bon, alors votre exemple doit inclure les dates en avril, car tout ce que vous entrez est des dates en janvier, mais votre exemple renvoie des dates en avril. – CanSpice

Répondre

0

Après des recherches sur SQL SERVER ISLANDS comme suggéré par @KM, je suis venu avec la requête suivante qui semble bien fonctionner lorsque les codes de classe supplémentaires sont ajoutés à l'ensemble de données.

select a.code, a.class, a.txdate as mindate, b.txdate as maxdate 
from (
    --Find minimum island 
    select code 
     , class 
     , txdate 
     , row_number() over (order by code, class, txdate) as n 
    from #items tb1 
    where not exists (
     select * 
     from #items tb2 
     where datediff(d, tb1.txdate, tb2.txdate) = -1  
      and tb1.class = tb2.class 
      and tb1.code = tb2.code 
    ) 
) as a 
inner join (
    --Find maximum island 
    select code 
     , class 
     , txdate 
     , row_number() over (order by code, class, txdate) as n 
    from #items tb1 
    where not exists (
     select * 
     from #items tb2 
     where datediff(d, tb1.txdate, tb2.txdate) = 1 
      and tb1.class = tb2.class 
      and tb1.code = tb2.code 
    ) 
) as b on a.n = b.n 

La seule réserve à cette approche est que le nombre d'entrées dans l'ensemble minimal doit correspondre au nombre d'entrées dans le jeu au maximum. Jusqu'à présent, je n'ai pas été capable de faire quoi que ce soit qui rendrait ce n'est pas vrai. Cependant, je n'ai pas testé les valeurs nulles ou les performances.

0

Je pense que vous ne pouvez pas faire cela avec une simple instruction select.

Vous pouvez utiliser un curseur pour itérer sur les lignes et identifier les modifications de "classe".

2

EDIT: Comme indiqué dans les commentaires, ce n'est toujours pas tout à fait raison.

;with cteNtile as (
    select code, class, txdate, 
      ntile((select count(*) from (select NULL as dummy from #items group by code, class) a)) over(partition by code, class order by txdate) as tilenum 
     from #items 
) 
select code, class, MIN(txdate) as mindate, MAX(txdate) as maxdate 
    from cteNtile 
    group by code, class, tilenum 
    order by mindate, maxdate 
+0

+1: renvoie la sortie correcte. De toute façon de le faire sans le nombre d'enregistrements initial? –

+0

@k rey: Je n'ai pas encore trouvé de solution. –

+0

+1, vous pouvez supprimer la variable locale et utiliser simplement: 'ntile ((select count (*) from (sélectionnez NULL comme dummy from @items group par code, class) a))' –

2

Voici la requête qui peut vous donner le résultat souhaité.

;WITH items1 AS (
SELECT ROW_NUMBER() OVER (ORDER BY txdate) rowid, code, class, txdate 
from #items 
), 
items2 AS (
SELECT ROW_NUMBER() OVER (ORDER BY rowid) id, rowid, i1.Code, i1.Class, i1.txdate 
FROM items1 i1 
WHERE NOT EXISTS (SELECT 1 FROM items1 i2 
        WHERE i2.txdate < i1.txdate 
        AND i2.class = i1.class 
        AND i2.Code = i1.Code 
        AND i2.rowid+1=i1.rowid) 
) 
SELECT items2.code, items2.class, items2.txdate mindate, items1.txdate maxdate 
FROM items2, items2 items3, items1 
WHERE (items2.id+1=items3.id AND items3.rowid-1=items1.rowid) 
OR items2.rowid = (SELECT MAX(t.rowid) FROM items1 t) 
UNION 
SELECT items2.code, items2.class, MAX(items2.txdate) mindate, MAX(items1.txdate) maxdate 
FROM items2, items1 
WHERE items1.class = items2.class 
GROUP BY items1.class, items2.class, items2.code, items2.class 
ORDER BY items2.txdate 
+0

+1: Cela fonctionne avec le jeu de résultats donné.Cependant, l'ajout d'une combinaison de code de classe additionne la sortie. –

+0

@k rey: ok .. J'ai la raison. Il ne fonctionnait pas pour des données comme (Code: B et Classe: D). J'avais manqué une comparaison entre les codes. J'ai modifié ma requête en conséquence. Faites-moi savoir si cela fonctionne pour vous maintenant .. –

Questions connexes