如何构建涉及缺少日期范围的此T-SQL查询?

时间:2015-08-11 11:05:03

标签: sql sql-server tsql

我会尝试将问题的具体细节保留在这个问题之外,并只关注相关问题。

假设我有Assets表,主键为AssetID
我有另一个名为ProcessedDates的表,主键为PID,其他列AssetIDStartDateEndDate
我想在开始日期和结束日期之间运行资产列表的流程。
在开始此过程之前,我需要知道哪些资产和哪些日期范围已经处理过。

例如,ProcessedDates中有2个条目:

AssetID  StartDate  EndDate
--------------------------
Asset1   Day4       day7
Asset1   Day10      Day12

我想在第2天和第11天之间处理Asset1。我不需要在已经完成的日子里通过处理来浪费时间,因此在本例中,我只会处理asset1从第2天到第3天以及从第8天到第9天。

所以我需要的是一个返回日期范围间隙的查询。在这种情况下,结果集将为2行:

AssetID  StartDate  EndDate
--------------------------
Asset1   day2       day3
Asset1   day8       day9

在我的实际要求中,我有很多assetID。 ProcessedDates表可能包含每个资产的多个条目,或者根本没有任何条目,并且每个资产不一定与任何其他资产具有相同的处理日期。

declare @StartDate date, @EndDate date (assume these are given)

--get distinct assets
select distinct AssetIDs from (some query) into #Assets
--get the already processed date ranges
select p.AssetID, p.StartDate, p.EndDate
from ProcessedDates p inner join #Assets a on p.AssetID = a.AssetID
where p.StartDate between @StartDate and @EndDate
or p.EndDate between @StartDate and @EndDate

从这里我不知道如何继续。如何让它返回AssetIDStartDateEndDate以查找其间的所有空白?

2 个答案:

答案 0 :(得分:0)

这样的事情:

declare @StartDate date = '2015-01-01', @EndDate date = '2015-05-05'

declare @Assets table (AssetID varchar(50), StartDate date, EndDate date)
declare @AssetTypes table (AssetID varchar(50))

insert into @AssetTypes values
('Asset1'), 
('Asset2')

insert into @Assets values
('Asset1', '2014-12-10', '2014-12-31'), -- Ignored
('Asset1', '2015-02-02', '2015-03-02'),
('Asset1', '2015-03-05', '2015-05-01'),
('Asset1', '2015-06-01', '2015-06-06') -- Ignored

;WITH Base AS (
    SELECT AT.AssetID
        , CASE WHEN A.AssetID IS NULL THEN 1 ELSE 0 END EmptyAsset
        , A.StartDate
        , A.EndDate
        , ROW_NUMBER() OVER (PARTITION BY AT.AssetID ORDER BY StartDate) RN
        FROM @AssetTypes AT
        LEFT JOIN @Assets A ON A.AssetID = AT.AssetID
        WHERE A.AssetID IS NULL -- case of totally missing asset
            OR (StartDate <= @EndDate AND EndDate >= @StartDate)
)

-- first missing range, before the first row
SELECT AssetID, @StartDate StartDate, DATEADD(dd, -1, StartDate) EndDate
    FROM Base 
    WHERE RN = 1 AND StartDate > @StartDate

UNION ALL

-- each row joined with the next one
SELECT B1.AssetID, DATEADD(dd, 1, B1.EndDate), ISNULL(DATEADD(dd, -1, B2.StartDate), @EndDate)
    FROM Base B1
    LEFT JOIN Base B2 ON B2.AssetID = B1.AssetID AND B2.RN = B1.RN + 1
    WHERE B1.EmptyAsset = 0
        AND (B2.AssetID IS NULL -- Last row case
            OR DATEADD(dd, 1, B1.EndDate) < B2.StartDate) -- Other rows case
        AND B1.EndDate < @EndDate -- If the range ends after @EndDate, nothing to do

UNION ALL

-- case of totally missing asset
SELECT AssetID, @StartDate, @EndDate
    FROM Base 
    WHERE EmptyAsset = 1

主要思想是每一行都与下一行连接。在EndDate + 1和StartDate之间生成一个新范围(如果需要) - 1.对最后一行(B2.AssetID IS NULLISNULL(... @EndDate))进行特殊处理。第一个SELECT在第一个范围之前生成一行,最后一个选择是针对资产没有范围的特殊情况。

正如我在评论中写的那样,它很快就会变得丑陋。

答案 1 :(得分:0)

这是一个简单的版本,可以获得您想要的结果。我使用整数作为日期,并假设最小日期为0,最大日期为999。

--DDL
create table Assets  (AssetID integer, StartDate integer, EndDate integer);
insert into Assets values
(1,4,7),
(1,10,12),
(1,15,17),
(2,5,7),
(2,9,10);

with temp as(
select a1.AssetId,
       a1.enddate+1 as StartDate, 
       coalesce(min(a2.startdate) - 1,999) as EndDate
from Assets a1
left join Assets a2
on a1.assetid = a2.assetid
and a1.enddate < a2.startdate
group by a1.assetid,a1.enddate  
union all
select a.assetid,0,min(startdate) -1
from Assets a
group by a.assetid
)
select AssetId,
       case when StartDate<2 then 2 else StartDate end as StartDate,
       case when EndDate>11 then 11 else EndDate   end as EndDate
from temp
where StartDate<=11 and EndDate>=2
order by AssetId,StartDate

临时表可以获取缺失的范围。然后过滤第2天和第11天之间的匹配范围,将获得您想要的结果。

AssetId StartDate   EndDate
1   2   3
1   8   9
2   2   4
2   8   8
2   11  11

这里是SqlFiddle Demo