横扫SQL面试——连续性登录问题

横扫SQL面试

📌 连续性登录问题

在这里插入图片描述

在互联网公司的SQL面试中,连续性问题堪称“必考之王”。💻🔍

用户连续登录7天送优惠券🌟服务器连续报警3次触发熔断⚠️图书馆连续3天人流破百开启限流⚡” …

既考察你对窗口函数的灵活运用,又考验你能否将业务场景抽象为数学模型。


博主总结一些经典题型,帮列位小伙伴拿下这类题目🤣 🤣 🤣 🤣


Tips:
暴力解法(如自连接、逐行遍历)在数据量小时勉强可用,但面对百万级📈数据时:

  • 性能灾难:自连接时间复杂度达O(n²),1万行数据需1亿次计算 🔥
  • 逻辑漏洞:简单lag/lead无法处理连续多天的复杂中断

而真正的工业级解法,只需一行窗口函数 + 虚拟分组标记,就能以O(n)时间复杂度解决问题! 🚀

🌟 连续问题通用解法框架

步骤核心操作 🔑适用场景
生成连续标记date - row_number() over(...)映射连续日期到同一虚拟组
分组统计group by 虚拟组标记计算连续天数/次数
结果筛选having count(*) >= N过滤满足条件的连续事件

话不多说——直接上题:🎈🎈🎈🎈🎈🎈🎈


🎯1. 最长连续登录天数

你正在搭建一个用户活跃度的画像,其中一个与活跃度相关的特征是“最长连续登录天数”

表名字段名描述数据类型
tb_daufdate登录日期DATE
user_id用户唯一标识INT

计算用户在指定时间段内的最长连续登录天数。例如:统计用户2023年1月的最长连续登录记录。

fdateuser_id
2023-01-0110000
2023-01-0210000
2023-01-0410000

预期结果🔑

user_idmax_consec_days
100002

博主按照解题框架 一步一步带大家看哈~🤣🤣🤣

步骤1:生成连续标记(CTE t1)🧩

为每个用户的登录日期生成序号,标记连续登录的潜在分组。

with t1 as (selectuser_id,fdate,-- 默认大家都是有基础的哈 窗口函数应该都会哈row_number() over(partition by user_id order by fdate) as rnfrom tb_dauwhere fdate between '2023-01-01' and '2023-01-31'
)

临时表 t1:✅

user_idfdatern
100002023-01-011
100002023-01-022
100002023-01-043

在这里插入图片描述

  • rn 表示用户按日期排序后的登录次数序号。
  • 连续日期的 rn 差值等于日期差值(例如:2023-01-02 是第2次登录,日期差为1天)。

步骤2:计算虚拟起始点(CTE t2)🧩

通过 date_sub(fdate, interval rn day) 将连续日期映射到同一虚拟起始点。

t2 as (selectuser_id,fdate,date_sub(fdate, interval rn day) as start_datefrom t1
)

临时表 t2:✅

user_idfdatestart_date
100002023-01-012022-12-31
100002023-01-022022-12-31
100002023-01-042023-01-01

在这里插入图片描述

  • 连续日期的 start_date 相同(如1月1日和1月2日均映射到2022-12-31)。
  • 非连续日期的 start_date 不同(如1月4日映射到2023-01-01)。

步骤3:统计连续天数(CTE t3)🧩

按用户和虚拟起始点分组,统计每组中的记录数(即连续天数)。

t3 as (selectuser_id,start_date,count(*) as cntfrom t2group by user_id, start_date
)

临时表 t3:✅

user_idstart_datecnt
100002022-12-312
100002023-01-011

在这里插入图片描述

  • cnt 表示每个虚拟起始点对应的连续登录天数。
  • 用户10000有两个连续区间:2天和1天。

最终结果取每个用户的最大连续天数。🧩
selectuser_id,max(cnt) as max_consec_days
from t3
group by user_id;

输出结果:✅

在这里插入图片描述

user_idmax_consec_days
100002
技术本质🧩

通过 date_sub(fdate, interval rn day),将连续日期的差值抵消,映射到同一虚拟起始点:

  • 连续日期fdate - rn 恒定✨(如 1月1日-1天=12月31日,1月2日-2天=12月31日)。
  • 非连续日期fdate - rn 不同(如1月4日-3天=1月1日)。

将连续性问题转化为分组计数问题,时间复杂度仅为 O(n)。✨

完整代码 ~

-- 定义第一个公共表表达式 (CTE) t1,用于计算每个用户登录日期的排序
with t1 as (selectuser_id,  -- 用户IDfdate,    -- 登录日期row_number() over(partition by user_id order by fdate) as rn  -- 为每个用户的登录日期生成排序编号from tb_dauwhere fdate between '2023-01-01' and '2023-01-31'  -- 选择指定日期范围内的记录
), -- 定义第二个公共表表达式 (CTE) t2,用于计算每个登录日期的起始日期
t2 as (selectuser_id,  -- 用户IDfdate,    -- 登录日期date_sub(fdate, interval rn day) as start_date  -- 计算起始日期:将登录日期减去排序编号天数from t1
), -- 定义第三个公共表表达式 (CTE) t3,用于计算每个用户在相同起始日期下的连续登录天数
t3 as (selectuser_id,  -- 用户IDcount(*) as cnt  -- 计算连续登录天数from t2group by user_id, start_date  -- 按用户和起始日期分组
)-- 从 t3 表中选择用户ID和其最大连续登录天数
selectuser_id,  -- 用户IDmax(cnt) as max_consec_days  -- 最大连续登录天数
from t3
group by user_id;  -- 按用户ID分组

🎯2. 连续出现的数字

从数字序列中找出至少连续出现3次的数字。例如:[1, 1, 1, 2, 2, 3] 中,1 连续出现3次。

表名字段名描述数据类型
logsid记录序号INT
num数字值INT
idnum
11
21
31
42
52
63
ConsecutiveNums
1

这题同理~连续性问题解法框架:
1.💡 标记连续性:使用 row_number() 生成序号。
2. 🔍生成虚拟组:通过差值(如 id - rn)抵消连续增量。
3. 🛠️分组统计:按虚拟组聚合,筛选满足条件的结果。

步骤1:生成连续标记(CTE t1)🚀

为每个数字按 id 排序生成行号,标记连续出现的潜在分组。

with t1 as (selectnum,id,row_number() over(partition by num order by id) as rnfrom logs
)

临时表 t1:✅

numidrn
111
122
133
241
252
361

在这里插入图片描述

  • rn 表示相同数字(num)按 id 排序后的出现次数序号。
  • 连续相同数字的 idrn 的差值恒定(例如:num=1 时,id - rn = 0)。

步骤2:计算虚拟分组标记(CTE t2)🚀

通过 id - rn 生成分组标记 group_id,将连续相同数字映射到同一虚拟组。

t2 as (selectnum,id - rn as group_idfrom t1
)

临时表 t2:✅

numgroup_id
10
10
10
23
23
35

在这里插入图片描述

  • 连续相同数字的 group_id 相同(如 num=1 的3条记录均为 group_id=0)。
  • 非连续或不同数字的 group_id 不同(如 num=2num=3)。

步骤3:统计连续出现次数(最终查询)🚀

numgroup_id 分组,筛选出出现次数≥3的组,并去重输出结果。

select distinct num as ConsecutiveNums
from t2
group by num, group_id
having count(*) >= 3;

分组统计结果:✅

numgroup_idcount(*)
103
232
351

在这里插入图片描述

最终输出:✅

ConsecutiveNums
1

技术本质🚀

通过 id - row_number(),将连续相同数字的差值抵消,映射到同一虚拟分组:

  • 连续相同数字id - rn 恒定🔥(如 num=1 时,id=1,2,31-1=0, 2-2=0, 3-3=0)。
  • 非连续或不同数字id - rn 不同(如 num=2 时,id=4,54-1=3, 5-2=3,但次数不足)。

如果题目要求连续出现4次,只需修改 having 条件:

having count(*) >= 4  -- 筛选连续出现4次的数字

🎯3. 新注册用户连续登录不少于3天

筛选出新注册用户在注册后至少连续登录3天的用户列表。例如:用户注册后连续登录了2023-01-01、01-02、01-03三天。

表名字段名描述数据类型
tb_usersuser_id用户唯一标识INT
reg_date用户注册日期DATE
tb_loginuser_id用户唯一标识INT
login_date用户登录日期DATE

用户表 (tb_users)✅

user_idreg_date
100012023-01-01
100022023-01-05

登录表 (tb_login)✅

user_idlogin_date
100012023-01-01
100012023-01-02
100012023-01-03
100022023-01-05
100022023-01-06

预期结果

user_id
10001
步骤1:关联用户与登录数据(CTE login_sequence)✅

筛选注册后7天内的登录记录,并为每个用户的登录日期生成行号。

with login_sequence as (selectu.user_id,l.login_date,-- 为每个用户的登录日期生成行号(按日期排序)row_number() over(partition by u.user_id order by l.login_date) as rnfrom tb_users ujoin tb_login l on u.user_id = l.user_idand l.login_date between u.reg_date and u.reg_date + interval 7 day
)

临时表 login_sequence:✅

user_idlogin_datern
100012023-01-011
100012023-01-022
100012023-01-033
100022023-01-051
100022023-01-062
  • rn 表示用户按登录日期排序后的连续次数。
  • login_date between reg_date and reg_date + 7 day 限定注册后7天内的登录行为。

限定用户注册后7天内的登录行为,聚焦新用户关键活跃期,数据进入窗口函数前剔除无效数据,避免对全量数据排序。聚焦核心业务目标(如新用户激活率、首周留存率)


步骤2:生成虚拟分组标记(CTE consec_groups)🚀

计算 login_date - rn,将连续日期映射到同一虚拟起始点。

consec_groups as (selectuser_id,login_date,-- 计算虚拟分组标记(连续日期的差值为0)date_sub(login_date, interval rn day) as group_idfrom login_sequence
)

临时表 consec_groups:✅

user_idlogin_dategroup_id
100012023-01-012022-12-31
100012023-01-022022-12-31
100012023-01-032022-12-31
100022023-01-052023-01-04
100022023-01-062023-01-04
  • 连续登录的日期差值相同(如用户10001的3次登录均映射到 2022-12-31)。🚀🚀🚀
  • 非连续登录的日期差值不同(如用户10002的2次登录映射到 2023-01-04)。

步骤3:统计连续登录天数(最终查询)

按用户和虚拟分组标记统计连续天数,筛选≥3天的用户。

distinct user_id 确保用户多次满足条件时只输出一次。

select distinct user_id
from consec_groups
group by user_id, group_id
having count(*) >= 3;

分组统计结果:✅

user_idgroup_idcount(*)
100012022-12-313
100022023-01-042

最终输出:✅

user_id
10001

💡 关键逻辑

虚拟分组标记
date_sub(login_date, interval rn day) 将连续日期映射到同一虚拟起始点,本质是公式:
连续天数 = 最大登录日期 - 最小登录日期 + 1 (若连续,则 login_date - rn 恒定)


🎯4. 图书馆高峰期检测

找出图书馆连续3天及以上人流量≥100的高峰时段。例如:2023-01-02至2023-01-04连续三天人流量达标。

表名字段名描述数据类型
infodate日期DATE
people人流量INT
datepeople
2023-01-0170
2023-01-02100
2023-01-03120
2023-01-04120
2023-01-0590

预期结果

start_dateend_dateconsecutive_days
2023-01-022023-01-043

后面博主就不再啰嗦啦 大家可以发现 套路是不是都一样~🤣🤣🤣 “标记→分组→过滤”✅✅✅

with valid_days as (select date,date - row_number() over(order by date) as grpfrom infowhere people >= 100
),
consec_groups as (selectmin(date) as start_date,max(date) as end_date,count(*) as consecutive_daysfrom valid_daysgroup by grphaving count(*) >= 3
)
select * from consec_groups;
  1. 筛选有效日期:过滤人流量≥100的天数。
  2. 生成连续组标记date - row_number() 将连续有效日期映射到同一组。
  3. 统计连续时段:按组统计起止日期和持续天数。

🎯5. 用户指标检测

从订单表中筛选出连续三天及以上每天总下单金额均超过100元的用户。例如:用户A在2023-01-01至2023-01-03每天的总消费分别为120元、150元、110元,满足条件。

表名字段名描述数据类型
order_tableid订单编号INT
dt下单日期DATE
amount订单金额INT
iddtamount
10012021-12-12123
10022021-12-1245
10012021-12-1343
10012021-12-1345
10012021-12-14230

预期结果

user_id
1001
-- 步骤1:按用户和日期汇总金额,过滤每天金额>100的记录
with daily_summary as (selectid as user_id,dt,sum(amount) as total_amountfrom order_tablegroup by user_id, dthaving sum(amount) > 100
),
-- 步骤2:生成连续标记
sequence_marker as (selectuser_id,dt,date_sub(dt, interval row_number() over(partition by user_id order by dt) day) as grpfrom daily_summary
),
-- 步骤3:统计连续天数
consec_groups as (selectuser_id,grp,count(*) as consec_days,min(dt) as start_date,max(dt) as end_datefrom sequence_markergroup by user_id, grphaving count(*) >= 3  -- 连续3天及以上
)
-- 步骤4:输出结果
select distinct user_id
from consec_groups;
  1. 按天汇总金额group by user_id, dt 处理一天多笔订单。
  2. 生成虚拟分组date_sub(dt, interval row_number() day) 将连续日期映射到同一虚拟组。
  3. 统计连续天数:筛选连续≥3天的用户。

🎯6. 用户最大连续缴费次数

计算每个用户的最长连续缴费天数。例如:用户U002在2023-01-03至2023-01-05连续缴费3天,结果为3。

表名字段名描述数据类型
payment_loguser_id用户唯一标识VARCHAR
pay_date缴费日期DATE
amount缴费金额INT
user_idpay_dateamount
U0012023-01-01100
U0012023-01-02200
U0012023-01-04150
U0022023-01-0380
U0022023-01-0490
U0022023-01-05120

预期结果

user_idmax_consec_days
U0012
U0023

with payment_sequence as (selectuser_id,pay_date,date_sub(pay_date, interval row_number() over(partition by user_id order by pay_date) day) as grpfrom payment_log
),
consec_groups as (selectuser_id,grp,count(*) as consec_daysfrom payment_sequencegroup by user_id, grp
)
selectuser_id,max(consec_days) as max_consec_days
from consec_groups
group by user_id;
  1. 生成虚拟分组date_sub(pay_date, interval row_number() day) 标记连续缴费序列。
  2. 统计连续天数:按用户和虚拟组计算连续缴费次数。
  3. 取最大值max(consec_days) 获取每个用户的最大连续天数。

🧩 连续性问题的通用解法框架

步骤核心操作适用场景
数据清洗按业务需求聚合数据(如按天汇总金额)处理多笔记录/噪声数据
生成连续标记date - row_number() 映射连续日期到虚拟组统一连续序列的时空标识
分组统计group by 虚拟组标记计算连续天数/次数
结果筛选havingmax() 过滤目标结果输出满足条件的用户或时段

这一套组合拳下来✅✅✅ 相信列位面试在遇到连续性登录问题 丝毫不慌了😂


留个作业~ 有些难度哈——合并用户停留位置

给定用户位置停留记录,需要对同一个用户在同一个位置的连续多条记录进行合并合并原则为开始时间取最早时间,停留时长加和。

表名字段名字段含义数据类型
用户位置停留记录表user用户标识VARCHAR
location位置标识VARCHAR
start_time停留开始时间DATETIME
stay_duration停留的时长(分钟)INT
userlocationstart_timestay_duration
UserALocationA2018-01-01 08:00:0060
UserALocationA2018-01-01 09:00:0060
UserALocationB2018-01-01 10:00:0060
UserALocationA2018-01-01 11:00:0060

结果预期:

userlocationstart_timestay_duration
UserALocationA2018-01-01 08:00:00120(08:00:00 开始的 60 分钟和 09:00:00 开始的 60 分钟合并)
UserALocationB2018-01-01 10:00:0060
UserALocationA2018-01-01 11:00:0060

很熟悉的套路啦

第一步:按照 用户 、 地点 分组 起始时间 去排序。

在这里插入图片描述

第二步:按照连续登录问题 A列 - 排序列 做一个 分组标记

在这里插入图片描述

第3步 分组之后 min sum聚合即可~

在这里插入图片描述


后续还会继续更新奥 求关注 求订阅~🌟🌟🌟🌟

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/899743.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring AI Alibaba 对话记忆使用

一、对话记忆 (ChatMemory)简介 1、对话记忆介绍 ”大模型的对话记忆”这一概念,根植于人工智能与自然语言处理领域,特别是针对具有深度学习能力的大型语言模型而言,它指的是模型在与用户进行交互式对话过程中,能够追踪、理解并利…

vdi模式是什么

‌VDI模式(Virtual Desktop Infrastructure)是一种基于服务器的计算模型,其核心思想是将所有计算和存储资源集中在服务器上,用户通过前端设备(如瘦客户机)访问服务器上的虚拟桌面‌‌ VDI模式的工作原理 在…

【分布式】深入剖析 Sentinel 限流:原理、实现

在当今分布式系统盛行的时代,流量的剧增给系统稳定性带来了巨大挑战。Sentinel 作为一款强大的流量控制组件,在保障系统平稳运行方面发挥着关键作用。本文将深入探讨 Sentinel 限流的原理、实现方案以及其优缺点,助力开发者更好地运用这一工具…

c#winform,倒鸭子字幕效果,typemonkey字幕效果,抖音瀑布流字幕效果

不废话 直接上效果图 C# winform 开发抖音的瀑布流字幕。 也是typemonkey插件字幕效果 或者咱再网上常说的倒鸭子字幕效果 主要功能 1,软件可以自定义添加字幕内容 2,软件可以添加字幕显示的时间区间 3,可以自定义字幕颜色,可以随…

Pycharm(八):字符串切片

一、字符串分片介绍 对操作的对象截取其中一部分的操作,比如想要获取字符串“888666qq.com前面的qq号的时候就可以用切片。 字符串、列表、元组都支持切片操作。 语法:字符串变量名 [起始:结束:步长] 口诀:切片其实很简单,只顾头来…

图片解释git的底层工作原理

(图片来源:自己画的) 基于同一个commit创建新分支 (图片来源:书籍《Linux运维之道》 ISBN 9787121461811) 在新分支上修改然后commit一次 (图片来源:书籍《Linux运维之道》 ISBN 978…

leetcode994.腐烂的橘子

思路源自 【力扣hot100】【LeetCode 994】腐烂的橘子|多源BFS 这里图中的腐烂的的橘子是同时对周围进行腐化,所以采用多源bfs就能解决 多源bfs与单源bfs的区别就在于队列取出时一轮是取出队列当中的全部元素 class Solution {public int orangesRotti…

【华为OD技术面试真题 - 技术面】- Java面试题(15)

华为OD面试真题精选 专栏:华为OD面试真题精选 目录: 2024华为OD面试手撕代码真题目录以及八股文真题目录 介绍下TCP/UDP TCP(传输控制协议)和 UDP(用户数据报协议) TCP(Transmission Control Protocol)和 UDP(User Datagram Protocol)是两种常见的传输层协议,主要…

‌在 Fedora 系统下备份远程 Windows SQL Server 数据库的完整方案

‌一、环境准备与工具安装‌ ‌1. 安装 Microsoft SQL Server 命令行工具‌ Fedora 需安装 mssql-tools 和 ODBC 驱动: # 添加 Microsoft 仓库 sudo curl -o /etc/yum.repos.d/msprod.repo https://packages.microsoft.com/config/rhel/8/prod.repo# 安装工具包 …

DeepSeek:巧用前沿AI技术,开启智能未来新篇章

引言 近年来,人工智能(AI)技术迅猛发展,大模型成为全球科技竞争的核心赛道。在这场AI革命中,DeepSeek作为中国领先的大模型研发团队,凭借其创新的技术架构、高效的训练方法和广泛的应用场景,迅…

R语言实现轨迹分析--traj和lcmm包体会

R语言实现轨迹分析–traj和lcmm包体会 轨迹分析是对重复测量数据的一种归纳,转化为一种分类变量,比如手术后1~7天内的疼痛评分,可以形成术后急性痛轨迹。形成的轨迹作为一个分类变量,可以用于预测疾病的预后&#xff…

Vue 3 事件总线详解:构建组件间高效通信的桥梁

Vue 3 事件总线详解:构建组件间高效通信的桥梁 为什么需要事件总线?使用 mitt 实现事件总线1. 安装 mitt2. 创建事件总线3. 在组件中使用事件总线发送端组件(例如 ComponentA.vue)接收端组件(例如 ComponentB.vue&…

MySQL的基础语法1(增删改查、DDL、DML、DQL和DCL)

目录 一、基本介绍 二、SQL通用语法 三、SQL分类(DDL、DML、DQL、DCL) 1.DDL 1.1数据库操作 1.2表操作 1.2.1表操作-查询创建 1.2.2表操作-数据类型 1)数值类型 2)字符串类型 3)日期时间类型​编辑 4)表操作-案例 1.2.3…

【NLP】15. NLP推理方法详解 --- 动态规划:序列标注,语法解析,共同指代

动态规划 (Dynamic Programming) 动态规划(Dynamic Programming,简称 DP)是一种通过将问题分解为较小子问题来优化计算效率的技术。它特别适用于优化最优解问题,比如序列标注(sequence tagging)这类任务。…

JavaScript中的NaN、undefined和null 的区别

NaN代表"Not a Number",它是一种特殊的数值,用于表示非数字值。当一个操作无法返回有效的数值时,通常会得到NaN作为结果。 let result = 10 / abc; console.log(result); // 输出 NaN需要注意的是,NaN与自身不相等,我们无法通过简单的比较操作符(如==或===)来…

Turtle事件处理(键盘与鼠标交互)

Turtle 提供了 事件驱动编程,允许我们使用 键盘 和 鼠标 控制 Turtle,从而实现交互式绘图。例如,我们可以让 Turtle 响应 按键、鼠标点击 和 拖动 事件,使其根据用户的输入进行移动、旋转或绘制图形。 1. 事件机制概述 Turtle 的事件处理主要依赖 turtle.Screen() 提供的 …

【Keepalived】Keepalived-2.3.3明确结束对CentOS 7的支持

2025年3月30日,官方发布了Keepalived的最新版,版本号:2.3.3 而2024年11月3日发布的2.3.2版本,在CentOS 7.9上编译的时候,就出现了报错,但是在Alma Linux 8.10上,则可以成功编译安装&#xff0c…

PyTorch --torch.cat张量拼接原理

在 PyTorch 的 torch.cat 函数中,out 参数用于指定输出张量的存储位置。是否使用 out 参数直接影响结果的存储方式和张量的内存行为。以下是详细解释: 不使用 out 参数(默认行为) 含义:不提供 out 参数时,…

人工智能之数学基础:矩阵对角化的本质

本文重点 前面的课程中,我们学习了矩阵的对角化,基于对角化可以将矩阵A转变为对角矩阵D,但是你有没有想过,为什么要进行矩阵对角化,矩阵对角化究竟做了一件什么事情呢? 矩阵对角化的本质 几何解释: 从几何变换的角度看,矩阵对角化意味着我们找到了一组基,使得线性变…

ubuntu的ubuntu--vg-ubuntu--lv磁盘扩容

在我们安装ubuntu时,如果选择的是自动分区,就会按照逻辑卷的形式来分区,并且只分配100G其余的并不会被分配,这对我们大多数情况来说都是不合理的,所以,如何扩充呢 下面以一个小的案例来说明如何扩充 问题…