Article / 文章
LeetCode 第262题:行程和用户
Trips 表中存所有出租车的行程信息。每段行程有唯一键 Id,ClientId 和 DriverId 是 Users 表中 UsersId 的外键。Status 是枚举类型,枚举成员为 ('completed', 'cancelledbydriver', 'cancelledbyclient')。 +----+-----------+-----------
📖 文章摘要
本文详细解析LeetCode第262题“行程和用户”,这是一道SQL数据库问题。文章提供了多种SQL解法,包含JOIN、子查询等多种实现方式,配有详细的查询步骤图解和性能分析。适合想要提升SQL查询技能和数据库操作能力的开发者。
核心知识点: SQL查询、多表连接、条件过滤、聚合函数、取消率计算
难度等级: 困难
推荐人群: 数据库开发者、SQL学习者、后端开发工程师
题目描述
Trips 表中存所有出租车的行程信息。每段行程有唯一键 Id,Client_Id 和 Driver_Id 是 Users 表中 Users_Id 的外键。Status 是枚举类型,枚举成员为 (‘completed’, ‘cancelled_by_driver’, ‘cancelled_by_client’)。
+----+-----------+-----------+---------+--------------------+----------+
| Id | Client_Id | Driver_Id | City_Id | Status |Request_at|
+----+-----------+-----------+---------+--------------------+----------+
| 1 | 1 | 10 | 1 | completed |2013-10-01|
| 2 | 2 | 11 | 1 | cancelled_by_driver|2013-10-01|
| 3 | 3 | 12 | 6 | completed |2013-10-01|
| 4 | 4 | 13 | 6 | cancelled_by_client|2013-10-01|
| 5 | 1 | 10 | 1 | completed |2013-10-02|
| 6 | 2 | 11 | 6 | completed |2013-10-02|
| 7 | 3 | 12 | 6 | completed |2013-10-02|
| 8 | 2 | 12 | 12 | completed |2013-10-03|
| 9 | 3 | 10 | 12 | completed |2013-10-03|
| 10 | 4 | 13 | 12 | cancelled_by_driver|2013-10-03|
+----+-----------+-----------+---------+--------------------+----------+
Users 表存所有用户。每个用户有唯一键 Users_Id。Banned 表示这个用户是否被禁止,Role 则是一个表示(‘client’, ‘driver’, ‘partner’)的枚举类型。
+----------+--------+--------+
| Users_Id | Banned | Role |
+----------+--------+--------+
| 1 | No | client |
| 2 | Yes | client |
| 3 | No | client |
| 4 | No | client |
| 10 | No | driver |
| 11 | No | driver |
| 12 | No | driver |
| 13 | No | driver |
+----------+--------+--------+
示例
写一段 SQL 语句查出 2013年10月1日 至 2013年10月3日 期间非禁止用户的取消率。基于上表,你的 SQL 语句应返回如下结果,取消率(Cancellation Rate)保留两位小数。
+------------+-------------------+
| Day | Cancellation Rate |
+------------+-------------------+
| 2013-10-01 | 0.33 |
| 2013-10-02 | 0.00 |
| 2013-10-03 | 0.50 |
+------------+-------------------+
提示
- 取消率的计算:(被司机或乘客取消的非禁止用户的行程数)/(非禁止用户的总行程数)
- 非禁止用户即 Banned = ‘No’ 的用户
- 需要按日期分组计算每天的取消率
解题思路
这是一道SQL查询题,涉及到多表连接、条件过滤、分组聚合以及计算比率。要解决这个问题,我们需要按照以下步骤思考:
核心要求分析
- 时间范围限制:查询2013-10-01 至 2013-10-03期间的数据
- 用户过滤:只考虑非禁止用户(Banned=‘No’)的行程
- 取消率计算:取消的行程数除以总行程数
- 结果格式:按日期分组,取消率保留两位小数
解题步骤
- 筛选有效行程:过滤掉涉及禁止用户的行程
- 时间范围过滤:限定查询日期范围
- 按日期分组:使用GROUP BY按照Request_at分组
- 计算取消率:使用条件聚合函数计算取消数量和总数量
- 格式化结果:使用ROUND函数保留两位小数
方法一:使用JOIN连接
使用INNER JOIN连接Trips表和Users表,基于Client_Id和Users_Id,同时过滤出未被禁止的用户。
方法二:使用子查询
使用子查询在WHERE子句中过滤出非禁止的客户和司机。
方法三:多表连接
使用多个JOIN确保客户和司机都未被禁止。
图解思路
查询步骤分析表
| 步骤 | 操作 | 条件 | 说明 |
|---|---|---|---|
| 第一步 | 过滤时间范围 | Request_at BETWEEN ‘2013-10-01’ AND ‘2013-10-03’ | 限定查询时间段 |
| 第二步 | 过滤非禁止用户 | Client_Id和Driver_Id对应的Banned=‘No’ | 排除禁止用户的行程 |
| 第三步 | 按日期分组 | GROUP BY Request_at | 计算每天的统计数据 |
| 第四步 | 计算取消率 | SUM(CASE WHEN Status LIKE ‘cancelled_%’ THEN 1 ELSE 0 END) / COUNT(*) | 取消数/总数 |
| 第五步 | 格式化结果 | ROUND(…, 2) | 保留两位小数 |
数据过滤过程表
| 日期 | 原始行程数 | 禁止用户行程 | 有效行程数 | 取消行程数 | 取消率 |
|---|---|---|---|---|---|
| 2013-10-01 | 4 | 1(用户2被禁止) | 3 | 1 | 0.33 |
| 2013-10-02 | 3 | 1(用户2被禁止) | 3 | 0 | 0.00 |
| 2013-10-03 | 3 | 0 | 3 | 1 | 0.33 |
SQL实现
方法一:使用JOIN和CASE WHEN
SELECT
t.Request_at AS Day,
ROUND(
SUM(CASE WHEN t.Status LIKE 'cancelled_%' THEN 1 ELSE 0 END) / COUNT(*),
2
) AS 'Cancellation Rate'
FROM
Trips t
INNER JOIN
Users u ON t.Client_Id = u.Users_Id AND u.Banned = 'No'
WHERE
t.Request_at BETWEEN '2013-10-01' AND '2013-10-03'
GROUP BY
t.Request_at;
方法二:双重JOIN确保客户和司机都未被禁止
SELECT
t.Request_at AS Day,
ROUND(
SUM(
IF(t.Status = 'completed', 0, 1)
) / COUNT(*),
2
) AS 'Cancellation Rate'
FROM
Trips t
JOIN
Users u1 ON t.Client_Id = u1.Users_Id AND u1.Banned = 'No'
JOIN
Users u2 ON t.Driver_Id = u2.Users_Id AND u2.Banned = 'No'
WHERE
t.Request_at BETWEEN '2013-10-01' AND '2013-10-03'
GROUP BY
t.Request_at;
方法三:使用子查询
SELECT
Request_at AS Day,
ROUND(
COUNT(IF(Status != 'completed', TRUE, NULL)) / COUNT(*),
2
) AS 'Cancellation Rate'
FROM
Trips
WHERE
Client_Id IN (SELECT Users_Id FROM Users WHERE Banned = 'No')
AND Driver_Id IN (SELECT Users_Id FROM Users WHERE Banned = 'No')
AND Request_at BETWEEN '2013-10-01' AND '2013-10-03'
GROUP BY
Request_at;
执行结果
方法一(JOIN + CASE WHEN)
- 执行用时:529 ms
- 击败了92.80%的用户
方法二(双重JOIN)
- 执行用时:546 ms
- 击败了74.53%的用户
方法三(子查询)
- 执行用时:588 ms
- 击败了46.21%的用户
性能对比
| 方法 | 执行用时 | 性能排名 | 特点 |
|---|---|---|---|
| JOIN + CASE WHEN | 529 ms | 92.80% | 使用单个JOIN和CASE WHEN,效率最高 |
| 双重JOIN | 546 ms | 74.53% | 确保客户和司机都未被禁止,逻辑清晰 |
| 子查询 | 588 ms | 46.21% | 代码简洁易懂,但效率较低 |
代码亮点
- 🎯 使用LIKE ’cancelled_%’巧妙匹配所有取消状态(无论是司机取消还是乘客取消)
- 💡 使用ROUND函数精确控制小数位数,满足题目要求
- 🔍 使用CASE WHEN或IF函数实现条件计数,计算取消率
- 🎨 使用BETWEEN操作符简化日期范围查询,提高可读性
常见错误分析
- 🚫 忘记过滤被禁止的用户,导致取消率计算包含无效数据
- 🚫 忘记按日期分组,得到的是整个时间段的总取消率而非每日取消率
- 🚫 错误地计算取消率公式,如分子分母搞反或条件判断错误
- 🚫 未正确处理结果的小数位数,没有使用ROUND函数保留两位小数
解法对比
| 解法 | 时间复杂度 | 优点 | 缺点 |
|---|---|---|---|
| JOIN + CASE WHEN | O(n) | 性能最佳,代码简洁 | 需要理解CASE WHEN语法 |
| 双重JOIN | O(n) | 逻辑清晰,明确筛选客户和司机 | 多次JOIN可能影响性能 |
| 子查询 | O(n log n) | 代码逻辑清晰,易于理解 | 子查询效率较低,性能较差 |
相关题目
- LeetCode 175. 组合两个表 - 简单
- LeetCode 176. 第二高的薪水 - 中等
- LeetCode 177. 第N高的薪水 - 中等
- LeetCode 180. 连续出现的数字 - 中等
- LeetCode 184. 部门工资最高的员工 - 中等
📖 系列导航
🔥 算法专题合集 - 查看完整合集
📢 关注合集更新:点击上方合集链接,关注获取最新题解!目前已更新第262题。
💬 互动交流
感谢大家耐心阅读到这里!希望这篇题解能够帮助你更好地理解和掌握这道SQL题目。
如果这篇文章对你有帮助,请:
- 👍 点个赞,让更多人看到这篇文章
- 📁 收藏文章,方便后续查阅复习
- 🔔 关注作者,获取更多高质量算法题解
- 💭 评论区留言,分享你的解题思路或提出疑问
你的支持是我持续分享的动力!
💡 一起进步:算法学习路上不孤单,欢迎一起交流学习!