【每日SQL打卡】

  • Post author:
  • Post category:其他




【每日SQL打卡】DAY1|部门工资最高的员工【难度中等】

  • Employee 表包含所有员工信息,每个员工有其对应的 Id, salary 和 department Id。
+----+-------+--------+--------------+
| Id | Name  | Salary | DepartmentId |
+----+-------+--------+--------------+
| 1  | Joe   | 70000  | 1            |
| 2  | Henry | 80000  | 2            |
| 3  | Sam   | 60000  | 2            |
| 4  | Max   | 90000  | 1            |
+----+-------+--------+--------------+
  • Department 表包含公司所有部门的信息。
+----+----------+
| Id | Name     |
+----+----------+
| 1  | IT       |
| 2  | Sales    |
+----+----------+
  • 编写一个 SQL 查询,找出每个部门工资最高的员工。例如,根据上述给定的表格,Max 在 IT 部门有最高工资,Henry 在 Sales 部门有最高工资。
+------------+----------+--------+
| Department | Employee | Salary |
+------------+----------+--------+
| IT         | Max      | 90000  |
| Sales      | Henry    | 80000  |
+------------+----------+--------+



解题思路

  • 方法一:首先找出每个部门的最高工资,然后再去连接Employee表,找到对应的员工姓名以及金额
SELECT 
	t.Department
	,e1.Name Employee
	,e1.Salary
FROM Employee e1
LEFT JOIN 
(
	SELECT 
	d.Id
	,d.Name Department
	,max(e.Salary) 最高工资
	FROM Employee e
	LEFT JOIN Department d ON e.DepartmentId = d.Id
	group by 1,2
) t ON e1.DepartmentId = t.Id AND e1.Salary = t.最高工资
  • 方法二:窗口函数
SELECT 
	 t.Department
	,t.Employee
	,t.Salary
FROM 
(
	SELECT 
		d1.Name  Department
		,e1.Name Employee
		,e1.Salary
		,rank() over(PARTITION BY e1.DepartmentId ORDER BY e1.Salary DESC) AS r_k
	FROM Employee e1
	LEFT JOIN Department d1 ON e1.DepartmentId = d1.Id
) t 
WHERE r_k = 1
  • row_number()函数
SELECT 
	 t.Department
	,t.Employee
	,t.Salary
FROM 
(
	SELECT 
		d1.Name  Department
		,e1.Name Employee
		,e1.Salary
		,row_number() over(PARTITION BY e1.DepartmentId ORDER BY e1.Salary DESC) AS r_k
	FROM Employee e1
	LEFT JOIN Department d1 ON e1.DepartmentId = d1.Id
) t 
WHERE r_k = 1
  • dense_rank()函数
SELECT 
	 t.Department
	,t.Employee
	,t.Salary
FROM 
(
	SELECT 
		d1.Name  Department
		,e1.Name Employee
		,e1.Salary
		,dense_rank() over(PARTITION BY e1.DepartmentId ORDER BY e1.Salary DESC) AS r_k
	FROM Employee e1
	LEFT JOIN Department d1 ON e1.DepartmentId = d1.Id
) t 
WHERE r_k = 1



【每日SQL打卡】DAY2|组合两个表【难度简单】

  • 表1: Person
+-------------+---------+
| 列名        | 类型     |
+-------------+---------+
| PersonId    | int     |
| FirstName   | varchar |
| LastName    | varchar |
+-------------+---------+
  • 表2: Address
+-------------+---------+
| 列名        | 类型    |
+-------------+---------+
| AddressId   | int     |
| PersonId    | int     |
| City        | varchar |
| State       | varchar |
+-------------+---------+
  • AddressId 是上表主键
  • 编写一个 SQL 查询,满足条件:无论 person 是否有地址信息,都需要基于上述两表提供 person 的以下信息:

    FirstName, LastName, City, State



解题思路

  • 考查点:SQL连接中的左连接,本题中person表为主表,通过PersonId左连接Addres获得新表
SELECT 
	P.FirstName
	,P.LastName
	,A.City
	,A.State
FROM Person P 
LEFT JOIN Address A ON P.PersonId  = A.PersonId



【每日SQL打卡】DAY2|连续出现的数字【难度中等】

  • 编写一个 SQL 查询,查找所有至少连续出现三次的数字。
+----+-----+
| Id | Num |
+----+-----+
| 1  |  1  |
| 2  |  1  |
| 3  |  1  |
| 4  |  2  |
| 5  |  1  |
| 6  |  2  |
| 7  |  2  |
+----+-----+
  • 例如,给定上面的 Logs 表, 1 是唯一连续出现至少三次的数字。
+-----------------+
| ConsecutiveNums |
+-----------------+
| 1               |
+-----------------+



解题思路

  • 考查点:窗口函数中前后函数
  • LAG(col,n,DEFAULT) 用于统计窗口内往下第n行值
  • LEAD(col,n,DEFAULT) 用于统计窗口内往上第n行值
SELECT 
t.向上偏移2行 ConsecutiveNums
FROM 
(
SELECT 
	Num
	,LEAD(Num,1,NULL) OVER() 向上偏移1,LEAD(Num,2,NULL) OVER() 向上偏移2FROM Logs
) t 
where t.Num = t.向上偏移1AND t.向上偏移1= t.向上偏移2



【每日SQL打卡】DAY3|删除重复的电子邮箱【难度简单】

  • 编写一个 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 语句
  • 思路:找出相同Email中较大的id后进行删除



解题思路

  • 方法一:利用窗口函数,可以删除两个以上重复项
delete from Person 
where Id in
(
	SELECT 
		Id 
	FROM 
	(
		SELECT 
		Id
		,RANK() OVER(PARTITION BY Email ORDER BY Id asc) r_k
		FROM Person
	) t 
	WHERE r_k >= 2
)
delete from Person 
where Id not in
(
	SELECT 
		Email最小Id 
	FROM 
	(
		SELECT 
		Email
		,min(Id) Email最小Id
		FROM Person
		group by 1
	) t 
)
  • 方法二:首先找出具有重复的Email后,在找出不是最小id的ID后删除
delete from Person 
where Id in
(
	SELECT 
	Id
	FROM Person 
	JOIN 
	(
		SELECT 
			Email 
			,Email最小Id
		FROM 
		(
			SELECT 
			Email
			,min(Id) Email最小Id
			,count(*) 次数
			FROM Person
			group by 1
		) t 
		WHERE 次数 >= 2
	) P2 ON Person.Email = P2.Email
	WHERE Person.Id != P2.Email最小Id
) 
delete1,表2(删除哪个表写哪个表的别名)
from1
inner|left|right|full join2
on 连接条件
where 筛选条件



【每日SQL打卡】DAY 3丨行程和用户【难度困难】

  • 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        |
+------------+-------------------+



解题思路

  • 考查点:利用case when函数
SELECT 
`Day`
,round(取消的非禁止用户生成的订单数量/非禁止用户生成的订单总数,2) `Cancellation Rate`
FROM 
(
	SELECT 
	Request_at `Day`
	,sum(是否取消)   取消的非禁止用户生成的订单数量
	,count(是否取消) 非禁止用户生成的订单总数
	FROM 
	(
	SELECT 
	Id 
	,Trips.Client_Id 
	,Trips.Driver_Id
	,CASE Trips.Status
		WHEN "completed" THEN 0
		WHEN "cancelled_by_driver" THEN 1 
		WHEN "cancelled_by_client" THEN 1 
	END 是否取消
	,Trips.Request_at
	FROM Trips
	LEFT JOIN Users U1 ON Trips.Client_Id = U1.Users_Id
	LEFT JOIN Users U2 ON Trips.Driver_Id = U2.Users_Id
	WHERE U1.Banned != "Yes"
	AND U2.Banned != "Yes"
	) t1
	group by 1
) t2
order by `Day`



【每日SQL打卡】DAY 4丨游戏玩法分析 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  |
+-----------+-------------+



解题思路

  • 思路:这题只涉及Activity表中的player_id以及event_date这两个字段
  • 直接利用聚合函数找出每个玩家对应的最小日期就行
SELECT 
player_id 
,min(event_date) first_login
FROM Activity
GROUP BY 1 



【每日SQL打卡】DAY 4丨员工薪水中位数【难度困难】

  • 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   |
+-----+------------+--------+



解题思路

  • 突然发现sql中求解中位数是一个比较大的难点,一般我们主要会用到sum/min/max/count/avg这五个聚合函数
  • 但sql中没有直接求中位数的函数,下面这种方法是比较好理解的方法
  • 如果一个数是中位数,那么就意味着正序和逆序时其位置是一致的:
  • 更严谨的说,奇数个数字是正逆序排序一致,偶数个数字时,两中位数顺序要互换一下,也就是相差为1。
  • 进而,我们发现无论数字总数是奇数还是偶数,中位数的正逆排序相差要么为0,要么为1。根据这一性质,我们分别实现正逆两遍排序,然后判断数字的排序编号即可。
SELECT 
Id
,Company
,Salary
FROM 
(
SELECT 
Id
,Company
,Salary
,row_number() over(PARTITION BY Company ORDER BY Salary,Id desc) rnt_asc
,row_number() over(PARTITION BY Company ORDER BY Salary desc,Id) rnt_desc
FROM Employee
) t
where abs(cast(rnt_asc as signed) - cast(rnt_desc as signed)) <= 1
  • 上面这种利用窗口函数的方法如果不用cast的方法会出现BIGINT UNSIGNED value is out of range in……的报错

    并且还要注意两个窗口函数后面order by 的部分是有点点不同的

  • 根据定义,我们来找一下 [1, 3, 2] 的中位数。首先 1 不是中位数,因为这个数组有三个元素,却有两个元素 (3,2) 大于 1。3 也不是中位数,

  • 因为有两个元素小于 3。对于 2 来说,大于 2 和 小于 2 的元素数量是相等的,因此 2 是当前数组的中位数。

    当数组长度为 偶数,且元素唯一时,中位数等于排序后 中间两个数 的平均值。

    对这两个数来说,大于当前数的数值个数跟小于当前数的数值个数绝对值之差为 1,恰好等于这个数出现的频率。

  • 结论:不管数组长度是奇是偶,也不管元素是否唯一,中位数出现的频率一定大于等于 大于它的数 和 小于它的数 的绝对值之差。

 SELECT
    e1.Id
    ,e1.Company
    ,e1.Salary
 FROM Employee e1,Employee e2
 WHERE e1.Company = e2.Company
 GROUP BY e1.Company , e1.Salary
 HAVING SUM(e1.Salary = e2.Salary) >= ABS(SUM(SIGN(e1.Salary - e2.Salary)))
ORDER BY e1.Id



【每日SQL打卡】DAY 5丨员工奖金【难度简单】

  • 选出所有 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  |
+-------+-------+



解题思路

  • 考查点:
  • 此题主要考查两个点:一个是左连接,一个是存在null值的判断
  • 判断中直接使用 b.bonus < 1000会将null的数据给排除掉,故要将为null的数值转换成0后在判断,这里使用的是ifnull()函数,也可以使用COALESCE()函数
SELECT 
e.name
,b.bonus
FROM Employee e 
LEFT JOIN Bonus b ON e.empId = b.empId
WHERE IFNULL(b.bonus,0) < 1000
SELECT 
e.name
,b.bonus
FROM Employee e 
LEFT JOIN Bonus b ON e.empId = b.empId
WHERE COALESCE(b.bonus,0) < 1000
SELECT name, bonus  FROM (
SELECT  
A.*,
B.bonus
FROM Employee  A 
LEFT JOIN Bonus  B ON  A.empID=B.empID
) C 
WHERE  bonus<1000 OR bonus IS NULL 



【每日SQL打卡】DAY 5丨至少有5名直接下属的经理【难度中等】

  • 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  |
+-------+
  • 注意: 没有人是自己的下属。



解题思路

  • 此题考查的是表格的自连接,即同为一张表格,前一张表Employee为所有的员工表,后面一张表Employee为所有的员工对应的经理

    两表通过e1.ManagerId = e2.Id(注意需要使用不同的别名)关联后在用having筛选
SELECT 
e2.Name
FROM Employee e1
LEFT JOIN Employee e2 ON e1.ManagerId = e2.Id
group by e2.Name
having count(*) >= 5 



【每日SQL打卡】DAY 6丨统计各专业学生人数【难度中等】

  • 一所大学有 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



解题思路

  • 考查点:
  • 一个是表连接:使用左连接,可以获的没有学生的专业
  • 一个是分组,对department.dept_name进行分组后求和
  • 一个是排序,先对学生人数降序排序,在对department.dept_name升序排序
SELECT 
department.dept_name
,COUNT(student_id) student_number
FROM department
LEFT JOIN student ON department.dept_id = student.dept_id
GROUP BY department.dept_name
ORDER BY student_number DESC,department.dept_name ASC



【每日SQL打卡】DAY 6丨寻找用户推荐人【难度简单】

  • 给定表 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 |
+------+



解题思路

  • 此题同 DAY 5丨员工奖金【难度简单】 存在null值的判断
  • 方法一:直接在判断里面使用referee_id IS NULL
SELECT name
FROM customer
WHERE referee_id != 2 OR referee_id IS NULL 
  • 方法二:使用ifnull函数将为null的值转换成0后在判断
SELECT name
FROM customer
WHERE ifnull(referee_id,0) != 2 



【每日SQL打卡】DAY 7丨大的国家【难度简单】

  • 这里有张 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)



【每日SQL打卡】DAY 7丨好友申请 I :总体通过率【难度简单】

  • 在 Facebook 或者 Twitter 这样的社交应用中,人们经常会发好友申请也会收到其他人的好友申请。现在给如下两个表:
  • 表: friend_request
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
  • 表: request_accepted
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
  • 写一个查询语句,求出好友申请的通过率,用 2 位小数表示。
  • 通过率由接受好友申请的数目除以申请总数。
  • 对于上面的样例数据,你的查询语句应该返回如下结果。
accept_rate
0.80
  • 注意:

  • 通过的好友申请不一定都在表 friend_request 中。

    在这种情况下,你只需要统计总的被通过的申请数(不管它们在不在原来的申请中),

    并将它除以申请总数,得到通过率 一个好友申请发送者有可能会给接受者发几条好友申请,也有可能一个好友申请会被通过好几次。

    这种情况下,重复的好友申请只统计一次。 如果一个好友申请都没有,通过率为 0.00 。

  • 解释: 总共有 5 个申请,其中 4 个是不重复且被通过的好友申请,所以成功率是 0.80 。

  • 进阶:

  • 你能写一个查询语句得到每个月的通过率吗? 你能求出每一天的累计通过率吗?



解题思路

  • 根据通过的好友申请不一定都在表 friend_request 中,说明request_accepted表中数据不全跟friend_request一致(虽然给出的表格是一致的),如果是这样这题的计算方法就是用 总申请数(去重)/总通过申请数(去重)得到这样子我们会发现没有连接条件,可以根据时间来进行连接
SELECT 
ROUND(总通过申请数/总申请数,2) accept_rate
FROM 
(
	SELECT
		"2016" 时间
		,COUNT(*) 总申请数
	FROM 
	(
		SELECT 
		sender_id
		,send_to_id
		,min(request_date)
		FROM friend_request 
		GROUP BY sender_id,send_to_id
	) r_send
) t1 
LEFT JOIN 
(
	SELECT
		"2016" 时间
		,COUNT(*) 总通过申请数
	FROM 
	(
		SELECT 
		requester_id
		,accepter_id
		,min(accept_date)
		FROM request_accepted
		GROUP BY requester_id,accepter_id
	) r_accept 
) t2 ON t1.时间 = t2.时间
  • 每个月的通过率
  • 通过上面的代码需要添加上对应的时间后在进行计算
SELECT 
t1.时间
,ROUND(IFNULL(总通过申请数,0)/总申请数,2) accept_rate
FROM 
(
	SELECT
		substring(申请时间,1,7) 时间
		,COUNT(*) 总申请数
	FROM 
	(
		SELECT 
		sender_id
		,send_to_id
		,min(request_date) 申请时间
		FROM friend_request 
		GROUP BY sender_id,send_to_id
	) r_send
	GROUP BY 时间
) t1 
LEFT JOIN 
(
	SELECT
		substring(接受时间,1,7) 时间
		,COUNT(*) 总通过申请数
	FROM 
	(
		SELECT 
		requester_id
		,accepter_id
		,min(accept_date) 接受时间
		FROM request_accepted
		GROUP BY requester_id,accepter_id
	) r_accept 
	GROUP BY 时间
) t2 ON t1.时间 = t2.时间
  • 每天的通过率
  • 通过上面的代码的时间范围
SELECT 
t1.时间
,ROUND(IFNULL(总通过申请数,0)/总申请数,2) accept_rate
FROM 
(
	SELECT
		substring(申请时间,1,10) 时间
		,COUNT(*) 总申请数
	FROM 
	(
		SELECT 
		sender_id
		,send_to_id
		,min(request_date) 申请时间
		FROM friend_request 
		GROUP BY sender_id,send_to_id
	) r_send
	GROUP BY 时间
) t1 
LEFT JOIN 
(
	SELECT
		substring(接受时间,1,10) 时间
		,COUNT(*) 总通过申请数
	FROM 
	(
		SELECT 
		requester_id
		,accepter_id
		,min(accept_date) 接受时间
		FROM request_accepted
		GROUP BY requester_id,accepter_id
	) r_accept 
	GROUP BY 时间
) t2 ON t1.时间 = t2.时间



【每日SQL打卡】DAY 7丨字节面试真题【难度困难】

  • 要求:
  • 已知前4列,用sql算出res列,即同一个uid下,上一次is_succ=1 时的 id是谁?
  • 样例数据:
id time     uid   is_suc res
1 2020-01-01 1      1 Null
2 2020-01-02 1      0 1
3 2020-01-03 1      0 1
4 2020-01-04 1      1 1
5 2020-01-05 1      0 4
6 2020-01-06 2      0 Null
7 2020-01-07 2      1 Null
8 2020-01-08 2      0 7 



解题思路

  • 首先找出每一个uid下成功的数据
SELECT 
id
,time 
,uid
FROM user_succ 
WHERE is_suc = 1
  • 在将原始表格u1与上面的表格u2通过uid连接,并且u1的时间一定大于u2的时间(只与比自己小的时间连接)

    这样的话,id=5会同时连接id=1与1d=4的数据,在通过筛选u2的时间为最大的那个数据即为我们需要的数据
SELECT
	u1.id
	,u1.time
	,u1.uid
	,u1.is_suc
	,u2.id res 
	,u2.time
FROM user_succ u1
LEFT JOIN 
(
	SELECT 
	id
	,time 
	,uid
	FROM user_succ 
	WHERE is_suc = 1
) u2 ON u1.uid = u2.uid
AND u1.time > u2.time 
  • 这里利用窗口函数来找到我们需要的数据,这里注意窗口函数分组的依据是 u1.id和u1.uid
  • 对u2.time进行降序排列,排在第一位的数据即为我们需要的数据
SELECT
	t.id
	,t.time
	,t.is_suc
	,t.res
FROM 
(
SELECT
	u1.id
	,u1.time 
	,u1.uid
	,u1.is_suc
	,u2.id res 
	,u2.time 时间
	,row_number() over(PARTITION BY u1.id,u1.uid ORDER BY u2.time DESC) r_k
FROM user_succ u1
LEFT JOIN 
(
	SELECT 
	id
	,time 
	,uid
	FROM user_succ 
	WHERE is_suc = 1
) u2 ON u1.uid = u2.uid
AND u1.time > u2.time 
) t
where r_k = 1



【每日SQL打卡】DAY 8丨判断三角形【难度简单】

  • 一个小学生 Tim 的作业是判断三条线段是否能形成一个三角形。
  • 然而,这个作业非常繁重,因为有几百组线段需要判断。
  • 假设表 triangle 保存了所有三条线段的三元组 x, y, z ,你能帮 Tim 写一个查询语句,来判断每个三元组是否可以组成一个三角形吗?
x	y	z
13	15	30
10	20	15
  • 对于如上样例数据,你的查询语句应该返回如下结果:
x	y	z	triangle
13	15	30	No
10	20	15	Yes



解题思路

  • 利用case when函数进行判断
  • 任意两边之差小于第三边:这里需要加上绝对值
  • 任意两边之和大于第三边
  • 为了更加严谨也可以先对x,y,z进行判断,是否为正数
SELECT 
x
,y
,z 
,case 
	when (	
		(ABS(x - y) < z AND (x + y) > z)
	AND (ABS(x - z) < y AND (x + z) > y) 
	AND (ABS(y - z) < x AND (y + z) > x)
		)then "Yes"
	else "No"
end triangle
FROM triangle



【每日SQL打卡】DAY 8丨平面上的最近距离【难度中等】

  • 表 point_2d 保存了所有点(多于 2 个点)的坐标 (x,y) ,这些点在平面上两两不重合。
  • 写一个查询语句找到两点之间的最近距离,保留 2 位小数。
x	y
-1	-1
0	0
-1	-2
  • 最近距离在点 (-1,-1) 和(-1,2) 之间,距离为 1.00 。所以输出应该为:
shortest
1.00
  • 注意:任意点之间的最远距离小于 10000 。
  • 提示:POW 函数



解题思路

  • 利用POW 函数求出两点之前的距离的平方
  • 在利用sqrt()开方
  • 排除距离为0的数据,距离为0说明为同一组x,y数据
SELECT 
MIN(sqrt(距离)) shortest
FROM 
(
	SELECT
		pow(p1.x-p2.x,2)+pow(p1.y-p2.y,2) 距离
	FROM point_2d p1,point_2d p2
) t 
WHERE 距离 != 0



【每日SQL打卡】DAY 9丨平均工资:部门与公司比较【难度困难】

  • 给如下两个表,写一个查询语句,求出在每一个工资发放日,每个部门的平均工资与公司的平均工资的比较结果 (高 / 低 / 相同)。
  • 表: salary
id	employee_id	amount	pay_date
1	1	9000	2017-03-31
2	2	6000	2017-03-31
3	3	10000	2017-03-31
4	1	7000	2017-02-28
5	2	6000	2017-02-28
6	3	8000	2017-02-28
  • employee_id 字段是表 employee 中 employee_id 字段的外键。
employee_id	department_id
1	1
2	2
3	2

对于如上样例数据,结果为:

pay_month	department_id	comparison
2017-03	1	higher
2017-03	2	lower
2017-02	1	same
2017-02	2	same
  • 解释
  • 在三月,公司的平均工资是 (9000+6000+10000)/3 = 8333.33…

    由于部门 ‘1’ 里只有一个 employee_id 为 ‘1’ 的员工,所以部门 ‘1’ 的平均工资就是此人的工资 9000 。因为 9000 > 8333.33 ,所以比较结果是 ‘higher’。
  • 第二个部门的平均工资为 employee_id 为 ‘2’ 和 ‘3’ 两个人的平均工资,为 (6000+10000)/2=8000 。因为 8000 < 8333.33 ,所以比较结果是 ‘lower’ 。
  • 在二月用同样的公式求平均工资并比较,比较结果为 ‘same’ ,因为部门 ‘1’ 和部门 ‘2’ 的平均工资与公司的平均工资相同,都是 7000 。



解题思路

  • 首先通过employee_id连接两表,找出对应员工的部门
  • 在对时间、每个部门求对应薪资的平均值
SELECT 
s.pay_date
,e.department_id
,avg(s.amount) 每个部门的平均工资
FROM Salary s 
LEFT JOIN employee e ON s.employee_id = e.employee_id
GROUP BY s.pay_date,e.department_id
  • 再在上表的基础上,利用窗口函数求出每一个工资发放日,公司的平均工资
SELECT
substring(t1.pay_date,1,7) pay_month
,t1.department_id
,t1.每个部门的平均工资
,avg(t1.每个部门的平均工资) OVER(PARTITION BY t1.pay_date) 公司的平均工资
FROM 
(
	SELECT 
	s.pay_date 
	,e.department_id
	,avg(s.amount) 每个部门的平均工资
	FROM Salary s 
	LEFT JOIN employee e ON s.employee_id = e.employee_id
	GROUP BY s.pay_date,e.department_id
) t1
  • 最后将每个部门的平均工资与公司的平均工资进行比较
SELECT 
	 t2.pay_month
	,t2.department_id
	,case 
		when t2.每个部门的平均工资 > t2.公司的平均工资 then "higher"
		when t2.每个部门的平均工资 = t2.公司的平均工资 then "same"
		when t2.每个部门的平均工资 < t2.公司的平均工资 then "lower"
	end comparison
FROM 
(
	SELECT
	substring(t1.pay_date,1,7) pay_month
	,t1.department_id
	,t1.每个部门的平均工资
	,avg(t1.每个部门的平均工资) OVER(PARTITION BY t1.pay_date) 公司的平均工资
	FROM 
	(
		SELECT 
		s.pay_date 
		,e.department_id
		,avg(s.amount) 每个部门的平均工资
		FROM Salary s 
		LEFT JOIN employee e ON s.employee_id = e.employee_id
		GROUP BY s.pay_date,e.department_id
	) t1
) t2
  • 这题的难点其实是平均工资在两个维度上统计,两个维度的表格的详细级别是不同的也可以单独算出公司的平均工资,在通过时间进行连接后比较



【每日SQL打卡】DAY 9丨有趣的电影【难度简单】

  • 某城市开了一家新的电影院,吸引了很多人过来看电影。该电影院特别注意用户体验,专门有个 LED显示板做电影推荐,上面公布着影评和相关电影描述。
  • 作为该电影院的信息部主管,您需要编写一个 SQL查询,找出所有影片描述为非 boring (不无聊) 的并且 id 为奇数 的影片,结果请按等级 rating 排列。
  • 例如,下表 cinema:
+---------+-----------+--------------+-----------+
|   id    | movie     |  description |  rating   |
+---------+-----------+--------------+-----------+
|   1     | War       |   great 3D   |   8.9     |
|   2     | Science   |   fiction    |   8.5     |
|   3     | irish     |   boring     |   6.2     |
|   4     | Ice song  |   Fantacy    |   8.6     |
|   5     | House card|   Interesting|   9.1     |
+---------+-----------+--------------+-----------+
  • 对于上面的例子,则正确的输出是为:
+---------+-----------+--------------+-----------+
|   id    | movie     |  description |  rating   |
+---------+-----------+--------------+-----------+
|   5     | House card|   Interesting|   9.1     |
|   1     | War       |   great 3D   |   8.9     |
+---------+-----------+--------------+-----------+



解题思路

  • 主要考查筛选以及排序
  • 非 boring (不无聊)的影片:description != “boring”
  • id 为奇数:id除以2余数为1
  • rating 排列:按rating降序排列
SELECT 
id
,movie
,description
,rating
FROM cinema
WHERE description != "boring"
AND MOD(id,2) = 1 
ORDER BY rating DESC



【每日SQL打卡】DAY 10丨换座位【难度中等】

  • 小美是一所中学的信息科技老师,她有一张 seat 座位表,平时用来储存学生名字和与他们相对应的座位 id。
  • 其中纵列的 id 是连续递增的
  • 小美想改变相邻俩学生的座位。
  • 你能不能帮她写一个 SQL query 来输出小美想要的结果呢?
+---------+---------+
|    id   | student |
+---------+---------+
|    1    | Abbot   |
|    2    | Doris   |
|    3    | Emerson |
|    4    | Green   |
|    5    | Jeames  |
+---------+---------+
  • 假如数据输入的是上表,则输出结果如下:
+---------+---------+
|    id   | student |
+---------+---------+
|    1    | Doris   |
|    2    | Abbot   |
|    3    | Green   |
|    4    | Emerson |
|    5    | Jeames  |
+---------+---------+
  • 注意:
  • 如果学生人数是奇数,则不需要改变最后一个同学的座位。
  • 提示:开窗



解题思路

  • 这题的思路是根据学生之前的id重新给学生换成相对应的id
id student 新id
1 Abbot 2
2 Doris 1
3 Emerson 4
4 Green 3
5 Jeames 5
  • 那我们看看怎么构造出学生新的id
id student 分组1 分组2 新id
1 Abbot 1 2 2
2 Doris 1 1 1
3 Emerson 2 2 4
4 Green 2 1 3
5 Jeames 3 1 5
  • 首先将根据ceil(id/2)两两一组,得到分组1字段
SELECT 
id		
,student
,ceil(id/2) 分组1
FROM seat 
id student 分组1
1 Abbot 1
2 Doris 1
3 Emerson 2
4 Green 2
5 Jeames 3
  • 在利用窗口函数,在分组1的基础上,用id进行降序排列,得到分组2字段
SELECT 
t1.id
,t1.student
,t1.分组1
,rank() over(PARTITION BY t1.分组1 ORDER BY t1.id DESC) 分组2
FROM 
(
	SELECT 
	id		
	,student
	,ceil(id/2) 分组1
	FROM seat 
) t1 
id student 分组1 分组2
1 Abbot 1 2
2 Doris 1 1
3 Emerson 2 2
4 Green 2 1
5 Jeames 3 1
  • 最后,继续利用窗口函数,在整体的基础上,利用分组1,分组2进行排序,得到新的id
SELECT
	row_number() OVER(ORDER BY t2.分组1,t2.分组2) id
	,t2.student
FROM 
(
	SELECT 
	t1.id
	,t1.student
	,t1.分组1
	,rank() over(PARTITION BY t1.分组1 ORDER BY t1.id DESC) 分组2
	FROM 
	(
		SELECT 
		id		
		,student
		,ceil(id/2) 分组1
		FROM seat 
	) t1 
) t2 
  • 此题的难点在于怎样重新构造学生的新id
  • 最开始我的想法是交换学生的姓名,迟迟不知道怎么做,后面想想直接构造一个新的id不就解决问题了嘛
  • 补充:交换学生姓名
SELECT 
	id 
	,student 
	,lag_s
	,lead_s
	,CASE 
		WHEN id%2 = 1 AND lead_s IS NOT NULL THEN lead_s
		WHEN id%2 = 0 THEN lag_s
		WHEN lead_s THEN student 
	END student 
FROM
(
	SELECT 
	*
	,LAG(student) OVER() AS lag_s
	,LEAD(student) OVER() AS lead_s
	FROM seat
) t 



【每日SQL打卡】DAY 10丨买下所有产品的客户【难度中等】

  • Customer 表:

    ±————±——–+

    | Column Name | Type |

    ±————±——–+

    | customer_id | int |

    | product_key | int |

    ±————±——–+
  • product_key 是 Customer 表的外键。
  • Product 表:

    ±————±——–+

    | Column Name | Type |

    ±————±——–+

    | product_key | int |

    ±————±——–+
  • product_key 是这张表的主键。
  • 写一条 SQL 查询语句,从 Customer 表中查询购买了 Product 表中所有产品的客户的 id。
  • Customer 表:
+-------------+-------------+
| customer_id | product_key |
+-------------+-------------+
| 1           | 5           |
| 2           | 6           |
| 3           | 5           |
| 3           | 6           |
| 1           | 6           |
+-------------+-------------+
  • Product 表:
+-------------+
| product_key |
+-------------+
| 5           |
| 6           |
+-------------+
  • Result 表:
+-------------+
| customer_id |
+-------------+
| 1           |
| 3           |
+-------------+
  • 购买了所有产品(5 和 6)的客户的 id 是 1 和 3 。



解题思路

  • 首先找到每个客户买到的不同产品
  • 在对每个客户分组,筛选出客户购买的产品数量与Product 表中的产品数量一致的,即为购买所有产品的客户
SELECT
	customer_id		
FROM 
	(
	SELECT 
		p.product_key
		,c.customer_id
	FROM Product p 
	LEFT JOIN customer c ON p.product_key = c.product_key
	GROUP BY c.customer_id,p.product_key
	) t
GROUP BY customer_id
having count(product_key) = (SELECT COUNT(product_key) FROM Product)



【每日SQL打卡】DAY 10丨买下所有产品的客户【难度中等】销售表 Sales:

+-------------+-------+
| Column Name | Type  |
+-------------+-------+
| sale_id     | int   |
| product_id  | int   |
| year        | int   |
| quantity    | int   |
| price       | int   |
+-------------+-------+
  • sale_id 是此表的主键。
  • product_id 是产品表的外键。
  • 请注意,价格是按每单位计的。
  • 产品表 Product:
+--------------+---------+
| Column Name  | Type    |
+--------------+---------+
| product_id   | int     |
| product_name | varchar |
+--------------+---------+
  • product_id 是此表的主键。
  • 编写一个 SQL 查询,选出每个销售产品的 第一年 的 产品 id、年份、数量 和 价格。
  • 查询结果格式如下:
  • Sales table:
+---------+------------+------+----------+-------+
| sale_id | product_id | year | quantity | price |
+---------+------------+------+----------+-------+ 
| 1       | 100        | 2008 | 10       | 5000  |
| 2       | 100        | 2009 | 12       | 5000  |
| 7       | 200        | 2011 | 15       | 9000  |
+---------+------------+------+----------+-------+
  • Product table:
+------------+--------------+
| product_id | product_name |
+------------+--------------+
| 100        | Nokia        |
| 200        | Apple        |
| 300        | Samsung      |
+------------+--------------+
  • Result table:
+------------+------------+----------+-------+
| product_id | first_year | quantity | price |
+------------+------------+----------+-------+ 
| 100        | 2008       | 10       | 5000  |
| 200        | 2011       | 15       | 9000  |
+------------+------------+----------+-------+



解题思路

  • 此题最直接的思路就是利用窗口函数,对同一件产品销售时间进行排序
  • 排序第一的就是我们需要的数据
SELECT
	t.product_id
	,t.year first_year
	,t.quantity 
	,t.price 
FROM 
(
	SELECT 
		product_id
		,year
		,quantity
		,price
		,row_number() OVER(PARTITION BY product_id ORDER BY year) r_k
	FROM sales
) t
WHERE r_k = 1
  • 当然不利用窗口函数同样可以解决问题
  • 首先找出每个产品销售最小时间
  • 在用原始的sales表格通过产品id以及时间连接上表后得到结果
SELECT 
	s.product_id
	,s.year first_year
	,s.quantity 
	,s.price 
FROM sales s
JOIN 
(
	SELECT 
		product_id
		,MIN(year) 最小时间
	FROM sales
	GROUP BY product_id
) t ON s.product_id = t.product_id AND s.year = t.最小时间



【每日SQL打卡】DAY 11丨产品销售分析 II【难度简单】

  • 销售表:Sales
+-------------+-------+
| Column Name | Type  |
+-------------+-------+
| sale_id     | int   |
| product_id  | int   |
| year        | int   |
| quantity    | int   |
| price       | int   |
+-------------+-------+
  • sale_id 是这个表的主键。
  • product_id 是 Product 表的外键。
  • 请注意价格是每单位的。
  • 产品表:Product
+--------------+---------+
| Column Name  | Type    |
+--------------+---------+
| product_id   | int     |
| product_name | varchar |
+--------------+---------+
  • product_id 是这个表的主键。
  • 编写一个 SQL 查询,按产品 id product_id 来统计每个产品的销售总量。
  • 查询结果格式如下面例子所示:
  • Sales 表:
+---------+------------+------+----------+-------+
| sale_id | product_id | year | quantity | price |
+---------+------------+------+----------+-------+ 
| 1       | 100        | 2008 | 10       | 5000  |
| 2       | 100        | 2009 | 12       | 5000  |
| 7       | 200        | 2011 | 15       | 9000  |
+---------+------------+------+----------+-------+
  • Product 表:
+------------+--------------+
| product_id | product_name |
+------------+--------------+
| 100        | Nokia        |
| 200        | Apple        |
| 300        | Samsung      |
+------------+--------------+

Result 表:

+--------------+----------------+
| product_id   | total_quantity |
+--------------+----------------+
| 100          | 22             |
| 200          | 15             |
+--------------+----------------+



解题思路

  • 从Result表格可以看出需要的是计算sales表格中每一样product_id的数量之和
  • 直接用Sales表对product_id分组后求quantity和就行
SELECT
product_id
,SUM(quantity) total_quantity
FROM sales 
GROUP BY product_id 
  • 本题采用左连接
SELECT
p.product_id
,SUM(s.quantity) total_quantity
FROM sales s 
LEFT JOIN product p ON s.product_id = p.product_id
GROUP BY p.product_id 



【每日SQL打卡】DAY 12丨游戏玩法分析 V【难度困难】

  • Activity 活动记录表
+--------------+---------+
| Column Name  | Type    |
+--------------+---------+
| player_id    | int     |
| device_id    | int     |
| event_date   | date    |
| games_played | int     |
+--------------+---------+
  • (player_id,event_date)是此表的主键
  • 这张表显示了某些游戏的玩家的活动情况
  • 每一行是一个玩家的记录,他在某一天使用某个设备注销之前登录并玩了很多游戏(可能是 0)

    我们将玩家的安装日期定义为该玩家的第一个登录日。

    我们还将某个日期 X 的第 1 天留存时间定义为安装日期为 X 的玩家的数量,他们在 X 之后的一天重新登录,除以安装日期为 X 的玩家的数量,四舍五入到小数点后两位。
  • 编写一个 SQL 查询,报告每个安装日期、当天安装游戏的玩家数量和第一天的留存时间。
  • 查询结果格式如下所示:
  • Activity 表:
+-----------+-----------+------------+--------------+
| 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-01 | 0            |
| 3         | 4         | 2016-07-03 | 5            |
+-----------+-----------+------------+--------------+
  • Result 表:
+------------+----------+----------------+
| install_dt | installs | Day1_retention |
+------------+----------+----------------+
| 2016-03-01 | 2        | 0.50           |
| 2017-06-25 | 1        | 0.00           |
+------------+----------+----------------+
  • 玩家 1 和 3 在 2016-03-01 安装了游戏,
  • 但只有玩家 1 在 2016-03-02 重新登录,所以 2016-03-01 的第一天留存时间是 1/2=0.50
  • 玩家 2 在 2017-06-25 安装了游戏,
  • 但在 2017-06-26 没有重新登录,因此 2017-06-25 的第一天留存时间为 0/1=0.00



解题思路

  • 首先找出每个玩家的安装日期
SELECT 
player_id
,MIN(event_date) install_dt
FROM Activity
GROUP BY player_id
  • 在将上表作为主表与原始的Activity 表通过player_id以及event_date进行连接
  • 这里需要将上表install_dt加上一天与event_date进行连接
SELECT 
t.install_dt
,round(COUNT(DISTINCT a.player_id)/COUNT(DISTINCT t.player_id),2)  Day1_retention
FROM 
(
	SELECT 
	player_id
	,MIN(event_date) install_dt
	FROM Activity
	GROUP BY player_id
) t 
LEFT JOIN Activity a ON t.player_id = a.player_id AND ADDDATE(t.install_dt,INTERVAL 1 DAY) = a.event_date
GROUP BY t.install_dt



【每日SQL打卡】DAY 12丨销售分析 III【难度简单】

  • Table: Product
+--------------+---------+
| Column Name  | Type    |
+--------------+---------+
| product_id   | int     |
| product_name | varchar |
| unit_price   | int     |
+--------------+---------+
  • product_id 是这个表的主键
  • Table: Sales
+-------------+---------+
| Column Name | Type    |
+-------------+---------+
| seller_id   | int     |
| product_id  | int     |
| buyer_id    | int     |
| sale_date   | date    |
| quantity    | int     |
| price       | int     |
+------ ------+---------+
  • 这个表没有主键,它可以有重复的行
  • product_id 是 Product 表的外键
  • 编写一个SQL查询,报告2019年春季才售出的产品。即仅在2019-01-01至2019-03-31(含)之间出售的商品。
  • 查询结果格式如下所示:
  • Product table:
+------------+--------------+------------+
| product_id | product_name | unit_price |
+------------+--------------+------------+
| 1          | S8           | 1000       |
| 2          | G4           | 800        |
| 3          | iPhone       | 1400       |
+------------+--------------+------------+
  • Sales table:
+-----------+------------+----------+------------+----------+-------+
| seller_id | product_id | buyer_id | sale_date  | quantity | price |
+-----------+------------+----------+------------+----------+-------+
| 1         | 1          | 1        | 2019-01-21 | 2        | 2000  |
| 1         | 2          | 2        | 2019-02-17 | 1        | 800   |
| 2         | 2          | 3        | 2019-06-02 | 1        | 800   |
| 3         | 3          | 4        | 2019-05-13 | 2        | 2800  |
+-----------+------------+----------+------------+----------+-------+
  • Result table:
+-------------+--------------+
| product_id  | product_name |
+-------------+--------------+
| 1           | S8           |
+-------------+--------------+
  • id为1的产品仅在2019年春季销售,其他两个产品在之后销售。



解题思路

  • 此题我是利用的case when函数对sale_date进行判断
  • 只要sale_date是在2019-01-01至2019-03-31(含)就判断为0,其余为1,在对这列求和为0即为仅在2019-01-01至2019-03-31(含)之间出售的商品
SELECT 
product_id
,product_name
FROM 
(
	SELECT 
	p.product_id
	,p.product_name
	,CASE 
		WHEN sale_date BETWEEN "2019-01-01" AND "2019-03-31" THEN "0"
		ELSE "1"
	END 是否只在2019年春季售出
	FROM Product p 
	LEFT JOIN Sales s ON p.product_id = s.product_id
) t 
GROUP BY product_id,product_name
HAVING SUM(是否只在2019年春季售出) = 0



【每日SQL打卡】DAY 13丨每日新用户统计【难度中等】

  • Traffic 表:
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| user_id       | int     |
| activity      | enum    |
| activity_date | date    |
+---------------+---------+
  • 该表没有主键,它可能有重复的行。
  • activity 列是 ENUM 类型,可能取 (‘login’, ‘logout’, ‘jobs’, ‘groups’, ‘homepage’) 几个值之一。
  • 编写一个 SQL 查询,以查询从今天起最多 90 天内,每个日期该日期首次登录的用户数。假设今天是 2019-06-30.
  • 查询结果格式如下例所示:
  • Traffic 表:
+---------+----------+---------------+
| user_id | activity | activity_date |
+---------+----------+---------------+
| 1       | login    | 2019-05-01    |
| 1       | homepage | 2019-05-01    |
| 1       | logout   | 2019-05-01    |
| 2       | login    | 2019-06-21    |
| 2       | logout   | 2019-06-21    |
| 3       | login    | 2019-01-01    |
| 3       | jobs     | 2019-01-01    |
| 3       | logout   | 2019-01-01    |
| 4       | login    | 2019-06-21    |
| 4       | groups   | 2019-06-21    |
| 4       | logout   | 2019-06-21    |
| 5       | login    | 2019-03-01    |
| 5       | logout   | 2019-03-01    |
| 5       | login    | 2019-06-21    |
| 5       | logout   | 2019-06-21    |
+---------+----------+---------------+

Result 表:

+------------+-------------+
| login_date | user_count  |
+------------+-------------+
| 2019-05-01 | 1           |
| 2019-06-21 | 2           |
+------------+-------------+
  • 请注意,我们只关心用户数非零的日期.
  • ID 为 5 的用户第一次登陆于 2019-03-01,因此他不算在 2019-06-21 的的统计内。



解题思路

  • 首先找出每个用户首次登陆的日期
SELECT 
user_id
,min(activity_date)  首次登陆日期
FROM Traffic
WHERE activity = "login"
GROUP BY user_id
  • 在再上表的基础上筛选首次登陆日期大于2019-04-01的用户
  • 再在首次登陆日期对用户计数
SELECT
首次登陆日期
,COUNT(user_id) user_count
FROM 
(
	SELECT 
	user_id
	,min(activity_date)  首次登陆日期
	FROM Traffic
	WHERE activity = "login"
	GROUP BY user_id
) t
WHERE 首次登陆日期 >= SUBDATE("2019-06-30",INTERVAL 90 DAY)
GROUP BY 首次登陆日期



【每日SQL打卡】DAY 13丨每位学生的最高成绩【难度中等】

  • 表:Enrollments
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| student_id    | int     |
| course_id     | int     |
| grade         | int     |
+---------------+---------+
  • (student_id, course_id) 是该表的主键。
  • 编写一个 SQL 查询,查询每位学生获得的最高成绩和它所对应的科目,若科目成绩并列,取 course_id 最小的一门。查询结果需按student_id 增序进行排序。
  • 查询结果格式如下所示:
  • Enrollments 表:
+------------+-------------------+
| student_id | course_id | grade |
+------------+-----------+-------+
| 2          | 2         | 95    |
| 2          | 3         | 95    |
| 1          | 1         | 90    |
| 1          | 2         | 99    |
| 3          | 1         | 80    |
| 3          | 2         | 75    |
| 3          | 3         | 82    |
+------------+-----------+-------+
  • Result 表:
+------------+-------------------+
| student_id | course_id | grade |
+------------+-----------+-------+
| 1          | 2         | 99    |
| 2          | 2         | 95    |
| 3          | 3         | 82    |
+------------+-----------+-------+
  • 提示:开窗



解题思路

SELECT 
t.student_id
,t.course_id
,t.grade
FROM 
(
	SELECT 
	student_id
	,course_id
	,grade
	,row_number() OVER(PARTITION BY student_id ORDER BY grade DESC,course_id) r_k
	FROM Enrollments
) t 
WHERE r_k = 1



【每日SQL打卡】DAY 14丨报告的记录 I【难度简单】

  • 动作表:Actions
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| user_id       | int     |
| post_id       | int     |
| action_date   | date    | 
| action        | enum    |
| extra         | varchar |
+---------------+---------+
  • 此表没有主键,所以可能会有重复的行。
  • action 字段是 ENUM 类型的,包含:(‘view’, ‘like’, ‘reaction’, ‘comment’, ‘report’, ‘share’)
  • extra 字段是可选的信息(可能为 null),其中的信息例如有:1.报告理由(a reason for report) 2.反应类型(a type of reaction)
  • 编写一条SQL,查询每种 报告理由(report reason)在昨天的报告数量。假设今天是 2019-07-05。
  • 查询及结果的格式示例:
  • Actions table:
+---------+---------+-------------+--------+--------+
| user_id | post_id | action_date | action | extra  |
+---------+---------+-------------+--------+--------+
| 1       | 1       | 2019-07-01  | view   | null   |
| 1       | 1       | 2019-07-01  | like   | null   |
| 1       | 1       | 2019-07-01  | share  | null   |
| 2       | 4       | 2019-07-04  | view   | null   |
| 2       | 4       | 2019-07-04  | report | spam   |
| 3       | 4       | 2019-07-04  | view   | null   |
| 3       | 4       | 2019-07-04  | report | spam   |
| 4       | 3       | 2019-07-02  | view   | null   |
| 4       | 3       | 2019-07-02  | report | spam   |
| 5       | 2       | 2019-07-04  | view   | null   |
| 5       | 2       | 2019-07-04  | report | racism |
| 5       | 5       | 2019-07-04  | view   | null   |
| 5       | 5       | 2019-07-04  | report | racism |
+---------+---------+-------------+--------+--------+
  • Result table:
+---------------+--------------+
| report_reason | report_count |
+---------------+--------------+
| spam          | 1            |
| racism        | 2            |
+---------------+--------------+ 
  • 注意,我们只关心报告数量非零的结果。



解题思路

  • 首先筛选出action_date为2019-07-04、action为report的数据
  • 在对extra进行聚合计算post_id的数量
SELECT 
extra report_reason
,COUNT(DISTINCT post_id) report_count
FROM Actions
WHERE action_date = "2019-07-04"
AND action = "report"
GROUP BY extra



【每日SQL打卡】DAY 14丨重新格式化部门表【难度中等】

  • 部门表 Department:
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| id            | int     |
| revenue       | int     |
| month         | varchar |
+---------------+---------+
  • (id, month) 是表的联合主键。
  • 这个表格有关于每个部门每月收入的信息。
  • 月份(month)可以取下列值 [“Jan”,“Feb”,“Mar”,“Apr”,“May”,“Jun”,“Jul”,“Aug”,“Sep”,“Oct”,“Nov”,“Dec”]。
  • 编写一个 SQL 查询来重新格式化表,使得新的表中有一个部门 id 列和一些对应 每个月 的收入(revenue)列。
  • 查询结果格式如下面的示例所示:
  • Department 表:
+------+---------+-------+
| id   | revenue | month |
+------+---------+-------+
| 1    | 8000    | Jan   |
| 2    | 9000    | Jan   |
| 3    | 10000   | Feb   |
| 1    | 7000    | Feb   |
| 1    | 6000    | Mar   |
+------+---------+-------+
  • 查询得到的结果表:
+------+-------------+-------------+-------------+-----+-------------+
| id   | Jan_Revenue | Feb_Revenue | Mar_Revenue | ... | Dec_Revenue |
+------+-------------+-------------+-------------+-----+-------------+
| 1    | 8000        | 7000        | 6000        | ... | null        |
| 2    | 9000        | null        | null        | ... | null        |
| 3    | null        | 10000       | null        | ... | null        |
+------+-------------+-------------+-------------+-----+-------------+
  • 注意,结果表有 13 列 (1个部门 id 列 + 12个月份的收入列)。



解题思路

SELECT `id`,
SUM(CASE `month` WHEN 'Jan' THEN `revenue` ELSE NULL END) `Jan_Revenue`,
SUM(CASE `month` WHEN 'Feb' THEN `revenue` ELSE NULL END) `Feb_Revenue`,
SUM(CASE `month` WHEN 'Mar' THEN `revenue` ELSE NULL END) `Mar_Revenue`,
SUM(CASE `month` WHEN 'Apr' THEN `revenue` ELSE NULL END) `Apr_Revenue`,
SUM(CASE `month` WHEN 'May' THEN `revenue` ELSE NULL END) `May_Revenue`,
SUM(CASE `month` WHEN 'Jun' THEN `revenue` ELSE NULL END) `Jun_Revenue`,
SUM(CASE `month` WHEN 'Jul' THEN `revenue` ELSE NULL END) `Jul_Revenue`,
SUM(CASE `month` WHEN 'Aug' THEN `revenue` ELSE NULL END) `Aug_Revenue`,
SUM(CASE `month` WHEN 'Sep' THEN `revenue` ELSE NULL END) `Sep_Revenue`,
SUM(CASE `month` WHEN 'Oct' THEN `revenue` ELSE NULL END) `Oct_Revenue`,
SUM(CASE `month` WHEN 'Nov' THEN `revenue` ELSE NULL END) `Nov_Revenue`,
SUM(CASE `month` WHEN 'Dec' THEN `revenue` ELSE NULL END) `Dec_Revenue`
FROM Department
GROUP BY `id`;



【每日SQL打卡】DAY 15丨查询活跃业务【难度中等】

  • 事件表:Events
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| business_id   | int     |
| event_type    | varchar |
| occurences    | int     | 
+---------------+---------+
  • 此表的主键是 (business_id, event_type)。
  • 表中的每一行记录了某种类型的事件在某些业务中多次发生的信息。
  • 写一段 SQL 来查询所有活跃的业务。
  • 如果一个业务的某个事件类型的发生次数大于此事件类型在所有业务中的平均发生次数,并且该业务至少有两个这样的事件类型,那么该业务就可被看做是活跃业务。
  • 查询结果格式如下所示:
  • Events table:
+-------------+------------+------------+
| business_id | event_type | occurences |
+-------------+------------+------------+
| 1           | reviews    | 7          |
| 3           | reviews    | 3          |
| 1           | ads        | 11         |
| 2           | ads        | 7          |
| 3           | ads        | 6          |
| 1           | page views | 3          |
| 2           | page views | 12         |
+-------------+------------+------------+
  • 结果表
+-------------+
| business_id |
+-------------+
| 1           |
+-------------+ 
  • ‘reviews’、 ‘ads’ 和 ‘page views’ 的总平均发生次数分别是 (7+3)/2=5, (11+7+6)/3=8, (3+12)/2=7.5。
  • id 为 1 的业务有 7 个 ‘reviews’ 事件(大于 5)和 11 个 ‘ads’ 事件(大于 8),所以它是活跃业务。



解题思路

  • 最直接的方法是利用窗口函数
  • 首先计算出事件类型在所有业务中的平均发生次数,即AVG(occurences) OVER(PARTITION by event_type) avg_ct
SELECT 
business_id
,event_type
,occurences
,AVG(occurences) OVER(PARTITION by event_type) avg_ct
FROM Events
  • 在上表的基础上,对business_id聚合,计算event_type的数量
SELECT 
business_id
FROM 
(
	SELECT 
	business_id
	,event_type
	,occurences
	,AVG(occurences) OVER(PARTITION by event_type) avg_ct
	FROM Events
) t 
WHERE occurences > avg_ct
GROUP BY business_id
HAVING COUNT(DISTINCT event_type) >= 2 



【每日SQL打卡】DAY 15丨用户购买平台【难度困难】

  • 支出表: Spending
+-------------+---------+
| Column Name | Type    |
+-------------+---------+
| user_id     | int     |
| spend_date  | date    |
| platform    | enum    | 
| amount      | int     |
+-------------+---------+
  • 这张表记录了用户在一个在线购物网站的支出历史,该在线购物平台同时拥有桌面端(‘desktop’)和手机端(‘mobile’)的应用程序。
  • 这张表的主键是 (user_id, spend_date, platform)。
  • 平台列 platform 是一种 ENUM ,类型为(‘desktop’, ‘mobile’)。
  • 写一段 SQL 来查找每天 仅 使用手机端用户、仅 使用桌面端用户和 同时 使用桌面端和手机端的用户人数和总支出金额。
  • 查询结果格式如下例所示:
  • Spending table:
+---------+------------+----------+--------+
| user_id | spend_date | platform | amount |
+---------+------------+----------+--------+
| 1       | 2019-07-01 | mobile   | 100    |
| 1       | 2019-07-01 | desktop  | 100    |
| 2       | 2019-07-01 | mobile   | 100    |
| 2       | 2019-07-02 | mobile   | 100    |
| 3       | 2019-07-01 | desktop  | 100    |
| 3       | 2019-07-02 | desktop  | 100    |
+---------+------------+----------+--------+
  • Result table:
+------------+----------+--------------+-------------+
| spend_date | platform | total_amount | total_users |
+------------+----------+--------------+-------------+
| 2019-07-01 | desktop  | 100          | 1           |
| 2019-07-01 | mobile   | 100          | 1           |
| 2019-07-01 | both     | 200          | 1           |
| 2019-07-02 | desktop  | 100          | 1           |
| 2019-07-02 | mobile   | 100          | 1           |
| 2019-07-02 | both     | 0            | 0           |
+------------+----------+--------------+-------------+ 
  • 在 2019-07-01, 用户1 同时 使用桌面端和手机端购买, 用户2 仅 使用了手机端购买,而用户3 仅 使用了桌面端购买。
  • 在 2019-07-02, 用户2 仅 使用了手机端购买, 用户3 仅 使用了桌面端购买,且没有用户 同时 使用桌面端和手机端购买。



解题思路

  • 首先需要对用户按照时间以及平台进行分组
SELECT 
user_id
,spend_date
,CASE 
	WHEN ct = 2 THEN "both"
	WHEN ct = 1 AND platform = "desktop" THEN "desktop"
	WHEN ct = 1 AND platform = "mobile" THEN  "mobile"
END new_platform
FROM 
(
SELECT 
	user_id
	,spend_date
	,platform
	,COUNT(platform) over(PARTITION BY spend_date,user_id) ct 
FROM Spending
GROUP BY 1,2,3
) t1 
group by user_id,spend_date,new_platform
  • 在用原始表与上面的表格通过用户和时间进行连接后对时间、平台计算金额和用户
SELECT
	s.spend_date
	,t2.new_platform
	,SUM(amount) total_amount
	,COUNT(DISTINCT s.user_id) total_users
FROM Spending s 
LEFT JOIN 
(
	SELECT 
	user_id
	,spend_date
	,CASE 
		WHEN ct = 2 THEN "both"
		WHEN ct = 1 AND platform = "desktop" THEN "desktop"
		WHEN ct = 1 AND platform = "mobile" THEN  "mobile"
	END new_platform
	FROM 
	(
		SELECT 
			user_id
			,spend_date
			,platform
			,COUNT(platform) over(PARTITION BY spend_date,user_id) ct 
		FROM Spending
		GROUP BY 1,2,3
	) t1 
	group by user_id,spend_date,new_platform
) t2 ON s.spend_date = t2.spend_date
AND s.user_id = t2.user_id
GROUP BY s.spend_date,t2.new_platform
  • 通过上面的表格发现与结果表格差了一行2019-07-02中both为0的行
  • 需要去创造一个每个日期下平台有desktop, mobile,both的表格
  • 利用union语句创建下方表格后与上面表格通过时间、平台连接即可
SELECT DISTINCT spend_date,'mobile' AS platform 
FROM spending
UNION ALL
SELECT DISTINCT spend_date,'desktop' AS platform 
FROM spending
UNION ALL
SELECT DISTINCT spend_date,'both' AS platform 
FROM spending
SELECT 
	A.spend_date
	,A.platform
	,IFNULL(B.total_amount,0) total_amount
	,IFNULL(B.total_users,0)  total_users
FROM 
(
	SELECT DISTINCT spend_date,'mobile' AS platform 
	FROM spending
	UNION ALL
	SELECT DISTINCT spend_date,'desktop' AS platform 
	FROM spending
	UNION ALL
	SELECT DISTINCT spend_date,'both' AS platform 
	FROM spending
) A 
LEFT JOIN 
(
	SELECT
		s.spend_date
		,t2.new_platform
		,SUM(amount) total_amount
		,COUNT(DISTINCT s.user_id) total_users
	FROM Spending s 
	LEFT JOIN 
	(
		SELECT 
		user_id
		,spend_date
		,CASE 
			WHEN ct = 2 THEN "both"
			WHEN ct = 1 AND platform = "desktop" THEN "desktop"
			WHEN ct = 1 AND platform = "mobile" THEN  "mobile"
		END new_platform
		FROM 
		(
			SELECT 
				user_id
				,spend_date
				,platform
				,COUNT(platform) over(PARTITION BY spend_date,user_id) ct 
			FROM Spending
			GROUP BY 1,2,3
		) t1 
		group by user_id,spend_date,new_platform
	) t2 ON s.spend_date = t2.spend_date
	AND s.user_id = t2.user_id
	GROUP BY s.spend_date,t2.new_platform
) B ON A.spend_date = B.spend_date AND A.platform  = B.new_platform



【每日SQL打卡】DAY 16丨报告的记录 II【难度中等】

  • 动作表: Actions
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| user_id       | int     |
| post_id       | int     |
| action_date   | date    |
| action        | enum    |
| extra         | varchar |
+---------------+---------+
  • 这张表没有主键,并有可能存在重复的行。

  • action 列的类型是 ENUM,可能的值为 (‘view’, ‘like’, ‘reaction’, ‘comment’, ‘report’, ‘share’)。

  • extra 列拥有一些可选信息,例如:报告理由(a reason for report)或反应类型(a type of reaction)等。

  • 移除表: Removals

+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| post_id       | int     |
| remove_date   | date    | 
+---------------+---------+
  • 这张表的主键是 post_id。
  • 这张表的每一行表示一个被移除的帖子,原因可能是由于被举报或被管理员审查。
  • 编写一段 SQL 来查找:在被报告为垃圾广告的帖子中,被移除的帖子的每日平均占比,四舍五入到小数点后 2 位。
  • 查询结果的格式如下:
  • Actions table:
+---------+---------+-------------+--------+--------+
| user_id | post_id | action_date | action | extra  |
+---------+---------+-------------+--------+--------+
| 1       | 1       | 2019-07-01  | view   | null   |
| 1       | 1       | 2019-07-01  | like   | null   |
| 1       | 1       | 2019-07-01  | share  | null   |
| 2       | 2       | 2019-07-04  | view   | null   |
| 2       | 2       | 2019-07-04  | report | spam   |
| 3       | 4       | 2019-07-04  | view   | null   |
| 3       | 4       | 2019-07-04  | report | spam   |
| 4       | 3       | 2019-07-02  | view   | null   |
| 4       | 3       | 2019-07-02  | report | spam   |
| 5       | 2       | 2019-07-03  | view   | null   |
| 5       | 2       | 2019-07-03  | report | racism |
| 5       | 5       | 2019-07-03  | view   | null   |
| 5       | 5       | 2019-07-03  | report | racism |
+---------+---------+-------------+--------+--------+
  • Removals table:
+---------+-------------+
| post_id | remove_date |
+---------+-------------+
| 2       | 2019-07-20  |
| 3       | 2019-07-18  |
+---------+-------------+
  • Result table:
+-----------------------+
| average_daily_percent |
+-----------------------+
| 75.00                 |
+-----------------------+
  • 2019-07-04 的垃圾广告移除率是 50%,因为有两张帖子被报告为垃圾广告,但只有一个得到移除。
  • 2019-07-02 的垃圾广告移除率是 100%,因为有一张帖子被举报为垃圾广告并得到移除。
  • 其余几天没有收到垃圾广告的举报,因此平均值为:(50 + 100) / 2 = 75%
  • 注意,输出仅需要一个平均值即可,我们并不关注移除操作的日期。



解题思路

  • 首先找到extra为spam(垃圾邮件)的数据
  • 由于Actions没有主键,存在重复行,所以需要对post_id,action_date去重
SELECT 
distinct post_id
,action_date
FROM Actions
WHERE extra = "spam"
  • 在将上表直接连接Removals表
SELECT 
distinct a.post_id
,a.action_date
,r.post_id
FROM Actions a 
LEFT JOIN Removals r ON a.post_id = r.post_id
WHERE extra = "spam" 
  • 根据action_date计算每天的比例
SELECT 
action_date
,COUNT(post_2)/COUNT(post_1) 比例
FROM 
(
	SELECT 
	distinct a.post_id post_1
	,a.action_date
	,r.post_id post_2
	FROM Actions a 
	LEFT JOIN Removals r ON a.post_id = r.post_id
	WHERE extra = "spam" 
) t1
GROUP BY action_date
  • 最后计算平均值
SELECT 
ROUND(AVG(比例),2)*100 average_daily_percent
FROM 
(
	SELECT 
	action_date
	,COUNT(post_2)/COUNT(post_1) 比例
	FROM 
	(
		SELECT 
		distinct a.post_id post_1
		,a.action_date
		,r.post_id post_2
		FROM Actions a 
		LEFT JOIN Removals r ON a.post_id = r.post_id
		WHERE extra = "spam" 
	) t1
	GROUP BY action_date
) t2



【每日SQL打卡】DAY 16丨市场分析 II【难度困难】

  • 表: Users
+----------------+---------+
| Column Name    | Type    |
+----------------+---------+
| user_id        | int     |
| join_date      | date    |
| favorite_brand | varchar |
+----------------+---------+
  • user_id 是该表的主键
  • 表中包含一位在线购物网站用户的个人信息,用户可以在该网站出售和购买商品。
  • 表: Orders
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| order_id      | int     |
| order_date    | date    |
| item_id       | int     |
| buyer_id      | int     |
| seller_id     | int     |
+---------------+---------+
  • order_id 是该表的主键
  • item_id 是 Items 表的外键
  • buyer_id 和 seller_id 是 Users 表的外键
  • 表: Items
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| item_id       | int     |
| item_brand    | varchar |
+---------------+---------+
  • item_id 是该表的主键
  • 写一个 SQL 查询确定每一个用户按日期顺序卖出的第二件商品的品牌是否是他们最喜爱的品牌。如果一个用户卖出少于两件商品,查询的结果是 no 。
  • 题目保证没有一个用户在一天中卖出超过一件商品
  • 下面是查询结果格式的例子:
  • Users table:
+---------+------------+----------------+
| user_id | join_date  | favorite_brand |
+---------+------------+----------------+
| 1       | 2019-01-01 | Lenovo         |
| 2       | 2019-02-09 | Samsung        |
| 3       | 2019-01-19 | LG             |
| 4       | 2019-05-21 | HP             |
+---------+------------+----------------+
  • Orders table:
+----------+------------+---------+----------+-----------+
| order_id | order_date | item_id | buyer_id | seller_id |
+----------+------------+---------+----------+-----------+
| 1        | 2019-08-01 | 4       | 1        | 2         |
| 2        | 2019-08-02 | 2       | 1        | 3         |
| 3        | 2019-08-03 | 3       | 2        | 3         |
| 4        | 2019-08-04 | 1       | 4        | 2         |
| 5        | 2019-08-04 | 1       | 3        | 4         |
| 6        | 2019-08-05 | 2       | 2        | 4         |
+----------+------------+---------+----------+-----------+
  • Items table:
+---------+------------+
| item_id | item_brand |
+---------+------------+
| 1       | Samsung    |
| 2       | Lenovo     |
| 3       | LG         |
| 4       | HP         |
+---------+------------+
  • Result table:
+-----------+--------------------+
| seller_id | 2nd_item_fav_brand |
+-----------+--------------------+
| 1         | no                 |
| 2         | yes                |
| 3         | yes                |
| 4         | no                 |
+-----------+--------------------+
  • id 为 1 的用户的查询结果是 no,因为他什么也没有卖出
  • id为 2 和 3 的用户的查询结果是 yes,因为他们卖出的第二件商品的品牌是他们自己最喜爱的品牌
  • id为 4 的用户的查询结果是 no,因为他卖出的第二件商品的品牌不是他最喜爱的品牌



解题思路

  • 首先通过Orders表找出每一个用户按日期顺序卖出的第二件商品的item_id
SELECT 
seller_id
,item_id 售卖品牌
FROM 
(
	SELECT 
	seller_id
	,item_id
	,ROW_NUMBER() over(PARTITION BY seller_id ORDER BY order_date) r_k
	FROM Orders
) t
where r_k = 2
  • 在将user表先于Items通过品牌连接找到用户喜爱品牌对应的item_id
  • 在与上表连接,找到用户对应的第二件商品的item_id
SELECT 
u.user_id
,Items.item_id 喜爱的品牌
,t1.售卖品牌
FROM Users u 
LEFT JOIN Items ON u.favorite_brand = Items.item_brand
LEFT JOIN 
(
	SELECT 
	seller_id
	,item_id  售卖品牌
	FROM 
	(
		SELECT 
		seller_id
		,item_id
		,ROW_NUMBER() over(PARTITION BY seller_id ORDER BY order_date) r_k
		FROM Orders
	) t
	where r_k = 2
) t1 ON u.user_id = t1.seller_id
  • 最后进行对每个用户喜爱的品牌与售卖品牌进行判断
SELECT 
t2.user_id seller_id
,case 
	when 售卖品牌 is null then "no"
	when 喜爱的品牌 = 售卖品牌 then "yes"
	when 喜爱的品牌 != 售卖品牌 then "no"
end 2nd_item_fav_brand
FROM 
(
	SELECT 
	u.user_id
	,Items.item_id 喜爱的品牌
	,t1.售卖品牌
	FROM Users u 
	LEFT JOIN Items ON u.favorite_brand = Items.item_brand
	LEFT JOIN 
	(
		SELECT 
		seller_id
		,item_id  售卖品牌
		FROM 
		(
			SELECT 
			seller_id
			,item_id
			,ROW_NUMBER() over(PARTITION BY seller_id ORDER BY order_date) r_k
			FROM Orders
		) t
		where r_k = 2
	) t1 ON u.user_id = t1.seller_id
) t2 



【每日SQL打卡】DAY 17丨文章浏览 I【难度简单】

  • Views 表:
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| article_id    | int     |
| author_id     | int     |
| viewer_id     | int     |
| view_date     | date    |
+---------------+---------+
  • 此表无主键,因此可能会存在重复行。
  • 此表的每一行都表示某人在某天浏览了某位作者的某篇文章。
  • 请注意,同一人的 author_id 和 viewer_id 是相同的。
  • 请编写一条 SQL 查询以找出所有浏览过自己文章的作者,结果按照 id 升序排列。
  • 查询结果的格式如下所示:
  • Views 表:
+------------+-----------+-----------+------------+
| article_id | author_id | viewer_id | view_date  |
+------------+-----------+-----------+------------+
| 1          | 3         | 5         | 2019-08-01 |
| 1          | 3         | 6         | 2019-08-02 |
| 2          | 7         | 7         | 2019-08-01 |
| 2          | 7         | 6         | 2019-08-02 |
| 4          | 7         | 1         | 2019-07-22 |
| 3          | 4         | 4         | 2019-07-21 |
| 3          | 4         | 4         | 2019-07-21 |
+------------+-----------+-----------+------------+
  • 结果表:
+------+
| id   |
+------+
| 4    |
| 7    |
+------+



解题思路

  • 此题筛选条件主要是根据”所有浏览过自己文章的作者”说明在一条数据中author_id与viewer_id相等
  • 在根据”可能会存在重复行”需要进行去重,并按照id排序
SELECT 
distinct author_id as id 
FROM Views
WHERE author_id = viewer_id
ORDER BY author_id



【每日SQL打卡】DAY 17丨文章浏览 II【难度中等】

+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| article_id    | int     |
| author_id     | int     |
| viewer_id     | int     |
| view_date     | date    |
+---------------+---------+
  • 此表无主键,因此可能会存在重复行。此表的每一行都表示某人在某天浏览了某位作者的某篇文章。
  • 请注意,同一人的 author_id 和 viewer_id 是相同的。
  • 编写一条 SQL 查询来找出在同一天阅读至少两篇文章的人,结果按照 id 升序排序。
  • 查询结果的格式如下:
  • Views table:
+------------+-----------+-----------+------------+
| article_id | author_id | viewer_id | view_date  |
+------------+-----------+-----------+------------+
| 1          | 3         | 5         | 2019-08-01 |
| 3          | 4         | 5         | 2019-08-01 |
| 1          | 3         | 6         | 2019-08-02 |
| 2          | 7         | 7         | 2019-08-01 |
| 2          | 7         | 6         | 2019-08-02 |
| 4          | 7         | 1         | 2019-07-22 |
| 3          | 4         | 4         | 2019-07-21 |
| 3          | 4         | 4         | 2019-07-21 |
+------------+-----------+-----------+------------+
  • Result table:
+------+
| id   |
+------+
| 5    |
| 6    |
+------+



解题思路

  • 首先根据article_id,viewer_id ,view_date 这三个字段对数据去重
  • 去重后的数据是每个人每天浏览的不同文章
SELECT 
article_id
,viewer_id 
,view_date 
FROM Views
GROUP BY article_id,viewer_id ,view_date 

在根据上表,筛选出同一天阅读至少两篇文章的人
即对于每个人来说article_id计数要大于等于2
SELECT 
viewer_id id 
FROM 
(
	SELECT 
	article_id
	,viewer_id 
	,view_date 
	FROM Views
	GROUP BY article_id,viewer_id,view_date 
) t 
GROUP BY viewer_id,view_date
HAVING COUNT(article_id) >= 2
ORDER BY viewer_id



【每日SQL打卡】DAY 18丨即时食物配送 I【难度简单】配送表: Delivery

+-----------------------------+---------+
| Column Name                 | Type    |
+-----------------------------+---------+
| delivery_id                 | int     |
| customer_id                 | int     |
| order_date                  | date    |
| customer_pref_delivery_date | date    |
+-----------------------------+---------+
  • delivery_id 是表的主键。
  • 该表保存着顾客的食物配送信息,顾客在某个日期下了订单,
  • 并指定了一个期望的配送日期(和下单日期相同或者在那之后)。
  • 如果顾客期望的配送日期和下单日期相同,则该订单称为 「即时订单」,否则称为「计划订单」。
  • 写一条 SQL 查询语句获取即时订单所占的百分比, 保留两位小数。
  • 查询结果如下所示:
  • Delivery 表:
+-------------+-------------+------------+-----------------------------+
| delivery_id | customer_id | order_date | customer_pref_delivery_date |
+-------------+-------------+------------+-----------------------------+
| 1           | 1           | 2019-08-01 | 2019-08-02                  |
| 2           | 5           | 2019-08-02 | 2019-08-02                  |
| 3           | 1           | 2019-08-11 | 2019-08-11                  |
| 4           | 3           | 2019-08-24 | 2019-08-26                  |
| 5           | 4           | 2019-08-21 | 2019-08-22                  |
| 6           | 2           | 2019-08-11 | 2019-08-13                  |
+-------------+-------------+------------+-----------------------------+
  • Result 表:
+----------------------+
| immediate_percentage |
+----------------------+
| 33.33                |
+----------------------+
  • 2 和 3 号订单为即时订单,其他的为计划订单。



解题思路

  • 通过case when 函数进行判断后计算
SELECT 
ROUND(SUM(是否为即时订单)/COUNT(delivery_id)*100,2) immediate_percentage
FROM 
(
SELECT 
delivery_id
,CASE WHEN 
	order_date = customer_pref_delivery_date THEN "1"
	ELSE "0"
END 是否为即时订单
FROM Delivery
) t 



【每日SQL打卡】DAY 18丨即时食物配送 II【难度中等】配送表: Delivery

+-----------------------------+---------+
| Column Name                 | Type    |
+-----------------------------+---------+
| delivery_id                 | int     |
| customer_id                 | int     |
| order_date                  | date    |
| customer_pref_delivery_date | date    |
+-----------------------------+---------+
  • delivery_id 是表的主键。
  • 该表保存着顾客的食物配送信息,顾客在某个日期下了订单,并指定了一个期望的配送日期(和下单日期相同或者在那之后)。
  • 如果顾客期望的配送日期和下单日期相同,则该订单称为 「即时订单」,否则称为「计划订单」。「首次订单」是顾客最早创建的订单。我们保证一个顾客只会有一个「首次订单」。
  • 写一条 SQL 查询语句获取即时订单在所有用户的首次订单中的比例。保留两位小数。
  • 查询结果如下所示:
  • Delivery 表:
+-------------+-------------+------------+-----------------------------+
| delivery_id | customer_id | order_date | customer_pref_delivery_date |
+-------------+-------------+------------+-----------------------------+
| 1           | 1           | 2019-08-01 | 2019-08-02                  |
| 2           | 2           | 2019-08-02 | 2019-08-02                  |
| 3           | 1           | 2019-08-11 | 2019-08-12                  |
| 4           | 3           | 2019-08-24 | 2019-08-24                  |
| 5           | 3           | 2019-08-21 | 2019-08-22                  |
| 6           | 2           | 2019-08-11 | 2019-08-13                  |
| 7           | 4           | 2019-08-09 | 2019-08-09                  |
+-------------+-------------+------------+-----------------------------+
  • Result 表:
+----------------------+
| immediate_percentage |
+----------------------+
| 50.00                |
+----------------------+
  • 1 号顾客的 1 号订单是首次订单,并且是计划订单。
  • 2 号顾客的 2 号订单是首次订单,并且是即时订单。
  • 3 号顾客的 5 号订单是首次订单,并且是计划订单。
  • 4 号顾客的 7 号订单是首次订单,并且是即时订单。
  • 因此,一半顾客的首次订单是即时的。



解题思路

  • 首先找到每个客户的首次订单并进行判断
SELECT 
delivery_id
,customer_id
,是否为即时订单
FROM 
(
SELECT 
delivery_id
,customer_id
,order_date
,customer_pref_delivery_date
,CASE WHEN 
	order_date = customer_pref_delivery_date THEN "1"
	ELSE "0"
END 是否为即时订单
,ROW_NUMBER() OVER(PARTITION BY customer_id ORDER BY order_date) r_k
FROM Delivery
) t1
where r_k = 1
  • 在计算即时订单在所有用户的首次订单中的比例
SELECT 
round(sum(是否为即时订单)/COUNT(是否为即时订单)*100,2) immediate_percentage
FROM 
(
	SELECT 
	delivery_id
	,customer_id
	,是否为即时订单
	FROM 
	(
	SELECT 
	delivery_id
	,customer_id
	,order_date
	,customer_pref_delivery_date
	,CASE WHEN 
		order_date = customer_pref_delivery_date THEN "1"
		ELSE "0"
	END 是否为即时订单
	,ROW_NUMBER() OVER(PARTITION BY customer_id ORDER BY order_date) r_k
	FROM Delivery
	) t1
	where r_k = 1
) t2



【每日SQL打卡】DAY 19丨行转列【难度中等】

  • 部门表 Department:
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| id            | int     |
| revenue       | int     |
| month         | varchar |
+---------------+---------+
  • (id, month) 是表的联合主键。
  • 这个表格有关于每个部门每月收入的信息。
  • 月份(month)可以取下列值 [“Jan”,“Feb”,“Mar”,“Apr”,“May”,“Jun”,“Jul”,“Aug”,“Sep”,“Oct”,“Nov”,“Dec”]。
  • 编写一个 SQL 查询来重新格式化表,
  • 使得新的表中有一个部门 id 列和一些对应 每个月 的收入(revenue)列。
  • 查询结果格式如下面的示例所示:
  • Department 表:
+------+---------+-------+
| id   | revenue | month |
+------+---------+-------+
| 1    | 8000    | Jan   |
| 2    | 9000    | Jan   |
| 3    | 10000   | Feb   |
| 1    | 7000    | Feb   |
| 1    | 6000    | Mar   |
+------+---------+-------+
  • 查询得到的结果表:
+------+-------------+-------------+-------------+-----+-------------+
| id   | Jan_Revenue | Feb_Revenue | Mar_Revenue | ... | Dec_Revenue |
+------+-------------+-------------+-------------+-----+-------------+
| 1    | 8000        | 7000        | 6000        | ... | null        |
| 2    | 9000        | null        | null        | ... | null        |
| 3    | null        | 10000       | null        | ... | null        |
+------+-------------+-------------+-------------+-----+-------------+
  • 注意,结果表有 13 列 (1个部门 id 列 + 12个月份的收入列)。



解题思路

此题同【每日SQL打卡】DAY 14丨重新格式化部门表【难度中等】

SELECT `id`,
SUM(CASE `month` WHEN 'Jan' THEN `revenue` ELSE NULL END) `Jan_Revenue`,
SUM(CASE `month` WHEN 'Feb' THEN `revenue` ELSE NULL END) `Feb_Revenue`,
SUM(CASE `month` WHEN 'Mar' THEN `revenue` ELSE NULL END) `Mar_Revenue`,
SUM(CASE `month` WHEN 'Apr' THEN `revenue` ELSE NULL END) `Apr_Revenue`,
SUM(CASE `month` WHEN 'May' THEN `revenue` ELSE NULL END) `May_Revenue`,
SUM(CASE `month` WHEN 'Jun' THEN `revenue` ELSE NULL END) `Jun_Revenue`,
SUM(CASE `month` WHEN 'Jul' THEN `revenue` ELSE NULL END) `Jul_Revenue`,
SUM(CASE `month` WHEN 'Aug' THEN `revenue` ELSE NULL END) `Aug_Revenue`,
SUM(CASE `month` WHEN 'Sep' THEN `revenue` ELSE NULL END) `Sep_Revenue`,
SUM(CASE `month` WHEN 'Oct' THEN `revenue` ELSE NULL END) `Oct_Revenue`,
SUM(CASE `month` WHEN 'Nov' THEN `revenue` ELSE NULL END) `Nov_Revenue`,
SUM(CASE `month` WHEN 'Dec' THEN `revenue` ELSE NULL END) `Dec_Revenue`
FROM Department
GROUP BY `id`;



【每日SQL打卡】DAY 19丨最后一个能进入电梯的人【难度中等】

  • 表: Queue
+-------------+---------+
| Column Name | Type    |
+-------------+---------+
| person_id   | int     |
| person_name | varchar |
| weight      | int     |
| turn        | int     |
+-------------+---------+
  • person_id 是这个表的主键。
  • 该表展示了所有等待电梯的人的信息。
  • 表中 person_id 和 turn 列将包含从 1 到 n 的所有数字,其中 n 是表中的行数。
  • 电梯最大载重量为 1000。
  • 写一条 SQL 查询语句查找最后一个能进入电梯且不超过重量限制的 person_name 。题目确保队列中第一位的人可以进入电梯 。
  • 查询结果如下所示 :
  • Queue 表
+-----------+-------------------+--------+------+
| person_id | person_name       | weight | turn |
+-----------+-------------------+--------+------+
| 5         | George Washington | 250    | 1    |
| 3         | John Adams        | 350    | 2    |
| 6         | Thomas Jefferson  | 400    | 3    |
| 2         | Will Johnliams    | 200    | 4    |
| 4         | Thomas Jefferson  | 175    | 5    |
| 1         | James Elephant    | 500    | 6    |
+-----------+-------------------+--------+------+
  • Result 表
+-------------------+
| person_name       |
+-------------------+
| Thomas Jefferson  |
+-------------------+
  • 为了简化,Queue 表按 turn 列由小到大排序。
  • 上例中 George Washington(id 5), John Adams(id 3) 和 Thomas Jefferson(id 6) 将可以进入电梯,因为他们的体重和为 250 + 350 + 400 = 1000。
  • Thomas Jefferson(id 6) 是最后一个体重合适并进入电梯的人。



解题思路

  • 此题思路主要是累计求和,按照turn排序对weight累计求和
  • 找到累计求和小于1000且turn最大的那个人
SELECT
 	person_name 
FROM Queue
WHERE turn = 
(
	SELECT 
	MAX(turn)
	FROM 
	(
		SELECT 
			 person_id
			,person_name 
			,weight
			,turn
			,SUM(weight) OVER(ORDER BY turn) 累计求和
		FROM Queue
	) t1 
	WHERE 累计求和 <= 1000 
) 



【每日SQL打卡】DAY 20丨查询结果的质量和占比【难度简单】

  • 查询表 Queries
+-------------+---------+
| Column Name | Type    |
+-------------+---------+
| query_name  | varchar |
| result      | varchar |
| position    | int     |
| rating      | int     |
+-------------+---------+
  • 此表没有主键,并可能有重复的行。
  • 此表包含了一些从数据库中收集的查询信息。
  • “位置”(position)列的值为 1 到 500 。
  • “评分”(rating)列的值为 1 到 5 。评分小于 3 的查询被定义为质量很差的查询。
  • 将查询结果的质量 quality 定义为:
  • 各查询结果的评分与其位置之间比率的平均值。
  • 将劣质查询百分比 poor_query_percentage 为:
  • 评分小于 3 的查询结果占全部查询结果的百分比。
  • 编写一组 SQL 来查找每次查询的名称(query_name)、质量(quality) 和 劣质查询百分比(poor_query_percentage)。
  • 质量(quality) 和劣质查询百分比(poor_query_percentage) 都应四舍五入到小数点后两位。
  • 查询结果格式如下所示:
  • Queries table:
+------------+-------------------+----------+--------+
| query_name | result            | position | rating |
+------------+-------------------+----------+--------+
| Dog        | Golden Retriever  | 1        | 5      |
| Dog        | German Shepherd   | 2        | 5      |
| Dog        | Mule              | 200      | 1      |
| Cat        | Shirazi           | 5        | 2      |
| Cat        | Siamese           | 3        | 3      |
| Cat        | Sphynx            | 7        | 4      |
+------------+-------------------+----------+--------+
  • Result table:
+------------+---------+-----------------------+
| query_name | quality | poor_query_percentage |
+------------+---------+-----------------------+
| Dog        | 2.50    | 33.33                 |
| Cat        | 0.66    | 33.33                 |
+------------+---------+-----------------------+
  • Dog 查询结果的质量为 ((5 / 1) + (5 / 2) + (1 / 200)) / 3 = 2.50
  • Dog 查询结果的劣质查询百分比为 (1 / 3) * 100 = 33.33
  • Cat 查询结果的质量为 ((2 / 5) + (3 / 3) + (4 / 7)) / 3 = 0.66
  • Cat 查询结果的劣质查询百分比为 (1 / 3) * 100 = 33.33



解题思路

  • 此题思路首先计算每一条查询结果的情况,算出查询结果的评分与其位置之间比率
  • 同时判断是否为劣质查询
SELECT 
query_name
,case when rating < 3 then 1 else 2 end 劣质查询
,round(rating/position,2) 比率
FROM Queries
  • 输出结果
query_name  比率  劣质查询  
----------  ------  --------------
Dog              5               0
Dog            2.5               0
Dog              0               1
Cat            0.4               1
Cat              1               0
Cat           0.57               0
  • 在通过比例以及劣质查询计算对应的查询结果的质量以及查询结果的劣质查询百分比
SELECT
t.query_name
,round(avg(t.比率),2) quality
,round((sum(t.劣质查询)/COUNT(t.劣质查询))*100,2) poor_query_percentage
FROM 
(
	SELECT 
	query_name
	,round(rating/position,2) 比率
	,case when rating < 3 then 1 else 0 end 劣质查询
	FROM Queries
) t 
GROUP BY t.query_name
  • 输出结果
query_name  quality  poor_query_percentage  
----------  -------  -----------------------
Dog             2.5                    33.33
Cat            0.66                    33.33



【每日SQL打卡】DAY 20丨查询球队积分【难度中等】

  • Table: Teams
+---------------+----------+
| Column Name   | Type     |
+---------------+----------+
| team_id       | int      |
| team_name     | varchar  |
+---------------+----------+
  • 此表的主键是 team_id,表中的每一行都代表一支独立足球队。
  • Table: Matches
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| match_id      | int     |
| host_team     | int     |
| guest_team    | int     | 
| host_goals    | int     |
| guest_goals   | int     |
+---------------+---------+
  • 此表的主键是 match_id,表中的每一行都代表一场已结束的比赛,比赛的主客队分别由它们自己的 id 表示,他们的进球由 host_goals 和 guest_goals 分别表示。
  • 积分规则如下:

    • 赢一场得三分;
    • 平一场得一分;
    • 输一场不得分。
  • 写出一条SQL语句以查询每个队的 team_id,team_name 和 num_points。结果根据 num_points 降序排序,如果有两队积分相同,那么这两队按 team_id 升序排序。
  • 查询结果格式如下:
  • Teams table:
+-----------+--------------+
| team_id   | team_name    |
+-----------+--------------+
| 10        | Leetcode FC  |
| 20        | NewYork FC   |
| 30        | Atlanta FC   |
| 40        | Chicago FC   |
| 50        | Toronto FC   |
+-----------+--------------+
  • Matches table:
+------------+--------------+---------------+-------------+--------------+
| match_id   | host_team    | guest_team    | host_goals  | guest_goals  |
+------------+--------------+---------------+-------------+--------------+
| 1          | 10           | 20            | 3           | 0            |
| 2          | 30           | 10            | 2           | 2            |
| 3          | 10           | 50            | 5           | 1            |
| 4          | 20           | 30            | 1           | 0            |
| 5          | 50           | 30            | 1           | 0            |
+------------+--------------+---------------+-------------+--------------+
  • Result table:
+------------+--------------+---------------+
| team_id    | team_name    | num_points    |
+------------+--------------+---------------+
| 10         | Leetcode FC  | 7             |
| 20         | NewYork FC   | 3             |
| 50         | Toronto FC   | 3             |
| 30         | Atlanta FC   | 1             |
| 40         | Chicago FC   | 0             |
+------------+--------------+---------------+



解题思路

  • 首先利用CASE WHEN 计算每个team的分数
  • 在用union all将host_team与guest_team的分数合并在一起
SELECT
host_team team
,CASE 
	WHEN host_goals > guest_goals THEN 3
	WHEN host_goals = guest_goals THEN 1
	else 0 
END score 
FROM Matches
UNION ALL 
SELECT
guest_team team
,CASE 
	WHEN host_goals < guest_goals THEN 3
	WHEN host_goals = guest_goals THEN 1
	else 0 
END score 
FROM Matches
  • 输出结果
  team   score  
------  --------
    10         3
    30         1
    10         3
    20         3
    50         3
    20         0
    10         1
    50         0
    30         0
    30         0
  • 通过上表计算每一队的分数,使用Teams表进行左连接后发现team_id = 40的没有分数
  • 在使用ifnull函数将null转换成0,最后进行排序
SELECT 
Teams.team_id
,Teams.team_name
,ifnull(SUM(score),0) num_points
FROM Teams
LEFT JOIN 
(
	SELECT
	host_team team
	,CASE 
		WHEN host_goals > guest_goals THEN 3
		WHEN host_goals = guest_goals THEN 1
		else 0 
	END score 
	FROM Matches
	UNION ALL 
	SELECT
	guest_team team
	,CASE 
		WHEN host_goals < guest_goals THEN 3
		WHEN host_goals = guest_goals THEN 1
		else 0 
	END score 
	FROM Matches
) t ON Teams.team_id = t.team
GROUP BY Teams.team_id,Teams.team_name
ORDER BY num_points DESC,Teams.team_id
  • 输出结果
team_id  team_name    num_points  
-------  -----------  ------------
     10  Leetcode FC             7
     20  NewYork FC              3
     50  Toronto FC              3
     30  Atlanta FC              1
     40  Chicago FC              0



【每日SQL打卡DAY 21丨每个帖子的评论数【难度中等】

  • 表 Submissions 结构如下:
+---------------+----------+
| 列名           | 类型     |
+---------------+----------+
| sub_id        | int      |
| parent_id     | int      |
+---------------+----------+
  • 上表没有主键, 所以可能会出现重复的行。
  • 每行可以是一个帖子或对该帖子的评论。
  • 如果是帖子的话,parent_id 就是 null。
  • 对于评论来说,parent_id 就是表中对应帖子的 sub_id。
  • 编写 SQL 语句以查找每个帖子的评论数。
  • 结果表应包含帖子的 post_id 和对应的评论数 number_of_comments 并且按 post_id 升序排列。
  • Submissions 可能包含重复的评论。您应该计算每个帖子的唯一评论数。
  • Submissions 可能包含重复的帖子。您应该将它们视为一个帖子。
  • Submissions table:
+---------+------------+
| sub_id  | parent_id  |
+---------+------------+
| 1       | Null       |
| 2       | Null       |
| 1       | Null       |
| 12      | Null       |
| 3       | 1          |
| 5       | 2          |
| 3       | 1          |
| 4       | 1          |
| 9       | 1          |
| 10      | 2          |
| 6       | 7          |
+---------+------------+
  • 结果表:
+---------+--------------------+
| post_id | number_of_comments |
+---------+--------------------+
| 1       | 3                  |
| 2       | 2                  |
| 12      | 0                  |
+---------+--------------------+
  • 表中 ID 为 1 的帖子有 ID 为 3、4 和 9 的三个评论。表中 ID 为 3 的评论重复出现了,所以我们只对它进行了一次计数。
  • 表中 ID 为 2 的帖子有 ID 为 5 和 10 的两个评论。
  • ID 为 12 的帖子在表中没有评论。
  • 表中 ID 为 6 的评论是对 ID 为 7 的已删除帖子的评论,因此我们将其忽略。



解题思路

SELECT 
s1.sub_id
,COUNT(DISTINCT s2.sub_id) number_of_comments
FROM Submissions s1 
LEFT JOIN Submissions s2 ON s1.sub_id = s2.parent_id
WHERE s1.parent_id IS NULL 
GROUP BY s1.sub_id



【每日SQL打卡】DAY 21丨报告系统状态的连续日期【难度困难】

  • Table: Failed
+--------------+---------+
| Column Name  | Type    |
+--------------+---------+
| fail_date    | date    |
+--------------+---------+
  • 该表主键为 fail_date。
  • 该表包含失败任务的天数.
  • Table: Succeeded
+--------------+---------+
| Column Name  | Type    |
+--------------+---------+
| success_date | date    |
+--------------+---------+
  • 该表主键为 success_date。
  • 该表包含成功任务的天数.
  • 系统 每天 运行一个任务。每个任务都独立于先前的任务。任务的状态可以是失败或是成功。
  • 编写一个 SQL 查询 2019-01-01 到 2019-12-31 期间任务连续同状态 period_state 的起止日期(start_date 和 end_date)。
  • 即如果任务失败了,就是失败状态的起止日期,如果任务成功了,就是成功状态的起止日期。
  • 最后结果按照起始日期 start_date 排序
  • Failed table:
+-------------------+
| fail_date         |
+-------------------+
| 2018-12-28        |
| 2018-12-29        |
| 2019-01-04        |
| 2019-01-05        |
+-------------------+
  • Succeeded table:
+-------------------+
| success_date      |
+-------------------+
| 2018-12-30        |
| 2018-12-31        |
| 2019-01-01        |
| 2019-01-02        |
| 2019-01-03        |
| 2019-01-06        |
+-------------------+
  • Result table:
+--------------+--------------+--------------+
| period_state | start_date   | end_date     |
+--------------+--------------+--------------+
| succeeded    | 2019-01-01   | 2019-01-03   |
| failed       | 2019-01-04   | 2019-01-05   |
| succeeded    | 2019-01-06   | 2019-01-06   |
+--------------+--------------+--------------+
  • 结果忽略了 2018 年的记录,因为我们只关心从 2019-01-01 到 2019-12-31 的记录
  • 从 2019-01-01 到 2019-01-03 所有任务成功,系统状态为 “succeeded”。
  • 从 2019-01-04 到 2019-01-05 所有任务失败,系统状态为 “failed”。
  • 从 2019-01-06 到 2019-01-06 所有任务成功,系统状态为 “succeeded”。



解题思路

我还没有想到怎么做



【每日SQL打卡】DAY 22丨平均售价【难度中等】

  • Table: Prices
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| product_id    | int     |
| start_date    | date    |
| end_date      | date    |
| price         | int     |
+---------------+---------+
  • (product_id,start_date,end_date) 是 Prices 表的主键。
  • Prices 表的每一行表示的是某个产品在一段时期内的价格。
  • 每个产品的对应时间段是不会重叠的,这也意味着同一个产品的价格时段不会出现交叉。
  • Table: UnitsSold
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| product_id    | int     |
| purchase_date | date    |
| units         | int     |
+---------------+---------+
  • UnitsSold 表没有主键,它可能包含重复项。
  • UnitsSold 表的每一行表示的是每种产品的出售日期,单位和产品 id。
  • 编写SQL查询以查找每种产品的平均售价。 average_price 应该四舍五入到小数点后两位。 查询结果格式如下例所示:
  • Prices table:
+------------+------------+------------+--------+
| product_id | start_date | end_date   | price  |
+------------+------------+------------+--------+
| 1          | 2019-02-17 | 2019-02-28 | 5      |
| 1          | 2019-03-01 | 2019-03-22 | 20     |
| 2          | 2019-02-01 | 2019-02-20 | 15     |
| 2          | 2019-02-21 | 2019-03-31 | 30     |
+------------+------------+------------+--------+
  • UnitsSold table:
+------------+---------------+-------+
| product_id | purchase_date | units |
+------------+---------------+-------+
| 1          | 2019-02-25    | 100   |
| 1          | 2019-03-01    | 15    |
| 2          | 2019-02-10    | 200   |
| 2          | 2019-03-22    | 30    |
+------------+---------------+-------+
  • Result table:
+------------+---------------+
| product_id | average_price |
+------------+---------------+
| 1          | 6.96          |
| 2          | 16.96         |
+------------+---------------+
  • 平均售价 = 产品总价 / 销售的产品数量。
  • 产品 1 的平均售价 = ((100 * 5)+(15 * 20) )/ 115 = 6.96
  • 产品 2 的平均售价 = ((200 * 15)+(30 * 30) )/ 230 = 16.96



解题思路

  • 此题关键在于两个表格的连接条件,purchase_date应在start_date与end_date之间
SELECT 
u.product_id
,ROUND(SUM(u.units*p.price)/SUM(units),2) average_price
FROM UnitsSold u
LEFT JOIN Prices p 
ON (u.product_id = p.product_id AND (u.purchase_date BETWEEN p.start_date AND p.end_date))
GROUP BY u.product_id



【每日SQL打卡】DAY 22丨页面推荐【难度中等】

  • 朋友关系列表: Friendship
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| user1_id      | int     |
| user2_id      | int     |
+---------------+---------+
  • 这张表的主键是 (user1_id, user2_id)。
  • 这张表的每一行代表着 user1_id 和 user2_id 之间存在着朋友关系。
  • 喜欢列表: Likes
+-------------+---------+
| Column Name | Type    |
+-------------+---------+
| user_id     | int     |
| page_id     | int     |
+-------------+---------+
  • 这张表的主键是 (user_id, page_id)。
  • 这张表的每一行代表着 user_id 喜欢 page_id。
  • 写一段 SQL 向user_id = 1 的用户,推荐其朋友们喜欢的页面。不要推荐该用户已经喜欢的页面。
  • 你返回的结果中不应当包含重复项。
  • 返回结果的格式如下例所示:
  • Friendship table:
+----------+----------+
| user1_id | user2_id |
+----------+----------+
| 1        | 2        |
| 1        | 3        |
| 1        | 4        |
| 2        | 3        |
| 2        | 4        |
| 2        | 5        |
| 6        | 1        |
+----------+----------+
  • Likes table:
+---------+---------+
| user_id | page_id |
+---------+---------+
| 1       | 88      |
| 2       | 23      |
| 3       | 24      |
| 4       | 56      |
| 5       | 11      |
| 6       | 33      |
| 2       | 77      |
| 3       | 77      |
| 6       | 88      |
+---------+---------+
  • Result table:
+------------------+
| recommended_page |
+------------------+
| 23               |
| 24               |
| 56               |
| 33               |
| 77               |
+------------------+
  • 用户1 同 用户2, 3, 4, 6 是朋友关系。
  • 推荐页面为: 页面23 来自于 用户2, 页面24 来自于 用户3, 页面56 来自于 用户3 以及 页面33 来自于 用户6。
  • 页面77 同时被 用户2 和 用户3 推荐。
  • 页面88 没有被推荐,因为 用户1 已经喜欢了它。



解题思路

  • 首先创造一列,如果user2_id = 1则放入user1_id
  • 这样新列就可以找到用户1的朋友
SELECT 
f.user1_id
,f.user2_id
,CASE WHEN f.user2_id = 1 THEN f.user1_id ELSE f.user2_id END 新列
FROM Friendship f 
WHERE (f.user1_id = 1 OR f.user2_id = 1)
user1_id  user2_id  新列  
--------  --------  --------
       1         2         2
       1         3         3
       1         4         4
       6         1         6
  • 再利用新列与Likes连接,找到喜欢的内容
  • 在排除用户1喜欢的内容
SELECT 
DISTINCT page_id recommended_page
FROM   
(
	SELECT 
	f.user1_id
	,f.user2_id
	,CASE WHEN f.user2_id = 1 THEN f.user1_id ELSE f.user2_id END 新列
	FROM Friendship f 
	WHERE (f.user1_id = 1 OR f.user2_id = 1)
) t 
LEFT JOIN Likes l ON t.新列 = l.user_id
WHERE l.page_id NOT IN 
(
	SELECT 
	DISTINCT l.page_id
	FROM Friendship f 
	LEFT JOIN Likes l ON f.user1_id = l.user_id
	WHERE f.user1_id = 1 
)
ORDER BY page_id
  • 通过这题有点懂了目前大数据推荐,比如某短视频平台不断给你推荐你感兴趣的东西



【每日SQL打卡】DAY 23丨向CEO汇报工作的人【难度中等】

  • 员工表:Employees
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| employee_id   | int     |
| employee_name | varchar |
| manager_id    | int     |
+---------------+---------+
  • employee_id 是这个表的主键。
  • 这个表中每一行中,employee_id 表示职工的 ID,employee_name 表示职工的名字,manager_id 表示该职工汇报工作的直线经理。
  • 这个公司 CEO 是 employee_id = 1 的人。
  • 用 SQL 查询出所有直接或间接向公司 CEO 汇报工作的职工的 employee_id 。
  • 由于公司规模较小,经理之间的间接关系不超过 3 个经理。
  • 可以以任何顺序返回的结果,不需要去重。
  • 查询结果示例如下:
  • Employees table:
+-------------+---------------+------------+
| employee_id | employee_name | manager_id |
+-------------+---------------+------------+
| 1           | Boss          | 1          |
| 3           | Alice         | 3          |
| 2           | Bob           | 1          |
| 4           | Daniel        | 2          |
| 7           | Luis          | 4          |
| 8           | Jhon          | 3          |
| 9           | Angela        | 8          |
| 77          | Robert        | 1          |
+-------------+---------------+------------+
  • Result table:
+-------------+
| employee_id |
+-------------+
| 2           |
| 77          |
| 4           |
| 7           |
+-------------+
  • 公司 CEO 的 employee_id 是 1.
  • employee_id 是 2 和 77 的职员直接汇报给公司 CEO。
  • employee_id 是 4 的职员间接汇报给公司 CEO 4 –> 2 –> 1 。
  • employee_id 是 7 的职员间接汇报给公司 CEO 7 –> 4 –> 2 –> 1 。
  • employee_id 是 3, 8 ,9 的职员不会直接或间接的汇报给公司 CEO。



解题思路

  • 首先找到Employees中每个employee_id对应的一级manager_id的二级manager_id
  • 接着在按照这样的思路,在找到二级manager_id对应的三级manager_id
  • 可以这样将表无线的自连下去,但是“由于公司规模较小,经理之间的间接关系不超过 3 个经理。”
  • 故只需要将表自连两次就行
  • 在筛选出 e3.manager_id = 1以及e1.employee_id != 1 的数据即可
SELECT 
e1.employee_id
FROM Employees e1
LEFT JOIN Employees e2 ON e1.manager_id = e2.employee_id 
LEFT JOIN Employees e3 ON e2.manager_id = e3.employee_id
WHERE e3.manager_id = 1
AND e1.employee_id != 1



【每日SQL打卡】DAY 23丨学生们参加各科测试的次数【难度简单】

  • 学生表: Students
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| student_id    | int     |
| student_name  | varchar |
+---------------+---------+
  • 主键为 student_id(学生ID),该表内的每一行都记录有学校一名学生的信息。
  • 科目表: Subjects
+--------------+---------+
| Column Name  | Type    |
+--------------+---------+
| subject_name | varchar |
+--------------+---------+
  • 主键为 subject_name(科目名称),每一行记录学校的一门科目名称。
  • 考试表: Examinations
+--------------+---------+
| Column Name  | Type    |
+--------------+---------+
| student_id   | int     |
| subject_name | varchar |
+--------------+---------+
  • 这张表压根没有主键,可能会有重复行。
  • 学生表里的一个学生修读科目表里的每一门科目,
  • 而这张考试表的每一行记录就表示学生表里的某个学生参加了一次科目表里某门科目的测试。



解题思路

SELECT a.student_id, a.student_name, b.subject_name, COUNT(e.subject_name) AS attended_exams
FROM Students a CROSS JOIN Subjects b
LEFT JOIN Examinations e ON a.student_id = e.student_id AND b.subject_name = e.subject_name
GROUP BY a.student_id, b.subject_name
ORDER BY a.student_id, b.subject_name
  • CROSS JOIN Mysql中没有full outer join hive中可以用



【每日SQL打卡】DAY 24丨不同国家的天气类型【难度简单】

  • 国家表:Countries
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| country_id    | int     |
| country_name  | varchar |
+---------------+---------+
  • country_id 是这张表的主键。
  • 该表的每行有 country_id 和 country_name 两列。
  • 天气表:Weather
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| country_id    | int     |
| weather_state | varchar |
| day           | date    |
+---------------+---------+
  • (country_id, day) 是该表的复合主键。
  • 该表的每一行记录了某个国家某一天的天气情况。
  • 写一段 SQL 来找到表中每个国家在 2019 年 11 月的天气类型。
  • 天气类型的定义如下:当 weather_state 的平均值小于或等于15返回 Cold,当 weather_state 的平均值大于或等于 25 返回 Hot,否则返回 Warm。
  • 你可以以任意顺序返回你的查询结果。
  • 查询结果格式如下所示:
  • Countries table:
+------------+--------------+
| country_id | country_name |
+------------+--------------+
| 2          | USA          |
| 3          | Australia    |
| 7          | Peru         |
| 5          | China        |
| 8          | Morocco      |
| 9          | Spain        |
+------------+--------------+
  • Weather table:
+------------+---------------+------------+
| country_id | weather_state | day        |
+------------+---------------+------------+
| 2          | 15            | 2019-11-01 |
| 2          | 12            | 2019-10-28 |
| 2          | 12            | 2019-10-27 |
| 3          | -2            | 2019-11-10 |
| 3          | 0             | 2019-11-11 |
| 3          | 3             | 2019-11-12 |
| 5          | 16            | 2019-11-07 |
| 5          | 18            | 2019-11-09 |
| 5          | 21            | 2019-11-23 |
| 7          | 25            | 2019-11-28 |
| 7          | 22            | 2019-12-01 |
| 7          | 20            | 2019-12-02 |
| 8          | 25            | 2019-11-05 |
| 8          | 27            | 2019-11-15 |
| 8          | 31            | 2019-11-25 |
| 9          | 7             | 2019-10-23 |
| 9          | 3             | 2019-12-23 |
+------------+---------------+------------+
  • Result table:
+--------------+--------------+
| country_name | weather_type |
+--------------+--------------+
| USA          | Cold         |
| Austraila    | Cold         |
| Peru         | Hot          |
| China        | Warm         |
| Morocco      | Hot          |
+--------------+--------------+
  • USA 11 月的平均 weather_state 为 (15) / 1 = 15 所以天气类型为 Cold。
  • Australia 11 月的平均 weather_state 为 (-2 + 0 + 3) / 3 = 0.333 所以天气类型为 Cold。
  • Peru 11 月的平均 weather_state 为 (25) / 1 = 25 所以天气类型为 Hot。
  • China 11 月的平均 weather_state 为 (16 + 18 + 21) / 3 = 18.333 所以天气类型为 Warm。
  • Morocco 11 月的平均 weather_state 为 (25 + 27 + 31) / 3 = 27.667 所以天气类型为 Hot。
  • 我们并不知道 Spain 在 11 月的 weather_state 情况所以无需将他包含在结果中。



解题思路

SELECT 
c.country_name
,case 
	when round(AVG(weather_state),2) <= 15 then "Cold" 
	when round(AVG(weather_state),2) >= 25 then "Hot" 
else "Warm" end weather_type
FROM Weather w 
LEFT JOIN Countries c ON w.country_id = c.country_id
WHERE w.day BETWEEN "2019-11-01" AND "2019-11-30"
GROUP BY w.country_id



【每日SQL打卡】DAY 24丨找到连续区间的开始和结束数字【难度中等】

  • 表:Logs
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| log_id        | int     |
+---------------+---------+
  • id 是上表的主键。
  • 上表的每一行包含日志表中的一个 ID。
  • 后来一些 ID 从 Logs 表中删除。编写一个 SQL 查询得到 Logs 表中的连续区间的开始数字和结束数字。
  • 将查询表按照 start_id 排序。
  • 查询结果格式如下面的例子:
  • Logs 表:
+------------+
| log_id     |
+------------+
| 1          |
| 2          |
| 3          |
| 7          |
| 8          |
| 10         |
+------------+
  • 结果表:
+------------+--------------+
| start_id   | end_id       |
+------------+--------------+
| 1          | 3            |
| 7          | 8            |
| 10         | 10           |
+------------+--------------+
  • 结果表应包含 Logs 表中的所有区间。
  • 从 1 到 3 在表中。
  • 从 4 到 6 不在表中。
  • 从 7 到 8 在表中。
  • 9 不在表中。
  • 10 在表中。



解题思路

SELECT 
start_id
,end_date
FROM 
(
	SELECT
	start_id
	,IFNULL(end_id,lead(end_id) over()) end_date
	FROM 
	(
		SELECT
		log_id
		,case 
			when 差值2 = 1 AND 差值1 != 1 then log_id
			WHEN 差值2 = 0 THEN log_id 
		END  start_id
		,case 
			when 差值1 = 1 AND 差值2 != 1 then log_id 
			WHEN 差值2 = 0 THEN log_id
		END end_id
		FROM 
		(
			SELECT
			log_id
			,IFNULL(log_id - 向下偏移1,0) 差值1
			,IFNULL(向上偏移1- log_id,0) 差值2
			FROM 
			(
				SELECT 
				log_id
				,LAG(log_id) OVER()  向下偏移1,LEAD(log_id) OVER() 向上偏移1FROM Logs
			) t1 
		) t2 
	) t3 
	WHERE (start_id IS NOT NULL) OR (end_id IS NOT NULL)
) t4
WHERE start_id IS NOT NULL



【每日SQL打卡】DAY 25丨求团队人数【难度中等】

  • 员工表:Employee
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| employee_id   | int     |
| team_id       | int     |
+---------------+---------+
  • employee_id 字段是这张表的主键,表中的每一行都包含每个员工的 ID 和他们所属的团队。
  • 编写一个 SQL 查询,以求得每个员工所在团队的总人数。
  • 查询结果中的顺序无特定要求。
  • 查询结果格式示例如下:
  • Employee Table:
+-------------+------------+
| employee_id | team_id    |
+-------------+------------+
|     1       |     8      |
|     2       |     8      |
|     3       |     8      |
|     4       |     7      |
|     5       |     9      |
|     6       |     9      |
+-------------+------------+
  • Result table:
+-------------+------------+
| employee_id | team_size  |
+-------------+------------+
|     1       |     3      |
|     2       |     3      |
|     3       |     3      |
|     4       |     1      |
|     5       |     2      |
|     6       |     2      |
+-------------+------------+
  • ID 为 1、2、3 的员工是 team_id 为 8 的团队的成员,
  • ID 为 4 的员工是 team_id 为 7 的团队的成员,
  • ID 为 5、6 的员工是 team_id 为 9 的团队的成员。



解题思路

  • 直接利用窗口函数解决问题
SELECT 
employee_id
,COUNT(employee_id) over(PARTITION BY team_id) team_size 
FROM Employee
ORDER BY employee_id



【每日SQL打卡】DAY 25丨不同性别每日分数总计【难度中等】

  • 表: Scores
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| player_name   | varchar |
| gender        | varchar |
| day           | date    |
| score_points  | int     |
+---------------+---------+
  • (gender, day)是该表的主键
  • 一场比赛是在女队和男队之间举行的
  • 该表的每一行表示一个名叫 (player_name) 性别为 (gender) 的参赛者在某一天获得了 (score_points) 的分数
  • 如果参赛者是女性,那么 gender 列为 ‘F’,如果参赛者是男性,那么 gender 列为 ‘M’
  • 写一条SQL语句查询每种性别在每一天的总分,并按性别和日期对查询结果排序
  • 下面是查询结果格式的例子
  • Scores表:
+-------------+--------+------------+--------------+
| player_name | gender | day        | score_points |
+-------------+--------+------------+--------------+
| Aron        | F      | 2020-01-01 | 17           |
| Alice       | F      | 2020-01-07 | 23           |
| Bajrang     | M      | 2020-01-07 | 7            |
| Khali       | M      | 2019-12-25 | 11           |
| Slaman      | M      | 2019-12-30 | 13           |
| Joe         | M      | 2019-12-31 | 3            |
| Jose        | M      | 2019-12-18 | 2            |
| Priya       | F      | 2019-12-31 | 23           |
| Priyanka    | F      | 2019-12-30 | 17           |
+-------------+--------+------------+--------------+
  • 结果表:
+--------+------------+-------+
| gender | day        | total |
+--------+------------+-------+
| F      | 2019-12-30 | 17    |
| F      | 2019-12-31 | 40    |
| F      | 2020-01-01 | 57    |
| F      | 2020-01-07 | 80    |
| M      | 2019-12-18 | 2     |
| M      | 2019-12-25 | 13    |
| M      | 2019-12-30 | 26    |
| M      | 2019-12-31 | 29    |
| M      | 2020-01-07 | 36    |
+--------+------------+-------+
  • 女性队伍:
  • 第一天是 2019-12-30,Priyanka 获得 17 分,队伍的总分是 17 分
  • 第二天是 2019-12-31, Priya 获得 23 分,队伍的总分是 40 分
  • 第三天是 2020-01-01, Aron 获得 17 分,队伍的总分是 57 分
  • 第四天是 2020-01-07, Alice 获得 23 分,队伍的总分是 80 分
  • 男性队伍:
  • 第一天是 2019-12-18, Jose 获得 2 分,队伍的总分是 2 分
  • 第二天是 2019-12-25, Khali 获得 11 分,队伍的总分是 13 分
  • 第三天是 2019-12-30, Slaman 获得 13 分,队伍的总分是 26 分
  • 第四天是 2019-12-31, Joe 获得 3 分,队伍的总分是 29 分
  • 第五天是 2020-01-07, Bajrang 获得 7 分,队伍的总分是 36 分



解题思路

  • 利用窗口函数累计求和
SELECT 
gender
,DAY
,SUM(score_points) OVER(PARTITION BY gender ORDER BY DAY) total
FROM Scores
ORDER BY gender,DAY



【每日SQL打卡】DAY 26丨广告效果【难度简单】

  • 表: Ads
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| ad_id         | int     |
| user_id       | int     |
| action        | enum    |
+---------------+---------+
  • (ad_id, user_id) 是该表的主键
  • 该表的每一行包含一条广告的 ID(ad_id),用户的 ID(user_id) 和用户对广告采取的行为 (action)
  • action 列是一个枚举类型 (‘Clicked’, ‘Viewed’, ‘Ignored’) 。
  • 一家公司正在运营这些广告并想计算每条广告的效果。
  • 广告效果用点击通过率(Click-Through Rate:CTR)来衡量,公式如下:
  • 写一条SQL语句来查询每一条广告的 ctr ,
  • ctr 要保留两位小数。结果需要按 ctr 降序、按 ad_id 升序 进行排序。
  • 查询结果示例如下:
  • Ads 表:
+-------+---------+---------+
| ad_id | user_id | action  |
+-------+---------+---------+
| 1     | 1       | Clicked |
| 2     | 2       | Clicked |
| 3     | 3       | Viewed  |
| 5     | 5       | Ignored |
| 1     | 7       | Ignored |
| 2     | 7       | Viewed  |
| 3     | 5       | Clicked |
| 1     | 4       | Viewed  |
| 2     | 11      | Viewed  |
| 1     | 2       | Clicked |
+-------+---------+---------+
  • 结果表:
+-------+-------+
| ad_id | ctr   |
+-------+-------+
| 1     | 66.67 |
| 3     | 50.00 |
| 2     | 33.33 |
| 5     | 0.00  |
+-------+-------+
  • 对于 ad_id = 1, ctr = (2/(2+1)) * 100 = 66.67
  • 对于 ad_id = 2, ctr = (1/(1+2)) * 100 = 33.33
  • 对于 ad_id = 3, ctr = (1/(1+1)) * 100 = 50.00
  • 对于 ad_id = 5, ctr = 0.00, 注意 ad_id = 5 没有被点击 (Clicked) 或查看 (Viewed) 过
  • 注意我们不关心 action 为 Ingnored 的广告
  • 结果按 ctr(降序),ad_id(升序)排序



解题思路

SELECT 
ad_id
,CASE WHEN ROUND((SUM(case when action = "Clicked" then 1 else 0 end)/SUM(case when action = "Clicked" then 1 when action = "Viewed" then 1 else 0 end))*100,2) IS NULL THEN 0 
ELSE ROUND((SUM(case when action = "Clicked" then 1 else 0 end)/SUM(case when action = "Clicked" then 1 when action = "Viewed" then 1 else 0 end))*100,2) END ctr
FROM Ads
GROUP BY ad_id
ORDER BY ctr DESC,ad_id ASC 



【每日SQL打卡】DAY 26丨餐馆营业额变化增长【难度中等】

  • 表: Customer
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| customer_id   | int     |
| name          | varchar |
| visited_on    | date    |
| amount        | int     |
+---------------+---------+
  • (customer_id, visited_on) 是该表的主键
  • 该表包含一家餐馆的顾客交易数据
  • visited_on 表示 (customer_id) 的顾客在 visited_on 那天访问了餐馆
  • amount 是一个顾客某一天的消费总额
  • 你是餐馆的老板,现在你想分析一下可能的营业额变化增长(每天至少有一位顾客)
  • 写一条 SQL 查询计算以 7 天(某日期 + 该日期前的 6 天)为一个时间段的顾客消费平均值
  • 查询结果格式的例子如下:
  • 查询结果按 visited_on 排序
  • average_amount 要 保留两位小数,日期数据的格式为 (‘YYYY-MM-DD’)
  • Customer 表:
+-------------+--------------+--------------+-------------+
| customer_id | name         | visited_on   | amount      |
+-------------+--------------+--------------+-------------+
| 1           | Jhon         | 2019-01-01   | 100         |
| 2           | Daniel       | 2019-01-02   | 110         |
| 3           | Jade         | 2019-01-03   | 120         |
| 4           | Khaled       | 2019-01-04   | 130         |
| 5           | Winston      | 2019-01-05   | 110         | 
| 6           | Elvis        | 2019-01-06   | 140         | 
| 7           | Anna         | 2019-01-07   | 150         |
| 8           | Maria        | 2019-01-08   | 80          |
| 9           | Jaze         | 2019-01-09   | 110         | 
| 1           | Jhon         | 2019-01-10   | 130         | 
| 3           | Jade         | 2019-01-10   | 150         | 
+-------------+--------------+--------------+-------------+
  • 结果表:
+--------------+--------------+----------------+
| visited_on   | amount       | average_amount |
+--------------+--------------+----------------+
| 2019-01-07   | 860          | 122.86         |
| 2019-01-08   | 840          | 120            |
| 2019-01-09   | 840          | 120            |
| 2019-01-10   | 1000         | 142.86         |
+--------------+--------------+----------------+
  • 第一个七天消费平均值从 2019-01-01 到 2019-01-07 是 (100 + 110 + 120 + 130 + 110 + 140 + 150)/7 = 122.86
  • 第二个七天消费平均值从 2019-01-02 到 2019-01-08 是 (110 + 120 + 130 + 110 + 140 + 150 + 80)/7 = 120
  • 第三个七天消费平均值从 2019-01-03 到 2019-01-09 是 (120 + 130 + 110 + 140 + 150 + 80 + 110)/7 = 120
  • 第四个七天消费平均值从 2019-01-04 到 2019-01-10 是 (130 + 110 + 140 + 150 + 80 + 110 + 130 + 150)/7 = 142.86
SELECT 
visited_on
,总金额+向下偏移1+向下偏移2+向下偏移3+向下偏移4+向下偏移5+向下偏移6行  amount
,ROUND((总金额+向下偏移1+向下偏移2+向下偏移3+向下偏移4+向下偏移5+向下偏移6)/7,2) average_amount
FROM 
(
	SELECT 
		visited_on
		,总金额
		,LAG(总金额,1) OVER() 向下偏移1,LAG(总金额,2) OVER() 向下偏移2,LAG(总金额,3) OVER() 向下偏移3,LAG(总金额,4) OVER() 向下偏移4,LAG(总金额,5) OVER() 向下偏移5,LAG(总金额,6) OVER() 向下偏移6FROM 
	(
		SELECT 
		visited_on
		,SUM(amount) 总金额
		FROM Customer 
		GROUP BY 1 
	) t1
) t2
WHERE 向下偏移6IS NOT NULL



【每日SQL打卡】DAY 27丨每次访问的交易次数【难度困难】

  • 表: Visits
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| user_id       | int     |
| visit_date    | date    |
+---------------+---------+
  • (user_id, visit_date) 是该表的主键
  • 该表的每行表示 user_id 在 visit_date 访问了银行
  • 表: Transactions
+------------------+---------+
| Column Name      | Type    |
+------------------+---------+
| user_id          | int     |
| transaction_date | date    |
| amount           | int     |
+------------------+---------+
  • 该表没有主键,所以可能有重复行
  • 该表的每一行表示 user_id 在 transaction_date 完成了一笔 amount 数额的交易
  • 可以保证用户 (user) 在 transaction_date 访问了银行 (也就是说 Visits 表包含 (user_id, transaction_date) 行)
  • 银行想要得到银行客户在一次访问时的交易次数和相应的在一次访问时该交易次数的客户数量的图表
  • 写一条 SQL 查询多少客户访问了银行但没有进行任何交易,多少客户访问了银行进行了一次交易等等
  • 结果包含两列:
  • transactions_count: 客户在一次访问中的交易次数
  • visits_count: 在 transactions_count 交易次数下相应的一次访问时的客户数量
  • transactions_count

    的值从

    0

    到所有用户一次访问中的

    max(transactions_count)
  • 按 transactions_count 排序
  • 下面是查询结果格式的例子:
  • Visits 表:
+---------+------------+
| user_id | visit_date |
+---------+------------+
| 1       | 2020-01-01 |
| 2       | 2020-01-02 |
| 12      | 2020-01-01 |
| 19      | 2020-01-03 |
| 1       | 2020-01-02 |
| 2       | 2020-01-03 |
| 1       | 2020-01-04 |
| 7       | 2020-01-11 |
| 9       | 2020-01-25 |
| 8       | 2020-01-28 |
+---------+------------+
  • Transactions 表:
+---------+------------------+--------+
| user_id | transaction_date | amount |
+---------+------------------+--------+
| 1       | 2020-01-02       | 120    |
| 2       | 2020-01-03       | 22     |
| 7       | 2020-01-11       | 232    |
| 1       | 2020-01-04       | 7      |
| 9       | 2020-01-25       | 33     |
| 9       | 2020-01-25       | 66     |
| 8       | 2020-01-28       | 1      |
| 9       | 2020-01-25       | 99     |
+---------+------------------+--------+
  • 结果表:
+--------------------+--------------+
| transactions_count | visits_count |
+--------------------+--------------+
| 0                  | 4            |
| 1                  | 5            |
| 2                  | 0            |
| 3                  | 1            |
+--------------------+--------------+
  • 对于 transactions_count = 0, visits 中 (1, “2020-01-01”), (2, “2020-01-02”), (12, “2020-01-01”) 和 (19, “2020-01-03”) 没有进行交易,所以 visits_count = 4 。
  • 对于 transactions_count = 1, visits 中 (2, “2020-01-03”), (7, “2020-01-11”), (8, “2020-01-28”), (1, “2020-01-02”) 和 (1, “2020-01-04”) 进行了一次交易,所以 visits_count = 5 。
  • 对于 transactions_count = 2, 没有客户访问银行进行了两次交易,所以 visits_count = 0 。
  • 对于 transactions_count = 3, visits 中 (9, “2020-01-25”) 进行了三次交易,所以 visits_count = 1 。
  • 对于 transactions_count >= 4, 没有客户访问银行进行了超过3次交易,所以我们停止在 transactions_count = 3 。
SELECT 
IFNULL(t2.交易次数,0) transactions_count
,COUNT(t1.访问次数)   visits_count
FROM 
(
	SELECT 
	user_id
	,visit_date
	,COUNT(*) 访问次数
	FROM Visits
	GROUP BY user_id,visit_date
) t1
LEFT JOIN 
(
	SELECT 
	user_id
	,transaction_date
	,COUNT(*) 交易次数
	FROM Transactions t
	GROUP BY user_id,transaction_date
) t2 ON t1.user_id = t2.user_id AND t1.visit_date = t2.transaction_date
GROUP BY IFNULL(t2.交易次数,0)
  • 考虑用最大的表通过row_number获取一个连续的序列,由于序列需要从0开始,故使rk-1从0到max(transactions_count)做主表关联题目两个表做统计
WITH tt AS (
	SELECT
		v.user_id,
		count( transaction_date ) cnt 
	FROM
		visits v
		LEFT JOIN transactions t ON v.user_id = t.user_id 
		AND v.visit_date = t.transaction_date 
	GROUP BY
		v.user_id,
		visit_date 
	) 
SELECT
	rk - 1 transactions_count,
	count( user_id ) visits_count 
FROM
	( SELECT row_number ( ) over ( ORDER BY user_id ) rk FROM visits ) t
	LEFT JOIN tt ON t.rk - 1 = tt.cnt 
WHERE
	rk <= ( SELECT max( cnt ) FROM tt ) + 1 
GROUP BY
	t.rk - 1



【每日SQL打卡】DAY 27丨列出指定时间段内所有的下单产品【难度简单】

  • 表: Products
+------------------+---------+
| Column Name      | Type    |
+------------------+---------+
| product_id       | int     |
| product_name     | varchar |
| product_category | varchar |
+------------------+---------+
  • product_id 是该表主键。
  • 该表包含该公司产品的数据。
  • 表: Orders
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| product_id    | int     |
| order_date    | date    |
| unit          | int     |
+---------------+---------+
  • 该表无主键,可能包含重复行。
  • product_id 是表单 Products 的外键。
  • unit 是在日期 order_date 内下单产品的数目。
  • 写一个 SQL 语句,要求获取在 2020 年 2 月份下单的数量不少于 100 的产品的名字和数目。
  • 返回结果表单的顺序无要求。
  • 查询结果的格式如下:
  • Products 表:
+-------------+-----------------------+------------------+
| product_id  | product_name          | product_category |
+-------------+-----------------------+------------------+
| 1           | Leetcode Solutions    | Book             |
| 2           | Jewels of Stringology | Book             |
| 3           | HP                    | Laptop           |
| 4           | Lenovo                | Laptop           |
| 5           | Leetcode Kit          | T-shirt          |
+-------------+-----------------------+------------------+
  • Orders 表:
+--------------+--------------+----------+
| product_id   | order_date   | unit     |
+--------------+--------------+----------+
| 1            | 2020-02-05   | 60       |
| 1            | 2020-02-10   | 70       |
| 2            | 2020-01-18   | 30       |
| 2            | 2020-02-11   | 80       |
| 3            | 2020-02-17   | 2        |
| 3            | 2020-02-24   | 3        |
| 4            | 2020-03-01   | 20       |
| 4            | 2020-03-04   | 30       |
| 4            | 2020-03-04   | 60       |
| 5            | 2020-02-25   | 50       |
| 5            | 2020-02-27   | 50       |
| 5            | 2020-03-01   | 50       |
+--------------+--------------+----------+
  • Result 表:
+--------------------+---------+
| product_name       | unit    |
+--------------------+---------+
| Leetcode Solutions | 130     |
| Leetcode Kit       | 100     |
+--------------------+---------+
  • 2020 年 2 月份下单 product_id = 1 的产品的数目总和为 (60 + 70) = 130 。
  • 2020 年 2 月份下单 product_id = 2 的产品的数目总和为 80 。
  • 2020 年 2 月份下单 product_id = 3 的产品的数目总和为 (2 + 3) = 5 。
  • 2020 年 2 月份 product_id = 4 的产品并没有下单。
  • 2020 年 2 月份下单 product_id = 5 的产品的数目总和为 (50 + 50) = 100 。
SELECT 
p.product_name
,SUM(unit) unit
FROM Products p 
LEFT JOIN Orders o ON p.product_id = o.product_id
WHERE SUBSTRING(o.order_date,1,7) = "2020-02"
GROUP BY p.product_name 
HAVING SUM(unit) >= 100 



月末大考试

  • 学生表 Student
create table Student(SId varchar(10),Sname varchar(10),Sage datetime,Ssex varchar(10));
insert into Student values('01' , '西红柿' , '1990-01-01' , '男');
insert into Student values('02' , '钱电' , '1990-12-21' , '男');
insert into Student values('03' , '孙风' , '1990-12-20' , '男');
insert into Student values('04' , '李云' , '1990-12-06' , '男');
insert into Student values('05' , '周梅' , '1991-12-01' , '女');
insert into Student values('06' , '吴兰' , '1992-01-01' , '女');
insert into Student values('07' , '郑竹' , '1989-01-01' , '女');
insert into Student values('09' , '张三' , '2017-12-20' , '女');
insert into Student values('10' , '西红柿' , '2017-12-25' , '女');
insert into Student values('11' , '李四' , '2012-06-06' , '女');
insert into Student values('12' , '赵六' , '2013-06-13' , '女');
insert into Student values('13' , '孙七' , '2014-06-01' , '女');
  • 科目表 Course
create table Course(CId varchar(10),Cname nvarchar(10),TId varchar(10));
insert into Course values('01' , '语文' , '02');
insert into Course values('02' , '数学' , '01');
insert into Course values('03' , '英语' , '03');
  • 教师表 Teacher
create table Teacher(TId varchar(10),Tname varchar(10));
insert into Teacher values('01' , '张三');
insert into Teacher values('02' , '李四');
insert into Teacher values('03' , '王五');
insert into Teacher values('04' , '西红柿');
  • 成绩表 SC
create table SC(SId varchar(10),CId varchar(10),score decimal(18,1));
insert into SC values('08' , '03' , 95);
insert into SC values('08' , '03' , 94);
insert into SC values('01' , '01' , 80);
insert into SC values('01' , '02' , 90);
insert into SC values('01' , '03' , 99);
insert into SC values('02' , '01' , 70);
insert into SC values('02' , '02' , 60);
insert into SC values('02' , '03' , 80);
insert into SC values('03' , '01' , 80);
insert into SC values('03' , '02' , 80);
insert into SC values('03' , '03' , 80);
insert into SC values('04' , '01' , 50);
insert into SC values('04' , '02' , 30);
insert into SC values('04' , '03' , 20);
insert into SC values('05' , '01' , 76);
insert into SC values('05' , '02' , 87);
insert into SC values('06' , '01' , 31);
insert into SC values('06' , '03' , 34);
insert into SC values('07' , '02' , 89);
insert into SC values('07' , '03' , 98);



1.查询” 01 “课程比” 02 “课程成绩高的学生的信息及课程分数

SELECT
s1.SId 学生学号
,Student.`Sname` 学生姓名
,C1.`Cname` 01课程
,s1.score   01课程成绩
,C2.`Cname` 02课程
,s2.score   02课程成绩
FROM SC s1
JOIN SC s2 ON (s1.SId = s2.SId AND s1.CId = "01" AND s2.CId = "02" AND s1.score > s2.score)
LEFT JOIN Student ON s1.SId = Student.SId
LEFT JOIN Course C1 ON s1.CId = C1.`CId`
LEFT JOIN Course C2 ON s2.CId = C2.`CId`
  • 输出
学生学号 学生姓名 01课程 01课程成绩 02课程 02课程成绩
02 钱电 语文 70.0 数学 60.0
04 李云 语文 50.0 数学 30.0



2.查询平均成绩大于等于 60 分的同学的学生编号和学生姓名和平均成绩

SELECT 
SC.SId 学生学号
,Student.`Sname` 学生姓名
,ROUND(AVG(SC.score),2)  平均成绩
FROM SC
LEFT JOIN Student ON SC.SId = Student.SId
GROUP BY SC.SId
HAVING AVG(SC.score) >= 60
  • 输出
学生学号 学生姓名 平均成绩
08 (NULL) 94.50
01 西红柿 89.67
02 钱电 70.00
03 孙风 80.00
05 周梅 81.50
07 郑竹 93.50



3.查询所有同学的学生编号、学生姓名、选课总数、所有课程的总成绩(没成绩的显示为 NULL )

SELECT 
Student.SId      学生学号
,Student.`Sname` 学生姓名
,COUNT(SC.`CId`) 选课总数
,SUM(SC.`score`) 总成绩
FROM Student
LEFT JOIN SC ON Student.SId = SC.SId
GROUP BY Student.SId,Student.`Sname`
  • 输出
学生学号 学生姓名 选课总数 总成绩
01 西红柿 3 269.0
02 钱电 3 210.0
03 孙风 3 240.0
04 李云 3 100.0
05 周梅 2 163.0
06 吴兰 2 65.0
07 郑竹 2 187.0
09 张三 0 (NULL)
10 西红柿 0 (NULL)
11 李四 0 (NULL)
12 赵六 0 (NULL)
13 孙七 0 (NULL)



4.查询学过「张三」老师授课的同学的信息

SELECT 
S.SId 学生学号
,S.`Sname` 学生姓名
FROM Teacher T
LEFT JOIN Course C ON T.TId = C.TId
LEFT JOIN SC ON SC.CId  = C.CId 
LEFT JOIN Student S ON S.SId = SC.SId
WHERE T.`Tname` = "张三"
  • 输出
学生学号 学生姓名
07 郑竹
05 周梅
04 李云
03 孙风
02 钱电
01 西红柿



5.查询没有学全所有课程的同学的信息

  • 将同学的选课总数与总课程数对比,找出没有学所有课程的同学
SELECT 
	Student.SId      学生学号
	,Student.`Sname` 学生姓名
FROM Student
LEFT JOIN SC ON Student.SId = SC.SId
GROUP BY Student.SId,Student.`Sname`
HAVING COUNT(SC.`CId`) < (SELECT COUNT(CId) FROM Course)
  • 输出
学生学号 学生姓名
05 周梅
06 吴兰
07 郑竹
09 张三
10 西红柿
11 李四
12 赵六
13 孙七



6.查询和” 01 “号的同学学习的课程 完全相同的其他同学的信息

SELECT
学生姓名
FROM 
(
	SELECT 
	Student.SId      学生学号
	,Student.`Sname` 学生姓名
	,SC.`CId`        课程编号
	FROM Student
	LEFT JOIN SC ON Student.SId = SC.SId 
	WHERE SC.`CId` IN 
	(
		SELECT 
		SC.`CId`        课程编号
		FROM Student
		LEFT JOIN SC ON Student.SId = SC.SId 
		WHERE Student.SId = "01"
		)
	AND Student.SId != "01"
) t1
GROUP BY 学生姓名
HAVING COUNT(课程编号) = 
(
	SELECT 
	COUNT(SC.`CId`) 
	FROM Student
	LEFT JOIN SC ON Student.SId = SC.SId 
	WHERE Student.SId = "01"
)
  • 输出
学生姓名
钱电
孙风
李云



7.查询两门及其以上不及格课程的同学的学号,姓名及其平均成绩

SELECT 
	Student.SId      学生学号
	,Student.`Sname` 学生姓名
	,ROUND(AVG(SC.`score`),2) 平均成绩
FROM Student
LEFT JOIN SC ON Student.SId = SC.SId
WHERE Student.SId IN 
(
	SELECT 
		学生学号
	FROM 
	(
		SELECT 
		Student.SId      学生学号
		,Student.`Sname` 学生姓名
		,SC.`CId`        课程编号
		,SC.`score`      成绩
		FROM Student
		LEFT JOIN SC ON Student.SId = SC.SId
		WHERE SC.`score` < 60
	) t1
	GROUP BY 学生学号,学生姓名
	HAVING COUNT(课程编号) >= 2
) 
GROUP BY Student.SId,Student.`Sname`
  • 输出
学生学号 学生姓名 平均成绩
04 李云 33.33
06 吴兰 32.50



8.检索” 01 “课程分数小于 60,按分数降序排列的学生信息

SELECT 
	Student.SId      学生学号
	,Student.`Sname` 学生姓名
FROM SC
LEFT JOIN Student ON SC.`SId` = Student.`SId`
WHERE SC.CId = "01"
AND SC.score < 60
ORDER BY Student.SId DESC
  • 输出
学生编号 学生姓名
06 吴兰
04 李云



9.按平均成绩从高到低显示所有学生的所有课程的成绩以及平均成绩

SELECT 
	学生学号
	,学生姓名
	,课程名
	,成绩
	,平均成绩
FROM 
(
SELECT 
	Student.SId      学生学号
	,Student.`Sname` 学生姓名
	,Course.`Cname`  课程名
	,SC.`score`      成绩
	,ROUND(AVG(SC.`score`) over(PARTITION BY Student.SId),2) 平均成绩
FROM Student
LEFT JOIN SC ON Student.SId = SC.SId
LEFT JOIN Course ON SC.CId = Course.CId
WHERE SC.`score` IS NOT NULL
) t1 
ORDER BY 平均成绩 DESC
  • 输出
学生学号 学生姓名 课程名 成绩 平均成绩
07 郑竹 数学 89.0 93.50
07 郑竹 英语 98.0 93.50
01 西红柿 语文 80.0 89.67
01 西红柿 数学 90.0 89.67
01 西红柿 英语 99.0 89.67
05 周梅 语文 76.0 81.50
05 周梅 数学 87.0 81.50
03 孙风 语文 80.0 80.00
03 孙风 数学 80.0 80.00
03 孙风 英语 80.0 80.00
02 钱电 语文 70.0 70.00
02 钱电 数学 60.0 70.00
02 钱电 英语 80.0 70.00
04 李云 语文 50.0 33.33
04 李云 数学 30.0 33.33
04 李云 英语 20.0 33.33
06 吴兰 语文 31.0 32.50
06 吴兰 英语 34.0 32.50



10.查询各科成绩最高分、最低分和平均分:

SELECT
	Course.CId       课程编号
	,Course.`Cname`  课程名
	,MAX(SC.`score`) 最高分
	,MIN(SC.`score`) 最低分
	,ROUND(AVG(SC.`score`),2) 平均分
FROM SC 
LEFT JOIN Course ON SC.CId = Course.CId
GROUP BY Course.CId,Course.`Cname`
  • 输出
课程编号 课程名 最高分 最低分 平 均分
03 英语 99.0 20.0 75.00
01 语文 80.0 31.0 64.50
02 数学 90.0 30.0 72.67



11.以如下形式显示:课程 ID,课程 name,最高分,最低分,平均分,及格率,中等率,优良率,优秀率

及格为>=60,中等为:70-80,优良为:80-90,优秀为:>=90

SELECT 
	课程编号
	,课程名
	,MAX(成绩) 最高分
	,MIN(成绩) 最低分
	,ROUND(成绩,2) 平均分
	,ROUND(SUM(是否及格)/COUNT(是否及格),2) 及格率
	,ROUND(SUM(是否中等)/COUNT(是否及格),2) 中等率
	,ROUND(SUM(是否优良)/COUNT(是否及格),2) 优良率
	,ROUND(SUM(是否优秀)/COUNT(是否及格),2) 优秀率
FROM 
(
SELECT
	Course.CId       课程编号
	,Course.`Cname`  课程名
	,SC.`score`      成绩
	,CASE WHEN SC.`score` >= 60 THEN "1" ELSE "0" END 是否及格
	,CASE WHEN SC.`score` >= 70 THEN "1" ELSE "0" END 是否中等
	,CASE WHEN SC.`score` >= 80 THEN "1" ELSE "0" END 是否优良
	,CASE WHEN SC.`score` >= 90 THEN "1" ELSE "0" END 是否优秀
FROM SC 
LEFT JOIN Course ON SC.CId = Course.CId
) t1
GROUP BY 课程编号,课程名
  • 输出
课程编号 课程名 最高分 最低分 平均分 及格率 中等率 优良率 优秀率
03 英语 99.0 20.0 95.0 0.75 0.75 0.75 0.5
01 语文 80.0 31.0 80.0 0.67 0.67 0.33 0
02 数学 90.0 30.0 90.0 0.83 0.67 0.67 0.17



12.要求输出课程号和选修人数,查询结果按人数降序排列,若人数相同,按课程号升序排列

SELECT 
CId 课程编号
,COUNT(SId) 选修人数
FROM SC
GROUP BY CId
ORDER BY COUNT(SId) DESC,CId ASC
  • 输出
课程编号 选修人数
03 8
01 6
02 6



13.按各科成绩进行排序,并显示排名, Score 重复时保留名次空缺

SELECT 
CId    课程编号
,score 成绩
,rank() over(PARTITION BY CId ORDER BY score DESC) 排名
FROM SC
ORDER BY CId,score DESC  
  • 输出
课程编号 成绩 排名
01 80.0 1
01 80.0 1
01 76.0 3
01 70.0 4
01 50.0 5
01 31.0 6
02 90.0 1
02 89.0 2
02 87.0 3
02 80.0 4
02 60.0 5
02 30.0 6
03 99.0 1
03 98.0 2
03 95.0 3
03 94.0 4
03 80.0 5
03 80.0 5
03 34.0 7
03 20.0 8



14.统计各科成绩各分数段人数:课程编号,课程名称,[100-85],[85-70],[70-60],[60-0] 及所占百分比

SELECT 
	课程编号
	,课程名
	,CONCAT(ROUND(SUM(CASE WHEN 分组 = "[100-85]" THEN "1" ELSE "0" END)/COUNT(分组)*100,2),"%") `[100-85]百分比`
	,CONCAT(ROUND(SUM(CASE WHEN 分组 = "[85-70]" THEN "1" ELSE "0" END)/COUNT(分组)*100,2),"%")  `[85-70]百分比`
	,CONCAT(ROUND(SUM(CASE WHEN 分组 = "[70-60]" THEN "1" ELSE "0" END)/COUNT(分组)*100,2),"%")  `[70-60]百分比`
	,CONCAT(ROUND(SUM(CASE WHEN 分组 = "[60-0]" THEN "1" ELSE "0" END)/COUNT(分组)*100,2),"%")   `[60-0]百分比`
FROM 
(
	SELECT
		Course.CId       课程编号
		,Course.`Cname`  课程名
		,SC.`score`      成绩
		,CASE 
			WHEN SC.`score` >= 85 THEN "[100-85]" 
			WHEN SC.`score` >= 70 THEN "[85-70]"
			WHEN SC.`score` >= 60 THEN "[70-60]"
			ELSE "[60-0]"
		END 分组
	FROM SC 
	LEFT JOIN Course ON SC.CId = Course.CId
) t
GROUP BY 课程编号,课程名
  • 输出
课程编号 课程名 [100-85]百分比 [85-70]百分比 [70-60]百分比 [60-0]百分比
03 英语 50% 25% 0% 25%
01 语文 0% 66.67% 0% 33.33%
02 数学 50% 16.67% 16.67% 16.67%



15.查询各科成绩前三名的记录

SELECT
	课程编号
	,成绩
	,排名
FROM
(
	SELECT 
	CId    课程编号
	,score 成绩
	,rank() over(PARTITION BY CId ORDER BY score DESC) 排名
	FROM SC
) t
WHERE 排名 IN (1,2,3)
  • 输出
课程编号 成绩 排名
01 80.0 1
01 80.0 1
01 76.0 3
02 90.0 1
02 89.0 2
02 87.0 3
03 99.0 1
03 98.0 2
03 95.0 3



16.查询每门课程被选修的学生数

SELECT 
Course.`Cname` 课程名
,COUNT(SC.SId) 选修人数
FROM Course
LEFT JOIN SC ON Course.CId = SC.CId
GROUP BY Course.CId
  • 输出
课程名 选修人数
语文 6
数学 6
英语 8



17.查询出只选修两门课程的学生学号和姓名

SELECT 
	Student.SId      学生学号
	,Student.`Sname` 学生姓名
FROM Student
LEFT JOIN SC ON Student.SId = SC.SId
GROUP BY Student.SId,Student.`Sname`
HAVING COUNT(SC.`CId`) < 2
  • 输出
学生学号 学生姓名
09 张三
10 西红柿
11 李四
12 赵六
13 孙七



18.查询同名同性学生名单,并统计同名人数

  • 感觉这题应该考察的是同名同姓的学生,如果是同名同性的话,学生表格里面是没有的
SELECT 
s1.`Sname`
,COUNT(DISTINCT s1.SId) 人数
FROM Student s1,Student s2 
WHERE s1.SId != s2.SId
AND s1.`Sname` = s2.`Sname`
  • 输出
Sname 人数
西红柿 2



19.查询每门课程的平均成绩,结果按平均成绩降序排列,平均成绩相同时,按课程编号升序排列

SELECT 
CId    课程编号
,ROUND(AVG(score),2) 平均成绩
FROM SC
GROUP BY CId  
ORDER BY AVG(score) DESC,CId ASC 
  • 输出
课程编号 平均成绩
03 75.00
02 72.67
01 64.50



20.查询平均成绩大于等于 85 的所有学生的学号、姓名和平均成绩

SELECT 
SC.SId 学生学号
,Student.`Sname` 学生姓名
,ROUND(AVG(SC.score),2)   平均成绩
FROM SC
LEFT JOIN Student ON SC.SId = Student.SId
GROUP BY SC.SId
HAVING AVG(SC.score) >= 85
  • 输出
学生学号 学生姓名 平均成绩
08 (NULL) 94.50
01 西红柿 89.67
07 郑竹 93.50

21.查询所有学生的课程及分数情况(存在学生没成绩,没选课的情况)

SELECT 
	Student.SId      学生学号
	,Student.`Sname` 学生姓名
	,Course.`Cname`  课程名	
	,SC.score        成绩
FROM Student
LEFT JOIN SC ON Student.SId = SC.SId
LEFT JOIN Course ON SC.CId = Course.CId
  • 输出
学生学号 学生姓名 课程名 成绩
01 西红柿 英语 99.0
01 西红柿 数学 90.0
01 西红柿 语文 80.0
02 钱电 英语 80.0
02 钱电 数学 60.0
02 钱电 语文 70.0
03 孙风 英语 80.0
03 孙风 数学 80.0
03 孙风 语文 80.0
04 李云 英语 20.0
04 李云 数学 30.0
04 李云 语文 50.0
05 周梅 数学 87.0
05 周梅 语文 76.0
06 吴兰 英语 34.0
06 吴兰 语文 31.0
07 郑竹 英语 98.0
07 郑竹 数学 89.0
09 张三 (NULL) (NULL)
10 西红柿 (NULL) (NULL)
11 李四 (NULL) (NULL)
12 赵六 (NULL) (NULL)
13 孙七 (NULL) (NULL)



22.查询任何一门课程成绩在 70 分以上的姓名、课程名称和分数

SELECT 
	 学生姓名
	,课程名
	,成绩
FROM 
(
	SELECT 
	Student.Sname   学生姓名
	,Course.`Cname` 课程名
	,SC.score       成绩
	,COUNT(SC.CId) OVER(PARTITION BY SC.SId) 课程数量
	FROM SC 
	LEFT JOIN Student ON SC.SId =  Student.SId
	LEFT JOIN Course ON SC.CId =  Course.CId
	WHERE score > 70
) t1 
WHERE 课程数量 = (SELECT COUNT(CId) FROM Course)
  • 输出
学生姓名 课程名 成绩
西红柿 语文 80.0
西红柿 数学 90.0
西红柿 英语 99.0
孙风 语文 80.0
孙风 数学 80.0
孙风 英语 80.0



23.查询课程编号为 01 且课程成绩在 80 分以上的学生的学号和姓名

SELECT 
Student.SId      学生学号
,Student.Sname   学生姓名
FROM SC 
LEFT JOIN Student ON SC.SId =  Student.SId
WHERE SC.CId = "01"
AND SC.score >= 80
  • 输出
学生学号 学生姓名
01 西红柿
03 孙风



24.求每门课程的学生人数

SELECT 
Course.`Cname` 课程名
,COUNT(SC.SId) 学生人数
FROM SC 
LEFT JOIN Course ON SC.CId =  Course.CId
GROUP BY Course.`Cname`
  • 输出
课程名 学生人数
英语 8
语文 6
数学 6



25.成绩不重复,查询选修「张三」老师所授课程的学生中,成绩最高的学生信息及其成绩 成绩有重复的情况下,

查询选修「张三」老师所授课程的学生中,成绩最高的学生信息及其成绩

SELECT 
	S.SId      学生学号
	,S.`Sname` 学生姓名
	,SC.score  成绩
FROM Teacher T
LEFT JOIN Course C ON T.TId = C.TId
LEFT JOIN SC ON SC.CId  = C.CId 
LEFT JOIN Student S ON S.SId = SC.SId
WHERE T.`Tname` = "张三"
AND SC.score = 
(
SELECT 
MAX(SC.score)
FROM Teacher T
LEFT JOIN Course C ON T.TId = C.TId
LEFT JOIN SC ON SC.CId  = C.CId 
WHERE T.`Tname` = "张三"
)
  • 输出
学生学号 学生姓名 成绩
01 西红柿 90.0



26.查询不同课程成绩相同的学生的学生编号、课程编号、学生成绩

SELECT 
sc1.CId    课程编号
,sc1.SId   学生编号
,sc1.score 学生成绩
FROM SC sc1,SC sc2
WHERE sc1.score = sc2.score 
AND sc1.SId = sc2.SId
AND sc1.CId != sc2.CId
GROUP BY sc1.CId,sc1.SId,sc1.score
  • 输出
课程编号 学生编号 学生成绩
03 03 80.0
02 03 80.0
01 03 80.0



27.查询每门功成绩最好的前两名

SELECT 
	课程名
	,学生学号
FROM
(
	SELECT 
	Course.`Cname` 课程名
	,SC.SId 学生学号
	,rank() over(PARTITION BY SC.CId ORDER BY SC.score DESC) 排名
	FROM Course
	LEFT JOIN SC ON Course.CId = SC.CId
) t 
WHERE 排名 IN (1,2)



统计每门课程的学生选修人数(超过 5 人的课程才统计)。

SELECT 
Course.`Cname` 课程名
,COUNT(SC.SId) 选修人数
FROM Course
LEFT JOIN SC ON Course.CId = SC.CId
GROUP BY Course.CId
HAVING COUNT(SC.SId) > 5 
  • 输出
课程名 选修人数
语文 6
数学 6
英语 8



28.检索至少选修两门课程的学生学号

SELECT 
SC.SId 学生学号
FROM SC 
GROUP BY SC.SId
HAVING COUNT(SC.CId) >= 2
  • 输出
学生学号
08
01
02
03
04
05
06
07



29.查询选修了全部课程的学生信息

SELECT 
	Student.SId      学生编号
	,Student.`Sname` 学生姓名
FROM Student
LEFT JOIN SC ON Student.SId = SC.SId
GROUP BY Student.SId,Student.`Sname`
HAVING COUNT(SC.`CId`) = (SELECT COUNT(CId) FROM Course)
  • 输出
学生编号 学生姓名
01 西红柿
02 钱电
03 孙风
04 李云



30.查询各学生的年龄,只按年份来算

SELECT
	Student.SId      学生学号
	,Student.`Sname` 学生姓名
	,Student.Sage 	 学生年份
FROM Student
  • 输出
学生学号 学生姓名 学生年份
01 西红柿 1990-01-01 00:00:00
02 钱电 1990-12-21 00:00:00
03 孙风 1990-12-20 00:00:00
04 李云 1990-12-06 00:00:00
05 周梅 1991-12-01 00:00:00
06 吴兰 1992-01-01 00:00:00
07 郑竹 1989-01-01 00:00:00
09 张三 2017-12-20 00:00:00
10 西红柿 2017-12-25 00:00:00
11 李四 2012-06-06 00:00:00
12 赵六 2013-06-13 00:00:00
13 孙七 2014-06-01 00:00:00



版权声明:本文为weixin_43816759原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。