Employee
表包含所有员工信息,每个员工有其对应的 Id, salary 和 department Id。Department
表包含公司所有部门的信息。编写一个 SQL 查询,找出每个部门工资最高的员工。
SELECT b.name as Department, a.Name as Employee, a.salary as Salary
FROM employee a JOIN department b ON a.departmentid = b.id
# 注意点:如果where条件只是salary in子查询,则会选到A部门salary(非A部门max) = B部门max(salary)
WHERE (a.departmentid,a.salary) IN (
SELECT departmentid, max(salary)
FROM employee
GROUP BY departmentid)
知识点:
- 可以用 ’ (a,b) in 子查询 ‘ 的方式同时查询两个字段。
CASE WHEN
IF
表Salary中,sex这一列的值是ENUM类型,只能从(‘m’,‘f’)中取。
请你编写一个 SQL 查询来交换所有的 ‘f’ 和 ‘m’ (即,将所有 ‘f’ 变为 ‘m’ ,反之亦然),仅使用 单个 update 语句 ,且不产生中间临时表。
注意,你必须仅使用一条 update 语句,且 不能 使用 select 语句。
-- 方法一:CASE WHEN
UPDATE salary
SET
sex = CASE sex
WHEN 'm' THEN 'f'
ELSE 'm'
END; -- 必须要有end。
-- 方法二:IF
update salary set sex=IF(sex='f','m','f');
知识点:
CASE WHEN
1) 第一种 格式 : 简单Case函数 :
格式说明
case 列名
when 条件值1 then 选择项1
when 条件值2 then 选项2…
else 默认值 end
2)第二种 格式 :Case搜索函数
格式说明
case
when 列名= 条件值1 then 选择项1
when 列名=条件值2 then 选项2…
else 默认值 end
提示:通常我们在写Case When的语句的时候,会容易忘记 end 这个结束,一定要记得哟!
比较: 两种格式,可以实现相同的功能。
简单Case函数的写法相对比较简洁,但是和Case搜索函数相比,功能方面会有些限制,比如写判断式。还有一个需要注意的问题,Case函数只返回第一个符合条件的值,剩下的Case部分将会被自动忽略。
IF(expression,a, b)
:如果 expression 为True,则返回 a,否则返回 b
部门表 Department
:(id, month)是表的联合主键,这个表格有关于每个部门每月收入的信息。
编写一个 SQL 查询来重新格式化表,使得新的表中有一个部门 id 列和一些对应 每个月 的收入(revenue)列。
select id,
sum(case month when 'Jan' then revenue end) as Jan_Revenue,
sum(case month when 'Feb' then revenue end) as Feb_Revenue,
sum(case month when 'Mar' then revenue end) as Mar_Revenue,
sum(case month when 'Apr' then revenue end) as Apr_Revenue,
sum(case month when 'May' then revenue end) as May_Revenue,
sum(case month when 'Jun' then revenue end) as Jun_Revenue,
sum(case month when 'Jul' then revenue end) as Jul_Revenue,
sum(case month when 'Aug' then revenue end) as Aug_Revenue,
sum(case month when 'Sep' then revenue end) as Sep_Revenue,
sum(case month when 'Oct' then revenue end) as Oct_Revenue,
sum(case month when 'Nov' then revenue end) as Nov_Revenue,
sum(case month when 'Dec' then revenue end) as Dec_Revenue
from Department
group by id
# 此题中sum处用max也是可以的。
知识点:
GROUP BY
的理解在MySQL中,允许下面这样的写法:
SELECT id,revenue -- 输出的revenue的结果是每个id的第一个值 FROM Department GROUP BY id
即在select子句中出现了group by子句中没有出现的列名revenue,而这种写法在SQL标准中是没有的,在MySQL以外的大部分数据库中也是不支持的,因为逻辑上没有意义。
sum(case month when 'Jan' then revenue end) as Jan_Revenue -- 该代码的意思即为将所有的revenue聚合处理,处理方法是如果month的值是Jan,那么结果就是revenue,否则忽略。
因为根据题目描述我们可以知道,每个月份最多只会出现一次,所以用
max
取出那个唯一值就可以了,所以也可以用max
来处理。
CASE WHEN
的理解当一个单元格中有多个数据时,case when只会提取当中的第一个数据。
以CASE WHEN month=‘Feb’ THEN revenue END 为例,当id=1时,它只会提取month对应单元格里的第一个数据,即Jan,它不等于Feb,所以找不到Feb对应的revenue,所以返回NULL。(可以试试把我上面答案里的sum()统统去掉,执行结果与预期不一样。错就错在当id=1时,Feb_Revenue和Mar_Revenue的值变成了NULL)
那该如何解决单元格内含多个数据的情况呢?答案就是使用聚合函数,聚合函数就用来输入多个数据,输出一个数据的。如SUM()或MAX(),而每个聚合函数的输入就是每一个多数据的单元格。
以SUM(CASE WHEN month=‘Feb’ THEN revenue END) 为例,当id=1时,它提取的Jan、Feb、Mar,从中找到了符合条件的Feb,并最终返回对应的revenue的值,即7000。
IFNULL()
、 LIMIT
编写一个 SQL 查询,获取 Employee
表中第二高的薪水(Salary) 。
如果不存在第二高的薪水,那么查询应返回 null
。
# 方法一:通过子查询找到最大值,然后再找出小于最大值的最大值
select max(salary) SecondHighestSalary
from employee
where salary < (select max(salary) from employee )
# 方法二:用ifnull函数和limit offset函数
SELECT
IFNULL(
(SELECT DISTINCT Salary
FROM Employee
ORDER BY Salary DESC
LIMIT 1,1), # LIMIT 1,1 等同于 'LIMIT 1 OFFSET 1'
NULL) AS SecondHighestSalary
知识点:
IFNULL(a,b)
函数用于判断第一个表达式是否为null,如果为null,则返回b,如果不为null,则返回a。
LIMIT子句
可以被用于强制SELECT语句返回指定的记录数。LIMIT接收一个或两个参数,参数必须是整数常量。如果给定2个参数,第一个参数制定第一个返回记录行的偏移量,第二个参数制定返回记录行的最大数目。注意:初始记录行的偏移量是0,不是1。为了与 PostgreSQL 兼容,MySQL 也支持句法: LIMIT # OFFSET #。例1:
mysql> SELECT * FROM table LIMIT 5,10; // 检索记录行 6-15
例2:
mysql> SELECT * FROM table LIMIT 95,-1; // 检索记录行 96-last.
编写一个 SQL 查询,获取 Employee
表中第 n 高的薪水(Salary)。
CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
SET N = N - 1;
RETURN ( # Write your MySQL query statement below.
SELECT ifnull( ( SELECT DISTINCT salary FROM Employee
ORDER BY salary DESC LIMIT N, 1 ), NULL ) );
END
知识点:
- LIMIT里面不能做运算,所以要在RETURN前先处理下N的值,
SET N = N-1
编写一个 SQL 查询来实现分数排名。
如果两个分数相同,则两个分数排名(Rank)相同。请注意,平分后的下一个名次应该是下一个连续的整数值。换句话说,名次之间不应该有“间隔”。
# 根据题目要求 分数相同时排名相同,且名词连续,因此适用`dense_rank()`排序函数
select Score, dense_rank() over(order by Score desc) `Rank`
from Scores
# 注意:由于Rank是mysql中的关键字,所以在用其作为别名的时候,需要用 '' 或者用 ``
-- 另一种不用排序函数的做法:
select a.Score as Score,
(select count(distinct b.Score)
from Scores b
where b.Score >= a.Score) as `Rank`
from Scores a
order by a.Score DESC
知识点:
四大排名函数
1)
ROW_NUMBER()
Row_number() 在排名时,序号连续不重复,即使遇到表中的两个一样的数值亦是如此。
2)
RANK()
Rank() 函数会把要求排序的值相同的归为一组且每组序号一样,排序不连续
3)
DENSE_RANK()
Dense_rank() 函数会把要求排序的值相同的归为一组且每组序号一样,且排序是连续的
4)
NTILE()
Ntile(group_num) 将所有记录分成group_num个组,每组序号一样
编写一个 SQL 查询,查找所有至少连续出现三次的数字。
返回的结果表中的数据可以按 任意顺序 排列。
# 连续出现的意味着相同数字的 Id 是连着的,由于这题问的是至少连续出现 3 次,我们使用 Logs 并检查是否有 3 个连续的相同数字。同时我们需要添加关键字 DISTINCT ,因为如果一个数字连续出现超过 3 次,会返回重复元素。
SELECT DISTINCT
l1.Num AS ConsecutiveNums
FROM
Logs l1,
Logs l2,
Logs l3
WHERE
l1.Id = l2.Id - 1
AND l2.Id = l3.Id - 1
AND l1.Num = l2.Num
AND l2.Num = l3.Num
小美是一所中学的信息科技老师,她有一张 seat 座位表,平时用来储存学生名字和与他们相对应的座位 id。
其中纵列的 id 是连续递增的,小美想改变相邻俩学生的座位。你能不能帮她写一个 SQL query 来输出小美想要的结果呢?
如果学生人数是奇数,则不需要改变最后一个同学的座位。
# 方法一:使用 CASE,对于所有座位 id 是奇数的学生,修改其 id 为 id+1,如果最后一个座位 id 也是奇数,则最后一个座位 id 不修改。对于所有座位 id 是偶数的学生,修改其 id 为 id-1。
SELECT
(CASE
WHEN MOD(id, 2) != 0 AND counts != id THEN id + 1
WHEN MOD(id, 2) != 0 AND counts = id THEN id
ELSE id - 1
END) AS id,
student
FROM
seat,
(SELECT
COUNT(*) AS counts
FROM
seat) AS seat_counts
ORDER BY id ASC;
## 这个做法不对,因为case when只返回第一个结果。
SELECT
(CASE
WHEN MOD(id, 2) != 0 AND count(id) != id THEN id + 1
WHEN MOD(id, 2) != 0 AND count(id) = id THEN id
ELSE id - 1
END) AS id, student
FROM seat
ORDER BY id ASC;
# 方法二:使用位操作和 COALESCE(),使用 (id+1)^1-1 计算交换后每个学生的座位 id。
SELECT
s1.id, COALESCE(s2.student, s1.student) AS student
FROM
seat s1
LEFT JOIN
seat s2 ON ((s1.id + 1) ^ 1) - 1 = s2.id
ORDER BY s1.id;
知识点:
COALESCE(a, b, ...)
:
用途:返回列表中第一个非null的值,如果列表中的所有值都是null,则返回null。
参数:a, b, …实要测试的值,所有这些值类型必须相同或为null,且参数至少要有一个,否则会引发异常。
返回值:返回值类型和参数类型相同。
Employee
表包含所有员工信息,每个员工有其对应的工号 Id
,姓名 Name
,工资 Salary
和部门编号 DepartmentId
。
Department
表包含公司所有部门的信息。
编写一个 SQL 查询,找出每个部门获得前三高工资的所有员工。
SELECT
d.Name AS 'Department', e1.Name AS 'Employee', e1.Salary
FROM
Employee e1
JOIN
Department d ON e1.DepartmentId = d.Id
WHERE
3 > (SELECT
COUNT(DISTINCT e2.Salary)
FROM
Employee e2
WHERE
e2.Salary > e1.Salary
AND e1.DepartmentId = e2.DepartmentId
)
;
有两张表,trips和users。
trips表,含id, client_id, driver_id, city_id, status, request_at;
users表,含 user_id, banned, role
写一段 SQL 语句查出 “2013-10-01” 至 “2013-10-03” 期间非禁止用户(乘客和司机都必须未被禁止)的取消率。非禁止用户即 Banned 为 No 的用户,禁止用户即 Banned 为 Yes 的用户。
取消率 的计算方式如下:(被司机或乘客取消的非禁止用户生成的订单数量) / (非禁止用户生成的订单总数)。
返回结果表中的数据可以按任意顺序组织。其中取消率 Cancellation Rate 需要四舍五入保留 两位小数 。
输出:result表,含 day, cancellation rate
-- 复杂版本,分别计算出取消的数据及总订单数,再并表计算。
select b.day Day, round(ifnull(a.cancelledtrip,0) / b.totaltrip , 2) as "Cancellation Rate"
from
(select t.request_at Day, count(*) totaltrip
from trips t
left join users u1 on t.client_id = u1.users_id
left join users u2 on t.driver_id = u2.users_id
where u1.banned <> 'Yes' and u2.banned <> 'Yes'
and t.request_at between '2013-10-01' and '2013-10-03'
group by t.request_at) b
left join
(select t.request_at Day, count(*) cancelledtrip
from trips t
left join users u1 on t.client_id = u1.users_id
left join users u2 on t.driver_id = u2.users_id
where u1.banned <> 'Yes' and u2.banned <> 'Yes'
and t.request_at between '2013-10-01' and '2013-10-03'
and t.status like "cancelled%"
group by t.request_at) a
on b.day = a.day
order by b.day
-- 基于以上复杂版本,可以使用sum-if进行统计,简化代码
select t.request_at Day, round(sum(IF(t.status = 'completed',0,1)) / count(*) ,2) 'Cancellation Rate'
from trips t
left join users u1 on t.client_id = u1.users_id
left join users u2 on t.driver_id = u2.users_id
where u1.banned <> 'Yes' and u2.banned <> 'Yes'
and t.request_at between '2013-10-01' and '2013-10-03'
group by t.request_at
order by t.request_at
知识点:
两种基于某个条件的数据统计方法:
# 方法一:sum+if sum(IF(t.status = 'completed',0,1) # 方法二:sum+case when sum( case t.status when 'cancelled_by_driver' then 1 when 'cancelled_by_client' then 1 else 0 end )