用户两次登录之间最大的天数差
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;