在SQL中将记录与重叠的日期范围组合在一起

时间:2015-02-06 17:27:39

标签: sql sql-server

**编辑:我们当前的服务器是SQL 2008 R2,所以LAG / LEAD功能不起作用。

我尝试在表中获取多个数据流并将它们合并为一个数据流。鉴于下面的3个数据流,我希望最终结果是1个流,它优先考虑状态' on'。递归似乎是最好的选择,但到目前为止,我还没有运气,把我想做的事情放在一起。

CREATE TABLE #Dates(
    id INT IDENTITY,
    status VARCHAR(4),
    StartDate Datetime,
    EndDate Datetime,
    booth int)

INSERT #Dates
VALUES
( 'off','2015-01-01 08:00','2015-01-01 08:15',1),
( 'on','2015-01-01 08:15','2015-01-01 09:15',1),
( 'off','2015-01-01 08:50','2015-01-01 09:00',2),
( 'on','2015-01-01 09:00','2015-01-01 09:30',2),
( 'off','2015-01-01 09:30','2015-01-01 09:35',2),
( 'on','2015-01-01 09:35','2015-01-01 10:15',2),
( 'off','2015-01-01 09:30','2015-01-01 10:30',3),
( 'on','2015-01-01 10:30','2015-01-01 11:00',3)

status  StartDate   EndDate
---------------------------
off     08:00       08:15
on      08:15       09:15
off     08:50       09:00
on      09:00       09:30
off     09:30       09:35
on      09:35       10:15
off     09:30       10:30
on      10:30       11:00

结束结果:

status  StartDate   EndDate
---------------------------
off     8:00        8:15
on      8:15        9:15
on      9:15        9:30
off     9:30        9:35
on      9:35        10:15
off     10:15       10:30
on      10:30       11:00

基本上,任何时候都有' on'它应该覆盖任何并发的' off'状态。

Source:
|----off----||---------on---------|
                     |---off--||------on----||---off---||--------on------|
                                             |--------------off------------------||------on------|

Result (Either result would work):
|----off----||----------on--------||---on---||---off---||--------on------||-off--||------on------|
|----off----||----------on------------------||---off---||--------on------||-off--||------on------|

2 个答案:

答案 0 :(得分:0)

所以基本上每次都有一个" on"记录,它打开,否则关闭?

这里有一个与此问题略有不同的方法,每次" on"循环开始,并在结束时加-1。然后我们可以使用运行总计作为状态,当状态为0时,它会关闭,否则它会打开:

select Date, 
sum(oncounter) over (order by Date) as onstat,
sum(offcounter) over (order by Date) as offstat
from (
select StartDate as Date, 
  case when status = 'on' then 1 else 0 end oncounter,
  case when status = 'off' then 1 else 0 end offcounter
from Dates
union all
select EndDate as Date,
  case when status = 'on' then -1 else 0 end oncounter,
  case when status = 'off' then -1 else 0 end offcounter
from Dates
) TMP

编辑:还为off -states添加了计数器。它的工作方式与" on"相同。计数器,当两者都为0时,状态既不开启也不关闭。

最后的结果,似乎可以做到,虽然它看起来不再那么好了,但至少它不是递归的:)

select
    Date as StartDate,
    lead(Date, 1, '21000101') over (order by Date) as EndDate,
    case onstat 
        when 0 then 
            case when offstat > 0 then 'Off' else 'N/A' end
        else 'On' end as State
from (
    select
        Date,
        onstat, prevon,
        offstat, prevoff
    from (
        Select 
            Date,
            onstat,
            lag(onstat, 1, 0) over (order by Date) as prevon,
            offstat,
            lag(offstat, 1, 0) over (order by Date) as prevoff
        from (
            select 
                Date, 
                sum(oncounter) over (order by Date) as onstat,
                sum(offcounter) over (order by Date) as offstat
            from (
                select 
                    StartDate as Date, 
                    case when status = 'on' then 1 else 0 end oncounter,
                    case when status = 'off' then 1 else 0 end offcounter
                from 
                    Dates

            union all

                select 
                    EndDate as Date,
                    case when status = 'on' then -1 else 0 end oncounter,
                    case when status = 'off' then -1 else 0 end offcounter
                from 
                    Dates
            ) TMP
        ) TMP2
    ) TMP3
    where (onstat = 1 and prevon = 0)
    or (onstat = 0 and prevon = 1)
    or (onstat = 0 and offstat = 1 and prevoff = 0)
    or (onstat = 0 and offstat = 0 and prevoff = 1)
) TMP4

窗口函数有很多派生表,只有状态更改到结果集中,因此潜在客户可以选择正确的日期。有可能摆脱其中一些。

SQL小提琴:http://sqlfiddle.com/#!6/b5cfa/7

答案 1 :(得分:0)

这是2008年最简单的版本,我能够弄清楚:

; with Data (Date) as (
select StartDate from Dates
union
select EndDate from Dates),

Ranges (StartDate, Status) as (
select D.Date, D2.Status
from Data D
outer apply (
  select top 1 D2.Status
  from Dates D2
  where D2.StartDate <= D.Date and D2.EndDate > D.Date
  order by case when Status = 'on' then 1 else 2 end
) D2)

select R.StartDate,
(select min(D.Date) from Data D where D.Date > R.StartDate) as EndDate,
Status
from Ranges R
order by R.StartDate

即使状态与之前相同,它也会从每个开始/结束点开始返回新行。没有找到任何简单的方法来组合它们。

编辑:将第一个CTE更改为此将合并行:

; with Data (Date) as (
select distinct StartDate from Dates D1
where not exists (Select 1 from Dates D2 
    where D2.StartDate < D1.StartDate and D2.EndDate > D1.StartDate and
    Status = 'on')
union
select distinct EndDate from Dates D1
where not exists (Select 1 from Dates D2 
    where D2.StartDate < D1.EndDate and D2.EndDate > D1.EndDate and
    Status = 'on')
),