力扣:https://leetcode-cn.com/
力扣网数据库练习:https://leetcode-cn.com/problemset/database/
题目连接:https://leetcode-cn.com/problems/combine-two-tables/
Create table Person (PersonId int, FirstName varchar(255), LastName varchar(255))
Create table Address (AddressId int, PersonId int, City varchar(255), State varchar(255))
Truncate table Person
insert into Person (PersonId, LastName, FirstName) values ('1', 'Wang', 'Allen')
Truncate table Address
insert into Address (AddressId, PersonId, City, State) values ('1', '2', 'New York City', 'New York')
表1: Person
+-------------+---------+
| 列名 | 类型 |
+-------------+---------+
| PersonId | int |
| FirstName | varchar |
| LastName | varchar |
+-------------+---------+
PersonId 是上表主键
表2: Address
+-------------+---------+
| 列名 | 类型 |
+-------------+---------+
| AddressId | int |
| PersonId | int |
| City | varchar |
| State | varchar |
+-------------+---------+
AddressId 是上表主键
编写一个 SQL 查询,满足条件:无论 person 是否有地址信息,都需要基于上述两表提供 person 的以下信息:
FirstName, LastName, City, State
方法:使用 outer join
算法
因为表 Address 中的 personId 是表 Person 的外关键字,所以我们可以连接这两个表来获取一个人的地址信息。
考虑到可能不是每个人都有地址信息,我们应该使用 outer join 而不是默认的 inner join。
select FirstName, LastName, City, State
from Person left join Address
on Person.PersonId = Address.PersonId
;
作者:LeetCode
链接:https://leetcode-cn.com/problems/combine-two-tables/solution/zu-he-liang-ge-biao-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
注意:如果没有某个人的地址信息,使用 where 子句过滤记录将失败,因为它不会显示姓名信息。
# Write your MySQL query statement below
select A.FirstName, A.LastName, B.City, B.State
from Person A
left join (select distinct PersonId, City, State from Address) B
on A.PersonId=B.PersonId;
数据库在通过连接两张或多张表来返回记录时,都会生成一张中间的临时表,然后再将这张临时表返回给用户。 在使用left jion时,on和where条件的区别如下:
1、on条件是在生成临时表时使用的条件,它不管on中的条件是否为真,都会返回左边表中的记录。
2、where条件是在临时表生成好后,再对临时表进行过滤的条件。这时已经没有left join的含义(必须返回左边表的记录)了,条件不为真的就全部过滤掉
题目链接:https://leetcode-cn.com/problems/second-highest-salary/
Create table If Not Exists Employee (Id int, Salary int)
Truncate table Employee
insert into Employee (Id, Salary) values ('1', '100')
insert into Employee (Id, Salary) values ('2', '200')
insert into Employee (Id, Salary) values ('3', '300')
编写一个 SQL 查询,获取 Employee 表中第二高的薪水(Salary) 。
+----+--------+
| Id | Salary |
+----+--------+
| 1 | 100 |
| 2 | 200 |
| 3 | 300 |
+----+--------+
例如上述 Employee 表,SQL查询应该返回 200 作为第二高的薪水。如果不存在第二高的薪水,那么查询应返回 null。
+---------------------+
| SecondHighestSalary |
+---------------------+
| 200 |
+---------------------+
方法一:使用子查询和 LIMIT 子句
算法:将不同的薪资按降序排序,然后使用 LIMIT 子句获得第二高的薪资。
SELECT
(SELECT DISTINCT
Salary
FROM
Employee
ORDER BY Salary DESC
LIMIT 1 OFFSET 1) AS SecondHighestSalary
;
然而,如果没有这样的第二最高工资,这个解决方案将被判断为 “错误答案”,因为本表可能只有一项记录。为了克服这个问题,我们可以将其作为临时表。
SELECT
(SELECT DISTINCT
Salary
FROM
Employee
ORDER BY Salary DESC
LIMIT 1 OFFSET 1) AS SecondHighestSalary
;
方法二:使用 IFNULL 和 LIMIT 子句
解决 “NULL” 问题的另一种方法是使用 “IFNULL” 函数,如下所示。
SELECT
IFNULL(
(SELECT DISTINCT Salary
FROM Employee
ORDER BY Salary DESC
LIMIT 1 OFFSET 1),
NULL) AS SecondHighestSalary
-------------------------------------------------------------------
作者:LeetCode
链接:https://leetcode-cn.com/problems/second-highest-salary/solution/di-er-gao-de-xin-shui-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
# Write your MySQL query statement below
select ifNull((select distinct Salary from Employee order by Salary desc limit 1,1),null) as SecondHighestSalary;
注意:考虑到有重复值的情况,使用distinct 成绩进行成绩去重。
select (select distinct salary from Employee order by salary desc limit 1,1) as SecondHighestSalary;
select max(Salary) SecondHighestSalary from employee where salary<(select max(salary) from employee);
mysql语句分析:
limit n
子句表示查询结果返回前n条数据offset n
表示跳过x条语句limit y offset x
分句表示查询结果跳过 x 条数据,读取前 y 条数据考虑特殊情况 isnull()
题目要求,如果没有第二高的成绩,返回空值,所以这里用判断空值的函数(ifnull)函数来处理特殊情况。
ifnull(a,b)函数解释:
如果value1不是空,结果返回a, 如果value1是空,结果返回b
select ifnull((select 子句),null) as '别名';
select IFNULL((select distinct(Salary) from Employee order by Salary desc limit 1,1),null) as SecondHighestSalary;
题目链接:https://leetcode-cn.com/problems/nth-highest-salary/
编写一个 SQL 查询,获取 Employee 表中第 n 高的薪水(Salary)。
+----+--------+
| Id | Salary |
+----+--------+
| 1 | 100 |
| 2 | 200 |
| 3 | 300 |
+----+--------+
例如上述 Employee 表,n = 2 时,应返回第二高的薪水 200。如果不存在第 n 高的薪水,那么查询应返回 null。
+------------------------+
| getNthHighestSalary(2) |
+------------------------+
| 200 |
+------------------------+
解题思路
CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
RETURN (
# Write your MySQL query statement below.
SELECT DISTINCT salary
FROM (SELECT salary, @r:=IF(@p=salary, @r, @r+1) AS 'rank', @p:= salary
FROM employee, (SELECT @r:=0, @p:=NULL)init
ORDER BY salary DESC) tmp
WHERE rank = N
);
END
-------------------------------------------------------------------
作者:luanz
链接:https://leetcode-cn.com/problems/nth-highest-salary/solution/mysql-zi-ding-yi-bian-liang-by-luanz/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
set n = n-1;
RETURN (
# Write your MySQL query statement below.
select distinct Salary from Employee order by Salary desc limit n,1
);
END
此题与 176. 第二高的薪水 类型相同,可以使用 limit n1,n2
语句,取n1~n2之间的数据。
另外,对MySQL来说, limit x,y = limit y offset x。
SQL查询语句中的 limit 与 offset 的区别:
-- 例1:从第0个开始,获取20条数据
select * from testtable limit 0, 20;
select * from testtable limit 20 offset 0;
-- 例2: 从第20个开始,获取20条数据
select * from testtable limit 20, 20;
select * from testtable limit 20 offset 20;
-- 例3: 从第40个开始,获取20条数据
select * from testtable limit 40, 20;
select * from testtable limit 20 offset 40;
如此题中,选用第n高的薪水。我们只需先将n设置为n-1(表中数据从0开始排列),然后使用 limt n,1 从第n个数据开始获取一条数据即可。
CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
SET N = N - 1;
RETURN (
# Write your MySQL query statement below.
select (
select distinct salary
from employee order by salary desc
limit N,1
)
);
END
题目链接:https://leetcode-cn.com/problems/employees-earning-more-than-their-managers/
Create table If Not Exists Employee (Id int, Name varchar(255), Salary int, ManagerId int)
Truncate table Employee
insert into Employee (Id, Name, Salary, ManagerId) values ('1', 'Joe', '70000', '3')
insert into Employee (Id, Name, Salary, ManagerId) values ('2', 'Henry', '80000', '4')
insert into Employee (Id, Name, Salary, ManagerId) values ('3', 'Sam', '60000', 'None')
insert into Employee (Id, Name, Salary, ManagerId) values ('4', 'Max', '90000', 'None')
Employee 表包含所有员工,他们的经理也属于员工。每个员工都有一个 Id,此外还有一列对应员工的经理的 Id。
+----+-------+--------+-----------+
| Id | Name | Salary | ManagerId |
+----+-------+--------+-----------+
| 1 | Joe | 70000 | 3 |
| 2 | Henry | 80000 | 4 |
| 3 | Sam | 60000 | NULL |
| 4 | Max | 90000 | NULL |
+----+-------+--------+-----------+
给定 Employee 表,编写一个 SQL 查询,该查询可以获取收入超过他们经理的员工的姓名。在上面的表格中,Joe 是唯一一个收入超过他的经理的员工。
+----------+
| Employee |
+----------+
| Joe |
+----------+
方法1:使用 WHERE 语句
算法:
如下面表格所示,表格里存有每个雇员经理的信息,我们也许需要从这个表里获取两次信息。
SELECT *
FROM Employee AS a, Employee AS b
;
注意:关键词 ‘AS’ 是可选的
Id | Name | Salary | ManagerId | Id | Name | Salary | ManagerId |
---|---|---|---|---|---|---|---|
1 | Joe | 70000 | 3 | 1 | Joe | 70000 | 3 |
2 | Henry | 80000 | 4 | 1 | Joe | 70000 | 3 |
3 | Sam | 60000 | 1 | Joe | 70000 | 3 | |
4 | Max | 90000 | 1 | Joe | 70000 | 3 | |
1 | Joe | 70000 | 3 | 2 | Henry | 80000 | 4 |
2 | Henry | 80000 | 4 | 2 | Henry | 80000 | 4 |
3 | Sam | 60000 | 2 | Henry | 80000 | 4 | |
4 | Max | 90000 | 2 | Henry | 80000 | 4 | |
1 | Joe | 70000 | 3 | 3 | Sam | 60000 | |
2 | Henry | 80000 | 4 | 3 | Sam | 60000 | |
3 | Sam | 60000 | 3 | Sam | 60000 | ||
4 | Max | 90000 | 3 | Sam | 60000 | ||
1 | Joe | 70000 | 3 | 4 | Max | 90000 | |
2 | Henry | 80000 | 4 | 4 | Max | 90000 | |
3 | Sam | 60000 | 4 | Max | 90000 | ||
4 | Max | 90000 | 4 | Max | 90000 |
前 3 列来自表格 a ,后 3 列来自表格 b
从两个表里使用 Select 语句可能会导致产生 笛卡尔乘积 。在这种情况下,输出会产生 4*4=16 个记录。然而我们只对雇员工资高于经理的人感兴趣。所以我们应该用 WHERE 语句加 2 个判断条件。
SELECT
*
FROM
Employee AS a,
Employee AS b
WHERE
a.ManagerId = b.Id
AND a.Salary > b.Salary
;
Id | Name | Salary | ManagerId | Id | Name | Salary | ManagerId |
---|---|---|---|---|---|---|---|
1 | Joe | 70000 | 3 | 3 | Sam | 60000 |
由于我们只需要输出雇员的名字,所以我们修改一下上面的代码,得到最终解法:
SELECT
a.Name AS 'Employee'
FROM
Employee AS a,
Employee AS b
WHERE
a.ManagerId = b.Id
AND a.Salary > b.Salary
;
方法 2:使用 JOIN 语句
算法:
实际上, JOIN 是一个更常用也更有效的将表连起来的办法,我们使用 ON 来指明条件。
SELECT
a.NAME AS Employee
FROM Employee AS a JOIN Employee AS b
ON a.ManagerId = b.Id
AND a.Salary > b.Salary
;
-------------------------------------------------------------------
作者:LeetCode
链接:https://leetcode-cn.com/problems/employees-earning-more-than-their-managers/solution/chao-guo-jing-li-shou-ru-de-yuan-gong-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
以上两种写法,一种是直接对两表
# Write your MySQL query statement below
select
e.Name Employee
from Employee e left join (select distinct Id,Salary from Employee) m
on e.ManagerId = m.Id
where e.Salary > m.Salary
官方题解一采用直接对两表进行笛卡尔积,而后对结果使用WHERE筛选出符合条件的数据,官方题解二使用内连接的方式对临时表过滤筛选。除此之外,我们还可以使用子查询语句解决问题。
SELECT
Name Employee
FROM
Employee AS e
WHERE
Salary > (SELECT
Salary
FROM
Employee
WHERE
Id = e.Managerid)
题目链接:https://leetcode-cn.com/problems/duplicate-emails/
Create table If Not Exists Person (Id int, Email varchar(255))
Truncate table Person
insert into Person (Id, Email) values ('1', '[email protected]')
insert into Person (Id, Email) values ('2', '[email protected]')
insert into Person (Id, Email) values ('3', '[email protected]')
编写一个 SQL 查询,查找 Person 表中所有重复的电子邮箱。
示例:
+----+---------+
| Id | Email |
+----+---------+
| 1 | [email protected] |
| 2 | [email protected] |
| 3 | [email protected] |
+----+---------+
根据以上输入,你的查询应返回以下结果:
+---------+
| Email |
+---------+
| [email protected] |
+---------+
说明:所有电子邮箱都是小写字母。
方法一:使用 GROUP BY 和临时表
算法:
重复的电子邮箱存在多次。要计算每封电子邮件的存在次数,我们可以使用以下代码。
MySQL
select Email, count(Email) as num
from Person
group by Email;
num | |
---|---|
[email protected] | 2 |
[email protected] | 1 |
以此作为临时表,我们可以得到下面的解决方案。
MySQL
select Email from
(
select Email, count(Email) as num
from Person
group by Email
) as statistic
where num > 1
;
方法二:使用 GROUP BY 和 HAVING 条件
向 GROUP BY 添加条件的一种更常用的方法是使用 HAVING 子句,该子句更为简单高效。
所以我们可以将上面的解决方案重写为:
MySQL
select Email
from Person
group by Email
having count(Email) > 1;
-------------------------------------------------------------------
作者:LeetCode
链接:https://leetcode-cn.com/problems/duplicate-emails/solution/cha-zhao-zhong-fu-de-dian-zi-you-xiang-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
# Write your MySQL query statement below
Select Email from Person
Group by Email
Having Count(Email)>1
在使用group by、having等语句是要注意它们的执行顺序。
执行顺序 :from > on > where > group by > having > select > distinct > order by > top
当一个查询语句同时出现了where,group by,having,order by的时候,执行顺序和编写顺序是:
1.执行where xx对全表数据做筛选,返回第1个结果集。
2.针对第1个结果集使用group by分组,返回第2个结果集。
3.针对第2个结集执行having xx进行筛选,返回第3个结果集。
4.针对第3个结果集中的每1组数据执行select xx,有几组就执行几次,返回第4个结果集。
5.针对第4个结果集排序。
题目链接:https://leetcode-cn.com/problems/customers-who-never-order/
Create table If Not Exists Customers (Id int, Name varchar(255))
Create table If Not Exists Orders (Id int, CustomerId int)
Truncate table Customers
insert into Customers (Id, Name) values ('1', 'Joe')
insert into Customers (Id, Name) values ('2', 'Henry')
insert into Customers (Id, Name) values ('3', 'Sam')
insert into Customers (Id, Name) values ('4', 'Max')
Truncate table Orders
insert into Orders (Id, CustomerId) values ('1', '3')
insert into Orders (Id, CustomerId) values ('2', '1')
某网站包含两个表,Customers 表和 Orders 表。编写一个 SQL 查询,找出所有从不订购任何东西的客户。
Customers 表:
+----+-------+
| Id | Name |
+----+-------+
| 1 | Joe |
| 2 | Henry |
| 3 | Sam |
| 4 | Max |
+----+-------+
Orders 表:
+----+------------+
| Id | CustomerId |
+----+------------+
| 1 | 3 |
| 2 | 1 |
+----+------------+
例如给定上述表格,你的查询应返回:
+-----------+
| Customers |
+-----------+
| Henry |
| Max |
+-----------+
方法:使用子查询和 NOT IN 子句
算法:如果我们有一份曾经订购过的客户名单,就很容易知道谁从未订购过。
我们可以使用下面的代码来获得这样的列表。
select customerid from orders;
然后,我们可以使用 NOT IN 查询不在此列表中的客户。
select customers.name as 'Customers'
from customers
where customers.id not in
(
select customerid from orders
);
-------------------------------------------------------------------
作者:LeetCode
链接:https://leetcode-cn.com/problems/customers-who-never-order/solution/cong-bu-ding-gou-de-ke-hu-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
# Write your MySQL query statement below
select Customers.Name as Customers
from Customers
where Id not in
(
select distinct CustomerId from Orders
)
题目链接:https://leetcode-cn.com/problems/delete-duplicate-emails/
编写一个 SQL 查询,来删除 Person 表中所有重复的电子邮箱,重复的邮箱里只保留 Id 最小 的那个。
+----+------------------+
| Id | Email |
+----+------------------+
| 1 | [email protected] |
| 2 | [email protected] |
| 3 | [email protected] |
+----+------------------+
Id 是这个表的主键。
例如,在运行你的查询语句之后,上面的 Person 表应返回以下几行:
+----+------------------+
| Id | Email |
+----+------------------+
| 1 | [email protected] |
| 2 | [email protected] |
+----+------------------+
提示:
执行 SQL 之后,输出是整个 Person 表。
使用 delete 语句。
方法:使用 DELETE 和 WHERE 子句
算法: 我们可以使用以下代码,将此表与它自身在电子邮箱列中连接起来。
SELECT p1.*
FROM Person p1,
Person p2
WHERE
p1.Email = p2.Email
;
然后我们需要找到其他记录中具有相同电子邮件地址的更大 ID。所以我们可以像这样给 WHERE 子句添加一个新的条件。
SELECT p1.*
FROM Person p1,
Person p2
WHERE
p1.Email = p2.Email AND p1.Id > p2.Id
;
因为我们已经得到了要删除的记录,所以我们最终可以将该语句更改为 DELETE。
MySQL
DELETE p1 FROM Person p1,
Person p2
WHERE
p1.Email = p2.Email AND p1.Id > p2.Id
-------------------------------------------------------------------
作者:LeetCode
链接:https://leetcode-cn.com/problems/delete-duplicate-emails/solution/shan-chu-zhong-fu-de-dian-zi-you-xiang-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
# Write your MySQL query statement below
DELETE FROM Person
WHERE Id NOT IN
(
SELECT P.Id FROM
(
SELECT MIN(Id) AS Id
FROM Person
GROUP BY Email
) AS P
)
You can't specify target table 'Person' for update in FROM clause
方法二:查询出所有的邮箱的最小Id,则不再此列表中的Id都重复Id。
报错:You can’t specify target table ‘Person’ for update in FROM clause,是因为:
mysql不支持对同一个表进行delete(update)和select操作,必须将查询结果保留进一个新表里,再次select,并且select出的表必须有一个自己的别名。
因此,我们在子查询中的查询到的临时表还需要进行再封装。
SELECT P.Id FROM
(
SELECT MIN(Id) AS Id
FROM Person
GROUP BY Email
) AS P
此表返回的结果集是所有不同邮箱的最小Id,接下来只需找到不再此列表中的Id即是我们需要删去的重复Id。
DELETE FROM Person
WHERE Id NOT IN
(
SELECT P.Id FROM
(
SELECT MIN(Id) AS Id
FROM Person
GROUP BY Email
) AS P
)
题目链接:https://leetcode-cn.com/problems/rising-temperature/
Create table If Not Exists Weather (Id int, RecordDate date, Temperature int)
Truncate table Weather
insert into Weather (Id, RecordDate, Temperature) values ('1', '2015-01-01', '10')
insert into Weather (Id, RecordDate, Temperature) values ('2', '2015-01-02', '25')
insert into Weather (Id, RecordDate, Temperature) values ('3', '2015-01-03', '20')
insert into Weather (Id, RecordDate, Temperature) values ('4', '2015-01-04', '30')
给定一个 Weather 表,编写一个 SQL 查询,来查找与之前(昨天的)日期相比温度更高的所有日期的 Id。
+---------+------------------+------------------+
| Id(INT) | RecordDate(DATE) | Temperature(INT) |
+---------+------------------+------------------+
| 1 | 2015-01-01 | 10 |
| 2 | 2015-01-02 | 25 |
| 3 | 2015-01-03 | 20 |
| 4 | 2015-01-04 | 30 |
+---------+------------------+------------------+
例如,根据上述给定的 Weather 表格,返回如下 Id:
+----+
| Id |
+----+
| 2 |
| 4 |
+----+
方法:使用 JOIN 和 DATEDIFF() 子句
算法: MySQL 使用 DATEDIFF 来比较两个日期类型的值。
因此,我们可以通过将 weather 与自身相结合,并使用 DATEDIFF() 函数。
MySQL
SELECT
weather.id AS 'Id'
FROM
weather
JOIN
weather w ON DATEDIFF(weather.date, w.date) = 1
AND weather.Temperature > w.Temperature
;
-------------------------------------------------------------------
作者:LeetCode
链接:https://leetcode-cn.com/problems/rising-temperature/solution/shang-sheng-de-wen-du-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
# Write your MySQL query statement below
select tmp.Id
from (
select Id, @flag := if((Temperature > @preTemperature and @PreDate = DATE_SUB(RecordDate, INTERVAL 1 DAY)), True, False) as flag, @preTemperature := Temperature, @PreDate := RecordDate
from Weather, (select @flag := False, @preTemperature := NULL, @PreDate := NULL) f
order by RecordDate
) tmp
where tmp.flag = 1
# Write your MySQL query statement below
SELECT a.Id
FROM (
SELECT w.Id, w.Temperature
, if(w.Temperature > @last_T
AND datediff(w.RecordDate, @last_D) = 1, 1, 0) AS is_greater
, @last_T := w.Temperature, @last_D := w.RecordDate
FROM Weather w, (
SELECT @last_T := 100, @last_D := 1
) b
ORDER BY RecordDate ASC
) a
WHERE a.is_greater = 1
ORDER BY a.Id ASC
常用时间函数:
CURDATE() | 返回当前日期 |
CURTIME() | 返回当前时间 |
NOW() | 返回当前的日期和时间 |
UNIX_TIMESTAMP(date) | 返回日期date的UNIX时间戳 |
FROM_UNIXTIME | 返回UNIX时间戳的日期值 |
WEEK(date) | 返回日期date为一年中的第几周 |
YEAR(date) | 返回日期date的年份 |
HOUR(time) | 返回time的小时值 |
MINUTE(time) | 返回time的分钟值 |
MONTHNAME(date) | 返回date的月份名 |
DATE_FORMAT(date,fmt) | 返回按字符串fmt格式化日期date值 |
DATE_ADD(date,INTERVAL expr type) | 返回一个日期或时间值加上- -个时间间隔的时间值 |
DATEDIFF(expr,expr2) | 返回起始时间expr 和结束时间expr2之间的天数 |
时间函数:https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_adddate