前言

最近整理了一下之前刷力扣SQL题的一些笔记。大部分都是DDL语句的使用,像笛卡尔积、各种Join、窗口函数、日期函数都是数据库很重要的知识点,还有where和having的区别、group by的底层,都是面试经常会问到的。

常数1的使用

作用:加快效率

当我们只关心数据表有多少记录行而不需要知道具体的字段值时,类似select 1 from tbl是一个很不错的SQL语句。它通常用于子查询。这样可以减少系统开销,提高运行效率,因为这样子写的SQL语句,数据库引擎就不会去检索数据表里一条条具体的记录和每条记录里一个个具体的字段值并将它们放到内存里,而是根据查询到有多少行存在就输出多少个“1”,每个“1”代表有1行记录,同时选用数字1还因为它所占用的内存空间最小,当然用数字0的效果也一样。在不需要知道具体的记录值是什么的情况下这种写法无疑更加可取。

image-20221207145816841

举个例子,统计每个班的学生人数

常规写法:select class,count (*) as pax from students group by class;
更优写法:select class,count (1) as pax from students group by class;

再比如,列出每个班最年轻的学生资料

常规写法:

1
2
select a.* from students a 
where not exists(select b.sid from students b where b.sid != a.sid and b.date_birth > a.date_birth);

上面sql语句的意思是,查询一个学生,使得除他以外的所有学生,不存在比这个学生更小(出生日期更大)的学生。

更优写法:

1
2
select a.* from students a 
where not exists(select 1 from students b where b.sid != a.sid and b.date_birth > a.date_birth);

从效率上,select count(*) > select count(1) > select count(列名)
因为select count(*)SQL底层会自动进行优化

where和having的区别

“where”是一个约束声明,在查询数据库的结果返回之前对数据库中的查询条件进行约束,即在结果返回之前起作用,且“where”后面不能写“聚合函数”。

“having”是一个过滤声明,是在查询数据库结果返回之后进行过滤,即在结果返回值后起作用,并且“having”后面可以写“聚合函数”。

where、聚合函数、having在from后面的执行顺序:where>聚合函数(sum,min,max,avg,count)>having
在看过176题中的方法二,你会更加清楚这一点。

group by细节

group by查询的字段必须某一个列或者某个列的聚合函数

image-20221214160547042

对于这种情况的解释:group by筛选以后其实产生的是这样一个临时结果:

1
2
3
4
5
6
7
8
9
10
11
12
+--------+---------+
| id | name |
+--------+---------+
| | a |
| 1 | b |
| | c |
+--------+---------+
| 2 | d |
| | e |
+--------+---------+
| 3 | f |
+--------+---------+

但是,由于一个单元格只能有一个值,所以最终的结果变成了这样:

1
2
3
4
5
6
7
8
9
+--------+---------+
| id | name |
+--------+---------+
| 1 | a |
+--------+---------+
| 2 | d |
+--------+---------+
| 3 | f |
+--------+---------+

image-20221214160700442

对于这种情况的解释:由于having是在结果返回之后进行过滤,而结果中并没有c,因此返回空表。

image-20221214160746229

而where是结果返回之前进行筛选,所以可以查到c。

当单元格里面有多个值的时候,我们如何进行筛选呢?下面的代码提供了一种思路:

image-20221214162635446

最好的办法就是使用聚合函数聚合函数就用来输入多个数据,输出一个数据的。如SUM()或MAX(),而每个聚合函数的输入就是每一个多数据的单元格。题1179就用到了这个思路。

各种join

image-20230228125532829

182.查找重复的电子邮箱

image-20221207140027547

法1:自连接

1
2
3
select distinct p1.Email as Email
from Person as p1, Person as p2
where p1.Email = p2.Email and p1.Id != p2.Id;

要注意,distinct关键字必须加

法2:group by + having

1
2
3
4
select Email
from person
group by Email
having count(Email) > 1;

法3:group by + 临时表

1
2
3
4
5
6
7
select Email from
(
select Email, count(Email) as num
from Person
group by Email
) as statistic
where num > 1;

183 从不订购的客户

image-20221207142630954

image-20221207142644770

法1:子查询+判断是否为null

1
2
3
4
5
6
7
8
9
# Write your MySQL query statement below
select statistic.name as Customers
from
(
select customers.name, orders.id as orders_id
from customers left join orders
on customers.id = orders.Customerid
) as statistic
where statistic.orders_id is null;

这里有个坑,不能用value == null,而是得用value is null

法2:子查询+not in

1
2
3
4
5
6
select customers.name as 'Customers'
from customers
where customers.id not in
(
select customerid from orders
);

196. 删除重复的电子邮箱

image-20221207143840120

法1:子查询+not in

首先进行group by 对email去重,并保留最小的id,得到id列表, 然后删除不在这个列表中的数据

1
2
3
4
delete from Person where id not in
(
select min(id) as id from Person group by Email
);

这样写,MySQL会报错(其他数据库可能不会),这是因为MySQL不允许同时查询和删除一张表,我们可以通过子查询的方式包装一下即可避免这个报错。

1
2
3
4
5
delete from Person where id not in
(
select t.id from
(select min(id) as id from Person group by Email) as t
);

法2:通过笛卡尔积(或join)来判断删除条件

1
2
3
DELETE p1 FROM Person p1, Person p2
WHERE
p1.Email = p2.Email AND p1.Id > p2.Id

但是这样效率会很低

★178. 分数排名(窗口函数)

表: Scores

1
2
3
4
5
6
7
8
+-------------+---------+
| Column Name | Type |
+-------------+---------+
| id | int |
| score | decimal |
+-------------+---------+
Id是该表的主键。
该表的每一行都包含了一场比赛的分数。Score是一个有两位小数点的浮点值。

编写 SQL 查询对分数进行排序。排名按以下规则计算:

  • 分数应按从高到低排列。
  • 如果两个分数相等,那么两个分数的排名应该相同。
  • 在排名相同的分数后,排名数应该是下一个连续的整数。换句话说,排名之间不应该有空缺的数字。

score 降序返回结果表。

查询结果格式如下所示。

示例 1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
输入: 
Scores 表:
+----+-------+
| id | score |
+----+-------+
| 1 | 3.50 |
| 2 | 3.65 |
| 3 | 4.00 |
| 4 | 3.85 |
| 5 | 4.00 |
| 6 | 3.65 |
+----+-------+
输出:
+-------+------+
| score | rank |
+-------+------+
| 4.00 | 1 |
| 4.00 | 1 |
| 3.85 | 2 |
| 3.65 | 3 |
| 3.65 | 3 |
| 3.50 | 4 |
+-------+------+

答案:

1
select score, dense_rank() over (order by score desc) as `rank` from scores;

解答:

此题考查的是窗口函数的使用。

在日常工作中,经常会遇到需要在每组内排名,比如下面的业务需求:

排名问题:每个部门按业绩来排名

topN问题:找出每个部门排名前N的员工进行奖励

面对这类需求,就需要使用sql的高级功能窗口函数了。

窗口函数,也叫OLAP函数(Online Anallytical Processing,联机分析处理),可以对数据库数据进行实时分析处理。

窗口函数(rank, dense_rank, row_number)的基本语法

1
2
<窗口函数> over (partition by <用于分组的列名> 
order by <用于排序的列名>)

<窗口函数>的位置,可以放以下两种函数:

1) 专用窗口函数,包括后面要讲到的rank, dense_rank, row_number等专用窗口函数。

1、rank 遇重复值排序并列,然后跳跃到当前排序记录次数开始(递增或递减)排序

2、row_number 遇重复值排序不并列,连续不间断(递增或递减)排序

3、dense_rank 遇重复值排序并列,然后继续不间断(递增或递减)排序

image-20230228160316416

2) 聚合函数,如sum, avg, count, max, min等

因为窗口函数是对where或者group by子句处理后的结果进行操作,所以窗口函数原则上只能写在select子句中

image-20221212222007780

如果我们想在每个班级内按成绩排名,得到下面的结果。

image-20221212222017062

以班级“1”为例,这个班级的成绩“95”排在第1位,这个班级的“83”排在第4位。上面这个结果确实按我们的要求在每个班级内,按成绩排名了。

得到上面结果的sql语句代码如下:

1
2
3
select *,
rank() over (partition by 班级 order by 成绩 desc) as ranking
from 班级表

我们来解释下这个sql语句里的select子句。rank是排序的函数。要求是“每个班级内按成绩排名”,这句话可以分为两部分:

1)每个班级内:按班级分组

partition by用来对表分组。在这个例子中,所以我们指定了按“班级”分组(partition by 班级)

2)按成绩排名

order by子句的功能是对分组后的结果进行排序,默认是按照升序(asc)排列。在本例中(order by 成绩 desc)是按成绩这一列排序,加了desc关键词表示降序排列。

通过下图,我们就可以理解partiition by(分组)和order by(在组内排序)的作用了。

image-20221212222132437

group by, order by和窗口函数的对比

窗口函数具备了我们之前学过的group by子句分组的功能和order by子句排序的功能。那么,为什么还要用窗口函数呢?

这是因为,group by分组汇总后改变了表的行数,一行只有一个类别。而partiition by和rank函数不会减少原表中的行数。例如下面统计每个班级的人数。

image-20221212222413331

窗口函数rank, dense_rank, row_number之间的区别

它们的区别我举个例子,你们一下就能看懂:

1
2
3
4
5
select *,   
rank() over (order by 成绩 desc) as ranking,
dense_rank() over (order by 成绩 desc) as dese_rank,
row_number() over (order by 成绩 desc) as row_num
from 班级表

得到结果:

image-20221212222737618

从上面的结果可以看出:

rank函数:这个例子中是5位,5位,5位,8位,也就是如果有并列名次的行,会占用下一名次的位置。比如正常排名是1,2,3,4,但是现在前3名是并列的名次,结果是:1,1,1,4。

dense_rank函数:这个例子中是5位,5位,5位,6位,也就是如果有并列名次的行,不占用下一名次的位置。比如正常排名是1,2,3,4,但是现在前3名是并列的名次,结果是:1,1,1,2。

row_number函数:这个例子中是5位,6位,7位,8位,也就是不考虑并列名次的情况。比如前3名是并列的名次,排名是正常的1,2,3,4。

最后,需要强调的一点是:在上述的这三个专用窗口函数中,函数后面的括号不需要任何参数,保持()空着就可以

聚和窗口函数和上面提到的专用窗口函数用法完全相同,只需要把聚合函数写在窗口函数的位置即可,但是函数后面括号里面不能为空,需要指定聚合的列名。

我们来看一下窗口函数是聚合函数时,会出来什么结果:

1
2
3
4
5
6
7
select *,
sum(成绩) over (order by 学号) as current_sum,
avg(成绩) over (order by 学号) as current_avg,
count(成绩) over (order by 学号) as current_count,
max(成绩) over (order by 学号) as current_max,
min(成绩) over (order by 学号) as current_min
from 班级表

得到结果:

image-20221212223403123

如上图,聚合函数sum在窗口函数中,是对自身记录、及位于自身记录以上的数据进行求和的结果。比如0004号,在使用sum窗口函数后的结果,是对0001,0002,0003,0004号的成绩求和,若是0005号,则结果是0001号~0005号成绩的求和,以此类推。

这样使用窗口函数有什么用呢?

聚合函数作为窗口函数,可以在每一行的数据里直观的看到,截止到本行数据,统计数据是多少(最大值、最小值等)。同时可以看出每一行数据,对整体统计数据的影响。

197. 上升的温度

表: Weather

1
2
3
4
5
6
7
8
9
+---------------+---------+
| Column Name | Type |
+---------------+---------+
| id | int |
| recordDate | date |
| temperature | int |
+---------------+---------+
id 是这个表的主键
该表包含特定日期的温度信息

编写一个 SQL 查询,来查找与之前(昨天的)日期相比温度更高的所有日期的 id

返回结果 不要求顺序

查询结果格式如下例。

示例 1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
输入:
Weather 表:
+----+------------+-------------+
| id | recordDate | Temperature |
+----+------------+-------------+
| 1 | 2015-01-01 | 10 |
| 2 | 2015-01-02 | 25 |
| 3 | 2015-01-03 | 20 |
| 4 | 2015-01-04 | 30 |
+----+------------+-------------+
输出:
+----+
| id |
+----+
| 2 |
| 4 |
+----+
解释:
2015-01-02 的温度比前一天高(10 -> 25)
2015-01-04 的温度比前一天高(20 -> 30)

答案:

1
2
3
select a.id as id
from Weather as a, Weather as b
where timestampdiff(day, a.recordDate, b.recordDate) = -1 and a.temperature > b.temperature;

1
2
3
select a.id as id
from Weather as a, Weather as b
where datediff(a.recordDate, b.recordDate) = 1 and a.temperature > b.temperature;

第二种更快一点。

本题主要考查这两个函数的使用。

datediff函数和timestampdiff函数

timestamp:时间戳

image-20221212225403001

timestampdiff(时间类型, 日期1, 日期2) 这个函数和上面diffdate的正、负号规则刚好相反。 日期1大于日期2,结果为负,日期1小于日期2,结果为正。在“时间类型”的参数位置,通过添加“day”, “hour”, “second”等关键词,来规定计算天数差、小时数差、还是分钟数差。

image-20221212231100599

586. 订单最多的客户

表: Orders

1
2
3
4
5
6
7
8
+-----------------+----------+
| Column Name | Type |
+-----------------+----------+
| order_number | int |
| customer_number | int |
+-----------------+----------+
Order_number是该表的主键。
此表包含关于订单ID和客户ID的信息。

编写一个SQL查询,为下了 最多订单 的客户查找 customer_number

测试用例生成后, 恰好有一个客户 比任何其他客户下了更多的订单。

查询结果格式如下所示。

示例 1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
输入: 
Orders 表:
+--------------+-----------------+
| order_number | customer_number |
+--------------+-----------------+
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | 3 |
+--------------+-----------------+
输出:
+-----------------+
| customer_number |
+-----------------+
| 3 |
+-----------------+
解释:
customer_number 为 '3' 的顾客有两个订单,比顾客 '1' 或者 '2' 都要多,因为他们只有一个订单。
所以结果是该顾客的 customer_number ,也就是 3 。

进阶: 如果有多位顾客订单数并列最多,你能找到他们所有的 customer_number 吗?

答案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Write your MySQL query statement below
# 题解一
SELECT customer_number
FROM
(
SELECT customer_number,
COUNT(customer_number) AS n
FROM Orders GROUP BY customer_number
) T
ORDER BY n DESC
LIMIT 1;

# 题解二
SELECT customer_number
FROM Orders
group by customer_number
order by count(*) desc
limit 1;

此题主要考查group byorder bylimit的使用。

进阶的答案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
SELECT
customer_number
FROM
Orders
GROUP BY customer_number
HAVING COUNT(*) = (
SELECT
COUNT(customer_number) AS 'cnt'
FROM
Orders
GROUP BY customer_number
ORDER BY cnt DESC
LIMIT 1
)
;

先查出最多的订单数,然后再查找最多订单数对应的客户

还有一个不错的思路:

1
2
3
4
5
6
7
SELECT customer_number FROM Orders
GROUP BY customer_number
HAVING COUNT(*) >= ALL(
SELECT COUNT(*) FROM Orders
GROUP BY customer_number
)
;

176.第二高的薪水

Employee 表:

1
2
3
4
5
6
7
8
+-------------+------+
| Column Name | Type |
+-------------+------+
| id | int |
| salary | int |
+-------------+------+
id 是这个表的主键。
表的每一行包含员工的工资信息。

编写一个 SQL 查询,获取并返回 Employee 表中第二高的薪水 。如果不存在第二高的薪水,查询应该返回 null

查询结果如下例所示。

示例 1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
输入:
Employee 表:
+----+--------+
| id | salary |
+----+--------+
| 1 | 100 |
| 2 | 200 |
| 3 | 300 |
+----+--------+
输出:
+---------------------+
| SecondHighestSalary |
+---------------------+
| 200 |
+---------------------+

示例 2:

1
2
3
4
5
6
7
8
9
10
11
12
13
输入:
Employee 表:
+----+--------+
| id | salary |
+----+--------+
| 1 | 100 |
+----+--------+
输出:
+---------------------+
| SecondHighestSalary |
+---------------------+
| null |
+---------------------+
1
2
3
4
5
6
7
select ifnull((
select distinct salary
from Employee
order by salary desc
limit 1,1
), NULL
) as SecondHighestSalary ;

本题考查ifnull语句和limit语句。

IFNULL(expr1,expr2),如果expr1的值为null,则返回expr2的值,如果expr1的值不为null,则返回expr1的值。

image-20221213002053685

以上三个select的结果分别为:

1
2
3
hi
hello
1

法1:

1
2
3
4
5
6
7
8
9
# Write your MySQL query statement below

select ifnull((
select distinct salary
from Employee
order by salary desc
limit 1,1
), NULL
) as SecondHighestSalary ;

法2:

1
2
3
4
5
6
7
8
9
select ifnull((
select max(distinct salary)
from Employee
where salary < (
select max(distinct salary)
from Employee
)
), NULL
) as SecondHighestSalary ;

先用where把最大的那个薪资筛掉,然后再在剩下的薪资里选择最大的

607.销售员

表: SalesPerson

1
2
3
4
5
6
7
8
9
10
11
+-----------------+---------+
| Column Name | Type |
+-----------------+---------+
| sales_id | int |
| name | varchar |
| salary | int |
| commission_rate | int |
| hire_date | date |
+-----------------+---------+
sales_id 是该表的主键列。
该表的每一行都显示了销售人员的姓名和 ID ,以及他们的工资、佣金率和雇佣日期。

表: Company

1
2
3
4
5
6
7
8
9
+-------------+---------+
| Column Name | Type |
+-------------+---------+
| com_id | int |
| name | varchar |
| city | varchar |
+-------------+---------+
com_id 是该表的主键列。
该表的每一行都表示公司的名称和 ID ,以及公司所在的城市。

表: Orders

1
2
3
4
5
6
7
8
9
10
11
12
13
+-------------+------+
| Column Name | Type |
+-------------+------+
| order_id | int |
| order_date | date |
| com_id | int |
| sales_id | int |
| amount | int |
+-------------+------+
order_id 是该表的主键列。
com_id 是 Company 表中 com_id 的外键。
sales_id 是来自销售员表 sales_id 的外键。
该表的每一行包含一个订单的信息。这包括公司的 ID 、销售人员的 ID 、订单日期和支付的金额。

编写一个SQL查询,报告没有任何与名为 “RED” 的公司相关的订单的所有销售人员的姓名。

任意顺序 返回结果表。

查询结果格式如下所示。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
输入:
SalesPerson 表:
+----------+------+--------+-----------------+------------+
| sales_id | name | salary | commission_rate | hire_date |
+----------+------+--------+-----------------+------------+
| 1 | John | 100000 | 6 | 4/1/2006 |
| 2 | Amy | 12000 | 5 | 5/1/2010 |
| 3 | Mark | 65000 | 12 | 12/25/2008 |
| 4 | Pam | 25000 | 25 | 1/1/2005 |
| 5 | Alex | 5000 | 10 | 2/3/2007 |
+----------+------+--------+-----------------+------------+
Company 表:
+--------+--------+----------+
| com_id | name | city |
+--------+--------+----------+
| 1 | RED | Boston |
| 2 | ORANGE | New York |
| 3 | YELLOW | Boston |
| 4 | GREEN | Austin |
+--------+--------+----------+
Orders 表:
+----------+------------+--------+----------+--------+
| order_id | order_date | com_id | sales_id | amount |
+----------+------------+--------+----------+--------+
| 1 | 1/1/2014 | 3 | 4 | 10000 |
| 2 | 2/1/2014 | 4 | 5 | 5000 |
| 3 | 3/1/2014 | 1 | 1 | 50000 |
| 4 | 4/1/2014 | 1 | 4 | 25000 |
+----------+------------+--------+----------+--------+
输出:
+------+
| name |
+------+
| Amy |
| Mark |
| Alex |
+------+
解释:
根据表 orders 中的订单 '3' 和 '4' ,容易看出只有 'John' 和 'Pam' 两个销售员曾经向公司 'RED' 销售过。
所以我们需要输出表 salesperson 中所有其他人的名字。

先来看一个错误的答案:

1
2
3
select distinct salesperson.name
from orders left join salesperson on salesperson.sales_id = orders.sales_id left join company on company.com_id = orders.com_id
where company.name != 'RED';

这样写的意思是从全部订单中去除与RED公司相关的订单,然后把其余订单相关的销售员列出来。试想有位销售员,他有RED公司的订单,也有其他公司的订单,这种情况下他仍然会被列出来。(题目要求是”报告没有任何与名为 ‘RED’ 的公司相关的订单的所有销售人员的姓名“,意思是这个销售员只要有过RED公司的订单,就不应该出现在结果中)

正确的解法应该是先查和RED公司有关的销售员,然后再用NOT IN进行筛选。

1
2
3
4
5
6
7
8
9
10
# Write your MySQL query statement below
select distinct SalesPerson.name as name
from SalesPerson
where name not in
(
select SalesPerson.name
from orders left join salesperson on salesperson.sales_id = orders.sales_id
left join company on company.com_id = orders.com_id
where company.name = 'RED'
);

627.变更性别

Salary 表:

1
2
3
4
5
6
7
8
9
10
11
+-------------+----------+
| Column Name | Type |
+-------------+----------+
| id | int |
| name | varchar |
| sex | ENUM |
| salary | int |
+-------------+----------+
id 是这个表的主键。
sex 这一列的值是 ENUM 类型,只能从 ('m', 'f') 中取。
本表包含公司雇员的信息。

请你编写一个 SQL 查询来交换所有的 'f''m' (即,将所有 'f' 变为 'm' ,反之亦然),仅使用 单个 update 语句 ,且不产生中间临时表。

注意,你必须仅使用一条 update 语句,且 不能 使用 select 语句。

1
2
update Salary
set sex = if(sex = 'm', 'f', 'm')

1179.重新格式化部门表

部门表 Department

1
2
3
4
5
6
7
8
9
10
+---------------+---------+
| 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)列。

查询结果格式如下面的示例所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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个月份的收入列)。

答案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
select id,
max(if (month = 'Jan', revenue, null)) as Jan_Revenue,
max(if (month = 'Feb', revenue, null)) as Feb_Revenue,
max(if (month = 'Mar', revenue, null)) as Mar_Revenue,
max(if (month = 'Apr', revenue, null)) as Apr_Revenue,
max(if (month = 'May', revenue, null)) as May_Revenue,
max(if (month = 'Jun', revenue, null)) as Jun_Revenue,
max(if (month = 'Jul', revenue, null)) as Jul_Revenue,
max(if (month = 'Aug', revenue, null)) as Aug_Revenue,
max(if (month = 'Sep', revenue, null)) as Sep_Revenue,
max(if (month = 'Oct', revenue, null)) as Oct_Revenue,
max(if (month = 'Nov', revenue, null)) as Nov_Revenue,
max(if (month = 'Dec', revenue, null)) as Dec_Revenue
from Department
group by id
order by id;

本题考查你对group by的理解。

180.连续出现的数字

表:Logs

1
2
3
4
5
6
7
+-------------+---------+
| Column Name | Type |
+-------------+---------+
| id | int |
| num | varchar |
+-------------+---------+
id 是这个表的主键。

编写一个 SQL 查询,查找所有至少连续出现三次的数字。

返回的结果表中的数据可以按 任意顺序 排列。

查询结果格式如下面的例子所示:

示例 1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
输入:
Logs 表:
+----+-----+
| Id | Num |
+----+-----+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 2 |
| 5 | 1 |
| 6 | 2 |
| 7 | 2 |
+----+-----+
输出:
Result 表:
+-----------------+
| ConsecutiveNums |
+-----------------+
| 1 |
+-----------------+
解释:1 是唯一连续出现至少三次的数字。

答案:

1
2
3
4
5
6
7
8
9
10
11
SELECT DISTINCT Num as ConsecutiveNums FROM 
(
SELECT Num,COUNT(1) as SerialCount FROM
(
SELECT Id,Num,
row_number() over(order by id) - ROW_NUMBER() over(partition by Num order by Id) as SerialNumberSubGroup
FROM Logs
) as Sub
GROUP BY Num,SerialNumberSubGroup
HAVING COUNT(1) >= 3
) as Result;

SerialNumberSubGroup:通过row_number() over(order by id)ROW_NUMBER() over(partition by Num order by Id)进行相减得到,若连续三次相同,那么就说明同一个数字出现了三次以上。