我有一个表,该表记录某个项目的某个字段发生更改的时间以及更改的日期。我需要查询数据以查找在请求的日期范围内随时该字段具有特定值的所有项目。
换句话说,如果该项目在数据范围的开始,结束或任何时候都具有该值,则应将其包括在内。
示例数据:
Item Valid Date Changed
---- ----- ------------
A Yes 2015-01-01
B No 2015-01-01
B Yes 2017-03-01
C Yes 2015-01-01
C No 2017-04-01
D No 2015-01-01
D Yes 2017-05-01
D No 2017-06-01
E Yes 2015-01-01
E No 2017-05-01
E Yes 2017-06-01
F Yes 2015-01-01
F No 2018-02-01
G Yes 2017-12-31
V No 2015-01-01
V Yes 2018-02-01
W Yes 2015-01-01
W No 2016-01-01
X No 2015-01-01
Y Yes 2018-01-01
Z Yes 2015-01-01
Z No 2017-01-01
因此,如果我需要2017年有效的所有商品,则查询将包括:
查询中将不包含V,W,X,Y或Z,在2017年期间,这些都不是有效的。(请特别注意G和Z,这是棘手的情况!)
-- Sample data
create table #Temp (
ItemID char,
Valid bit,
StartDate date
);
insert into #Temp (ItemID, Valid, StartDate)
values ('A', 1, '2015-01-01'),
('B', 0, '2015-01-01'),
('B', 1, '2017-03-01'),
('C', 1, '2015-01-01'),
('C', 0, '2017-04-01'),
('D', 0, '2015-01-01'),
('D', 1, '2017-05-01'),
('D', 0, '2017-06-01'),
('E', 1, '2015-01-01'),
('E', 0, '2017-05-01'),
('E', 1, '2017-06-01'),
('F', 1, '2015-01-01'),
('F', 0, '2018-02-01'),
('G', 1, '2017-12-31'),
('V', 0, '2015-01-01'),
('V', 1, '2018-02-01'),
('W', 1, '2015-01-01'),
('W', 0, '2016-01-01'),
('X', 0, '2015-01-01'),
('Y', 1, '2018-01-01'),
('Z', 1, '2015-01-01'),
('Z', 0, '2017-01-01');
仅供参考,这是我发现的其他一些SO问题,它们提出了类似的问题,但并不完全相同:
答案 0 :(得分:3)
首先,您可以打开原始的时间戳列表:
ItemID Valid StartDate ------ ----- ---------- A 1 2015-01-01 B 0 2015-01-01 B 1 2017-03-01 C 1 2015-01-01 C 0 2017-04-01 D 0 2015-01-01 D 1 2017-05-01 D 0 2017-06-01 E 1 2015-01-01 E 0 2017-05-01 E 1 2017-06-01 F 1 2015-01-01 F 0 2018-02-01 G 1 2017-12-31 V 0 2015-01-01 V 1 2018-02-01 W 1 2015-01-01 W 0 2016-01-01 X 0 2015-01-01 Y 1 2018-01-01 Z 1 2015-01-01 Z 0 2017-01-01
进入范围列表,其中结束日期是项目的下一个条目的StartDate
,或者,如果当前行是最后一个条目,则是今天的日期:
ItemID Valid StartDate EndDate ------ ----- ---------- ---------- A 1 2015-01-01 (today) B 0 2015-01-01 2017-03-01 B 1 2017-03-01 (today) C 1 2015-01-01 2017-04-01 C 0 2017-04-01 (today) D 0 2015-01-01 2017-05-01 D 1 2017-05-01 2017-06-01 D 0 2017-06-01 (today) E 1 2015-01-01 2017-05-01 E 0 2017-05-01 2017-06-01 E 1 2017-06-01 (today) F 1 2015-01-01 2018-02-01 F 0 2018-02-01 (today) G 1 2017-12-31 (today) V 0 2015-01-01 2018-02-01 V 1 2018-02-01 (today) W 1 2015-01-01 2016-01-01 W 0 2016-01-01 (today) X 0 2015-01-01 (today) Y 1 2018-01-01 (today) Z 1 2015-01-01 2017-01-01 Z 0 2017-01-01 (today)
您可以使用LEAD
analytic function来实现:
EndDate = LEAD(StartDate, 1, CAST(CURRENT_TIMESTAMP AS date))
OVER (PARTITION BY ItemID ORDER BY StartDate ASC)
一旦有了范围列表,就可以使用这种确定的相交范围(表中的范围与查询参数中指定的范围相交的范围)的既定方法轻松匹配行:
StartDate < @EndDate AND EndDate > @StartDate
这是完整的解决方案:
DECLARE
@StartDate date = '2017-01-01',
@EndDate date = '2018-01-01',
@ValidValue bit = 1
;
WITH
ranges AS
(
SELECT
ItemID,
Valid,
StartDate,
EndDate = LEAD(StartDate, 1, CAST(CURRENT_TIMESTAMP AS date))
OVER (PARTITION BY ItemID ORDER BY StartDate ASC)
FROM
#Temp
)
SELECT DISTINCT
ItemID
FROM
ranges
WHERE
Valid = @ValidValue
AND StartDate < @EndDate
AND EndDate > @StartDate
;
您可以在this demo at db<>fiddle中使用此方法。
注意:完成我的回答后,我意识到它最终变得非常similar to Sami's。区别在于处理项目的最后条目。
答案 1 :(得分:1)
这是一个解决方案
DECLARE @SD DATE = '2017-01-01',
@ED DATE = '2017-12-31';
WITH BSD AS
(
SELECT *,
LAST_VALUE(Valid) OVER(PARTITION BY ItemID ORDER BY StartDate) LV,
COUNT(1) OVER(PARTITION BY ItemID ORDER BY StartDate DESC) CNT
FROM #Temp
WHERE StartDate <= @SD
)
SELECT ItemID
FROM BSD
WHERE LV = 1 AND CNT = 1
UNION
SELECT ItemID
FROM #Temp
WHERE Valid = 1
AND
StartDate <= @ED
AND
StartDate >= @SD;
答案 2 :(得分:0)
这是我想出的解决方案:
-- Date range includes all of 2017
declare
@beginSearchDate date = '2017-01-01',
@endSearchDate date = '2017-12-31';
with
-- CTE: Existing data combined with current value as of today
a as (
select ItemID, Valid, StartDate
from #Temp
union
select t1.ItemID, t1.Valid, convert(date, getdate())
from (
select ItemID, max(StartDate) as LatestStartDate
from #Temp
group by ItemID
) as t2
inner join #Temp as t1
on t1.ItemID = t2.ItemID
and t1.StartDate = t2.LatestStartDate
),
-- CTE: Current and previous values included in each record
b as (
select a1.*,
lag(a1.Valid) over ( partition by a1.ItemID order by a1.StartDate )
as PrevValid,
lag(a1.StartDate) over ( partition by a1.ItemID order by a1.StartDate )
as PrevStartDate
from a as a1
inner join a as a2
on a1.ItemID = a2.ItemID
and a1.StartDate = a2.StartDate
),
-- CTE: Values as a series of date ranges
c as (
select distinct ItemID,
StartDate as UntilDate,
PrevValid as Valid,
PrevStartDate as FromDate
from b
where PrevValid is not null
)
-- Find all records where date range overlaps
select distinct ItemID
from c
where Valid = 1
and FromDate <= @endSearchDate
and UntilDate > @beginSearchDate
order by ItemID;
结果:
ItemID
------
A
B
C
D
E
F
G
答案 3 :(得分:0)
这是我的滑动。我用有效标记= 1落在结束日期以下任何地方的项目构建第一个表。这将说明项目A或类似项目。
然后我将其匹配到每个项目的最后一个无效日期(如果有的话),然后按日期将其过滤掉。
declare
@beginSearchDate date = '2017-01-01',
@endSearchDate date = '2017-12-31';
;WITH CTE as (
select itemid, VALID, MAX(StartDate) stDate from #temp
where valid <> 0 and StartDate <= @endSearchDate
group by itemID, VALID
)
SELECT t1.ItemID, VALID , stDate
from CTE t1
outer apply (
SELECT ItemID, MAX(StartDate) inValDate from #Temp
where Valid = 0
and StartDate <= @endSearchDate
and ItemID = t1.ItemID GROUP BY ItemID) t2
WHERE t2.inValDate IS NULL
or (t1.stDate > t2.inValDate OR t1.stDate > @beginSearchDate OR t2.inValDate > @beginSearchDate)