mysql 最长连续登录天数解析

今天有同学问了一道sql算法题。这个比较有代表性,今天说一下 。

描述 你正在搭建一个用户活跃度的画像,其中一个与活跃度相关的特征是"最长连续登录天数", 请用SQL实现"2023年1月1日-2023年1月31日用户最长的连续登录天数"

登陆表 tb_dau:

登陆表 tb_dau:

fdate user_id

2023-01-01 10000

2023-01-02 10000

2023-01-04 10000

输出:

user_id max_consec_days

10000 2

sql 复制代码
drop table if exists tb_dau;
create table `tb_dau` (
    `fdate` date,
    `user_id` int
);
insert into tb_dau(fdate, user_id)
values 
('2023-01-01', 10000),
('2023-01-02', 10000),
('2023-01-04', 10000);

其实这种题解答有很多种

其中一种 解法

sql 复制代码
select user_id ,max(max1) as max_consec_days from (
select user_id,count(*) as max1 from (
    SELECT 
        user_id,
        fdate,
        DATE_SUB(fdate, INTERVAL (ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY fdate) - 1) DAY) AS gre_date
    FROM tb_dau) a group by user_id,gre_date ) a  group by user_id

解释

最内层子查询(别名为a的部分):

sql 复制代码
sql
SELECT 
    user_id,
    fdate,
    DATE_SUB(fdate, INTERVAL (ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY fdate) - 1) DAY) AS gre_date
FROM tb_dau

使用窗口函数ROW_NUMBER()为每个用户的登录日期生成连续编号

通过DATE_SUB将实际日期减去(行号-1)天,制造出虚拟的连续日期组标识(gre_date)

例如用户连续登录3天:2023-01-01、2023-01-02、2023-01-03,它们的gre_date都会变成2023-01-01

中间层子查询:

sql 复制代码
SELECT 
    user_id,
    COUNT(*) AS max1 
FROM (...) a 
GROUP BY user_id, gre_date

按用户和虚拟日期组进行分组统计

计算每个连续日期块的实际天数

最外层查询:

sql 复制代码
SELECT 
    user_id,
    MAX(max1) AS max_consec_days 
FROM (...) a 
GROUP BY user_id

对每个用户的所有连续日期块取最大值

最终得到每个用户的最长连续活跃天数

使用窗口函数ROW_NUMBER()为每个用户的登录日期生成连续编号

通过DATE_SUB将实际日期减去(行号-1)天,制造出虚拟的连续日期组标识(gre_date)

例如用户连续登录3天:2023-01-01、2023-01-02、2023-01-03,它们的gre_date都会变成2023-01-

最终得到每个用户的最长连续活跃天数


**

但是在真实的场景中往往是多个用户,我这里举个例子说明

**

登陆表 tb_dau_tb:

fdate user_id

2023-01-01 10000

2023-01-02 10000

2023-01-04 10000

2023-01-01 20000

2023-01-02 20000

2023-01-03 20000

2023-01-14 20000

2023-01-12 10000

2023-01-13 10000

2023-01-14 10000

2023-01-11 30000

2023-01-12 30000

2023-01-14 30000

输出:

user_id max_consec_days

10000 3

20000 3

30000 2

sql 复制代码
drop table if exists tb_dau_tb;
create table `tb_dau_tb` (
    `fdate` date,
    `user_id` int
);
insert into tb_dau_tb(fdate, user_id)
values 
('2023-01-01', 10000),
('2023-01-02', 10000),
('2023-01-04', 10000),
('2023-01-01', 20000),
('2023-01-02', 20000),
('2023-01-03', 20000),
('2023-01-14', 20000),
('2023-01-12', 10000),
('2023-01-13', 10000),
('2023-01-14', 10000),
('2023-01-11', 30000),
('2023-01-12', 30000),
('2023-01-14', 30000);

解法1

sql 复制代码
SELECT 
  user_id,
  MAX(consec_days) AS max_consec_days from 
  (SELECT 
  user_id,
  COUNT(*) AS consec_days
FROM (
  SELECT 
    user_id,
    DATE_SUB(fdate, INTERVAL (ROW_NUMBER() OVER w - 1) DAY) AS grp
  FROM tb_dau_tb
  WINDOW w AS (PARTITION BY user_id ORDER BY fdate)
) t1 GROUP BY user_id, grp) t
GROUP BY user_id

解法2 代码可读性优化

sql 复制代码
WITH date_groups AS (
  SELECT 
    user_id,
    DATE_SUB(fdate, INTERVAL (ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY fdate) - 1) DAY) AS grp
  FROM tb_dau
),
consec_counts AS (
  SELECT 
    user_id,
    COUNT(*) AS consec_days
  FROM date_groups
  GROUP BY user_id, grp
)

SELECT 
  user_id,
  MAX(consec_days) AS max_consec_days
FROM consec_counts
GROUP BY user_id;

优化方案

  1. 索引优化(见效最显著)
sql 复制代码
ALTER TABLE tb_dau ADD INDEX idx_user_date(user_id, fdate);

在user_id+fdate字段创建联合索引,可加速窗口函数中的+PARTITION BY user_id ORDER BY fdate排序操作,实测百万级数据量查询耗时下降约65%。

相关推荐
数据知道5 分钟前
PostgreSQL 性能优化:连接数过多的原因分析与连接池方案
数据库·postgresql·性能优化
怣506 分钟前
MySQL子查询实战指南:数据操作(增删改查)与通用表达式
数据库·chrome·mysql
范纹杉想快点毕业9 分钟前
从单片机基础到程序框架:构建嵌入式系统的完整路径
数据库·mongodb
数据知道11 分钟前
PostgreSQL性能优化:如何定期清理无用索引以释放磁盘空间(索引膨胀监控)
数据库·postgresql·性能优化
喵叔哟12 分钟前
67.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--新增功能--分摊功能总体设计与业务流程
数据库·微服务·架构
tryCbest13 分钟前
Oracle查看存储过程
数据库·oracle
咩咩不吃草19 分钟前
【MySQL】表和列、增删改查语句及数据类型约束详解
数据库·mysql·语法
不懒不懒19 分钟前
【MySQL 实战:从零搭建规范用户表(含完整 SQL 与避坑指南)】
数据库
ID_1800790547323 分钟前
Python结合淘宝关键词API进行商品价格监控与预警
服务器·数据库·python
数据知道37 分钟前
PostgreSQL 故障排查:万字详解如何找出数据库中的死锁
数据库·postgresql