Appearance
175.组合两个表
sql
表1: Person
+----------+-----------+----------+
| PersonId | FirstName | LastName |
+----------+-----------+----------+
| 1 | Allen | Wang |
+----------+-----------+----------+
表2: Address
+-----------+----------+---------------+----------+
| AddressId | PersonId | City | State |
+-----------+----------+---------------+----------+
| 1 | 2 | New York City | New York |
+-----------+----------+---------------+----------+
编写一个 SQL 查询,满足条件:无论 person 是否有地址信息,都需要基于上述两表提供 person 的以下信息:FirstName, LastName, City, State
sql
左连接即可
SELECT
firstname, lastname, city, state
FROM
person p
LEFT JOIN address ad
ON p.personid = ad.personid
+-----------+----------+------+-------+
| FirstName | LastName | City | State |
+-----------+----------+------+-------+
| Allen | Wang | NULL | NULL |
+-----------+----------+------+-------+
176. 第二高的薪水
sql
+----+--------+
| Id | Salary |
+----+--------+
| 1 | 100 |
| 2 | 200 |
| 3 | 300 |
+----+--------+
编写一个 SQL 查询,获取 Employee 表中第二高的薪水(Salary)例如上述 Employee 表,SQL查询应该返回 200 ,作为第二高的薪水如果不存在第二高的薪水,那么查询应返回 null。
sql
SELECT
IFNULL(
(SELECT DISTINCT
Salary
FROM
employee
ORDER BY Salary
LIMIT 1 OFFSET 1), NULL
) AS SecondHighestSalary
+---------------------+
| SecondHighestSalary |
+---------------------+
| 200 |
+---------------------+
IFNULL(_
expr1_,_
expr2_)
If expr1
is not NULL
, IFNULL()
returns expr1
; otherwise it returns expr2
.
sql
mysql> SELECT IFNULL(1,0);
-> 1
mysql> SELECT IFNULL(NULL,10);
-> 10
mysql> SELECT IFNULL(1/0,10);
-> 10
mysql> SELECT IFNULL(1/0,'yes');
-> 'yes'
LIMIT [offset,] rows | rows OFFSET offset
sql
SELECT * FROM table LIMIT [offset,] rows | rows OFFSET offset
Limit子句可以被用于强制 SELECT 语句返回指定的记录数。Limit接受一个或两个数字参数。参数必须是一个整数常量。如果给定两个参数,第一个参数指定第一个返回记录行的位置,第二个参数指定返回从那个记录开始往后的数量。
sql
--初始记录行的偏移量是 0(而不是 1):检索记录行6-15
mysql> SELECT * FROM table LIMIT 5,10;
--为了检索从某一个偏移量到记录集的结束所有的记录行,
--可以指定第二个参数为 -1:检索记录行 96-last
mysql> SELECT * FROM table LIMIT 95,-1;
--如果只给定一个参数,它表示返回最大的记录行数目。LIMIT n 等价于 LIMIT 0,n:
--检索前 5 个记录行
mysql> SELECT * FROM table LIMIT 5;
limit X offset Y = limit Y, X 从第Y条开始,选取X条 limit 1 offset 1 从第2条开始,选一条,所以就是排名第二的那条 limit X,Y 从第X条开始,选取Y条 limit X 选取前X条的记录
177.第n高的薪水
例如上述 Employee 表,n = 2 时,应返回第二高的薪水 200。如果不存在第 n 高的薪水,那么查询应返回 null。
sql
SELECT
IFNULL(
(SELECT DISTINCT
Salary
FROM
employee
ORDER BY Salary DESC
LIMIT n ,1), NULL
) AS SecondHighestSalary
如果用自定义函数则是
CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
set N = N - 1;
RETURN (
select distinct salary from Employee order by salary desc limit N, 1
);
END
178.分数排名
sql
+----+-------+
| Id | Score |
+----+-------+
| 1 | 3.50 |
| 2 | 3.65 |
| 3 | 4.00 |
| 4 | 3.85 |
| 5 | 4.00 |
| 6 | 3.65 |
+----+-------+
编写一个 SQL 查询来实现分数排名。如果两个分数相同,则两个分数排名(Rank)相同。请注意,平分后的下一个名次应该是下一个连续的整数值。换句话说,名次之间不应该有“间隔”。
sql
例如,根据上述给定的 Scores 表,你的查询应该返回(按分数从高到低排列):
+-------+------+
| Score | Rank |
+-------+------+
| 4.00 | 1 |
| 4.00 | 1 |
| 3.85 | 2 |
| 3.65 | 3 |
| 3.65 | 3 |
| 3.50 | 4 |
+-------+------+
--自连接,找到表1小于表2的分数,数量,按照id分组,数量排序
--套路:group by order by group by 能去重
SELECT
s1.Score, COUNT(DISTINCT (s2.Score)) rank1
FROM
scores s1
LEFT JOIN scores s2
ON s1.Score <= s2.Score
+------+-------+------+-------+--------+
| Id | Score | Id | Score | result |
+------+-------+------+-------+--------+
| 1 | 3.50 | 1 | 3.50 | 4 |
+------+-------+------+-------+--------+
GROUP BY s1.Id
+------+-------+------+-------+--------+
| Id | Score | Id | Score | result |
+------+-------+------+-------+--------+
| 1 | 3.50 | 1 | 3.50 | 4 |
| 2 | 3.65 | 2 | 3.65 | 3 |
| 3 | 4.00 | 3 | 4.00 | 1 |
| 4 | 3.85 | 3 | 4.00 | 2 |
| 5 | 4.00 | 3 | 4.00 | 1 |
| 6 | 3.65 | 2 | 3.65 | 3 |
+------+-------+------+-------+--------+
ORDER BY rank1
+------+-------+------+-------+--------+
| Id | Score | Id | Score | result |
+------+-------+------+-------+--------+
| 3 | 4.00 | 3 | 4.00 | 1 |
| 5 | 4.00 | 3 | 4.00 | 1 |
| 4 | 3.85 | 3 | 4.00 | 2 |
| 2 | 3.65 | 2 | 3.65 | 3 |
| 6 | 3.65 | 2 | 3.65 | 3 |
| 1 | 3.50 | 1 | 3.50 | 4 |
+------+-------+------+-------+--------+
--或者用窗口函数
SELECT
Score, dense_rank () over (
ORDER BY Score DESC)
FROM
Scores ;
+-------+-------------------------------------------+
| Score | dense_rank () over (ORDER BY Score DESC) |
+-------+-------------------------------------------+
| 4.00 | 1 |
| 4.00 | 1 |
| 3.85 | 2 |
| 3.65 | 3 |
| 3.65 | 3 |
| 3.50 | 4 |
+-------+-------------------------------------------+
180. 连续出现的数字
sql
+------+------+
| Id | Num |
+------+------+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 2 |
| 5 | 1 |
| 6 | 2 |
| 7 | 2 |
+------+------+
编写一个 SQL 查询,查找所有至少连续出现三次的数字。例如,给定上面的 Logs
表, 1
是唯一连续出现至少三次的数字。
sql
SELECT DISTINCT a.Num ConsecutiveNums FROM
(
SELECT t.Num ,
@cnt:=IF(@pre=t.Num, @cnt+1, 1) cnt,
--如果pre与t.num 相等,则cnt加1, 否则cnt重置为1
@pre:=t.Num pre
FROM Logs t, (SELECT @pre:=null, @cnt:=0) b
) a
--变量初始化,pre记录上一个值,cnt统计次数
WHERE a.cnt >= 3
+-----------------+
| ConsecutiveNums |
+-----------------+
| 1 |
+-----------------+
181.超过经理收入的员工
sql
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 是唯一一个收入超过他的经理的员工。
sql
SELECT
e2.name Employee
FROM
employee e1
LEFT JOIN employee e2
ON e1.id = e2.ManagerId
--只有员工表才有manager,所以才会有managerid,所以e1是员工表,e2是经理表
+------+-------+--------+-----------+------+-------+--------+-----------+
| Id | Name | Salary | ManagerId | Id | Name | Salary | ManagerId |
+------+-------+--------+-----------+------+-------+--------+-----------+
| 3 | Sam | 60000 | NULL | 1 | Joe | 70000 | 3 |
| 4 | Max | 90000 | NULL | 2 | Henry | 80000 | 4 |
| 1 | Joe | 70000 | 3 | NULL | NULL | NULL | NULL |
| 2 | Henry | 80000 | 4 | NULL | NULL | NULL | NULL |
+------+-------+--------+-----------+------+-------+--------+-----------+
WHERE e1.Salary < e2.Salary ;
--AND e1.Salary < e2.Salary ;
+----------+
| Employee |
+----------+
| Joe |
+----------+
182. 查找重复的电子邮箱
sql
+----+---------+
| Id | Email |
+----+---------+
| 1 | [email protected] |
| 2 | [email protected] |
| 3 | [email protected] |
+----+---------+
编写一个 SQL 查询,查找 Person 表中所有重复的电子邮箱。
sql
SELECT
email
FROM
person
GROUP BY email
HAVING COUNT(email) > 1;
+---------+
| Email |
+---------+
| [email protected] |
+---------+
183. 从不订购的客户
sql
Customers 表:
+----+-------+
| Id | Name |
+----+-------+
| 1 | Joe |
| 2 | Henry |
| 3 | Sam |
| 4 | Max |
+----+-------+
Orders 表:
+----+------------+
| Id | CustomerId |
+----+------------+
| 1 | 3 |
| 2 | 1 |
+----+------------+
某网站包含两个表,Customers
表和 Orders
表。编写一个 SQL 查询,找出所有从不订购任何东西的客户。
sql
SELECT NAME
FROM
customers c
LEFT JOIN orders o
ON c.Id = o.CustomerId
--自连接,找到order表里为空的记录,就是从不订购的客户
WHERE o.id IS NULL ;
+-------+
| NAME |
+-------+
| Henry |
| Max |
+-------+
184.部门工资最高的员工
sql
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 部门有最高工资。
sql
SELECT
dp.Name Department, e.Name Employee, Salary
FROM
employee e
LEFT JOIN department dp
ON e.DepartmentId = dp.id
WHERE (e.DepartmentId, Salary) IN
-- where筛选id, salary 符合条件的数据
(SELECT
DepartmentId, MAX(Salary)
FROM
employee
GROUP BY DepartmentId);
+-------+-------+--------+
| Name | Name | Salary |
+-------+-------+--------+
| IT | Jim | 90000 |
| IT | Max | 90000 |
| Sales | Henry | 80000 |
+-------+-------+--------+
185.求部门工资前三高的所有员工
sql
SELECT
DepartmentId, NAME, Salary
FROM
(SELECT
*, row_number () over (
PARTITION BY DepartmentId
ORDER BY Salary DESC
) AS ranking
FROM
Employee) AS a
WHERE ranking <= 3 ;
SELECT
d.Name Department, c.name Employee, c.Salary
FROM
(SELECT
e1.*, COUNT(DISTINCT e2.Salary) num
FROM
Employee e1
LEFT JOIN Employee e2
ON e1.Salary < e2.Salary
AND e1.DepartmentId = e2.DepartmentId
GROUP BY e1.name) c, Department d
WHERE c.DepartmentId = d.id
AND num < 3 ;
+--------------+-------+--------+
| DepartmentId | NAME | Salary |
+--------------+-------+--------+
| 1 | Jim | 90000 |
| 1 | Max | 90000 |
| 1 | Joe | 70000 |
| 2 | Henry | 80000 |
| 2 | Sam | 60000 |
+--------------+-------+--------+
196.删除重复的电子邮箱
sql
+----+------------------+
| Id | Email |
+----+------------------+
| 1 | [email protected] |
| 2 | [email protected] |
| 3 | [email protected] |
+----+------------------+
Id 是这个表的主键。
编写一个 SQL 查询,来删除 Person 表中所有重复的电子邮箱,重复的邮箱里只保留 Id 最小 的那个。
sql
DELETE
P1
FROM
Person AS P1
JOIN Person AS P2
ON (
P1.email = P2.email
AND P1.id > P2.id
)
197.上升的温度
sql
+---------+------------------+------------------+
| 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 表,编写一个 SQL 查询,来查找与之前(昨天的)日期相比温度更高的所有日期的 Id。
sql
SELECT
w2.id
FROM
Weather w1
JOIN Weather w2
ON w2.Temperature > w1.Temperature
WHERE DATEDIFF(w2.RecordDate, w1.RecordDate) = 1
+------+
| id |
+------+
| 2 |
| 4 |
+------+
262.行程和用户
sql
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)保留两位小数。取消率的计算方式如下:(被司机或乘客取消的非禁止用户生成的订单数量) / (非禁止用户生成的订单总数)
sql
SELECT
t.request_at DAY, (
ROUND(
COUNT(
IF(STATUS != 'completed', STATUS, NULL)
) / COUNT(STATUS), 2
)
) AS 'Cancellation Rate'
FROM
Users u
INNER JOIN Trips t
ON u.Users_id = t.Client_Id
AND u.banned != 'Yes'
WHERE t.Request_at >= '2013-10-01'
AND t.Request_at <= '2013-10-03'
GROUP BY t.Request_at;
+------------+-------------------+
| DAY | Cancellation Rate |
+------------+-------------------+
| 2013-10-01 | 0.33 |
| 2013-10-02 | 0.00 |
| 2013-10-03 | 0.50 |
+------------+-------------------+