跳到主要内容

字节跳动大数据面试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,以及他们连续递增的起始日期、结束日期、连续天数。

三、思路分析

这是"连续问题"的变体——不是判断连续日期,而是判断连续日期 + 递增金额

  1. LAG 函数获取前一行的日期和金额
  2. 判断"日期连续 AND 金额递增":DATEDIFF = 1 AND amount > prev_amount
  3. 用"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) = NULLNULL = 1 为 False,所以 CASE 会走到 ELSE 分支,break_flag=1。这意味着每组的第一行必然是断点。正确✅。

六、举一反三

  1. 非严格递增:如果允许相等(>=),去掉 amount > prev_amount 中的 >
  2. 连续N天任意指标:把 amount 换成其他字段,即可推广到"连续N天XXX递增/递减/满足条件"
  3. 允许间隔:如果允许中间隔 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真题

交流微信二维码

你可能还想看