我会尝试将问题的具体细节保留在这个问题之外,并只关注相关问题。
假设我有Assets
表,主键为AssetID
。
我有另一个名为ProcessedDates
的表,主键为PID
,其他列AssetID
,StartDate
,EndDate
。
我想在开始日期和结束日期之间运行资产列表的流程。
在开始此过程之前,我需要知道哪些资产和哪些日期范围已经处理过。
例如,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
从这里我不知道如何继续。如何让它返回AssetID
,StartDate
,EndDate
以查找其间的所有空白?
答案 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 NULL
和ISNULL(... @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