2009-02-10 4 views
1

Celui-ci est difficile à résoudre.Résolution des plages de chevauchement hiérarchisées dans SQL Server

J'ai une table contenant des plages de dates, chaque plage de dates a une priorité. La priorité la plus élevée signifie que cette plage de dates est la plus importante.

Ou dans SQL

create table #ranges (Start int, Finish int, Priority int) 


insert #ranges values (1 , 10, 0) 
insert #ranges values (2 , 5 , 1) 
insert #ranges values (3 , 4 , 2) 
insert #ranges values (1 , 5 , 0) 
insert #ranges values (200028, 308731, 0) 


Start  Finish  Priority 
----------- ----------- ----------- 
1   10   0 
2   5   1 
3   4   2 
1   5   0 
200028  308731  0 

Je voudrais lancer une série de requêtes SQL sur ce tableau qui se traduira par la table ayant aucune plage qui se chevauchent, il est de prendre les plages la plus haute priorité sur les niveaux inférieurs . Séparez les plages au besoin et débarrassez-vous des plages dupliquées. Cela permet des lacunes.

Ainsi, le résultat devrait être:

Start  Finish  Priority  
----------- ----------- ----------- 
1   2   0 
2   3   1 
3   4   2 
4   5   1 
5   10   0 
200028  308731  0 

Quelqu'un veut donner un coup de feu au SQL? Je voudrais aussi que ce soit aussi efficace que possible.

Répondre

4

Ceci est la plupart du temps là, l'amélioration possible serait de rejoindre des plages adjacentes de la même priorité. C'est plein de supercheries.

select Start, cast(null as int) as Finish, cast(null as int) as Priority 
into #processed 
from #ranges 
union 
select Finish, NULL, NULL 
from #ranges 


update p 
set Finish = (
    select min(p1.Start) 
    from #processed p1 
    where p1.Start > p.Start 
) 
from #processed p 

create clustered index idxStart on #processed(Start, Finish, Priority) 
create index idxFinish on #processed(Finish, Start, Priority) 

update p 
set Priority = 
    (
     select max(r.Priority) 
     from #ranges r 
     where 
     (
      (r.Start <= p.Start and r.Finish > p.Start) or 
      (r.Start >= p.Start and r.Start < p.Finish) 
     ) 
    ) 
from #processed p 

delete from #processed 
where Priority is null 

select * from #processed 
+0

Solution impressionnante! –

+0

Assez intelligent. J'ai pris un peu d'huile de coude pour l'adapter à des colonnes de départ/arrivée nulles et à des groupes de niveau supérieur, mais cela m'a probablement sauvé deux bonnes journées de claquement de mur! – Aaronaught

0

Je suis un peu confus au sujet de ce que vous voulez finir avec. Est-ce la même chose que d'avoir un ensemble de dates où une série continue jusqu'au prochain commence (auquel cas vous n'avez pas vraiment besoin de la date de fin, n'est-ce pas?)

Ou peut une gamme Terminer et il y a un écart jusqu'à la prochaine commence parfois?

Si la plage Start et Finish est explicitement définie, alors je serais enclin à laisser les deux, mais j'ai la logique d'appliquer la priorité la plus élevée pendant le chevauchement. Je soupçonne que si les dates commencent à être ajustées, vous devrez éventuellement annuler une gamme qui a été rasée, et le réglage d'origine sera parti.

Et vous ne serez jamais en mesure d'expliquer "comment c'est ainsi".

Voulez-vous simplement une table avec une ligne pour chaque date, y compris sa valeur de priorité? Puis, lorsque vous avez une nouvelle règle, vous pouvez augmenter les dates qui seraient dépassées par la nouvelle règle? J'ai fait une application de planification de bureau médical une fois que cela a commencé avec le travail/vacances/etc. demandes avec des données de type plage (plus un modèle de travail par semaine par défaut.) Une fois que j'ai compris que stocker les informations de programme actives en tant qu'utilisateur/date/timerange, les choses se sont mises en place beaucoup plus facilement. YMMV.

+0

dorfer voir ma question clarifiée. juste de contourner les règles n'est pas assez bon je dois diviser des choses. J'ai aussi besoin de cela pour les rapports de modélisation, donc je ne suis pas vraiment en contrôle de ce qui est inséré –

0

Voici quelque chose pour vous aider à démarrer. Il est utile si vous utilisez une table de calendrier:

CREATE TABLE dbo.Calendar 
( 
    dt SMALLDATETIME NOT NULL 
     PRIMARY KEY CLUSTERED 
) 
GO 

SET NOCOUNT ON 
DECLARE @dt SMALLDATETIME 
SET @dt = '20000101' 
WHILE @dt < '20200101' 
BEGIN 
    INSERT dbo.Calendar(dt) SELECT @dt 
    SET @dt = @dt + 1 
END 
GO 

Code pour configurer le problème:

create table #ranges (Start DateTime NOT NULL, Finish DateTime NOT NULL, Priority int NOT NULL) 
create table #processed (dt DateTime NOT NULL, Priority int NOT NULL) 

ALTER TABLE #ranges ADD PRIMARY KEY (Start,Finish, Priority) 
ALTER TABLE #processed ADD PRIMARY KEY (dt) 


declare @day0 datetime, 
    @day1 datetime, 
    @day2 datetime, 
    @day3 datetime, 
    @day4 datetime, 
    @day5 datetime 

select @day0 = '2000-01-01', 
    @day1 = @day0 + 1, 
    @day2 = @day1 + 1, 
    @day3 = @day2 + 1, 
    @day4 = @day3 + 1, 
    @day5 = @day4 + 1 

insert #ranges values (@day0, @day5, 0) 
insert #ranges values (@day1, @day4, 1) 
insert #ranges values (@day2, @day3, 2) 
insert #ranges values (@day1, @day4, 0) 

solution actuelle:

DECLARE @start datetime, @finish datetime, @priority int 

WHILE 1=1 BEGIN 
    SELECT TOP 1 @start = start, @finish = finish, @priority = priority 
    FROM #ranges 
    ORDER BY priority DESC, start, finish 

    IF @@ROWCOUNT = 0 
     BREAK 

    INSERT INTO #processed (dt, priority) 
     SELECT dt, @priority FROM calendar 
     WHERE dt BETWEEN @start and @finish 
     AND NOT EXISTS (SELECT * FROM #processed WHERE dt = calendar.dt) 

    DELETE FROM #ranges WHERE @start=start AND @finish=finish AND @priority=priority 
END 

Résultats: SELECT * FROM #processed

dt      Priority 
----------------------- ----------- 
2000-01-01 00:00:00.000 0 
2000-01-02 00:00:00.000 1 
2000-01-03 00:00:00.000 2 
2000-01-04 00:00:00.000 2 
2000-01-05 00:00:00.000 1 
2000-01-06 00:00:00.000 0 

La solution n'est pas dans le même format exact, mais l'idée est là.

+0

plage, voir ma question clarifiée –

0

Cela peut être fait en 1 SQL (i d'abord fait la requête dans Oracle en utilisant le décalage et le plomb, mais étant donné que MSSQL ne supporte pas ces fonctions i Réécriture de la requête à l'aide row_number. Je ne sais pas si la résultat est conforme MSSQL, mais il devrait être très proche):

with x as (
select rdate rdate 
,  row_number() over (order by rdate) rn 
from (
     select start rdate 
     from ranges 
     union 
     select finish rdate 
     from ranges 
     ) 
) 
select d.begin 
,  d.end 
,  max(r.priority) 
from ( 
     select begin.rdate begin 
     ,  end.rdate  end 
     from x  begin 
     ,  x  end 
     where begin.rn = end.rn - 1 
     )   d 
,  ranges  r 
where r.start <= d.begin 
and r.finish >= d.end 
and d.begin <> d.end 
group by d.begin 
,  d.end 
order by 1, 2 

J'ai d'abord fait une table (x) avec toutes les dates. Ensuite, j'ai transformé cela en seaux en joignant x avec lui-même et en prenant 2 rangées suivantes. Après cela, j'ai lié toutes les priorités possibles avec le résultat. En prenant le max (priorité) j'obtiens le résultat demandé.