最近多次看到用SQL查询连续打卡信息问题,自己也实践一波。抛开问题本身,也是对MySQL窗口函数和自定义变量用法的一种练习。

01 建表 所用数据库为MySQL8.0,简单而不失一般性,建立一个仅有记录id、用户id、日期和打卡标记共4个字段的数据表。建表语句为:

1CREATE TABLE `testd` (

2  `id` int NOT NULL AUTO_INCREMENT,

3  `userid` int NOT NULL,

4  `dday` date DEFAULT(CURRENT_DATE),

5  `flag` tinyint(1) DEFAULT(0),

6  PRIMARY KEY (`id`)

7) ENGINE=InnoDB

为使查询更具一般性,设计数据表中有两个用户、日期存在跨月、且可能存在日期不连续的情形(虽然实际中可能并不存在这样的情况),插入如下数据:

1INSERT INTO `testd`(`id`, `userid`, `dday`, `flag`) VALUES (1, 1, '2020-03-31', 1);

2INSERT INTO `testd`(`id`, `userid`, `dday`, `flag`) VALUES (2, 1, '2020-04-01', 0);

3INSERT INTO `testd`(`id`, `userid`, `dday`, `flag`) VALUES (3, 1, '2020-04-02', 1);

4INSERT INTO `testd`(`id`, `userid`, `dday`, `flag`) VALUES (4, 1, '2020-04-03', 1);

5INSERT INTO `testd`(`id`, `userid`, `dday`, `flag`) VALUES (5, 1, '2020-04-04', 1);

6INSERT INTO `testd`(`id`, `userid`, `dday`, `flag`) VALUES (6, 1, '2020-04-05', 1);

7INSERT INTO `testd`(`id`, `userid`, `dday`, `flag`) VALUES (7, 1, '2020-04-07', 0);

8INSERT INTO `testd`(`id`, `userid`, `dday`, `flag`) VALUES (8, 1, '2020-04-08', 1);

9INSERT INTO `testd`(`id`, `userid`, `dday`, `flag`) VALUES (9, 1, '2020-04-09', 1);

10INSERT INTO `testd`(`id`, `userid`, `dday`, `flag`) VALUES (10, 1, '2020-04-10', 1);

11INSERT INTO `testd`(`id`, `userid`, `dday`, `flag`) VALUES (11, 1, '2020-04-11', 0);

12INSERT INTO `testd`(`id`, `userid`, `dday`, `flag`) VALUES (12, 2, '2020-03-31', 0);

13INSERT INTO `testd`(`id`, `userid`, `dday`, `flag`) VALUES (13, 2, '2020-04-01', 1);

14INSERT INTO `testd`(`id`, `userid`, `dday`, `flag`) VALUES (14, 2, '2020-04-02', 1);

15INSERT INTO `testd`(`id`, `userid`, `dday`, `flag`) VALUES (15, 2, '2020-04-03', 1);

16INSERT INTO `testd`(`id`, `userid`, `dday`, `flag`) VALUES (16, 2, '2020-04-04', 1);

17INSERT INTO `testd`(`id`, `userid`, `dday`, `flag`) VALUES (17, 2, '2020-04-05', 0);

18INSERT INTO `testd`(`id`, `userid`, `dday`, `flag`) VALUES (18, 2, '2020-04-07', 0);

19INSERT INTO `testd`(`id`, `userid`, `dday`, `flag`) VALUES (19, 2, '2020-04-08', 1);

20INSERT INTO `testd`(`id`, `userid`, `dday`, `flag`) VALUES (20, 2, '2020-04-09', 1);

21INSERT INTO `testd`(`id`, `userid`, `dday`, `flag`) VALUES (21, 2, '2020-04-10', 1);

22INSERT INTO `testd`(`id`, `userid`, `dday`, `flag`) VALUES (22, 2, '2020-04-11', 1);

查询目标是获得用户的连续打卡天数,包括最近连续打卡信息、历史最长打卡信息和所有连续打卡信息。 02 分析 对于这种类型的需求,个人认为应该属于滑动窗口问题,即满足同一取值的最大窗口长度(打卡情况的窗口长度),MySQL自从8.0版本开始,提供了常用的窗口函数用法,像之前的3种排名(参考一文解决所有MySQL分类排名问题)问题在8.0中运用窗口函数就非常简单。

MySQL8.0支持窗口函数 但对于这个具体需求,似乎现有窗口函数并不能直接得到答案,所以我们考虑退而求其次——采用自定义变量的方法曲线求解。 为简单起见,我们采取先单用户再多用户的思路逐步分析。

02 单用户打卡查询 单用户情况下,求解连续打卡信息意味着,在对日期顺序排序的基础上:

如果用户今天打卡:

如果昨天也打卡,则今天连续打卡天数是在昨天基础上+1

如果昨天未打卡,则连续打卡天数从1开始,计数1

如果未打卡,则记连续打卡天数为0

进一步地,我们发现在在定义用户未打卡时打卡天数=0的基础上,当用户打卡时无论前一天是否打卡,其打卡天数均为前一天+1(即要么是0+1,要么是N+1)

进而,可以写出如下SQL语句:

1SELECT

2    userid, dday, flag, @pre_check := IF(flag, @pre_check + 1, 0) AS 'check_days'

3FROM

4    testd, (SELECT @pre_check := 0 ) init

5WHERE

6    userid = 1

7ORDER BY

8    dday

其中限定userid=1是为了仅考虑单用户情形,自定义变量@pre_check表示前一天打卡天数, init子表用于初始化变量@pre_check=0。得到查询结果:

单用户连续打卡信息

得到这个结果,那么对于单用户时求其打卡信息就简单多了,例如求其最近连续打卡天数,则用如下SQL:

1SELECT

2    userid, dday, check_days

3FROM

4    (

5        SELECT

6            userid, dday, flag, @pre_check := IF( flag, @pre_check + 1, 0 ) AS 'check_days'

7        FROM

8            testd, (SELECT @pre_check := 0 ) init

9        WHERE

10            userid = 1

11        ORDER BY

12            dday

13    ) tmp

14WHERE

15    check_days > 0

16ORDER BY

17    dday DESC

18LIMIT 1

实际上就是在前一步得到的衍生表基础上,筛选打卡天数大于0的日期按降序排列,取出最近一条记录即为最近的连续打卡日期。筛选条件改成大于7就是最近的连续7天打卡的日期。得到查询结果:

单用户最近连续打卡信息 那么如果要查询历史打卡天数最长的日期呢?只需要按打卡天数降序排列即可:

1SELECT

2    userid, dday, check_days

3FROM

4    (

5        SELECT

6            userid, dday, flag, @pre_check := IF( flag, @pre_check + 1, 0 ) AS 'check_days'

7        FROM

8            testd, (SELECT @pre_check := 0 ) init

9        WHERE

10            userid = 1

11        ORDER BY

12            dday

13    ) tmp

14ORDER BY

15    check_days DESC

16LIMIT 1

得到结果:

单用户历史最长打卡信息

03 多用户打卡查询

在单用户打卡查询的基础上,其实多用户打卡查询的思路是一致的,只不过为了区分用户维度,需要再增加一个自定义变量。对用户和日期进行排序,而后采取以下逻辑:

如果当前记录的用户与上一个用户相同:

如果该用户当天打卡,则其打卡天数是前一天打卡天数+1

否则,即当天未打卡,则打卡天数为0

如果当前记录用户是新用户:

如果打卡,则打卡计数为1

否则,计数为0

基于以上思路,可写出基本的SQL语句如下:

1SELECT

2    userid, dday, flag,

3    @pre_check := IF(userid = @pre_userid, IF(flag, @pre_check + 1, 0), IF(flag, 1, 0)) AS 'check_days',

4    @pre_userid := userid AS 'Pre_user'

5FROM

6    testd, (SELECT @pre_check := 0, @pre_userid := null ) init

7ORDER BY

8    userid, dday

其中增加了一个自定义变量@preuser,表示当前行的前一条记录用户信息。得到查询结果:

多用户连续打卡信息

在获得各用户连续打卡天数信息后,如果是查询各用户最近连续打卡天数,则可依据用户进行分组后查询该用户最近连续打卡天数大于0的信息(为表述简单,记前面查询到的衍生表为tmp表):

1SELECT

2    userid, dday, check_days

3FROM

4    tmp

5WHERE

6    (userid, dday) in ( SELECT userid, max(dday)

7                        FROM tmp

8                        WHERE check_days>

9                        GROUP BY userid   )

得到查询结果:

多用户最近连续打卡信息

类似的,如果要查询各用户的历史最长连续打卡信息,依然采取类似思路,有:

1SELECT

2    userid, dday, check_days

3FROM

4    tmp

5WHERE

6    (userid, check_days) in ( SELECT userid, max(check_days)

7                              FROM tmp

8                              GROUP BY userid )

得到查询结果:

多用户历史最长连续打卡信息 其中,注意到用户2有两次历史连续打卡天数为4的记录,且都是该用户最长打卡记录。

04 各用户所有连续打卡信息

以上是查询了各用户1次特定的打卡信息(要么是最近,要么是历史最长),如果要查询各用户所有连续打卡信息呢?例如,某用户在’2020-04-01’至’2020-04-04’连续4天打卡、在’2020-04-06’至’2020-04-10’连续5天打卡,则最终显示的2020-04-04的4天和2020-04-10的6天两条信息。 实际上,在以上查询的基础上,这样的查询就是在多用户连续打卡信息表(03部分第一张结果)中筛选出其后一天打卡为0的记录。也就是说,如果当前记录的下一天仍然是连续打卡,那么当前记录不作为最终结果;如果下一天打卡为0,才是最终想提取的信息。

为了实现这一需求,如果是8.0版本,可直接借助窗口函数lead()进行判断。例如:

1SELECT

2    userid, dday, check_days,

3    lead(flag) over(partition by userid) as 'nxt_flag'

4FROM

5    (SELECT

6        userid, dday, flag,

7        @pre_check := IF(userid = @pre_userid, IF(flag, @pre_check + 1, 0), IF(flag, 1, 0)) AS 'check_days',

8        @pre_userid := userid AS 'Pre_user'

9    FROM

10        testd, (SELECT @pre_check := 0, @pre_userid := null ) init

11    ORDER BY

12        userid, dday )tmp

得到结果:

带次日打卡信息的多用户连续打卡记录

基于此衍生表,进一步查出次日当日连续打卡>0且次日打卡为0或者为null的记录(null代表当前是最后一天)即可。其SQL语句:

1SELECT userid, dday, check_days

2FROM

3    (

4        SELECT

5            userid, dday, check_days,

6            lead(flag) over(partition by userid) as 'nxt_flag'

7        FROM

8            (

9                SELECT

10                    userid, dday, flag,

11                    @pre_check := IF(userid = @pre_userid, IF(flag, @pre_check + 1, 0), IF(flag, 1, 0)) AS 'check_days',

12                    @pre_userid := userid AS 'Pre_user'

13                FROM

14                    testd, (SELECT @pre_check := 0, @pre_userid := null ) init

15                ORDER BY

16                    userid, dday

17            ) tmp

18    ) tt

19WHERE check_days and (nxt_flag is null or nxt_flag=0)

最终,得到查询结果:

各用户连续打卡记录 当然,如果是MySQL8.0以下版本,是没有lead()窗口函数可以直接调用的,次此时可借助连接查询或者子查询,设定连接条件是表1和表2用户相同、日期相差为1即可。

05 总结

本文对MySQL中查询用户连续打卡这一问题进行了分析,主要是基于自定义变量的方式,实现了以下问题:

查询各用户每天的连续打卡信息(包括未打卡时记为0)

查询各用户最近连续打卡信息

查询各用户历史最长打卡信息

查询各用户所有打卡记录信息

mysql 连续打卡_MySQL查询连续打卡信息?相关推荐

  1. mysql连续打卡次数_MySQL查询连续打卡信息?

    导读:最近多次看到用SQL查询连续打卡信息问题,自己也实践一波.抛开问题本身,也是对MySQL窗口函数和自定义变量用法的一种练习. 00 建表 所用数据库为MySQL8.0,简单而不失一般性,建立一个 ...

  2. mysql 连续日期统计_MYSQL -- 计算连续日期天数

    一.题目 -- 查询产品出现连续订单情况 二.数据 -- 自建表名:test,字段名:goodid(产品id),date(日期) 三.解题思路 1.观察发现,产品a有连续4天,产品b有一个3天,一个4 ...

  3. mysql 30天销量_mysql查询今天,昨天,近7天,近30天,本月,上一月数据方法

    下面是编程之家 jb51.cc 通过网络收集整理的代码片段. 编程之家小编现在分享给大家,也给大家做个参考. 话说有一文章表article,存储文章的添加文章的时间是add_time字段,该字段为in ...

  4. mysql 一周一月_mysql查询当天、本周,本月,上一个月的数据

    今天 select * from 表名 where to_days(时间字段名) = to_days(now()); 昨天 SELECT * FROM 表名 WHERE TO_DAYS( NOW( ) ...

  5. mysql 8.0 直方图_MySQL 8.0 中统计信息直方图的尝试

    直方图是表上某个字段在按照一定百分比和规律采样后的数据分布的一种描述,最重要的作用之一就是根据查询条件,预估符合条件的数据量,为sql执行计划的生成提供重要的依据 在MySQL 8.0之前的版本中,M ...

  6. mysql更新锁机制_mysql查询更新时的锁表机制分析

    欢迎进入Linux社区论坛,与200万技术人员互动交流 >>进入 为了给高并发情况下的mysql进行更好的优化,有必要了解一下mysql查询更新时的锁表机制. 一.概述 MySQL有三种锁 ...

  7. mysql 服务器 kill进程_mysql查询结束进程kill

    ### 首先登陆mysql shell $ mysql -u root -h 127.0.0.1 -p $密码输入 ### 查看mysql中现在执行的 所有线程 mysql>show proce ...

  8. mysql 查找课程最高分_mysql 查询 学生id最高分的科目和日期

    mysql>select*fromstudent;+------+---------+------+---------+|id|subject|mark|date|+------+------- ...

  9. mysql md5版本校验_MySQL查询以名称的md5版本更新所有条目?

    为此,您可以使用MD5().让我们首先创建一个表-mysql> create table DemoTable1887 ( Password text, HashPassword text ); ...

最新文章

  1. 在Java中使用final关键字可以提高性能吗?
  2. Docker安装Hadoop
  3. 《程序是怎样跑起来的》第二章
  4. 简单,方便,功能全的php分页类
  5. VS插件的开发 - Visual Studio Addin
  6. Spring Boot MyBatis配置多种数据库
  7. java boolean 多线程_JAVA多线程两个实用的辅助类(CountDownLatch和AtomicBoolean)
  8. 程序员面试题100题第14题-圆圈中最后剩下的数字
  9. EXCEL【数据处理之数据抽取——随机抽样】
  10. IDEA Maven遇到的问题 wating for maven import completionomitted for duplicate jar
  11. 吸拖一体机和扫地机器人哪个好,吸拖一体机值得买吗
  12. 软件开发团队成员分工_分析软件开发人员的能力–选择合适的团队成员
  13. docker使用dockerfile方式运行java程序
  14. 企业微信api接口调用-企业微信好友收发消息
  15. CSDN日报191105:2019年11月全国程序员工资统计,区块链工程师比算法工资高
  16. Google Maps API licensing
  17. 计算机中倒v符号,电脑倒过来的问号怎么打?特殊符号倒问号输入的方法
  18. 公元前,公元后纪年区别
  19. vue three.js3d效果
  20. 在ParaView中计算圆柱绕流算例里圆柱所受的升阻力

热门文章

  1. 两个div实现十字架
  2. 谨赠20篇技术热文营造一个不一样的节日气氛!
  3. tomcat 中部署的应用响应json数据乱码解决办法
  4. 使用 Swift 语言编程的优缺点
  5. xiuno论坛部署及常见问题处理
  6. 【XiunoBBS】开源简易论坛学习
  7. 十大主流小说平台畅销榜TOP1:诡秘、剑来、元尊、赘婿流风云争霸
  8. 自己实现Lock(独享锁)
  9. web.config与mysql的连接 appsettings_Web.config配置文件中定义了如下数据库连接串
  10. MOOS-ivp 实验四 MOOS编程入门(1)