SQL 获取最长的日期序列

时间:2022-07-22
本文章向大家介绍SQL 获取最长的日期序列,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

有一张学习打卡表 his_sign 表,简单起见,只设置了两个字段(id,create_ts),一个是主键,另一个是打卡时间。his_sign 表的数据如下,我们要统计出这张表里面最长的连续打卡记录。

    id            create_ts  
------  ---------------------
     1    2020-05-01 09:04:26
     2    2020-05-02 11:54:45
     3    2020-05-04 23:05:03
     4    2020-05-06 07:12:31
     5    2020-05-06 08:01:52
     6    2020-05-07 22:06:48
     7    2020-05-08 12:36:58
     8    2020-05-09 11:49:13
     9    2020-05-12 08:52:35
    10    2020-05-13 23:45:57
    11    2020-05-14 00:02:24
    12    2020-05-14 09:24:18
    13    2020-05-19 15:34:45
    14    2020-05-21 21:10:02

先检查数据,我们发现在一天之内可以多次打卡,因此需要先去掉重复打卡的记录,并将字段 create_ts 使用日期格式展示。

SELECT DISTINCT 
  (DATE(create_ts)) AS create_ts 
FROM
  his_sign

去重并格式化后的数据如下:

create_ts   
------------
2020-05-01  
2020-05-02  
2020-05-04  
2020-05-06  
2020-05-07  
2020-05-08  
2020-05-09  
2020-05-12  
2020-05-13  
2020-05-14  
2020-05-19  
2020-05-21

由于数据量不大,我们观察表数据可知,2020-05-062020-05-09 是最长的序列,总共 4 天。

解题的思路就是把连续的日期编为一组,然后从多组数据中找到数量最多的一组数据,那组数据就是最长的序列。

将表里面的数据按日期的升序排序,并给每个日期分配一个连续的自然数序号,用日期减去它对应的序号,会得到一个新的日期值。我们发现,连续的日期它们对应的新的日期值为同一个,因此,这个新的日期值就是序列的组别。

找到连续日期的组的 SQL 如下:

WITH t1 AS 
(SELECT DISTINCT 
  (DATE(create_ts)) AS create_ts 
FROM
  his_sign),
t2 AS 
(SELECT 
  create_ts,
  row_number () over (
ORDER BY create_ts) AS rn 
FROM
  t1) 
SELECT 
  create_ts,
  DATE_SUB(create_ts, INTERVAL rn DAY) AS grp 
FROM
  t2

上面 SQL 执行后输出的结果:

create_ts   grp         
----------  ------------
2020-05-01  2020-04-30  
2020-05-02  2020-04-30  
2020-05-04  2020-05-01  
2020-05-06  2020-05-02  
2020-05-07  2020-05-02  
2020-05-08  2020-05-02  
2020-05-09  2020-05-02  
2020-05-12  2020-05-04  
2020-05-13  2020-05-04  
2020-05-14  2020-05-04  
2020-05-19  2020-05-08  
2020-05-21  2020-05-09

剩下的操作就简单多了,把数据最多的那组找出来就对了。只是需要注意,最长的序列有可能有多个,因此在找最长的序列的时候需要注意方法。

结合开窗函数 rank() over(ORDER BY xxx) 可以找到多个最长序列,完整的 SQL 如下:

# 1.去掉重复日期,并格式化
WITH t1 AS 
(SELECT DISTINCT 
  (DATE(create_ts)) AS create_ts 
FROM
  his_sign),
# 2.给每个日期指定一个序号
t2 AS 
(SELECT 
  create_ts,
  row_number () over (
ORDER BY create_ts) AS rn 
FROM
  t1),
# 3.找到分组的依据
t3 AS 
(SELECT 
  create_ts,
  DATE_SUB(create_ts, INTERVAL rn DAY) AS grp 
FROM
  t2),
# 4.分组 
t4 AS 
(SELECT 
  MIN(create_ts) AS start_date,
  MAX(create_ts) AS end_date,
  COUNT(*) AS cnt 
FROM
  t3 
GROUP BY grp),
# 5.对所有序列按照长度降序排序
t5 AS 
(SELECT 
  *,
  rank () over (
ORDER BY cnt DESC) AS rk 
FROM
  t4) 
# 6.只选择最长的序列
SELECT 
  start_date,
  end_date,
  cnt 
FROM
  t5 
WHERE rk = 1 

输出:

start_date  end_date       cnt  
----------  ----------  --------
2020-05-06  2020-05-09         4

为了让大家看得更明白,我用 CTE 表达式把每个过程都写出来了。每段表达式都加了注释,理解起来应该不难。

注意,上述的 SQL 需要在 MySQL 8.0 + 环境里才能正常执行。