清除SQL Server中的优先重叠范围

时间:2009-02-10 05:55:24

标签: sql sql-server

这个很难解决。

我有一个包含日期范围的表,每个日期范围都有一个优先级。最高优先级意味着此日期范围是最重要的。

或者在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

我想在此表上运行一系列SQL查询,这将导致表没有重叠范围,它将采用优先级较高的范围而不是较低的范围。根据需要拆分范围,并摆脱重复范围。它允许存在差距。

所以结果应该是:

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

有人想关注SQL吗?我也希望它尽可能高效。

4 个答案:

答案 0 :(得分:4)

这是大多数方式,可能的改进是加入相同优先级的相邻范围。它充满了酷炫的诡计。

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

答案 1 :(得分:0)

我对你最终想要的东西感到有点困惑。这是否只是简单地设置一组日期,其中一个范围一直持续到下一个范围开始(在这种情况下,你真的不需要完成日期,对吗?)

或者一个范围可以完成,并且有时会出现间隙,直到下一个有时开始?

如果明确设置了Start和Finish范围,那么我倾向于保留两者,但是在重叠期间有逻辑应用更高的优先级。我怀疑如果日期开始调整,你最终需要回滚剃光范围,原来的设置就会消失。

你永远无法解释“它是如何做到的”。

您是否只想要一个每个日期都有一行的表,包括其优先级值?那么当你有一个新规则时,你可以碰撞新规则会胜过的日期吗?

我做了一个医疗办公室调度应用程序,一旦开始工作/休假等。具有范围类型数据的请求(加上默认的工作周模板。)一旦我想出将活动计划信息存储为用户/日期/时间范围记录,事情就会变得更加容易。 YMMV。

答案 2 :(得分:0)

这是让你入门的东西。如果您使用日历表,它会很有用:

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

设置问题的代码:

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)

实际解决方案:

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

结果: 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

解决方案的格式不完全相同,但想法就在那里。

答案 3 :(得分:0)

这可以在1个SQL中完成(我首先在Oracle中使用lag和lead进行查询,但由于MSSQL不支持这些函数,我使用row_number重写了查询。我不确定结果是否符合MSSQL ,但它应该非常接近):

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

我首先制作了一张包含所有日期的表格(x)。然后我通过将x与自身连接并将以下两行连接到桶中。在此之后,我将所有可能的优先级与结果联系起来。通过取max(优先级),我得到了请求的结果。