字节跳动大数据面试SQL-连续3天交易金额递增的用户
一、题目背景
这道题来自字节跳动支付业务的数据分析岗面试。抖音电商需要识别"消费升级"的用户——连续几天购买金额在增加,说明用户对平台的信任度在提升,可以推送更高客单价的商品。
业务场景:普通连续登录问题考察"日期是否连续",这道题升级为"金额是否递增"。需要同时判断日期连续性和数值递增性,两个条件缺一不可。
二、题目
现有一张用户交易记录表 t14_zj_transaction,记录了用户每天的消费总额。请找出连续 3 天以上(含)交易金额严格递增的用户。
t14_zj_transaction 表
+----------+------------+--------+
| user_id | trade_date | amount |
+----------+------------+--------+
| 1 | 2025-01-01 | 100 |
| 1 | 2025-01-02 | 150 |
| 1 | 2025-01-03 | 200 |
| 1 | 2025-01-04 | 180 |
| 2 | 2025-01-01 | 50 |
| 2 | 2025-01-02 | 80 |
| 2 | 2025-01-03 | 70 |
| 3 | 2025-01-01 | 100 |
| 3 | 2025-01-02 | 120 |
| 3 | 2025-01-03 | 150 |
| 3 | 2025-01-05 | 200 |
+----------+------------+--------+
期望输出:满足条件的用户ID,以及他们连续递增的起始日期、结束日期、连续天数。
三、思路分析
这是"连续问题"的变体——不是判断连续日期,而是判断连续日期 + 递增金额:
- LAG 函数获取前一行的日期和金额
- 判断"日期连续 AND 金额递增":
DATEDIFF = 1 AND amount > prev_amount - 用"0/1标志 + 累积求和"法分组(经典连续问题解法)
| 维度 | 评分 |
|---|---|
| 题目难度 | ⭐️⭐️⭐️⭐️ |
| 题目清晰度 | ⭐️⭐️⭐️ |
| 业务常见度 | ⭐️⭐️⭐️ |
四、逐步推导
步骤1:LAG 获取前一行的日期和金额
SELECT
user_id,
trade_date,
amount,
LAG(trade_date) OVER (PARTITION BY user_id ORDER BY trade_date) AS prev_date,
LAG(amount) OVER (PARTITION BY user_id ORDER BY trade_date) AS prev_amount
FROM t14_zj_transaction;
用户1:
01-01: prev(NULL, NULL)
01-02: prev(01-01, 100) → 连续✅ 递增✅ (150>100)
01-03: prev(01-02, 150) → 连续✅ 递增✅ (200>150)
01-04: prev(01-03, 200) → 连续✅ 递增❌ (180<200)
用户2:
01-01: prev(NULL, NULL)
01-02: prev(01-01, 50) → 连续✅ 递增✅ (80>50)
01-03: prev(01-02, 80) → 连续✅ 递增❌ (70<80)
用户3:
01-01: prev(NULL, NULL)
01-02: prev(01-01, 100) → 连续✅ 递增✅
01-03: prev(01-02, 120) → 连续✅ 递增✅
01-05: prev(01-03, 150) → 连续❌ (隔了1天) 递增✅
步骤2:打标(连续递增 = 1,否则 = 0,首行 = 0)
SELECT
user_id, trade_date, amount,
CASE
WHEN DATEDIFF(trade_date, LAG(trade_date) OVER (PARTITION BY user_id ORDER BY trade_date)) = 1
AND amount > LAG(amount) OVER (PARTITION BY user_id ORDER BY trade_date)
THEN 0 ELSE 1
END AS break_flag
FROM t14_zj_transaction;
break_flag=1 表示"不满足连续递增",作为分组断点。
步骤3:累积求和断点 → 分组 + 统计
WITH labeled AS (
SELECT
user_id, trade_date, amount,
CASE
WHEN DATEDIFF(trade_date, LAG(trade_date) OVER w) = 1
AND amount > LAG(amount) OVER w
THEN 0 ELSE 1
END AS break_flag
FROM t14_zj_transaction
WINDOW w AS (PARTITION BY user_id ORDER BY trade_date)
),
grouped AS (
SELECT *,
SUM(break_flag) OVER (PARTITION BY user_id ORDER BY trade_date) AS grp
FROM labeled
)
SELECT
user_id,
MIN(trade_date) AS start_date,
MAX(trade_date) AS end_date,
COUNT(*) AS consecutive_days
FROM grouped
WHERE break_flag = 0 -- 只保留连续递增的记录
GROUP BY user_id, grp
HAVING COUNT(*) >= 3;
最终结果:
+----------+------------+------------+------------------+
| user_id | start_date | end_date | consecutive_days |
+----------+------------+------------+------------------+
| 1 | 2025-01-01 | 2025-01-03 | 3 |
+----------+------------+------------+------------------+
用户1 在 01-01 到 01-03 连续 3 天递增(100→150→200),但 01-04 金额下降到 180,中断。
五、常见坑点
坑1:两个 LAG 产生两次窗口扫描
LAG(trade_date) 和 LAG(amount) 是同一个窗口的不同列。用 WINDOW w AS (...) 只扫描一次。
坑2:首行的 NULL 处理
LAG 的首行返回 NULL,DATEDIFF(NULL, NULL) = NULL,NULL = 1 为 False,所以 CASE 会走到 ELSE 分支,break_flag=1。这意味着每组的第一行必然是断点。正确✅。
六、举一反三
- 非严格递增:如果允许相等(>=),去掉
amount > prev_amount中的> - 连续N天任意指标:把 amount 换成其他字段,即可推广到"连续N天XXX递增/递减/满足条件"
- 允许间隔:如果允许中间隔 1 天,把
DATEDIFF = 1改为DATEDIFF <= 2
七、知识点总结
| 考点 | 说明 |
|---|---|
| LAG + DATEDIFF | 判断日期是否连续 |
| LAG + 比较 | 判断数值是否递增 |
| 0/1标志 + SUM OVER | 连续问题分组(经典解法) |
| WINDOW 子句 | 复用窗口定义,减少重复代码 |
八、建表语句和数据插入
点击展开 DDL & DML
CREATE TABLE IF NOT EXISTS t14_zj_transaction (
user_id INT, trade_date DATE, amount BIGINT
);
INSERT INTO t14_zj_transaction VALUES
(1, '2025-01-01', 100),
(1, '2025-01-02', 150),
(1, '2025-01-03', 200),
(1, '2025-01-04', 180),
(2, '2025-01-01', 50),
(2, '2025-01-02', 80),
(2, '2025-01-03', 70),
(3, '2025-01-01', 100),
(3, '2025-01-02', 120),
(3, '2025-01-03', 150),
(3, '2025-01-05', 200);
📱关注公众号
「数据仓库技术」文章同步更新,不错过每一篇干货

💬加群交流
备注「数据仓库技术」加入社群,每日一道大厂SQL真题
