统计下单后 30 分钟内未支付而被系统自动取消的订单占总下单量的比例,并按一级商品品类进行分组
面试真题:统计各一级品类下,下单 30 分钟内未支付被系统自动取消的订单占比
1. 题目背景
在电商业务中,用户下单后往往会有一定的支付等待期(如30分钟)。如果用户在规定时间内未支付,系统会自动取消订单并释放库存。高比例的“超时未支付自动取消”通常意味着用户流失率高或存在恶意刷单行为。
面试要求:编写 SQL 统计每个一级商品品类下,下单后 30 分钟内(含30分钟)未支付而被系统自动取消的订单占该品类总下单量的比例。结果按比例降序排列。
2. 候选表结构及数据样例
表 1:订单表 t_order
记录订单的基础状态及时间信息。
order_status说明:paid(已支付)、user_canceled(用户主动取消)、auto_canceled(系统超时自动取消)。
| order_id | user_id | create_time | cancel_time | pay_time | order_status |
|---|---|---|---|---|---|
| O001 | U101 | 2023-10-01 10:00:00 | 2023-10-01 10:30:00 | NULL | auto_canceled |
| O002 | U102 | 2023-10-01 10:15:00 | NULL | 2023-10-01 10:20:00 | paid |
| O003 | U103 | 2023-10-01 11:00:00 | 2023-10-01 11:15:00 | NULL | user_canceled |
| O004 | U104 | 2023-10-01 12:00:00 | 2023-10-01 12:45:00 | NULL | auto_canceled |
| O005 | U105 | 2023-10-01 13:00:00 | 2023-10-01 13:25:00 | NULL | auto_canceled |
表 2:订单明细表 t_order_detail
记录订单包含的商品。
| order_id | goods_id | goods_num |
|---|---|---|
| O001 | G001 | 1 |
| O002 | G002 | 1 |
| O003 | G003 | 2 |
| O004 | G003 | 1 |
| O005 | G004 | 1 |
表 3:商品信息表 t_goods
记录商品及其类目归属。
| goods_id | goods_name | category_id_1 | category_name_1 |
|---|---|---|---|
| G001 | 智能手机 | C01 | 电子产品 |
| G002 | 蓝牙耳机 | C01 | 电子产品 |
| G003 | 运动卫衣 | C02 | 服装鞋帽 |
| G004 | 休闲牛仔裤 | C02 | 服装鞋帽 |
3. 期望输出结果
| 一级品类 | 总下单量 | 超时自动取消量 | 自动取消占比(%) |
|---|---|---|---|
| 电子产品 | 2 | 1 | 50.00% |
| 服装鞋帽 | 3 | 1 | 33.33% |
- 解析说明:
- 电子产品下有订单
O001(30分钟内自动取消)和O002(已支付),占比 1/2 = 50.00%。 - 服装鞋帽下有订单
O003(用户主动取消)、O004(45分钟才自动取消,超过30分钟限制)、O005(25分钟自动取消),符合条件的仅O005,占比 1/3 = 33.33%。
- 电子产品下有订单
4. SQL 解答(MySQL 8.0 标准)
sql
SELECT
g.category_name_1 AS `一级品类`,
COUNT(DISTINCT o.order_id) AS `总下单量`,
COUNT(DISTINCT CASE
WHEN o.order_status = 'auto_canceled'
AND TIMESTAMPDIFF(SECOND, o.create_time, o.cancel_time) <= 1800
THEN o.order_id
END) AS `超时自动取消量`,
CONCAT(
ROUND(
COUNT(DISTINCT CASE
WHEN o.order_status = 'auto_canceled'
AND TIMESTAMPDIFF(SECOND, o.create_time, o.cancel_time) <= 1800
THEN o.order_id
END) * 100.0 / COUNT(DISTINCT o.order_id),
2
),
'%'
) AS `自动取消占比(%)`
FROM t_order o
JOIN t_order_detail od ON o.order_id = od.order_id
JOIN t_goods g ON od.goods_id = g.goods_id
GROUP BY g.category_name_1
ORDER BY `自动取消占比(%)` DESC;
5. 核心考点与 SQL 分析(面试通关秘籍)
考点一:时间差计算的精准度(TIMESTAMPDIFF)
- 常见误区:使用
TIMESTAMPDIFF(MINUTE, start, end) <= 30。 - 细节陷阱:在 MySQL 中,
TIMESTAMPDIFF(MINUTE, ...)是向下取整的。例如,相差 30 分 59 秒也会被计算为 30 分钟。 - 大厂解法:在要求精准的业务场景(如30分钟限制)中,建议将单位转换为“秒”,即
TIMESTAMPDIFF(SECOND, start, end) <= 1800,以此规避边界误差。
考点二:条件过滤与指标共存(Conditional Aggregation)
- 考查重点:不要盲目在
WHERE子句中过滤order_status = 'auto_canceled'。 - 原因:如果在外层直接使用
WHERE过滤,会导致分母(总下单量)同样被过滤,从而无法计算占比。 - 解法:必须使用条件聚合(
CASE WHEN或IF)。在COUNT(DISTINCT ...)内部嵌套条件,只对分子进行过滤,分母依然保留全量。
考点三:精度丢失与格式化输出
- 除零异常防御:由于使用了
GROUP BY,需注意分子为 0 的情况,COUNT(...)为 0 时除法可以正常运行。若担心分母为 0 导致报错,可用NULLIF(COUNT(...), 0)保证安全。 - 类型转换:在 SQL 中,整数除以整数(如
1 / 2)在某些数据库(如 SQL Server/PostgreSQL)中会直接截断为0。因此计算比例时,务必将分子乘以100.0(转换为浮点数)后再做除法。 - 美化展示:使用
ROUND(..., 2)保留两位小数,并使用CONCAT(..., '%')拼接百分号,能极大提升结果的业务可读性,展现良好的工程素养。
考点四:一对多关联(Join)引发的数据翻倍
- 隐患:如果一个订单包含多件同属于一个一级品类的商品,在
t_order_detail中会产生多条记录。 - 对策:在聚合计算订单量时,必须使用
COUNT(DISTINCT o.order_id)进行去重,防止因为商品明细的多条记录导致订单量被重复放大计算。