【问题标题】:How can I identify groups of consecutive dates in SQL?如何识别 SQL 中的连续日期组?
【发布时间】:2012-07-10 01:29:34
【问题描述】:

我正在尝试编写一个函数来识别日期组并测量组的大小。

到目前为止,我一直在 Python 中按程序执行此操作,但我想将其移至 SQL 中。

例如,列表

Bill 01/01/2011 
Bill 02/01/2011 
Bill 03/01/2011 
Bill 05/01/2011 
Bill 07/01/2011 

应该输出到一个新表中:

Bill 01/01/2011  3 
Bill 02/01/2011  3 
Bill 03/01/2011  3 
Bill 05/01/2011  1 
Bill 07/01/2011  1

理想情况下,这也应该能够考虑周末和公共假期 - 我表中的日期将是周一至周五(我想我可以通过制作一个新的工作日表并按顺序编号来解决这个问题)。工作中有人建议我尝试 CTE。我对此很陌生,所以我很感激任何人可以提供的任何指导!谢谢。

【问题讨论】:

  • 您使用的是什么关系型数据库?SQL 只是语言。
  • 您如何识别假期?
  • 如果您希望能够考虑周末和节假日,保存日期表的想法是可行的方法 - 它通常称为“日历文件”(或“表”)和有一堆的额外用途;除其他外,会计日期(即会计年度期间、期间日期)。如果您的企业还没有,请创建它 - 到处都有示例脚本。
  • 当您说“为周末和公众假期记账”时,这是什么意思?这是否意味着它们不被计算为“连续”天数(即允许间隔)?或者那些日子应该会自动产生差距?

标签: sql date gaps-and-islands


【解决方案1】:

您可以通过巧妙地应用窗口函数来做到这一点。考虑以下几点:

select name, date, row_number() over (partition by name order by date)
from t

这会添加一个行号,在您的示例中,它只是 1、2、3、4、5。现在,取日期的差值,您就有了该组的常量值。

select name, date,
       dateadd(d, - row_number() over (partition by name order by date), date) as val
from t

最后,您需要按顺序排列的组数。我还会添加一个组标识符(例如,以区分最后两个)。

select name, date,
       count(*) over (partition by name, val) as NumInSeq,
       dense_rank() over (partition by name order by val) as SeqID
from (select name, date,
             dateadd(d, - row_number() over (partition by name order by date), date) as val
      from t
     ) t

不知何故,我错过了关于工作日和节假日的部分。此解决方案不能解决该问题。

【讨论】:

  • 不错的解决方案 - 您可能想要处理重复的日期,尽管对此的要求并不明确。
  • 如果您想问另一个关于重复日期的问题,我很乐意回答。这不是原始问题的一部分。
【解决方案2】:

以下查询帐号周末和节假日。该查询提供了即时包含假期的规定,但为了使查询更清晰,我只是将假期具体化到一个实际的表中。

CREATE TABLE tx
    (n varchar(4), d date);

INSERT INTO tx
    (n, d)
VALUES
    ('Bill', '2006-12-29'), -- Friday    
    -- 2006-12-30 is Saturday
    -- 2006-12-31 is Sunday

    -- 2007-01-01 is New Year's Holiday    
    ('Bill', '2007-01-02'), -- Tuesday
    ('Bill', '2007-01-03'), -- Wednesday
    ('Bill', '2007-01-04'), -- Thursday
    ('Bill', '2007-01-05'), -- Friday    
    -- 2007-01-06 is Saturday
    -- 2007-01-07 is Sunday

    ('Bill', '2007-01-08'), -- Monday
    ('Bill', '2007-01-09'), -- Tuesday

    ('Bill', '2012-07-09'), -- Monday
    ('Bill', '2012-07-10'), -- Tuesday
    ('Bill', '2012-07-11'); -- Wednesday

create table holiday(d date);
insert into holiday(d) values
('2007-01-01');


/* query should return 7 consecutive good 
   attendance(from December 29 2006 to January 9 2007) */    
/* and 3 consecutive attendance from July 7 2012 to July 11 2012. */

查询:

with first_date as
(
    -- get the monday of the earliest date
    select dateadd( ww, datediff(ww,0,min(d)), 0 ) as first_date 
    from tx 
)
,shifted as
(
    select 
        tx.n, tx.d,                     
        diff = datediff(day, fd.first_date, tx.d) 
                   - (datediff(day, fd.first_date, tx.d)/7 * 2)             
    from tx
    cross join first_date fd
    union
    select 
        xxx.n, h.d,             
        diff = datediff(day, fd.first_date, h.d) 
                   - (datediff(day, fd.first_date, h.d)/7 * 2) 
    from holiday h 
    cross join first_date fd
    cross join (select distinct n from tx) as xxx
)
,grouped as
(
    select *, grp = diff - row_number() over(partition by n order by d)
    from shifted
)
select 
    d, n, dense_rank() over (partition by n order by grp) as nth_streak
    ,count(*) over (partition by n, grp) as streak
from grouped
where d not in (select d from holiday)  -- remove the holidays

输出:

|          D |    N | NTH_STREAK | STREAK |
-------------------------------------------
| 2006-12-29 | Bill |          1 |      7 |
| 2007-01-02 | Bill |          1 |      7 |
| 2007-01-03 | Bill |          1 |      7 |
| 2007-01-04 | Bill |          1 |      7 |
| 2007-01-05 | Bill |          1 |      7 |
| 2007-01-08 | Bill |          1 |      7 |
| 2007-01-09 | Bill |          1 |      7 |
| 2012-07-09 | Bill |          2 |      3 |
| 2012-07-10 | Bill |          2 |      3 |
| 2012-07-11 | Bill |          2 |      3 |

现场测试:http://www.sqlfiddle.com/#!3/815c5/1

查询的主要逻辑是将所有日期向后移动两天。这是通过将日期除以 7 并乘以 2,然后从原始数字中减去它来完成的。例如,如果给定日期是 15 日,这将计算为 15/7 * 2 == 4;然后从原始数字中减去 4,15 - 4 == 11。15 将成为第 11 天。同样,第 8 天变成第 6 天; 8 - (8/7 * 2) == 6。

                  Weekends are not in attendance(e.g. 6,7,13,14)
 1  2  3  4  5     6  7
 8  9 10 11 12    13 14
15

将计算应用于所有工作日数字将产生这些值:

 1  2  3  4  5    
 6  7  8  9 10    
11

对于节假日,您需要在出勤时安排他们,因此可以轻松确定连续性,然后将其从最终查询中删除。上述出勤率产生连续 11 次良好出勤率。

查询逻辑的详细解释在这里:http://www.ienablemuch.com/2012/07/monitoring-perfect-attendance.html

【讨论】:

  • 这很容易成为我在互联网上读过的最美的东西。谢谢迈克尔。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-08-19
  • 1970-01-01
  • 2021-12-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多