LeetCode SQL 刷题

MySQL配置

  1. 关闭safe updates模式,该模式下非键条件(wehre字段不是key)无法执行update和delete
    show variables like ‘sql_safe_updates’;
    set sql_safe_updates = ‘off’;

  2. only_full_group_by
    5.7默认开启,该模式下,select和order by等所涉及的字段必须在group by中出现过。查询字段含有聚合函数除外,例如select count(xx).
    谨慎使用select any_value(xx),
    例如 select any_value(a), b from table gorup by b;
    该查询只会返回一个分组结果,对于若b分组内有多个a,其只会随机返回一个,而不是返回该分组下的所有记录。

数据库准备

  1. 创建干净的数据库
    drop database if exists leetcode;
    create database leetcode;
    use leetcode;

1. 大国

如果一个国家的面积超过300万平方公里,或者人口超过2500万,那么这个国家就是大国家。
编写一个SQL查询,输出表中所有大国家的名称、人口和面积。
schema

drop table if exists World;
CREATE TABLE World (
    name VARCHAR(255),
    continent VARCHAR(255),
    area INT,
    population INT,
    gdp INT
);
insert into World (name, continent, area, population, gdp) 
values 
 ('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');

solution

SELECT 
    name, population, area
FROM
    World
WHERE
    area > 3000000 OR population > 25000000;

2. 换座位

小美是一所中学的信息科技老师,她有一张 seat 座位表,平时用来储存学生名字和与他们相对应的座位 id。
其中纵列的 id 是连续递增的
小美想改变相邻俩学生的座位。
如果学生人数是奇数,则不需要改变最后一个同学的座位。

ps:调换1-2,3-4…的座位上的学生。

schema

create table if not exists seat (
    id int unique,
    student varchar(255)
);
truncate table seat;
insert into seat (id, student) values
    ('1', 'Abbot'),
    ('2', 'Doris'),
    ('3', 'Emerson'), 
    ('4', 'Green'),
    ('5', 'Jeames');

2.1 solution-1

使用case when

CASE WHEN THEN ELSE END语法
Case具有两种格式。简单Case函数和Case搜索函数。

--简单Case函数
CASE sex
         WHEN '1' THEN '男'
         WHEN '2' THEN '女'
ELSE '其他' END
--Case搜索函数
CASE WHEN sex = '1' THEN '男'
         WHEN sex = '2' THEN '女'
ELSE '其他' END

Case会返回第一个符合条件的值,需要注意的是,需要使用AS指定CASE语句的列名,否则该列的名称将会为CASE语句本身。

# 交换操作 = 偶数id自减,奇数id自增(最后一个除外)
SELECT 
    (CASE
        WHEN id % 2 != 0 AND seat_counts.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 # 内联视图(SELECT产生的临时表),SELECT结果作为seat_counts 表
        COUNT(*) AS counts # COUNT函数结果作为counts列值
    FROM
        seat) AS seat_counts
ORDER BY id ASC;

2.2 solution-2

使用union
UNION语法
UNION用于合并多个SELECT执行结果,SELECT的列数相同、列数据类型相似。结果集的列名等于第一个SELECT中的列名。UNION会剔除相同的值,UNION ALL允许有重复值。

SELECT column_name(s) FROM table1
UNION
SELECT column_name(s) FROM table2;

使用UNION合并多个情况的查询结果

SELECT id - 1 AS id, student FROM seat WHERE id % 2 = 0
UNION SELECT id + 1, student FROM seat WHERE id % 2 != 0
    AND id != (SELECT MAX(id) FROM seat) 
UNION SELECT id, student FROM seat WHERE id % 2 != 0
    AND id = (SELECT MAX(id) FROM seat)
ORDER BY id ASC;

3. 交换工资

给定一个 salary表,如下所示,有m=男性 和 f=女性的值 。交换所有的 f 和 m 值
(例如,将所有 f 值更改为 m,反之亦然)。要求使用一个更新查询,并且没有中间临时表。

schema

create table if not exists salary (
    id int,
    name varchar(255),
    sex char(1),
    salary int
);
truncate table salary;
insert into salary (id, name, sex, salary) values
    (1, 'A', 'm', 2500),
    (2, 'B', 'f', 1500),
    (3, 'C', 'm', 5500),
    (4, 'D', 'f', 500);

solution-1

update salary set sex = 
    (case sex 
        when 'm' then 'f'
        when 'f' then 'm'
    end);

4. 有趣的电影

某城市开了一家新的电影院,吸引了很多人过来看电影。该电影院特别注意用户体验,专门有个 LED显示板做电影推荐,上面公布着影评和相关电影描述。

作为该电影院的信息部主管,您需要编写一个 SQL查询,
找出所有影片描述为非 boring (不无聊) 的并且 id 为奇数 的影片,
结果请按等级 rating 排列。

schema

create table if not exists cinema (
    id int8 primary key, # int 有 1 2 4 8字节
    movie varchar(255),
    description varchar(255),
    rating float(2, 1) # 浮点数可指定精度,(2,1)表示小数点后一位共两位,例如1.3
);
truncate table cinema;
insert into cinema (id, movie, description, rating) values 
    ('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');

solution

SELECT 
    id, movie, description, rating
FROM
    cinema
WHERE
    description != 'boring' AND id % 2 != 0
ORDER BY rating DESC;

5. 超过5名学生的课

有一个courses 表 ,有: student (学生) 和 class (课程)。
请列出所有超过或等于5名学生的课。

schema

create table if not exists courses (
    student varchar(255),
    class varchar(255)
);
truncate table courses;
insert into courses (student, class) values 
    ('A', 'Math'),
    ('B', 'English'),
    ('C', 'Math'),
    ('D', 'Biology'),
    ('E', 'Math'),
    ('F', 'Computer'),
    ('G', 'Math'),
    ('H', 'Math'),
    ('I', 'Math');

列出每门课的学生(已去除重复记录)

SELECT 
    class, COUNT(DISTINCT student) AS stu_count # count计数,sum累加
FROM
    courses
GROUP BY class; # group by用于对结果集进行分组,将相同值的列放在同一组中。若无,则COUNT列统计所有列。

solution-1 group by + having

SELECT 
    class
FROM
    courses
GROUP BY class
HAVING COUNT(DISTINCT student) >= 5; # where过滤行,having过滤分组

solution-2 group by + 子查询

SELECT 
    class
FROM
    (SELECT 
        class, COUNT(DISTINCT student) AS stu_count
    FROM
        courses
    GROUP BY class) AS temp # 使用子查询统计每门课的学生数量
WHERE
    stu_count >= 5;

6. 查找重复的电子邮箱

编写一个 SQL 查询,查找 Person 表中所有重复的电子邮箱。

create table if not exists Person (
    Id int8 primary key,
    Email varchar(255)
);
truncate table Person;
insert into Person (Id, Email) values 
    ('1', '[email protected]'),
    ('2', '[email protected]'),
    ('3', '[email protected]');

**solution **

# 分组统计每个Email的ID数,找出大于1的。
SELECT 
    Email
FROM
    Person
GROUP BY Email
HAVING COUNT(Id) >= 2;

7. 删除重复的电子邮箱

编写一个 SQL 查询,来删除 Person 表中所有重复的电子邮箱,
重复的邮箱里只保留 Id 最小 的那个。

schema

create table if not exists Person (
    Id int8 primary key,
    Email varchar(255)
);
truncate table Person;
insert into Person (Id, Email) values 
    ('1', '[email protected]'),
    ('2', '[email protected]'),
    ('3', '[email protected]');

solutionn-1 子查询

先按照Email分组,查询min(id),然后删除非min(id)的记录。

/**
错误写法
You can't specify target table 'Person' for update in FROM clause
在MySQL中,不能先select出同一表中的某些值,再更新这个表(在同一语句中) 

**/
DELETE FROM Person 
WHERE
    Id NOT IN (SELECT MIN(Id) FROM Person GROUP BY Email);

/**
正确写法,在嵌套一个子查询
**/
DELETE FROM Person 
WHERE
    Id NOT IN (SELECT Id FROM 
    (SELECT MIN(id) AS Id FROM Person GROUP BY Email) AS temp);

solution-2 自连接

/**
隐式连接“,”等同于内连接inner join(简写join)。
内连接扫描左表的每一行,将其与右表的每一行比较,
符合连接条件时,返回由这两行组成的新行。
**/
DELETE p1
FROM
    Person p1,
    Person p2
WHERE
    p1.Email = p2.Email AND p1.Id > p2.Id; # Email相等的行中(分组效果),ID较大者。

8. 组合两个表

编写一个 SQL 查询,满足条件:无论 person 是否有地址信息,
都需要基于上述两表提供 person 的以下信息:
FirstName, LastName, City, State

schema

drop table Person;
create table Person (
    PersonId int8 primary key,
    FirstName varchar(255),
    LastName varchar(255)
);
drop table Address;
create table Address (
    AddressId int8 primary key,
    PersonId int,
    City varchar(255),
    State varchar(255)
);
insert into Person (PersonId, LastName, FirstName) values ('1', 'Wang', 'Allen');
insert into Address (AddressId, PersonId, City, State) values ('1', '2', 'New York City', 'New York');

solution 左连接,返回左表所有数据。

SELECT 
    FirstName, LastName, City, State
FROM
    Person p
        LEFT JOIN
    Address a ON p.PersonId = a.PersonId;

9. 超过经理收入的员工

Employee 表包含所有员工,他们的经理也属于员工。每个员工都有一个 Id,
此外还有一列对应员工的经理的 Id。
给定 Employee 表,编写一个 SQL 查询,该查询可以获取收入超过他们经理的员工的姓名。

schema

create table if not exists Employee (
    Id int8 primary key,
    Name varchar(255),
    Salary varchar(255),
    ManagerId int
);
truncate table Employee;
insert into Employee (Id, Name, Salary, ManagerId) values 
    ('1', 'Joe', '70000', '3'),
    ('2', 'Henry', '80000', '4'),
    ('3', 'Sam', '60000', null),
    ('4', 'Max', '90000', null);

solution 自连接

SELECT 
    e1.Name AS Employee
FROM
    Employee AS e1
        INNER JOIN
    Employee e2 ON e1.ManagerId = e2.Id
        AND e1.Salary > e2.Salary;


10. 从不订购的客户

某网站包含两个表,Customers 表和 Orders 表。编写一个 SQL 查询,找出所有从不订购任何东西的客户。

shcema

create table if not exists Customers (
    Id int8 primary key,
    Name varchar(255)
);
truncate table Customers;
create table if not exists Orders (
    Id int8 primary key,
    CustomerId int
);
truncate table Orders;
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');
insert into Orders (Id, CustomerId) values ('1', '3');
insert into Orders (Id, CustomerId) values ('2', '1');

solution1 子查询

SELECT 
    Name AS Customers
FROM
    Customers
WHERE
    id NOT IN (SELECT CustomerId FROM Orders);

solution2 左连接 + null值判断

/**
on条件:生成临时表的条件
where条件:在生成的临时表中的过滤条件
null值判断:使用is或者is not,不能使用=。null与其它值均不相等,包括null。
**/
SELECT 
    *
FROM
    Customers c
        LEFT JOIN
    Orders o ON c.Id = o.CustomerId
WHERE
    o.CustomerId IS NULL
;

11. 部门工资最高的员工

Employee 表包含所有员工信息,每个员工有其对应的 Id, salary 和 department Id。
Department 表包含公司所有部门的信息。
编写一个 SQL 查询,找出每个部门工资最高的员工。

schema

drop table if exists Employee;
create table Employee (
    Id int8 primary key,
    Name varchar(255),
    Salary int8,
    DepartmentId int8
);
drop table if exists Department;
Create table If Not Exists Department (Id int, Name varchar(255));
INSERT INTO Employee ( Id, NAME, Salary, DepartmentId )
VALUES
    ( 1, 'Joe', 70000, 1 ),
    ( 2, 'Henry', 80000, 2 ),
    ( 3, 'Sam', 60000, 2 ),
    ( 4, 'Max', 90000, 1 );
INSERT INTO Department ( Id, NAME )
VALUES
    ( 1, 'IT' ),
    ( 2, 'Sales' );

错误解法 包含非group by字段,返回结果不确定

select any_value(d.Name) as Department, any_value(e.Name) as Employee, max(e.Salary)  
from Employee e 
left join Department d on e.DepartmentId = d.Id
group by e.DepartmentId;

solution

# 创建一个临时表(内联视图),查询每个部门的最大薪资。之后在Employeee和Department的联合表中找到一名部门与薪资与
# 临时表中相符的记录。

SELECT 
    d.name AS Department, e.Name AS Employee, e.Salary AS Salary
FROM
    Employee e,
    Department d,
    (SELECT DepartmentId, MAX(Salary) AS max_salary FROM Employee GROUP BY DepartmentId) AS temp
WHERE
    e.DepartmentId = d.Id
    AND e.Salary = temp.max_salary
    AND e.DepartmentId = temp.DepartmentId;

solution -2

# 使用子查询+in操作(可以匹配多个列)
SELECT 
    d.name Department, e.name Employee, e.Salary Salary
FROM
    Employee e,
    Department d
WHERE
    e.DepartmentId = d.Id
        AND (e.DepartmentId , e.Salary) IN 
        (SELECT DepartmentId, MAX(Salary) FROM Employee GROUP BY DepartmentId);

12. 第二高的薪水

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

schema

drop table if exists Employee;
Create table Employee (Id int, Salary int);
insert into Employee (Id, Salary) values ('1', '100');
insert into Employee (Id, Salary) values ('2', '200');
insert into Employee (Id, Salary) values ('3', '300');

solution distinct + limit(从0开始的offset, 行数(-1表示末尾)) + 子查询

# 额外嵌套一个select以实现没有符合记录时返回null
SELECT 
    (SELECT DISTINCT
            Salary
        FROM
            Employee
        ORDER BY Salary DESC
        LIMIT 1 , 1) AS SecondHighestSalary;

13. 编写一个 SQL 查询,获取 Employee 表中第 n 高的薪水(Salary)。

如果不存在第 n 高的薪水,那么查询应返回 null。

solution 自定义函数

/**
MySQL默认结束符是;,但函数声明中含有:,所以使用delimiter将
分隔符临时改成其它符号,例如$$;
**/
delimiter $$
create function getNthHighestSalary (N int) returns int
begin
 set N = N - 1;
 return (select (select distinct Salary from Employee order by Salary DESC limit N, 1));
end
$$
delimiter ;

select getNthHighestSalary(1);

14. 分数排名

编写一个 SQL 查询来实现分数排名。如果两个分数相同,则两个分数排名(Rank)相同。
请注意,平分后的下一个名次应该是下一个连续的整数值。换句话说,名次之间不应该有“间隔”。

schema

/**
decimal:数字型,不存在精度损失
decimal(18, 9) 表示总共18位,小数部分9位。
**/
DROP TABLE IF EXISTS Scores;
CREATE TABLE Scores ( Id INT, Score DECIMAL ( 3, 2 ) );
INSERT INTO Scores ( Id, Score )
VALUES
    ( 1, 3.5 ),
    ( 2, 3.65 ),
    ( 3, 4.0 ),
    ( 4, 3.85 ),
    ( 5, 4.0 ),
    ( 6, 3.65 );

solution

select Score, temp.Rank from
(select s1.Id id, count(distinct s2.Score) Rank
from Scores s1 Inner join Scores s2 on s1.Score <= s2.Score group by
s1.Id) as temp, Scores
order by Rank DEsc;

15. 补充-1 分段统计 贝壳面试题

有person表,字段有ID、Age,统计每个年龄段的人数,10-20,20-30等等。
shcema

drop table person_beike;
create table if not exists person_beike (id int primary key auto_increment, age int1);
truncate table person_beike;
insert into person_beike (age) values 
(1),
(12),
(21),
(31),
(41),
(51),
(52),
(90);

solution-1 利用union合并多个查询
SELECT “xxx” col可在结果集中添加col列,其列内容为xxx。

select "0-10" type, count(*) count from person_beike where age <= 10
union 
select "10-20" type, count(*) count from person_beike where age > 10 and age <= 20;

猜你喜欢

转载自blog.csdn.net/cooper20/article/details/98470496