查询在指定日期范围内处于活动状态的记录

时间:2019-01-11 21:45:40

标签: sql-server

我有一个表,该表记录某个项目的某个字段发生更改的时间以及更改的日期。我需要查询数据以查找在请求的日期范围内随时该字段具有特定值的所有项目。

换句话说,如果该项目在数据范围的开始,结束或任何时候都具有该值,则应将其包括在内。

示例数据:

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年有效的所有商品,则查询将包括:

  • A(自2015年有效)
  • B(将于2017年生效)
  • C(有效期至2017年中)
  • D(在2017年有效期为一个月)
  • E(于2017年初和年底有效)
  • F(在2017年全年有效)
  • G(在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问题,它们提出了类似的问题,但并不完全相同:

4 个答案:

答案 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;

Live Demo

答案 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)