使用ROW_NUMBER()为用户登录记录排序,通过登录日期减去序号生成分组键,将连续登录记录归为一组,再用GROUP BY统计每组天数,从而计算出用户的连续登录天数。

要使用SQL窗口函数计算用户的连续登录天数,核心思路在于通过为每个用户的登录记录排序,然后将登录日期减去这个序号(以天为单位),从而生成一个独特的“分组键”。如果用户的登录是连续的,那么这个“日期减序号”的结果会保持不变,我们就可以据此将连续的登录记录聚合起来,计算其长度。
在数据分析的日常工作中,我们经常需要识别用户行为的连续性,比如连续登录。传统的聚合函数在这方面显得力不从心,而SQL的窗口函数,特别是
ROW_NUMBER()
假设我们有一个名为
user_logins
user_id
login_date
以下是实现这一目标的SQL查询(以PostgreSQL语法为例,会注明其他数据库的差异):
WITH DistinctUserLogins AS (
-- 步骤1:首先,我们需要确保每个用户每天只有一条登录记录。
-- 如果原始数据可能包含同一用户在同一天多次登录,去重是必要的。
SELECT DISTINCT
user_id,
CAST(login_date AS DATE) AS login_day -- 确保我们只关注日期部分
FROM
user_logins
),
NumberedLogins AS (
-- 步骤2:为每个用户的登录记录按日期顺序分配一个行号。
-- 这是窗口函数发挥作用的关键一步。
SELECT
user_id,
login_day,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_day) AS rn
FROM
DistinctUserLogins
),
ConsecutiveLoginGroups AS (
-- 步骤3:创建“连续登录分组键”。
-- 这里的巧妙之处在于:如果 login_day 是连续的(例如,2023-01-01, 2023-01-02, 2023-01-03),
-- 那么 login_day 减去对应的 rn 值(1, 2, 3)后,结果将是一个恒定值。
-- 例如:2023-01-01 - 1天 = 2022-12-31
-- 2023-01-02 - 2天 = 2022-12-31
-- 2023-01-03 - 3天 = 2022-12-31
-- 但如果出现中断(例如,2023-01-05),那么 2023-01-05 - 4天 = 2023-01-01,
-- 这就创建了一个新的分组键。
SELECT
user_id,
login_day,
rn,
(login_day - rn * INTERVAL '1 DAY') AS login_group_key -- PostgreSQL语法
-- MySQL: DATE_SUB(login_day, INTERVAL rn DAY)
-- SQL Server: DATEADD(day, -rn, login_day)
FROM
NumberedLogins
)
-- 步骤4:根据用户ID和分组键聚合,计算每个连续登录区间的开始日期、结束日期和天数。
SELECT
user_id,
MIN(login_day) AS streak_start_date,
MAX(login_day) AS streak_end_date,
COUNT(*) AS consecutive_days_count
FROM
ConsecutiveLoginGroups
GROUP BY
user_id,
login_group_key
HAVING
COUNT(*) >= 1 -- 可以根据需要调整,例如只显示连续2天或以上的登录
ORDER BY
user_id,
streak_start_date;
-- 如果你只想知道每个用户的最长连续登录天数,可以在上述查询外层再加一层:
/*
SELECT
user_id,
MAX(consecutive_days_count) AS max_consecutive_days
FROM (
-- 上述计算连续登录天数的完整查询
-- ...
) AS user_streaks
GROUP BY
user_id
ORDER BY
max_consecutive_days DESC;
*/这个解决方案的精髓在于
login_day - rn * INTERVAL '1 DAY'
GROUP BY
我经常听到有人问,为什么不能直接用
GROUP BY user_id, login_day
COUNT(*)
COUNT()
SUM()
AVG()
GROUP BY
问题在于,“连续”这个概念本身就包含了“顺序”和“相邻”的含义。比如,2023-01-01和2023-01-02是连续的,但2023-01-01和2023-01-03就不是严格意义上的“连续”。传统的
GROUP BY
为了解决这种顺序和相邻关系的问题,我们可能需要使用复杂的自连接(Self-Join)或者游标(Cursor),但这些方法往往效率低下,代码复杂且难以维护。窗口函数则完全不同,它允许我们在一个“窗口”内,也就是一个定义好的数据集子集内,对行进行排序并执行计算,而这个“窗口”本身是基于某种分区和排序规则动态生成的。
ROW_NUMBER()
实际业务场景中,“连续登录”的定义远比我们想象的要灵活。刚才的方案是基于严格的“每日连续”来计算的,但很多时候,业务方可能会提出一些“奇怪”的需求。
比如,他们可能说:“如果用户周一登录了,周二没登录,但周三又登录了,这应该算作一个3天的连续登录,因为中间只断了一天。” 这种带有“宽限期”的连续性定义,就不能简单地通过
login_day - rn
LAG()
-- 考虑有1天宽限期的连续登录 (这会复杂很多,只是一个思路提示)
WITH UserLoginSequence AS (
SELECT
user_id,
CAST(login_date AS DATE) AS login_day,
LAG(CAST(login_date AS DATE), 1) OVER (PARTITION BY user_id ORDER BY CAST(login_date AS DATE)) AS prev_login_day
FROM user_logins
),
LoginGroupsWithGrace AS (
SELECT
user_id,
login_day,
-- 如果当前登录日期与前一次登录日期相差超过宽限期,则视为新的一组
CASE
WHEN prev_login_day IS NULL OR (login_day - prev_login_day) <= INTERVAL '2 DAY' THEN 0 -- 允许1天间隔,即最多相差2天
ELSE 1
END AS is_new_group
FROM UserLoginSequence
),
GroupMarkers AS (
SELECT
user_id,
login_day,
SUM(is_new_group) OVER (PARTITION BY user_id ORDER BY login_day) AS group_id
FROM LoginGroupsWithGrace
)
SELECT
user_id,
MIN(login_day) AS streak_start_date,
MAX(login_day) AS streak_end_date,
COUNT(*) AS consecutive_days_count
FROM GroupMarkers
GROUP BY
user_id,
group_id
ORDER BY
user_id,
streak_start_date;再比如,如果你的
login_date
CAST(login_date AS DATE)
DATE_TRUNC('day', login_date)还有,业务方可能对“登录”的定义也有不同,是只要访问就算,还是必须成功完成身份验证才算?这些都会影响我们从源数据中筛选出哪些记录来参与计算。因此,在开始编写SQL之前,与业务方充分沟通,明确“连续登录”的精确定义,是至关重要的一步。这不仅仅是技术实现的问题,更是数据产品能否满足业务需求的关键。
窗口函数就像是SQL的瑞士军刀,一旦你掌握了它的用法,会发现它能解决大量传统SQL难以处理的时序和排名问题。
会话分析 (Session Analysis): 识别用户会话是一个经典场景。比如,我们可以定义如果用户两次操作之间间隔超过30分钟,就认为是一个新的会话。这时,
LAG()
累计总和与移动平均 (Running Totals and Moving Averages): 这在财务分析、销售趋势分析中非常常见。
SUM() OVER (PARTITION BY ... ORDER BY ... ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
AVG() OVER (PARTITION BY ... ORDER BY ... ROWS BETWEEN N PRECEDING AND CURRENT ROW)
同期比较 (Year-over-Year/Month-over-Month Comparison): 想知道这个月(或今年)的销售额比上个月(或去年同期)增长了多少?
LAG()
排名问题 (Ranking Problems): 谁是销售冠军?谁是访问量最高的页面?
RANK()
DENSE_RANK()
NTILE()
首次/末次事件 (First/Last Event): 找出每个用户的首次购买日期,或者最后一次登录的设备。
FIRST_VALUE()
LAST_VALUE()
ROW_NUMBER()
WHERE rn = 1
检测数据异常 (Anomaly Detection): 比如,某个传感器读数突然远超前N个读数的平均值。通过计算移动平均和标准差,并与当前值进行比较,窗口函数能帮助我们快速识别潜在的异常点。
可以说,任何涉及到“基于顺序的计算”、“与相邻行比较”、“在某个范围内汇总”的需求,都可能成为窗口函数大显身手的舞台。它们让复杂的时序分析变得更加简洁、高效,并且易于理解。
以上就是如何使用SQL窗口函数解连续登录_利用窗口函数计算连续登录的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号