题目描述:
表:Trips
+-------------+----------+
| Column Name | Type |
+-------------+----------+
| id | int |
| client_id | int |
| driver_id | int |
| city_id | int |
| status | enum |
| request_at | varchar |
+-------------+----------+
id 是这张表的主键(具有唯一值的列)。
这张表中存所有出租车的行程信息。每段行程有唯一 id ,其中 client_id 和 driver_id 是 Users 表中 users_id 的外键。
status 是一个表示行程状态的枚举类型,枚举成员为(‘completed’, ‘cancelled_by_driver’, ‘cancelled_by_client’) 。
表:Users
+-------------+----------+
| Column Name | Type |
+-------------+----------+
| users_id | int |
| banned | enum |
| role | enum |
+-------------+----------+
users_id 是这张表的主键(具有唯一值的列)。
这张表中存所有用户,每个用户都有一个唯一的 users_id ,role 是一个表示用户身份的枚举类型,枚举成员为 (‘client’, ‘driver’, ‘partner’) 。
banned 是一个表示用户是否被禁止的枚举类型,枚举成员为 (‘Yes’, ‘No’) 。
取消率 的计算方式如下:(被司机或乘客取消的非禁止用户生成的订单数量) / (非禁止用户生成的订单总数)。
编写解决方案找出 "2013-10-01"
至 "2013-10-03"
期间非禁止用户(乘客和司机都必须未被禁止)的取消率。非禁止用户即 banned 为 No 的用户,禁止用户即 banned 为 Yes 的用户。其中取消率 Cancellation Rate
需要四舍五入保留 两位小数 。
返回结果表中的数据 无顺序要求 。
题解:
with client as (
select users_id
from Users
where role = 'client' and banned = 'No'
),
driver as(
select users_id
from Users
where role = 'driver' and banned = 'No'
),
tmp as (
select id,status,request_at as Day
from Trips
where client_id in (select users_id from client)
and driver_id in (select users_id from driver)
),
summary as (
select Day,
sum(case when status !='completed' then 1 else 0 end) as cancel_num,
count(*) as order_num
from tmp
group by Day
)
select Day, convert((cancel_num / order_num) ,decimal(4,2)) as `Cancellation Rate`
from summary
where Day >='2013-10-01' and Day <='2013-10-03'
我的思考逻辑
- 根据题目的要求,要计算有效订单的取消率,也就是要从所有订单中去除掉client_id和driver_id已经banned 即去除banned=’Yes’ 的client_id和driver_id
- 同时,需要考虑到的一个情况是,client_id是可能等于driver_id的 如果简单的对Users表进行筛选banned=’No’,那么有可能client_id=10 其banned=’Yes’,同时有一个driver_id=10,但是它的banned=’No’,那么在简单的筛选时就会发生筛选对象不明确导致的错误
- 为了解决以上这个问题,我多使用了两个表格,其实完全可以使用client_id/driver_id in select users_id from Users where role=’client/driver’ and banned = ‘No’ 同时成立的行 的行程即为有效订单
- 那么最后就是一个计算的过程,值得再详细说明的,对取消率的计算并保留2位小数,可以使用的方法有这些
round(x,num)
:四舍五入保留num位小数,例如round(cancel_num / order_num,2)就是对取消率进行计算 并直接保留2位小数convert(x,type)
:conver会相较于round更加灵活,可以转换成其他的格式,进行一个格式的保留,例如,convert(cancel_num / order_num,decimal(10,2))那么就是将计算结果转化为 10位的浮点数,保留2位小数,会更加精准精确format(x,num)
:四舍五入保留num位小数,format的使用感受上和round差不多 但是 返回的对象时string字符串类型truncate(x,num)
:返回小数的前num位 不四舍五入
- 同时,另外值得说明的是在summary临时表中,使用了两个很使用的sql方法,
- 1.使用聚合函数+case when的统计,使得sql的函数计算会更加灵活,但值得注意的是,case when xxxx then xx else xx end 不要将最后的end给忘记,在编写上述sql的过程中,就因为这个报错了很久。
- 2.使用count()来统计某个字段的数量,那么在这里我要详细的说明一下,在上述sql中使用不同的count它们之间的细微差别(虽然在本题中无论使用说明count都是可以的)
- count(字段)也就是count(status),计算status中非空的行数,假如有的status为NULL那么就不会统计进去
count(1)
和count(*)
都用来计算行数,但它们之间有一个微妙的区别:count(1)
通常更快一些,因为它只需要检查每一行是否存在即可,不需要真正读取任何列的数据。count(*)
则可能稍微慢一点,因为它需要读取每一行的所有列来确保没有遇到任何隐藏的分区键等导致的过滤情况。然而,在大多数情况下,这种性能差异可以忽略不计。