本文详细解析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 |
+------------+-------------------+
这是一道SQL查询题,涉及到多表连接、条件过滤、分组聚合以及计算比率。要解决这个问题,我们需要按照以下步骤思考:
使用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 |
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;
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和CASE WHEN,效率最高 |
双重JOIN | 546 ms | 74.53% | 确保客户和司机都未被禁止,逻辑清晰 |
子查询 | 588 ms | 46.21% | 代码简洁易懂,但效率较低 |
解法 | 时间复杂度 | 优点 | 缺点 |
---|---|---|---|
JOIN + CASE WHEN | O(n) | 性能最佳,代码简洁 | 需要理解CASE WHEN语法 |
双重JOIN | O(n) | 逻辑清晰,明确筛选客户和司机 | 多次JOIN可能影响性能 |
子查询 | O(n log n) | 代码逻辑清晰,易于理解 | 子查询效率较低,性能较差 |
算法专题合集 - 查看完整合集
关注合集更新:点击上方合集链接,关注获取最新题解!目前已更新第262题。
感谢大家耐心阅读到这里!希望这篇题解能够帮助你更好地理解和掌握这道SQL题目。
如果这篇文章对你有帮助,请:
你的支持是我持续分享的动力!
一起进步:算法学习路上不孤单,欢迎一起交流学习!