SQL连续登录问题

用户两次登录之间最大的天数差

sql 复制代码
-- 有一张用户登录日期表dwd_xhs_usr_login_log,包含两列user_id(用户id)、login_dt (登录日期)
-- 问题:计算用户最大流失天数
-- 举例如下:
-- 输入
-- user_id   login_dt
-- 001      20241101
-- 002      20241111
-- 001      20241120
-- 002      20241102
-- 002      20241115
-- 001      20241108

-- 输出
-- user_id  max_loss_days
-- 001      12
-- 002      9

-- 建表
CREATE TABLE IF NOT EXISTS dwd_xhs_usr_login_log (
    user_id STRING COMMENT '用户ID',
    login_dt STRING COMMENT '登录日期(格式:YYYYMMDD)'
)
COMMENT '用户登录日期表'
LIFECYCLE 30; -- 可选:设置表生命周期

-- 插入数据
INSERT INTO dwd_xhs_usr_login_log VALUES
('001', '20241101'),
('002', '20241111'),
('001', '20241120'),
('002', '20241102'),
('002', '20241115'),
('001', '20241108');

SELECT * FROM dwd_xhs_usr_login_log;

-- 核心思路
-- 去重与排序:每个用户可能有重复登录日期,需先去重,再按日期升序排序。
-- 计算相邻间隔:使用窗口函数 LAG() 获取上一次登录日期,计算与当前日期的间隔。
-- 取最大间隔:对每个用户的间隔取最大值。

WITH
-- 步骤1:去重并按用户、日期排序
-- 使用GROUP BY来完成去重,并完成STRING到datetime的转化
deduped_log AS (
    SELECT
        user_id,
        login_dt,
        -- 转换为日期类型(方便计算间隔)
        TO_DATE(login_dt, 'yyyymmdd') AS login_date
    FROM dwd_xhs_usr_login_log
    GROUP BY user_id, login_dt -- 去重(同一用户同一天多次登录只算一次)
),

-- 步骤2:计算相邻登录日期的间隔
-- 同一个user_id来分组,按照登录时间排序
interval_log AS (
    SELECT
        user_id,
        login_date,
        -- 获取上一次登录日期(按用户分组,日期升序)
            -- LAG() 这个特别说明一下,第一个是放入  被LAG的字段,第二是LAG几格 
            -- 第三 LAG() 一定要写ORDER BY,否则怎么知道把现在这一格移动到哪个下一个格子呢?总要有个次序吧
            -- 第四 PARTITION BY是在哪个组内进行排序LAG
        LAG(login_date, 1) OVER (PARTITION BY user_id ORDER BY login_date) AS prev_login_date
    FROM deduped_log
),

-- 步骤3:计算间隔天数(当前日期 - 上一次日期)
gap_log AS (
    SELECT
        user_id,
        -- 计算间隔天数(减1是因为"流失天数"是两次登录之间的自然日数)
        -- 它确实支持第三个参数 'dd'、'mm'、'yy'。注意顺序:它是 date1 - date2(前者减后者)
        DATEDIFF(login_date, prev_login_date, 'dd') - 1 AS gap_days
    FROM interval_log
    WHERE prev_login_date IS NOT NULL -- 排除第一次登录(无"上一次"日期)
)

-- 步骤4:取每个用户的最大间隔天数
    -- 同一个用户进行GROUP BY,然后取这个用户id下最大的那个 
SELECT
    user_id,
    MAX(gap_days) AS max_loss_days
FROM gap_log
GROUP BY user_id
ORDER BY user_id
;


-- 自己按照思路写的
-- 数据是什么样的?第一列user_id,第二列login_date
SELECT * FROM dwd_xhs_usr_login_log;

WITH 
--首先把dt转化为datetime格式,其次做LAG,连续登录,所以LAG 1格,
date_trans AS (
    SELECT *,TO_DATE(login_dt,'yyyymmdd') AS login_date FROM dwd_xhs_usr_login_log
)
-- LAG排序是同一个user_id内部,所以PARTITION BY user_id,order by login_date
-- 然后现在的登录日期和上次登录日期做差,等于1的说明是连续登录,大于一才算是流失
,date_lag_1 AS (
    SELECT user_id, login_date, LAG(login_date,1) OVER(PARTITION BY user_id ORDER BY login_date) AS pre_date FROM date_trans
)

,lag_day AS(
        --将首次登录的数据排除出去,因为首次登录,无所谓流失不流失
        -- LAG(1) 只能判断这一次登陆和上一次登录之间差距几天,(无法判断连续登录几天)
        SELECT *, DATEDIFF(login_date,pre_date,'dd') AS date_diff FROM date_lag_1 WHERE pre_date IS NOT NULL

)
,max_lag_day AS(
    SELECT user_id,max(date_diff) AS max_loss_days FROM lag_day GROUP BY user_id
)
SELECT * FROM max_lag_day;

连续登录天数统计

核心思想:日期减去行号,这个辅助数字相同的就是同一列

sql 复制代码
-- 连续登录次数统计

-- 1. 建表
-- 注意:MaxCompute 中通常使用 STRING 或 DATETIME 类型
CREATE TABLE IF NOT EXISTS LOGIN (
    user_id STRING,
    login_time DATETIME
);

-- 2. 插入数据
-- 使用 TO_DATE 函数将字符串转换为 DATETIME 类型
INSERT INTO TABLE LOGIN VALUES
('user1', TO_DATE('01-11-2025', 'dd-mm-yyyy')),
('user2', TO_DATE('01-11-2025', 'dd-mm-yyyy')),
('user3', TO_DATE('01-11-2025', 'dd-mm-yyyy')),
('user4', TO_DATE('01-11-2025', 'dd-mm-yyyy')),
('user1', TO_DATE('02-11-2025', 'dd-mm-yyyy')),
('user2', TO_DATE('02-11-2025', 'dd-mm-yyyy')),
('user3', TO_DATE('02-11-2025', 'dd-mm-yyyy')),
('user1', TO_DATE('03-11-2025', 'dd-mm-yyyy')),
('user2', TO_DATE('03-11-2025', 'dd-mm-yyyy')),
('user4', TO_DATE('03-11-2025', 'dd-mm-yyyy')),
('user1', TO_DATE('04-11-2025', 'dd-mm-yyyy')),
('user3', TO_DATE('04-11-2025', 'dd-mm-yyyy')),
('user4', TO_DATE('04-11-2025', 'dd-mm-yyyy')),
('user1', TO_DATE('05-11-2025', 'dd-mm-yyyy')),
('user2', TO_DATE('05-11-2025', 'dd-mm-yyyy')),
('user1', TO_DATE('06-11-2025', 'dd-mm-yyyy')),
('user3', TO_DATE('06-11-2025', 'dd-mm-yyyy')),
('user4', TO_DATE('06-11-2025', 'dd-mm-yyyy')),
('user2', TO_DATE('07-11-2025', 'dd-mm-yyyy')),
('user3', TO_DATE('07-11-2025', 'dd-mm-yyyy')),
('user1', TO_DATE('08-11-2025', 'dd-mm-yyyy')),
('user2', TO_DATE('08-11-2025', 'dd-mm-yyyy')),
('user4', TO_DATE('08-11-2025', 'dd-mm-yyyy')),
('user1', TO_DATE('09-11-2025', 'dd-mm-yyyy')),
('user3', TO_DATE('09-11-2025', 'dd-mm-yyyy')),
('user2', TO_DATE('10-11-2025', 'dd-mm-yyyy')),
('user4', TO_DATE('10-11-2025', 'dd-mm-yyyy')),
('user1', TO_DATE('11-11-2025', 'dd-mm-yyyy')),
('user2', TO_DATE('11-11-2025', 'dd-mm-yyyy')),
('user3', TO_DATE('11-11-2025', 'dd-mm-yyyy')),
('user1', TO_DATE('12-11-2025', 'dd-mm-yyyy')),
('user4', TO_DATE('12-11-2025', 'dd-mm-yyyy')),
('user2', TO_DATE('13-11-2025', 'dd-mm-yyyy')),
('user3', TO_DATE('13-11-2025', 'dd-mm-yyyy')),
('user1', TO_DATE('14-11-2025', 'dd-mm-yyyy')),
('user2', TO_DATE('14-11-2025', 'dd-mm-yyyy')),
('user4', TO_DATE('14-11-2025', 'dd-mm-yyyy')),
('user1', TO_DATE('15-11-2025', 'dd-mm-yyyy')),
('user3', TO_DATE('15-11-2025', 'dd-mm-yyyy')),
('user2', TO_DATE('16-11-2025', 'dd-mm-yyyy')),
('user4', TO_DATE('16-11-2025', 'dd-mm-yyyy')),
('user1', TO_DATE('17-11-2025', 'dd-mm-yyyy')),
('user2', TO_DATE('17-11-2025', 'dd-mm-yyyy')),
('user3', TO_DATE('17-11-2025', 'dd-mm-yyyy')),
('user1', TO_DATE('18-11-2025', 'dd-mm-yyyy')),
('user4', TO_DATE('18-11-2025', 'dd-mm-yyyy')),
('user2', TO_DATE('19-11-2025', 'dd-mm-yyyy')),
('user3', TO_DATE('19-11-2025', 'dd-mm-yyyy')),
('user1', TO_DATE('20-11-2025', 'dd-mm-yyyy')),
('user2', TO_DATE('20-11-2025', 'dd-mm-yyyy')),
('user4', TO_DATE('20-11-2025', 'dd-mm-yyyy'));

-- 题目:统计连续3天登录过的用户;


-- 核心思想:日期减去行号,这个辅助数字相同的就是同一列

WITH daily_login AS (
    -- 1. 去重:防止同一天多次登录导致行号错乱
    SELECT DISTINCT 
        user_id, 
        -- TO_DATE(login_time, 'yyyy-mm-dd') AS login_date -- 转为日期格式
        login_time AS login_date
    FROM LOGIN
),
ranked_login AS (
    -- 2. 排序:给每个用户的登录日期打上行号
    SELECT 
        user_id,
        login_date,
        ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date) AS rn
    FROM daily_login
),
grouped_login AS (
    -- 3. 分组:利用"日期 - 行号"构造分组标识
    -- 注意:MaxCompute 中日期减整数默认单位是天
    SELECT 
        user_id,
        login_date,
        -- 这里的 date_sub 结果如果相同,说明属于同一段连续登录
        -- group_flag 是连续登录最开始那一天的行号的前一天的日期
        DATE_SUB(login_date, rn) AS group_flag 
    FROM ranked_login
)

-- 4. 统计:按分组统计天数,筛选 >= 3 的
SELECT 
    user_id,
    MIN(login_date) AS start_date, -- 连续登录的开始日期
    MAX(login_date) AS end_date,   -- 连续登录的结束日期
    COUNT(1) AS consecutive_days   -- 连续天数
FROM grouped_login
GROUP BY user_id, group_flag
HAVING COUNT(1) >= 3;
相关推荐
葡萄城技术团队3 小时前
告别臃肿 SQL:HR 系统如何实现“字段级”权限控制与动态脱敏方案?
数据库·sql
校羽干3 小时前
ubuntu22.04 安装卸载更新 ollama
运维·服务器
SQL必知必会3 小时前
SQL HAVING 是什么?一篇讲清 WHERE 和 HAVING 的区别
数据库·sql
weixin_568996063 小时前
c++如何实现日志文件的异步落盘功能_基于无锁队列方案【附代码】
jvm·数据库·python
tongyiixiaohuang3 小时前
技术案例分享:金蝶云星空客户数据同步到MySQL的实现
android·数据库·mysql
淘矿人4 小时前
2026年4月-DeepSeek V4 vs GPT-5.5深度对比测评:weelinking一键切换实测
服务器·数据库·人工智能·python·gpt·学习·php
忡黑梨4 小时前
eNSP_ACL原理及应用
运维·服务器·网络·tcp/ip·github·负载均衡
运维全栈笔记4 小时前
K8S部署WordPress+MySQL:模块化YAML配置详解
服务器·mysql·docker·云原生·容器·kubernetes·服务发现
日取其半万世不竭4 小时前
用云服务器搭建Frp内网穿透,实现远程访问家里电脑
运维·服务器