【问题标题】:Is there a way to aggregate a variable range of dates in SQL using a SET operation有没有办法使用 SET 操作在 SQL 中聚合可变日期范围
【发布时间】:2016-11-10 10:40:38
【问题描述】:

我有一张这样的桌子......

CREATE TABLE AbsentStudents
(
    Id int not null primary key identity(1,1),
    StudentId int not null,
    AbsentDate datetime not null
)

这是一个非常大的表格,每个学生缺勤的每一天都有 1 行。

我被要求编写一个按日期范围获取学生缺勤的存储过程。使这个查询变得棘手的是我必须通过“缺席事件”进行过滤/聚合。构成“缺勤事件”的天数是一个程序参数,因此可以变化。

因此,例如,我需要获取 2016 年 1 月 1 日至 2016 年 1 月 17 日期间缺勤的学生列表,但前提是他们缺勤时间超过 @Days(2 或 3 天或任何参数要求) ) 天。

我认为只有这一点我才能弄清楚。但是,在该日期范围内,学生可以有多个“缺勤事件”。因此,学生可能在日期范围开始时缺席 3 天,在日期范围中间缺席 2 天,在日期范围结束时缺席 4 天,并且每一个都构成不同的“缺席事件”。假设我的 @Days 参数为 2,则应该为该学生返回 3 行。并且,每个返回的行都应该计算学生在“缺勤事件”中缺勤的天数。

所以我希望我的程序需要 3 个参数(@StartDate datetime、@EndDate datetime、@Days int)并返回类似这样的内容...

StudentId、InitialAbsentDate、ConsecutiveDaysMissed

理想情况下,它会使用 SET 操作并避免使用游标。 (尽管如果这是唯一的选择,游标也可以。)

更新(由 Shnugo)

一个测试场景

DECLARE @AbsentStudents TABLE(
    Id int not null primary key identity(1,1),
    StudentId int not null,
    AbsentDate datetime not null
);
INSERT INTO @AbsentStudents VALUES
--student 1
 (1,{d'2016-10-01'}),(1,{d'2016-10-02'}),(1,{d'2016-10-03'}) --three days 
,(1,{d'2016-10-05'}) --one day
,(1,{d'2016-10-07'}),(1,{d'2016-10-08'}) --two days
--student 2
,(2,{d'2016-10-01'}),(2,{d'2016-10-02'}),(2,{d'2016-10-03'}),(2,{d'2016-10-04'}) --four days
,(2,{d'2016-10-08'}),(2,{d'2016-10-09'}),(2,{d'2016-10-10'}) --three days
,(2,{d'2016-10-12'}); --one day

DECLARE @startDate DATETIME={d'2016-10-01'};
DECLARE @endDate DATETIME={d'2016-10-31'};
DECLARE @Days INT = 3;

【问题讨论】:

  • 看看这篇文章。你需要的是一组连续的日期。 sqlservercentral.com/articles/T-SQL/71550
  • 如果您准备了MCVE,您将对我们有很大帮助。请使用DECLARE @AbsentStudents TABLE...INSERT INTO @AbsentStudents VALUES... 提供可复制的'N'可粘贴样本数据。显示您迄今为止尝试过的内容以及预期的输出。
  • SQL Server 的哪个版本?
  • @Shnugo 对不起,我应该说...SQL12。周末和节假日无关紧要。

标签: sql sql-server tsql aggregate


【解决方案1】:

如果您只想要学生缺席的时间段,您可以使用不同的行号方法来做到这一点。

现在,以下假设天是连续的,没有间隔,并使用行号的差异来获取缺勤时间:

select student_id, 
       min(AbsentDate), 
       max(AbsentDate), 
       count(*) as number_of_days
from (select a.*,
             row_number() over (partition by student_id order by AbsentDate) as seqnum_sa
      from AbsentStudents a
     ) a
group by student_id, 
         dateadd(day, - seqnum_sa, AbsentDate);

注意事项:

  • 您对最短天数和日期范围有其他要求。使用where 子句可以轻松处理这些问题。
  • 我怀疑您有一个隐藏的要求,即避免周末和假期。这个(或其他答案)都没有涵盖这一点。如果这是一个问题,请问另一个问题。

【讨论】:

  • 哇...这完全奏效了!我喜欢简单。不需要避开假期,所以我给你答案。
  • ......而且速度非常快......它在我的几十万数据集上运行不到一秒。天才。
  • @DForck42 。 . .我在使用 SQL 和 Excel 进行数据分析的第 1 章中记录了我的缩进样式。请不要更改我的格式化代码。
  • 但您没有恢复更改,而是保留了大部分更改??
【解决方案2】:

你可以试试这个查询:

SELECT
    StudentId
    , MIN(AbsentDate) AS InitialDate
    , COUNT(*) AS ConsecutiveDaysMissed
FROM (
SELECT 
    dateNumber - ROW_NUMBER() OVER(PARTITION BY StudentId ORDER BY dateNumber) AS PeriodId
    , AbsentDate
    , StudentId
FROM(
        SELECT
            StudentId
            , AbsentDate
            , CAST(CONVERT(CHAR(8), AbsentDate, 112) AS INT) AS dateNumber
        FROM AbsentStudents
        WHERE AbsentDate BETWEEN @StartDate AND @EndDate
    ) AS T
) AS StudentPeriod
GROUP BY StudentID, PeriodId

好吧,您可以制作一个包含日期及其订单号的表格,而无需节假日和周末。然后按日期加入 AbsentStudents 并使用订单号而不是 CAST(CONVERT(CHAR(8), AbsentDate, 112) AS INT) AS dateNumber。

【讨论】:

  • 如果某人在 1 天内有两次不同的缺勤,这将算作两次 - 您需要另一个分组依据和针对 @Days 的过滤器
  • @Hogan 当然,您可以在执行此查询之前和添加过滤器之后使用 distinct by 'AbsentDate' 和 'StudentId' 。您是否建议提供所有约束?
  • 如果周末或假期无关紧要,我喜欢这个答案
  • 周末或节假日无关紧要。我的 ACTUAL 申请是在医疗保健领域,但我想选择比我需要的更通用的东西。
  • @JamieD77 好吧,您可以在没有节假日和周末的情况下制作包含日期及其订单号的表格。然后按日期加入 AbsentStudents 并使用订单号而不是 CAST(CONVERT(CHAR(8), AbsentDate, 112) AS INT) AS dateNumber。
【解决方案3】:

你可以使用一个技巧。如果您按日期排序,则可以通过从最小元素中减去天数并添加一个每行递增 1 的计数器来查找日期组。

SELECT StudentID 
FROM (
  SELECT StudentID, GROUP_NUM, COUNT(*) AS GROUP_DAY_CNT
  FROM (
    SELECT StudentId,
           DATEDIFF(dd,DATEADD(dd,M.Min, ROW_NUMBER() OVER (ORDER BY  AbsetntDate),AbsentDate) as GROUP_NUM
    FROM AbsentStudent
    CROSS JOIN (SELECT MIN(AbsentDate) as Min FROM AbsentStudents WHERE  AbsentDate BETWEEN @StartDate AND @EndDate) M
    WHERE AbsentDate BETWEEN @StartDate AND @EndDate
  ) X
  GROUP BY  StudentID, GROUP_NUM
) Z
WHERE GROUP_DAY_CNT >= @Days

【讨论】:

  • 需要返回 StudentId, InitialAbsentDate, ConsecutiveDaysMissed 我也相信 dateadd 是 dateadd(datapart,number,date)
猜你喜欢
  • 2018-09-02
  • 1970-01-01
  • 1970-01-01
  • 2013-11-19
  • 1970-01-01
  • 2019-07-29
  • 2017-07-20
  • 2014-08-23
相关资源
最近更新 更多