目录
177.第N高的薪水
https://leetcode-cn.com/problems/nth-highest-salary/
编写一个 SQL 查询,获取 Employee 表中第 n 高的薪水(Salary)。
+----+--------+
| Id | Salary |
+----+--------+
| 1 | 100 |
| 2 | 200 |
| 3 | 300 |
+----+--------+
例如上述 Employee 表,n = 2 时,应返回第二高的薪水 200。如果不存在第 n 高的薪水,那么查询应返回 null。+------------------------+
| getNthHighestSalary(2) |
+------------------------+
| 200 |
+------------------------+
首先set N = N-1, salary要distinct,降序排序,用limit 1 offset N取第N高的薪水,或者limit N,1
记得要加上ifnull判断是否存在记录,如果不存在要返回null
ps: limit接受一个或两个整型参数,第一个参数指定第一个返回记录行的偏移量,第二个参数指定返回记录行的最大数目,如limit 5,1就是偏移5行返回一行,即返回第6行;或者使用limit 1 offset 5也可以达到同样效果
CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
set N=N-1;
RETURN (
# Write your MySQL query statement below.
select
ifnull(
(select distinct Salary
from Employee
order by Salary DESC
limit 1 offset N),
null
)
);
END
178.分数排名
https://leetcode-cn.com/problems/rank-scores/
编写一个 SQL 查询来实现分数排名。
如果两个分数相同,则两个分数排名(Rank)相同。请注意,平分后的下一个名次应该是下一个连续的整数值。换句话说,名次之间不应该有“间隔”。
+----+-------+
| Id | Score |
+----+-------+
| 1 | 3.50 |
| 2 | 3.65 |
| 3 | 4.00 |
| 4 | 3.85 |
| 5 | 4.00 |
| 6 | 3.65 |
+----+-------+
例如,根据上述给定的 Scores 表,你的查询应该返回(按分数从高到低排列):+-------+------+
| Score | Rank |
+-------+------+
| 4.00 | 1 |
| 4.00 | 1 |
| 3.85 | 2 |
| 3.65 | 3 |
| 3.65 | 3 |
| 3.50 | 4 |
+-------+------+
重要提示:对于 MySQL 解决方案,如果要转义用作列名的保留字,可以在关键字之前和之后使用撇号。例如 `Rank`
(1)分析:首先恶补了一下窗口函数的用法https://blog.csdn.net/weixin_39010770/article/details/87862407
其中,序号函数包括ROW_NUMBER(),RANK(),DENSE_RANK()
ROW_NUMBER():顺序排序——1、2、3
RANK():并列排序,跳过重复序号——1、1、3
DENSE_RANK():并列排序,不跳过重复序号——1、1、2
(2)解题思路:根据这道题的情况,应该使用DENSE_RANK(),并且使用ORDER BY字句对Score字段进行降序排序,之后DENSE_RANK()会将排序后的记录顺序进行编号。
# Write your MySQL query statement below
select Score,
DENSE_RANK() OVER (ORDER BY Score desc) as 'Rank'
from Scores;
180. 连续出现的数字
https://leetcode-cn.com/problems/consecutive-numbers/
表:Logs
+-------------+---------+
| Column Name | Type |
+-------------+---------+
| id | int |
| num | varchar |
+-------------+---------+
id 是这个表的主键。
编写一个 SQL 查询,查找所有至少连续出现三次的数字。
返回的结果表中的数据可以按 任意顺序 排列。
查询结果格式如下面的例子所示:
Logs 表:
+----+-----+
| Id | Num |
+----+-----+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 2 |
| 5 | 1 |
| 6 | 2 |
| 7 | 2 |
+----+-----+Result 表:
+-----------------+
| ConsecutiveNums |
+-----------------+
| 1 |
+-----------------+
1 是唯一连续出现至少三次的数字。
思路:使用窗口函数 LEAD(expr,n)返回当前行的后n行的expr值。
具体来说,使用LEAD函数获取后1行和后2行的Num值,然后看当前行的Num和后1行、后2行的Num值相不相等,若相等则说明这个Num连续出现了3次以上。要注意一点的是要使用distinct对得到的ConsecutiveNums去重(有连续出现4次以上的情况)。
select distinct Num as ConsecutiveNums
from (
select Id, Num, LEAD(Num, 1) OVER () AS post_1, LEAD(Num, 2) OVER () AS post_2
from Logs
) as t where Num=post_1 and post_1=post_2
181. 超过经理收入的员工
https://leetcode-cn.com/problems/employees-earning-more-than-their-managers/
Employee 表包含所有员工,他们的经理也属于员工。每个员工都有一个 Id,此外还有一列对应员工的经理的 Id。
+----+-------+--------+-----------+
| Id | Name | Salary | ManagerId |
+----+-------+--------+-----------+
| 1 | Joe | 70000 | 3 |
| 2 | Henry | 80000 | 4 |
| 3 | Sam | 60000 | NULL |
| 4 | Max | 90000 | NULL |
+----+-------+--------+-----------+
给定 Employee 表,编写一个 SQL 查询,该查询可以获取收入超过他们经理的员工的姓名。在上面的表格中,Joe 是唯一一个收入超过他的经理的员工。+----------+
| Employee |
+----------+
| Joe |
+----------+
思路:把Employee表复制一张,用第一张表的a.ManagerId和第二张表的b.Id连接两张表,然后判断两张表的Salary大小
SELECT a.Name AS Employee
FROM Employee AS a JOIN Employee AS b ON a.ManagerId=b.Id
WHERE a.Salary>b.Salary
182. 查找重复的电子邮箱
https://leetcode-cn.com/problems/duplicate-emails/
编写一个 SQL 查询,查找 Person 表中所有重复的电子邮箱。
示例:
+----+---------+
| Id | Email |
+----+---------+
| 1 | a@b.com |
| 2 | c@d.com |
| 3 | a@b.com |
+----+---------+
根据以上输入,你的查询应返回以下结果:+---------+
| Email |
+---------+
| a@b.com |
+---------+
说明:所有电子邮箱都是小写字母。
思路:用GROUP BY对Email进行分组,然后用HAVING对分组后的Email进行COUNT统计,判断该email的出现次数是否大于1
SELECT Email
FROM Person
GROUP BY Email
HAVING COUNT(Email)>1
183. 从不订购的客户
https://leetcode-cn.com/problems/customers-who-never-order/
某网站包含两个表,Customers 表和 Orders 表。编写一个 SQL 查询,找出所有从不订购任何东西的客户。
Customers 表:
+----+-------+
| Id | Name |
+----+-------+
| 1 | Joe |
| 2 | Henry |
| 3 | Sam |
| 4 | Max |
+----+-------+
Orders 表:+----+------------+
| Id | CustomerId |
+----+------------+
| 1 | 3 |
| 2 | 1 |
+----+------------+
例如给定上述表格,你的查询应返回:+-----------+
| Customers |
+-----------+
| Henry |
| Max |
+-----------+
思路1:LEFT JOIN Customers表和Orders表,然后选择拼接后的表中Orders.Id is null的字段
SELECT a.Name as Customers
FROM Customers AS a LEFT JOIN Orders AS b ON a.Id=b.CustomerId
WHERE b.Id is null
思路2:使用NOT IN可以查找一张表的某个字段的值不在另一张表的某个字段的值
SELECT Name as Customers
FROM Customers
WHERE Id NOT IN(
SELECT CustomerId
FROM Orders
)
184. 部门工资最高的员工
https://leetcode-cn.com/problems/department-highest-salary/
Employee 表包含所有员工信息,每个员工有其对应的 Id, salary 和 department Id。
+----+-------+--------+--------------+
| Id | Name | Salary | DepartmentId |
+----+-------+--------+--------------+
| 1 | Joe | 70000 | 1 |
| 2 | Jim | 90000 | 1 |
| 3 | Henry | 80000 | 2 |
| 4 | Sam | 60000 | 2 |
| 5 | Max | 90000 | 1 |
+----+-------+--------+--------------+
Department 表包含公司所有部门的信息。+----+----------+
| Id | Name |
+----+----------+
| 1 | IT |
| 2 | Sales |
+----+----------+
编写一个 SQL 查询,找出每个部门工资最高的员工。对于上述表,您的 SQL 查询应返回以下行(行的顺序无关紧要)。+------------+----------+--------+
| Department | Employee | Salary |
+------------+----------+--------+
| IT | Max | 90000 |
| IT | Jim | 90000 |
| Sales | Henry | 80000 |
+------------+----------+--------+
解释:Max 和 Jim 在 IT 部门的工资都是最高的,Henry 在销售部的工资最高。
思路1:使用窗口函数RANK()先对Employee表根据DepartmentId进行分组,使用ORDER BY对窗口内字段根据Salary进行DESC排序,得到rank,将该表与Department表连接,取rank=1的行即为所求
SELECT b.Name as Department, a.Name AS Employee, a.Salary
FROM
(SELECT *, RANK() OVER(PARTITION BY DepartmentId ORDER BY Salary DESC) AS rk FROM Employee) AS a
JOIN Department AS b
ON a.DepartmentId=b.Id
WHERE a.rk=1
思路2:先拼接Employee表和Department表,然后找到Employee表中每个部门最高的薪水及对应的部门id,返回拼接的表中薪水为最高薪水且部门对应的行
SELECT b.Name as Department, a.Name AS Employee, a.Salary
FROM Employee AS a JOIN Department AS b ON a.DepartmentId=b.Id
WHERE (a.DepartmentId, a.Salary) IN
(
SELECT DepartmentId, MAX(Salary)
FROM Employee
GROUP BY DepartmentId
)
185. 部门工资前三高的所有员工
https://leetcode-cn.com/problems/department-top-three-salaries/
Employee 表包含所有员工信息,每个员工有其对应的工号 Id,姓名 Name,工资 Salary 和部门编号 DepartmentId 。
+----+-------+--------+--------------+
| Id | Name | Salary | DepartmentId |
+----+-------+--------+--------------+
| 1 | Joe | 85000 | 1 |
| 2 | Henry | 80000 | 2 |
| 3 | Sam | 60000 | 2 |
| 4 | Max | 90000 | 1 |
| 5 | Janet | 69000 | 1 |
| 6 | Randy | 85000 | 1 |
| 7 | Will | 70000 | 1 |
+----+-------+--------+--------------+
Department 表包含公司所有部门的信息。+----+----------+
| Id | Name |
+----+----------+
| 1 | IT |
| 2 | Sales |
+----+----------+
编写一个 SQL 查询,找出每个部门获得前三高工资的所有员工。例如,根据上述给定的表,查询结果应返回:+------------+----------+--------+
| Department | Employee | Salary |
+------------+----------+--------+
| IT | Max | 90000 |
| IT | Randy | 85000 |
| IT | Joe | 85000 |
| IT | Will | 70000 |
| Sales | Henry | 80000 |
| Sales | Sam | 60000 |
+------------+----------+--------+
解释:IT 部门中,Max 获得了最高的工资,Randy 和 Joe 都拿到了第二高的工资,Will 的工资排第三。销售部门(Sales)只有两名员工,Henry 的工资最高,Sam 的工资排第二。
思路:和上一题的思路1是一致的,使用DENSE_RANK()先对Employee的每个部门按照Salary进行DESC排序,得到rank,将该表与Department表连接,取rank<=3的行即为所求
ps: 按照题目解释应该使用DENSE_RANK,DENSE_RANK是并列排序且不跳过序号,结果是1,1,2,3的形式,因此只要筛选rank<=3即可得到前三的工资
SELECT b.Name as Department, a.Name AS Employee, a.Salary
FROM
(SELECT *, DENSE_RANK() OVER(PARTITION BY DepartmentId ORDER BY Salary DESC) AS rk FROM Employee) AS a
JOIN Department AS b
ON a.DepartmentId=b.Id
WHERE a.rk<=3
196. 删除重复的电子邮箱
https://leetcode-cn.com/problems/delete-duplicate-emails/
编写一个 SQL 查询,来删除 Person 表中所有重复的电子邮箱,重复的邮箱里只保留 Id 最小 的那个。
+----+------------------+
| Id | Email |
+----+------------------+
| 1 | john@example.com |
| 2 | bob@example.com |
| 3 | john@example.com |
+----+------------------+
Id 是这个表的主键。
例如,在运行你的查询语句之后,上面的 Person 表应返回以下几行:+----+------------------+
| Id | Email |
+----+------------------+
| 1 | john@example.com |
| 2 | bob@example.com |
+----+------------------+
提示:
执行 SQL 之后,输出是整个 Person 表。
使用 delete 语句。
思路:将Person表看成a,b两张一样的表,从a表中取出一条记录去b表中按条件查找,若满足条件则删除a表中的这条记录
DELETE a
FROM Person AS a, Person AS b
WHERE a.Email=b.Email and a.Id>b.Id
197. 上升的温度
https://leetcode-cn.com/problems/rising-temperature/
表 Weather
+---------------+---------+
| Column Name | Type |
+---------------+---------+
| id | int |
| recordDate | date |
| temperature | int |
+---------------+---------+
id 是这个表的主键
该表包含特定日期的温度信息
编写一个 SQL 查询,来查找与之前(昨天的)日期相比温度更高的所有日期的 id 。
返回结果 不要求顺序 。
查询结果格式如下例:
Weather
+----+------------+-------------+
| id | recordDate | Temperature |
+----+------------+-------------+
| 1 | 2015-01-01 | 10 |
| 2 | 2015-01-02 | 25 |
| 3 | 2015-01-03 | 20 |
| 4 | 2015-01-04 | 30 |
+----+------------+-------------+Result table:
+----+
| id |
+----+
| 2 |
| 4 |
+----+
2015-01-02 的温度比前一天高(10 -> 25)
2015-01-04 的温度比前一天高(20 -> 30)
思路:一开始本来打算用LAG(Temperature, 1) 获取当前行的前一行的温度值,然后将当前行的温度和前一行的温度进行比较,后来发现日期有可能是乱序的,然后想先ORDER BY recodeDate一下再比较,后来又发现排完序之后日期不一定是连续的。。。所以这个方法也就作罢
那就只能将Weather表看成w1w2两张表,拿出w1表中的数据和w2表一行一行按条件筛选
SELECT w1.id
FROM Weather w1, Weather w2
WHERE DATEDIFF(w1.recordDate, w2.recordDate)=1 AND w1.Temperature>w2.Temperature
262. 行程和用户
https://leetcode-cn.com/problems/trips-and-users/
表:Trips
+-------------+----------+
| Column Name | Type |
+-------------+----------+
| Id | int |
| Client_Id | int |
| Driver_Id | int |
| City_Id | int |
| Status | enum |
| Request_at | date |
+-------------+----------+
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’) 。
写一段 SQL 语句查出 "2013-10-01" 至 "2013-10-03" 期间非禁止用户(乘客和司机都必须未被禁止)的取消率。非禁止用户即 Banned 为 No 的用户,禁止用户即 Banned 为 Yes 的用户。
取消率 的计算方式如下:(被司机或乘客取消的非禁止用户生成的订单数量) / (非禁止用户生成的订单总数)。
返回结果表中的数据可以按任意顺序组织。其中取消率 Cancellation Rate 需要四舍五入保留 两位小数 。
查询结果格式如下例所示:
Trips 表:
+----+-----------+-----------+---------+---------------------+------------+
| 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 |
+----------+--------+--------+
| 1 | No | client |
| 2 | Yes | client |
| 3 | No | client |
| 4 | No | client |
| 10 | No | driver |
| 11 | No | driver |
| 12 | No | driver |
| 13 | No | driver |
+----------+--------+--------+Result 表:
+------------+-------------------+
| Day | Cancellation Rate |
+------------+-------------------+
| 2013-10-01 | 0.33 |
| 2013-10-02 | 0.00 |
| 2013-10-03 | 0.50 |
+------------+-------------------+2013-10-01:
- 共有 4 条请求,其中 2 条取消。
- 然而,Id=2 的请求是由禁止用户(User_Id=2)发出的,所以计算时应当忽略它。
- 因此,总共有 3 条非禁止请求参与计算,其中 1 条取消。
- 取消率为 (1 / 3) = 0.33
2013-10-02:
- 共有 3 条请求,其中 0 条取消。
- 然而,Id=6 的请求是由禁止用户发出的,所以计算时应当忽略它。
- 因此,总共有 2 条非禁止请求参与计算,其中 0 条取消。
- 取消率为 (0 / 2) = 0.00
2013-10-03:
- 共有 3 条请求,其中 1 条取消。
- 然而,Id=8 的请求是由禁止用户发出的,所以计算时应当忽略它。
- 因此,总共有 2 条非禁止请求参与计算,其中 1 条取消。
- 取消率为 (1 / 2) = 0.50
思路:这道题比较复杂,首先要理清思路。
关键是理解这条公式,取消率 =(被司机或乘客取消的非禁止用户生成的订单数量) / (非禁止用户生成的订单总数),分母的非禁止用户其实指的是client里面的非禁止用户,不包含driver,因为只有client才能生成订单,而分子则在非禁止client生成的订单数的基础上筛选出被client或driver取消的订单。
(1)第一步:获取非禁止client的行,使用JOIN连接Trips表和Users表
(2)第二步:获取日期范围在'2013-10-01' AND '2013-10-03'之间的数据
(3)第三步:将数据按日期进行分组
(4)第四步:计算不同日期内的订单数,使用COUNT()
(5)第五步:计算不同日期内被取消的订单数(非completed),使用SUM和IF函数
(6)第六步:将第五步的结果除以第四步的结果,ROUND一下
SELECT t.Request_at AS Day,
ROUND(SUM(IF(t.Status='completed',0,1))/COUNT(t.Status), 2) AS 'Cancellation Rate'
FROM Trips AS t
JOIN Users AS u1 ON (t.Client_Id=u1.Users_Id AND u1.Banned='No')
WHERE t.Request_at BETWEEN '2013-10-01' AND '2013-10-03'
GROUP BY t.Request_at
511. 游戏玩法分析 I
https://leetcode-cn.com/problems/game-play-analysis-i/
活动表 Activity:
+--------------+---------+
| Column Name | Type |
+--------------+---------+
| player_id | int |
| device_id | int |
| event_date | date |
| games_played | int |
+--------------+---------+
表的主键是 (player_id, event_date)。
这张表展示了一些游戏玩家在游戏平台上的行为活动。
每行数据记录了一名玩家在退出平台之前,当天使用同一台设备登录平台后打开的游戏的数目(可能是 0 个)。
写一条 SQL 查询语句获取每位玩家 第一次登陆平台的日期。
查询结果的格式如下所示:
Activity 表:
+-----------+-----------+------------+--------------+
| player_id | device_id | event_date | games_played |
+-----------+-----------+------------+--------------+
| 1 | 2 | 2016-03-01 | 5 |
| 1 | 2 | 2016-05-02 | 6 |
| 2 | 3 | 2017-06-25 | 1 |
| 3 | 1 | 2016-03-02 | 0 |
| 3 | 4 | 2018-07-03 | 5 |
+-----------+-----------+------------+--------------+Result 表:
+-----------+-------------+
| player_id | first_login |
+-----------+-------------+
| 1 | 2016-03-01 |
| 2 | 2017-06-25 |
| 3 | 2016-03-02 |
+-----------+-------------+
思路:按照player_id进行分组,然后去event_date的最小值作为该player的first_login值
SELECT player_id, MIN(event_date) AS first_login
FROM Activity
GROUP BY player_id
512. 游戏玩法分析 II
https://leetcode-cn.com/problems/game-play-analysis-ii/
Table: Activity
+--------------+---------+
| Column Name | Type |
+--------------+---------+
| player_id | int |
| device_id | int |
| event_date | date |
| games_played | int |
+--------------+---------+
(player_id, event_date) 是这个表的两个主键
这个表显示的是某些游戏玩家的游戏活动情况
每一行是在某天使用某个设备登出之前登录并玩多个游戏(可能为0)的玩家的记录
请编写一个 SQL 查询,描述每一个玩家首次登陆的设备名称查询结果格式在以下示例中:
Activity table:
+-----------+-----------+------------+--------------+
| player_id | device_id | event_date | games_played |
+-----------+-----------+------------+--------------+
| 1 | 2 | 2016-03-01 | 5 |
| 1 | 2 | 2016-05-02 | 6 |
| 2 | 3 | 2017-06-25 | 1 |
| 3 | 1 | 2016-03-02 | 0 |
| 3 | 4 | 2018-07-03 | 5 |
+-----------+-----------+------------+--------------+Result table:
+-----------+-----------+
| player_id | device_id |
+-----------+-----------+
| 1 | 2 |
| 2 | 3 |
| 3 | 1 |
+-----------+-----------+
思路1:先查找出每个玩家最早的日期,然后在原表中一条一条数据对照
SELECT player_id, device_id
FROM Activity
WHERE (player_id, event_date) IN
(
SELECT player_id, MIN(event_date)
FROM Activity
GROUP BY player_id
)
思路2:用窗口函数rank()对Activity表的event_date进行排序,获取每个player中rank=1的行
SELECT player_id, device_id
FROM(
SELECT player_id, device_id, RANK() OVER(PARTITION BY player_id ORDER BY event_date) AS rk
FROM Activity
) t
WHERE t.rk=1
534. 游戏玩法分析 III
https://leetcode-cn.com/problems/game-play-analysis-iii/
Table: Activity
+--------------+---------+
| Column Name | Type |
+--------------+---------+
| player_id | int |
| device_id | int |
| event_date | date |
| games_played | int |
+--------------+---------+
(player_id,event_date)是此表的主键。
这张表显示了某些游戏的玩家的活动情况。
每一行是一个玩家的记录,他在某一天使用某个设备注销之前登录并玩了很多游戏(可能是 0 )。
编写一个 SQL 查询,同时报告每组玩家和日期,以及玩家到目前为止玩了多少游戏。也就是说,在此日期之前玩家所玩的游戏总数。详细情况请查看示例。
查询结果格式如下所示:
Activity table:
+-----------+-----------+------------+--------------+
| player_id | device_id | event_date | games_played |
+-----------+-----------+------------+--------------+
| 1 | 2 | 2016-03-01 | 5 |
| 1 | 2 | 2016-05-02 | 6 |
| 1 | 3 | 2017-06-25 | 1 |
| 3 | 1 | 2016-03-02 | 0 |
| 3 | 4 | 2018-07-03 | 5 |
+-----------+-----------+------------+--------------+Result table:
+-----------+------------+---------------------+
| player_id | event_date | games_played_so_far |
+-----------+------------+---------------------+
| 1 | 2016-03-01 | 5 |
| 1 | 2016-05-02 | 11 |
| 1 | 2017-06-25 | 12 |
| 3 | 2016-03-02 | 0 |
| 3 | 2018-07-03 | 5 |
+-----------+------------+---------------------+
对于 ID 为 1 的玩家,2016-05-02 共玩了 5+6=11 个游戏,2017-06-25 共玩了 5+6+1=12 个游戏。
对于 ID 为 3 的玩家,2018-07-03 共玩了 0+5=5 个游戏。
请注意,对于每个玩家,我们只关心玩家的登录日期。
思路1:使用SUM() OVER(PARTITION BY ... ORDER BY ...)
ps: 以下几种语法的区别
1、SUM(x1) GROUP BY x2 :根据x2进行分组后求每一组的x1的总和,每组记录聚合成一行记录,总和只出现一次
2、SUM(x1) OVER(PARTITION BY x2):根据x2进行分组后,求每一组的x1的总和,每组记录不进行聚合,组内的每一行记录都出现这个总和
3、SUM(x1) OVER(PARTITION BY x2 ORDER BY x3):根据x2分组后,按照x3进行排序,根据这个顺序在组内累加x1的值,组内每一行的SUM(x1)即累加前面以及当前行的x1的和
SELECT player_id, event_date,
SUM(games_played) OVER(PARTITION BY player_id ORDER BY event_date) AS games_played_so_far
FROM Activity
思路2:或者对表进行自连接,取a表的日期大于b表的日期的记录,按照a.event_date进行分组,求每一组games_played的总和
550. 游戏玩法分析 IV
https://leetcode-cn.com/problems/game-play-analysis-iv/
Table: Activity
+--------------+---------+
| Column Name | Type |
+--------------+---------+
| player_id | int |
| device_id | int |
| event_date | date |
| games_played | int |
+--------------+---------+
(player_id,event_date)是此表的主键。
这张表显示了某些游戏的玩家的活动情况。
每一行是一个玩家的记录,他在某一天使用某个设备注销之前登录并玩了很多游戏(可能是 0)。
编写一个 SQL 查询,报告在首次登录的第二天再次登录的玩家的比率,四舍五入到小数点后两位。换句话说,您需要计算从首次登录日期开始至少连续两天登录的玩家的数量,然后除以玩家总数。
查询结果格式如下所示:
Activity table:
+-----------+-----------+------------+--------------+
| player_id | device_id | event_date | games_played |
+-----------+-----------+------------+--------------+
| 1 | 2 | 2016-03-01 | 5 |
| 1 | 2 | 2016-03-02 | 6 |
| 2 | 3 | 2017-06-25 | 1 |
| 3 | 1 | 2016-03-02 | 0 |
| 3 | 4 | 2018-07-03 | 5 |
+-----------+-----------+------------+--------------+Result table:
+-----------+
| fraction |
+-----------+
| 0.33 |
+-----------+
只有 ID 为 1 的玩家在第一天登录后才重新登录,所以答案是 1/3 = 0.33
思路:先筛选出每个player_id的最小登陆日,将筛选的得到的表left join到原表,按照player_id和datediff()=1进行匹配,然后统计满足匹配条件的player_id
SELECT ROUND(COUNT(t2.player_id)/COUNT(t1.player_id), 2) AS fraction
FROM
(
SELECT player_id, MIN(event_date) AS min_date
FROM Activity
GROUP BY player_id
) t1
LEFT JOIN Activity t2
ON (t1.player_id=t2.player_id)
AND (DATEDIFF(t2.event_date, t1.min_date)=1)
569. 员工薪水中位数
https://leetcode-cn.com/problems/median-employee-salary/
Employee 表包含所有员工。Employee 表有三列:员工Id,公司名和薪水。
+-----+------------+--------+
|Id | Company | Salary |
+-----+------------+--------+
|1 | A | 2341 |
|2 | A | 341 |
|3 | A | 15 |
|4 | A | 15314 |
|5 | A | 451 |
|6 | A | 513 |
|7 | B | 15 |
|8 | B | 13 |
|9 | B | 1154 |
|10 | B | 1345 |
|11 | B | 1221 |
|12 | B | 234 |
|13 | C | 2345 |
|14 | C | 2645 |
|15 | C | 2645 |
|16 | C | 2652 |
|17 | C | 65 |
+-----+------------+--------+
请编写SQL查询来查找每个公司的薪水中位数。挑战点:你是否可以在不使用任何内置的SQL函数的情况下解决此问题。+-----+------------+--------+
|Id | Company | Salary |
+-----+------------+--------+
|5 | A | 451 |
|6 | A | 513 |
|12 | B | 234 |
|9 | B | 1154 |
|14 | C | 2645 |
+-----+------------+--------+
思路:使用窗口函数ROW_NUMBER() OVER(PARTITION BY Company ORDER BY Salary) AS rk对原表数据按照company进行分组并按照salary进行排序,row_num()会得到排序后的序号,如1,2,3,4,5。
再使用COUNT() OVER(PARTITION BY Company) AS cnt在窗口内统计行数,使得组内每一行记录都有count值。
用 t.rk>=t.cnt/2 AND t.rk<=t.cnt/2+1条件筛选符合条件的记录。
SELECT t.Id, t.Company, t.Salary
FROM
(
SELECT *,
ROW_NUMBER() OVER(PARTITION BY Company ORDER BY Salary) AS rk,
COUNT(Id) OVER(PARTITION BY Company) AS cnt
FROM Employee
) t
WHERE t.rk>=t.cnt/2 AND t.rk<=t.cnt/2+1
570. 至少有5名直接下属的经理
https://leetcode-cn.com/problems/managers-with-at-least-5-direct-reports/
Employee 表包含所有员工和他们的经理。每个员工都有一个 Id,并且还有一列是经理的 Id。
+------+----------+-----------+----------+
|Id |Name |Department |ManagerId |
+------+----------+-----------+----------+
|101 |John |A |null |
|102 |Dan |A |101 |
|103 |James |A |101 |
|104 |Amy |A |101 |
|105 |Anne |A |101 |
|106 |Ron |B |101 |
+------+----------+-----------+----------+
给定 Employee 表,请编写一个SQL查询来查找至少有5名直接下属的经理。对于上表,您的SQL查询应该返回:+-------+
| Name |
+-------+
| John |
+-------+
注意:
没有人是自己的下属。
思路:先找出至少有5名直接下属的ManagerId,然后JOIN到原表,根据原表Id和ManagerId进行匹配, 就可以得到ManagerId对应的Name
SELECT b.Name
FROM
(
SELECT ManagerId
FROM Employee
GROUP BY ManagerId
HAVING COUNT(Id)>=5
) a
JOIN Employee b
ON b.Id=a.ManagerId
571. 给定数字的频率查询中位数
https://leetcode-cn.com/problems/find-median-given-frequency-of-numbers/
Numbers 表保存数字的值及其频率。
+----------+-------------+
| Number | Frequency |
+----------+-------------|
| 0 | 7 |
| 1 | 1 |
| 2 | 3 |
| 3 | 1 |
+----------+-------------+
在此表中,数字为 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 3,所以中位数是 (0 + 0) / 2 = 0。+--------+
| median |
+--------|
| 0.0000 |
+--------+
请编写一个查询来查找所有数字的中位数并将结果命名为 median 。
思路:使用SUM(Frequency) OVER(ORDER BY Number)和SUM(Frequency) OVER(ORDER BY Number DESC)对数字个数进行正序和逆序累计,当某一数字的正序和逆序累计均大于整个序列的数字个数的一半时即为中位数,将最后选定的一个或两个中位数进行求均值即可。
SELECT AVG(t.Number) AS median
FROM
(
SELECT *,
SUM(Frequency) OVER(ORDER BY Number) AS ascend_sum,
SUM(Frequency) OVER(ORDER BY Number DESC) AS descend_sum,
SUM(Frequency) OVER() AS total_frequency
FROM Numbers
) t
WHERE t.ascend_sum>=t.total_frequency/2 AND t.descend_sum>=t.total_frequency/2
574. 当选者
https://leetcode-cn.com/problems/winning-candidate/
表: Candidate
+-----+---------+
| id | Name |
+-----+---------+
| 1 | A |
| 2 | B |
| 3 | C |
| 4 | D |
| 5 | E |
+-----+---------+
表: Vote+-----+--------------+
| id | CandidateId |
+-----+--------------+
| 1 | 2 |
| 2 | 4 |
| 3 | 3 |
| 4 | 2 |
| 5 | 5 |
+-----+--------------+
id 是自动递增的主键,
CandidateId 是 Candidate 表中的 id.
请编写 sql 语句来找到当选者的名字,上面的例子将返回当选者 B.+------+
| Name |
+------+
| B |
+------+
注意:你可以假设没有平局,换言之,最多只有一位当选者。
思路:先从Vote表中用GROUP BY,ORDER BY和LIMIT 1获取得票最多的candidateid,然后将candidateid和Candidate表JOIN起来 ,匹配一下就得到唯一一行了
SELECT b.Name
FROM
(
SELECT CandidateId
FROM Vote
GROUP BY CandidateId
ORDER BY COUNT(id) DESC
LIMIT 1
) a
JOIN Candidate b
ON b.id=a.CandidateId
577. 员工奖金
https://leetcode-cn.com/problems/employee-bonus/
选出所有 bonus < 1000 的员工的 name 及其 bonus。
Employee 表单
+-------+--------+-----------+--------+
| empId | name | supervisor| salary |
+-------+--------+-----------+--------+
| 1 | John | 3 | 1000 |
| 2 | Dan | 3 | 2000 |
| 3 | Brad | null | 4000 |
| 4 | Thomas | 3 | 4000 |
+-------+--------+-----------+--------+
empId 是这张表单的主关键字
Bonus 表单+-------+-------+
| empId | bonus |
+-------+-------+
| 2 | 500 |
| 4 | 2000 |
+-------+-------+
empId 是这张表单的主关键字
输出示例:+-------+-------+
| name | bonus |
+-------+-------+
| John | null |
| Dan | 500 |
| Brad | null |
+-------+-------+
思路:直接把Employee表和Bonus表LEFT JOIN起来,取bonus<1000或is null的行即可
SELECT a.name, b.bonus
FROM Employee a LEFT JOIN Bonus b
ON a.empId=b.empId
WHERE b.bonus<1000 OR b.bonus is null
578. 查询回答率最高的问题
https://leetcode-cn.com/problems/get-highest-answer-rate-question/
从 survey_log 表中获得回答率最高的问题,survey_log 表包含这些列:id, action, question_id, answer_id, q_num, timestamp。
id 表示用户 id;action 有以下几种值:"show","answer","skip";当 action 值为 "answer" 时 answer_id 非空,而 action 值为 "show" 或者 "skip" 时 answer_id 为空;q_num 表示当前会话中问题的编号。
请编写 SQL 查询来找到具有最高回答率的问题。
示例:
输入:
+------+-----------+--------------+------------+-----------+------------+
| id | action | question_id | answer_id | q_num | timestamp |
+------+-----------+--------------+------------+-----------+------------+
| 5 | show | 285 | null | 1 | 123 |
| 5 | answer | 285 | 124124 | 1 | 124 |
| 5 | show | 369 | null | 2 | 125 |
| 5 | skip | 369 | null | 2 | 126 |
+------+-----------+--------------+------------+-----------+------------+
输出:
+-------------+
| survey_log |
+-------------+
| 285 |
+-------------+
解释:
问题 285 的回答率为 1/1,而问题 369 回答率为 0/1,因此输出 285 。
提示:回答率最高的含义是:同一问题编号中回答数占显示数的比例最高。
思路:根据question_id分组,组内统计分别出现answer和show的次数,计算两者的比例即为回答率,按照回答率进行排序,取回答率最高的那个question_id
SELECT question_id AS survey_log
FROM survey_log
GROUP BY question_id
ORDER BY SUM(IF(action='answer', 1, 0))/SUM(IF(action='show', 1, 0)) DESC
LIMIT 1
579. 查询员工的累计薪水
https://leetcode-cn.com/problems/find-cumulative-salary-of-an-employee/
Employee 表保存了一年内的薪水信息。
请你编写 SQL 语句,对于每个员工,查询他除最近一个月(即最大月)之外,剩下每个月的近三个月的累计薪水(不足三个月也要计算)。
结果请按 Id 升序,然后按 Month 降序显示。
示例:
输入:| Id | Month | Salary |
|----|-------|--------|
| 1 | 1 | 20 |
| 2 | 1 | 20 |
| 1 | 2 | 30 |
| 2 | 2 | 30 |
| 3 | 2 | 40 |
| 1 | 3 | 40 |
| 3 | 3 | 60 |
| 1 | 4 | 60 |
| 3 | 4 | 70 |
输出:| Id | Month | Salary |
|----|-------|--------|
| 1 | 3 | 90 |
| 1 | 2 | 50 |
| 1 | 1 | 20 |
| 2 | 1 | 20 |
| 3 | 3 | 100 |
| 3 | 2 | 40 |
解释:
员工 '1' 除去最近一个月(月份 '4'),有三个月的薪水记录:月份 '3' 薪水为 40,月份 '2' 薪水为 30,月份 '1' 薪水为 20。
所以近 3 个月的薪水累计分别为 (40 + 30 + 20) = 90,(30 + 20) = 50 和 20。
| Id | Month | Salary |
|----|-------|--------|
| 1 | 3 | 90 |
| 1 | 2 | 50 |
| 1 | 1 | 20 |
员工 '2' 除去最近的一个月(月份 '2')的话,只有月份 '1' 这一个月的薪水记录。| Id | Month | Salary |
|----|-------|--------|
| 2 | 1 | 20 |
员工 '3' 除去最近一个月(月份 '4')后有两个月,分别为:月份 '3' 薪水为 60 和 月份 '2' 薪水为 40。所以各月的累计情况如下:| Id | Month | Salary |
|----|-------|--------|
| 3 | 3 | 100 |
| 3 | 2 | 40 |
错误的思路:一开始没有看清楚题目,以为是计算剩下每个月的累计薪水,因此使用 SUM(Salary) OVER(PARTITION BY Id ORDER BY Month) AS accmu_salary对每个月薪水进行累计,然而题目要求的是剩下每个月的近三个月的累计薪水(不满三个月也要计算),如果直接用SUM()则会将在当前月之前所有月份的薪水都累计进去,所以不能直接使用SUM()。
SELECT t.Id, t.Month, t.accmu_salary AS Salary
FROM
(
SELECT Id, Month, SUM(Salary) OVER(PARTITION BY Id ORDER BY Month) AS accmu_salary,
ROW_NUMBER() OVER(PARTITION BY Id ORDER BY Month DESC) AS rk
FROM Employee
) t
WHERE t.rk>1
正确的思路:使用LAG(Salary, 1)和LAG(Salary, 2)分别获取当前月的前一个月和前二个月的薪水,然后将当月薪水+前一个月薪水+前二个月薪水即可。要注意,因为题目要求输出是按月份倒序,所以还要使用ROW_NUMBER()对Month进行DESC排序,然后取rank>1的记录就可以满足题目要求的“除最近一个月(最大月)之外”
SELECT t.Id, t.Month, t.Salary + IF(t.pre1, t.pre1, 0) + IF(t.pre2, t.pre2, 0) AS Salary
FROM
(
SELECT *,
LAG(Salary, 1) OVER(PARTITION BY Id ORDER BY Month) AS pre1,
LAG(Salary, 2) OVER(PARTITION BY Id ORDER BY Month) AS pre2,
ROW_NUMBER() OVER(PARTITION BY Id ORDER BY Month DESC) AS rk
FROM Employee
) t
WHERE t.rk>1
580. 统计各专业学生人数
https://leetcode-cn.com/problems/count-student-number-in-departments/
一所大学有 2 个数据表,分别是 student 和 department ,这两个表保存着每个专业的学生数据和院系数据。
写一个查询语句,查询 department 表中每个专业的学生人数 (即使没有学生的专业也需列出)。
将你的查询结果按照学生人数降序排列。 如果有两个或两个以上专业有相同的学生数目,将这些部门按照部门名字的字典序从小到大排列。
student 表格如下:
| Column Name | Type |
|--------------|-----------|
| student_id | Integer |
| student_name | String |
| gender | Character |
| dept_id | Integer |
其中, student_id 是学生的学号, student_name 是学生的姓名, gender 是学生的性别, dept_id 是学生所属专业的专业编号。department 表格如下:
| Column Name | Type |
|-------------|---------|
| dept_id | Integer |
| dept_name | String |
dept_id 是专业编号, dept_name 是专业名字。这里是一个示例输入:
student 表格:| student_id | student_name | gender | dept_id |
|------------|--------------|--------|---------|
| 1 | Jack | M | 1 |
| 2 | Jane | F | 1 |
| 3 | Mark | M | 2 |
department 表格:| dept_id | dept_name |
|---------|-------------|
| 1 | Engineering |
| 2 | Science |
| 3 | Law |
示例输出为:| dept_name | student_number |
|-------------|----------------|
| Engineering | 2 |
| Science | 1 |
| Law | 0 |
思路:先ORDER BY和COUNT()统计student表中每个专业的学生人数 ,然后和department表按照dept_id RIGHT JOIN一下,最后的结果要按照学生人数降序排序,且按dept_name的字典序升序排序。注意student_number为null的值要转换为0
SELECT b.dept_name, IF(a.student_number, a.student_number, 0) AS student_number
FROM
(
SELECT dept_id, COUNT(student_id) AS student_number
FROM student
GROUP BY dept_id
) a
RIGHT JOIN department b
ON a.dept_id=b.dept_id
ORDER BY a.student_number DESC, b.dept_name
584. 寻找用户推荐人
https://leetcode-cn.com/problems/find-customer-referee/
给定表 customer ,里面保存了所有客户信息和他们的推荐人。
+------+------+-----------+
| id | name | referee_id|
+------+------+-----------+
| 1 | Will | NULL |
| 2 | Jane | NULL |
| 3 | Alex | 2 |
| 4 | Bill | NULL |
| 5 | Zack | 1 |
| 6 | Mark | 2 |
+------+------+-----------+
写一个查询语句,返回一个客户列表,列表中客户的推荐人的编号都 不是 2。对于上面的示例数据,结果为:
+------+
| name |
+------+
| Will |
| Jane |
| Bill |
| Zack |
+------+
思路:这道题比较简单,直接用WHERE语句从原表中筛选referee_id!=2或者referee_id is null的记录就可以
SELECT name
FROM customer
WHERE referee_id!=2 OR referee_id is null
585. 2016年的投资
https://leetcode-cn.com/problems/investments-in-2016/
写一个查询语句,将 2016 年 (TIV_2016) 所有成功投资的金额加起来,保留 2 位小数。
对于一个投保人,他在 2016 年成功投资的条件是:
他在 2015 年的投保额 (TIV_2015) 至少跟一个其他投保人在 2015 年的投保额相同。
他所在的城市必须与其他投保人都不同(也就是说维度和经度不能跟其他任何一个投保人完全相同)。
输入格式:
表 insurance 格式如下:| Column Name | Type |
|-------------|---------------|
| PID | INTEGER(11) |
| TIV_2015 | NUMERIC(15,2) |
| TIV_2016 | NUMERIC(15,2) |
| LAT | NUMERIC(5,2) |
| LON | NUMERIC(5,2) |
PID 字段是投保人的投保编号, TIV_2015 是该投保人在2015年的总投保金额, TIV_2016 是该投保人在2016年的投保金额, LAT 是投保人所在城市的维度, LON 是投保人所在城市的经度。样例输入
| PID | TIV_2015 | TIV_2016 | LAT | LON |
|-----|----------|----------|-----|-----|
| 1 | 10 | 5 | 10 | 10 |
| 2 | 20 | 20 | 20 | 20 |
| 3 | 10 | 30 | 20 | 20 |
| 4 | 10 | 40 | 40 | 40 |
样例输出| TIV_2016 |
|----------|
| 45.00 |
解释就如最后一个投保人,第一个投保人同时满足两个条件:
1. 他在 2015 年的投保金额 TIV_2015 为 '10' ,与第三个和第四个投保人在 2015 年的投保金额相同。
2. 他所在城市的经纬度是独一无二的。第二个投保人两个条件都不满足。他在 2015 年的投资 TIV_2015 与其他任何投保人都不相同。
且他所在城市的经纬度与第三个投保人相同。基于同样的原因,第三个投保人投资失败。所以返回的结果是第一个投保人和最后一个投保人的 TIV_2016 之和,结果是 45 。
思路:自连接原表,在连接的时候匹配规则是a.PID!=b.PID且a表的经纬度不在原表中除了自己之外的记录中出现过,然后用WHERE筛选a表和b表中TIV_2015相同的记录,此时一个pid可能会有多条记录,所以还要DISTINCT一下a.pid,同时获取对应的TIV_2016值,最后在外层SUM一下前面得到的所有pid的TIV_2016值
SELECT SUM(t.TIV_2016) AS TIV_2016
FROM
(
SELECT DISTINCT a.pid, a.TIV_2016
FROM insurance a JOIN insurance b
ON a.PID!=b.PID
AND (a.LAT, a.LON) NOT IN(
SELECT LAT, LON
FROM insurance
WHERE PID!=a.PID
)
WHERE a.TIV_2015=b.TIV_2015
GROUP BY a.PID
) t
586. 订单最多的客户
https://leetcode-cn.com/problems/customer-placing-the-largest-number-of-orders/
在表 orders 中找到订单数最多客户对应的 customer_number 。
数据保证订单数最多的顾客恰好只有一位。
表 orders 定义如下:
| Column | Type |
|-------------------|-----------|
| order_number (PK) | int |
| customer_number | int |
| order_date | date |
| required_date | date |
| shipped_date | date |
| status | char(15) |
| comment | char(200) |
样例输入| order_number | customer_number | order_date | required_date | shipped_date | status | comment |
|--------------|-----------------|------------|---------------|--------------|--------|---------|
| 1 | 1 | 2017-04-09 | 2017-04-13 | 2017-04-12 | Closed | |
| 2 | 2 | 2017-04-15 | 2017-04-20 | 2017-04-18 | Closed | |
| 3 | 3 | 2017-04-16 | 2017-04-25 | 2017-04-20 | Closed | |
| 4 | 3 | 2017-04-18 | 2017-04-28 | 2017-04-25 | Closed | |
样例输出| customer_number |
|-----------------|
| 3 |
解释customer_number 为 '3' 的顾客有两个订单,比顾客 '1' 或者 '2' 都要多,因为他们只有一个订单
所以结果是该顾客的 customer_number ,也就是 3 。
进阶: 如果有多位顾客订单数并列最多,你能找到他们所有的 customer_number 吗?
思路:按照customer_number进行分组后,计算每组的order_number的数量,再据此进行降序排序,取第一行记录即为订单数最多的顾客记录
SELECT customer_number
FROM orders
GROUP BY customer_number
ORDER BY COUNT(order_number) DESC
LIMIT 1
595. 大的国家
https://leetcode-cn.com/problems/big-countries/
这里有张 World 表
+-----------------+------------+------------+--------------+---------------+
| name | continent | area | population | gdp |
+-----------------+------------+------------+--------------+---------------+
| Afghanistan | Asia | 652230 | 25500100 | 20343000 |
| Albania | Europe | 28748 | 2831741 | 12960000 |
| Algeria | Africa | 2381741 | 37100000 | 188681000 |
| Andorra | Europe | 468 | 78115 | 3712000 |
| Angola | Africa | 1246700 | 20609294 | 100990000 |
+-----------------+------------+------------+--------------+---------------+
如果一个国家的面积超过 300 万平方公里,或者人口超过 2500 万,那么这个国家就是大国家。编写一个 SQL 查询,输出表中所有大国家的名称、人口和面积。
例如,根据上表,我们应该输出:
+--------------+-------------+--------------+
| name | population | area |
+--------------+-------------+--------------+
| Afghanistan | 25500100 | 652230 |
| Algeria | 37100000 | 2381741 |
+--------------+-------------+--------------+
思路:这题比较简单,就不多说了,直接用WHERE写两个条件就可以了
SELECT name, population, area
FROM World
WHERE area>3000000 OR population>25000000
596. 超过5名学生的课
https://leetcode-cn.com/problems/classes-more-than-5-students/
有一个courses 表 ,有: student (学生) 和 class (课程)。
请列出所有超过或等于5名学生的课。
例如,表:
+---------+------------+
| student | class |
+---------+------------+
| A | Math |
| B | English |
| C | Math |
| D | Biology |
| E | Math |
| F | Computer |
| G | Math |
| H | Math |
| I | Math |
+---------+------------+
应该输出:+---------+
| class |
+---------+
| Math |
+---------+
提示:
学生在每个课中不应被重复计算。
思路:这道题也比较简单,创建子表查询就行,在子表中得到每门课的上课人数,在外表中取上课人数大于等于5的课就行。要注意的是每门课中学生不能重复计算,因此在COUNT()学生的时候要加上DISTINCT
SELECT class
FROM(
SELECT class, COUNT(DISTINCT student) AS cnt
FROM courses
GROUP BY class
) t
WHERE t.cnt>=5
597. 好友申请 I:总体通过率
https://leetcode-cn.com/problems/friend-requests-i-overall-acceptance-rate/
在 Facebook 或者 Twitter 这样的社交应用中,人们经常会发好友申请也会收到其他人的好友申请。
表:FriendRequest
+----------------+---------+
| Column Name | Type |
+----------------+---------+
| sender_id | int |
| send_to_id | int |
| request_date | date |
+----------------+---------+
此表没有主键,它可能包含重复项。
该表包含发送请求的用户的 ID ,接受请求的用户的 ID 以及请求的日期。
表:RequestAccepted+----------------+---------+
| Column Name | Type |
+----------------+---------+
| requester_id | int |
| accepter_id | int |
| accept_date | date |
+----------------+---------+
此表没有主键,它可能包含重复项。
该表包含发送请求的用户的 ID ,接受请求的用户的 ID 以及请求通过的日期。
写一个查询语句,求出好友申请的通过率,用 2 位小数表示。通过率由接受好友申请的数目除以申请总数。
提示:
通过的好友申请不一定都在表 friend_request 中。你只需要统计总的被通过的申请数(不管它们在不在表 FriendRequest 中),并将它除以申请总数,得到通过率
一个好友申请发送者有可能会给接受者发几条好友申请,也有可能一个好友申请会被通过好几次。这种情况下,重复的好友申请只统计一次。
如果一个好友申请都没有,通过率为 0.00 。
查询结果应该如下例所示:
FriendRequest 表:
+-----------+------------+--------------+
| sender_id | send_to_id | request_date |
+-----------+------------+--------------+
| 1 | 2 | 2016/06/01 |
| 1 | 3 | 2016/06/01 |
| 1 | 4 | 2016/06/01 |
| 2 | 3 | 2016/06/02 |
| 3 | 4 | 2016/06/09 |
+-----------+------------+--------------+RequestAccepted 表:
+--------------+-------------+-------------+
| requester_id | accepter_id | accept_date |
+--------------+-------------+-------------+
| 1 | 2 | 2016/06/03 |
| 1 | 3 | 2016/06/08 |
| 2 | 3 | 2016/06/08 |
| 3 | 4 | 2016/06/09 |
| 3 | 4 | 2016/06/10 |
+--------------+-------------+-------------+Result 表:
+-------------+
| accept_rate |
+-------------+
| 0.8 |
+-------------+
总共有 5 个请求,有 4 个不同的通过请求,所以通过率是 0.80
思路:创建两个子表a,b分别统计RequestAccepted 和 FriendRequest中的不同记录数,在外表中将两者相除即可
SELECT IFNULL(ROUND(SUM(a.cnt_accept)/SUM(b.cnt_send), 2), 0) AS accept_rate
FROM
(
SELECT COUNT(*) AS cnt_accept
FROM(
SELECT DISTINCT requester_id, accepter_id
FROM RequestAccepted
) t1
)a,
(
SELECT COUNT(*) AS cnt_send
FROM
(
SELECT DISTINCT sender_id, send_to_id
FROM FriendRequest
) t2
)b