Leetcode高频 SQL 50 题(基础版)题目记录

Leetcode sql题目记录

文章目录

    • [Leetcode sql题目记录](#Leetcode sql题目记录)
      • [570. 至少有5名直接下属的经理](#570. 至少有5名直接下属的经理)
      • [1934. 确认率](#1934. 确认率)
      • [1193. 每月交易I](#1193. 每月交易I)
      • [1174. 即时食物配送II](#1174. 即时食物配送II)
      • [176. 第二高的薪水](#176. 第二高的薪水)
      • [550. 游戏玩法分析IV](#550. 游戏玩法分析IV)
      • [1045. 买下所有产品的客户](#1045. 买下所有产品的客户)
      • [180. 连续出现的数字](#180. 连续出现的数字)
      • [1164. 指定日期的产品价格](#1164. 指定日期的产品价格)
        • [(1)非相关子查询 + 等值比较](#(1)非相关子查询 + 等值比较)
        • (2)相关子查询
        • [(3)窗口函数 + 条件筛选](#(3)窗口函数 + 条件筛选)
        • [(4)ORDER BY + LIMIT](#(4)ORDER BY + LIMIT)
        • 对比总结
      • 1204.最后一个能进入巴士的人
      • [1907. 按分类统计薪水](#1907. 按分类统计薪水)
      • [626. 换座位](#626. 换座位)
        • (1)嵌套查询
        • [(2)CASE WHEN](#(2)CASE WHEN)
          • [a. 简单 CASE](#a. 简单 CASE)
          • [b. 搜索 CASE (最常用)](#b. 搜索 CASE (最常用))
          • [c. 作为一个字段输出](#c. 作为一个字段输出)
          • [d. 在聚合函数里做条件统计](#d. 在聚合函数里做条件统计)
          • [e. 排序(ORDER BY)](#e. 排序(ORDER BY))

本博客仅记录高频 SQL 50 题(基础版)的中等难度题目 苯人的解答与学习过程,后续不定期更新

570. 至少有5名直接下属的经理

表: Employee

复制代码
+-------------+---------+
| Column Name | Type    |
+-------------+---------+
| id          | int     |
| name        | varchar |
| department  | varchar |
| managerId   | int     |
+-------------+---------+
id 是此表的主键(具有唯一值的列)。
该表的每一行表示雇员的名字、他们的部门和他们的经理的id。
如果managerId为空,则该员工没有经理。
没有员工会成为自己的管理者。

编写一个解决方案,找出至少有五个直接下属的经理。

任意顺序 返回结果表。

查询结果格式如下所示。

示例 1:

复制代码
输入: 
Employee 表:
+-----+-------+------------+-----------+
| id  | name  | department | managerId |
+-----+-------+------------+-----------+
| 101 | John  | A          | Null      |
| 102 | Dan   | A          | 101       |
| 103 | James | A          | 101       |
| 104 | Amy   | A          | 101       |
| 105 | Anne  | A          | 101       |
| 106 | Ron   | B          | 101       |
+-----+-------+------------+-----------+
输出: 
+------+
| name |
+------+
| John |
+------+

解答:

sql 复制代码
select a.name from Employee a join Employee b on a.id = b.managerId group by a.id having count(*) >= 5;

没有 GROUP BY
HAVING 是对分组后的结果 过滤的。你没分组时,MySQL把整张结果集当成一个大组COUNT(a.id) 统计的是所有经理--下属配对的总行数,不是"每个经理的下属数"。因此当测试数据里只有一个经理且刚好有 5 个下属时看起来"正确",但只要有两个经理,合计行数 ≥5,你这句就会把两位经理都放进结果里,甚至还会重复多行。


1934. 确认率

表: Signups

复制代码
+----------------+----------+
| Column Name    | Type     |
+----------------+----------+
| user_id        | int      |
| time_stamp     | datetime |
+----------------+----------+
User_id是该表的主键。
每一行都包含ID为user_id的用户的注册时间信息。

表: Confirmations

复制代码
+----------------+----------+
| Column Name    | Type     |
+----------------+----------+
| user_id        | int      |
| time_stamp     | datetime |
| action         | ENUM     |
+----------------+----------+
(user_id, time_stamp)是该表的主键。
user_id是一个引用到注册表的外键。
action是类型为('confirmed', 'timeout')的ENUM
该表的每一行都表示ID为user_id的用户在time_stamp请求了一条确认消息,该确认消息要么被确认('confirmed'),要么被过期('timeout')。

用户的 确认率'confirmed' 消息的数量除以请求的确认消息的总数。没有请求任何确认消息的用户的确认率为 0 。确认率四舍五入到 小数点后两位

编写一个SQL查询来查找每个用户的 确认率 。

以 任意顺序 返回结果表。

查询结果格式如下所示。

示例1:

复制代码
输入:
Signups 表:
+---------+---------------------+
| user_id | time_stamp          |
+---------+---------------------+
| 3       | 2020-03-21 10:16:13 |
| 7       | 2020-01-04 13:57:59 |
| 2       | 2020-07-29 23:09:44 |
| 6       | 2020-12-09 10:39:37 |
+---------+---------------------+
Confirmations 表:
+---------+---------------------+-----------+
| user_id | time_stamp          | action    |
+---------+---------------------+-----------+
| 3       | 2021-01-06 03:30:46 | timeout   |
| 3       | 2021-07-14 14:00:00 | timeout   |
| 7       | 2021-06-12 11:57:29 | confirmed |
| 7       | 2021-06-13 12:58:28 | confirmed |
| 7       | 2021-06-14 13:59:27 | confirmed |
| 2       | 2021-01-22 00:00:00 | confirmed |
| 2       | 2021-02-28 23:59:59 | timeout   |
+---------+---------------------+-----------+
输出: 
+---------+-------------------+
| user_id | confirmation_rate |
+---------+-------------------+
| 6       | 0.00              |
| 3       | 0.00              |
| 7       | 1.00              |
| 2       | 0.50              |
+---------+-------------------+
解释:
用户 6 没有请求任何确认消息。确认率为 0。
用户 3 进行了 2 次请求,都超时了。确认率为 0。
用户 7 提出了 3 个请求,所有请求都得到了确认。确认率为 1。
用户 2 做了 2 个请求,其中一个被确认,另一个超时。确认率为 1 / 2 = 0.5。

解答:

sql 复制代码
WITH base AS (
  SELECT 
    user_id,
    COUNT(*) AS total,
    SUM(CASE WHEN action = 'confirmed' THEN 1 ELSE 0 END) AS confirmed
  FROM Confirmations
  GROUP BY user_id
)
SELECT 
  s.user_id,
  ROUND(IFNULL(b.confirmed / b.total, 0), 2) AS confirmation_rate
FROM Signups s
LEFT JOIN base b
  ON s.user_id = b.user_id;

1193. 每月交易I

表:Transactions

复制代码
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| id            | int     |
| country       | varchar |
| state         | enum    |
| amount        | int     |
| trans_date    | date    |
+---------------+---------+
id 是这个表的主键。
该表包含有关传入事务的信息。
state 列类型为 ["approved", "declined"] 之一。

编写一个 sql 查询来查找每个月和每个国家/地区的事务数及其总金额、已批准的事务数及其总金额。

任意顺序 返回结果表。

查询结果格式如下所示。

示例 1:

复制代码
输入:
Transactions table:
+------+---------+----------+--------+------------+
| id   | country | state    | amount | trans_date |
+------+---------+----------+--------+------------+
| 121  | US      | approved | 1000   | 2018-12-18 |
| 122  | US      | declined | 2000   | 2018-12-19 |
| 123  | US      | approved | 2000   | 2019-01-01 |
| 124  | DE      | approved | 2000   | 2019-01-07 |
+------+---------+----------+--------+------------+
输出:
+----------+---------+-------------+----------------+--------------------+-----------------------+
| month    | country | trans_count | approved_count | trans_total_amount | approved_total_amount |
+----------+---------+-------------+----------------+--------------------+-----------------------+
| 2018-12  | US      | 2           | 1              | 3000               | 1000                  |
| 2019-01  | US      | 1           | 1              | 2000               | 2000                  |
| 2019-01  | DE      | 1           | 1              | 2000               | 2000                  |
+----------+---------+-------------+----------------+--------------------+-----------------------+

解答:

多个 CTE 用 WITH a AS (...), b AS (...)

month、country 分组;approved 相关用 CASE WHEN 计数/求和。

取月份用 DATE_FORMAT(trans_date, '%Y-%m') 更稳妥。

sql 复制代码
WITH base AS (
  SELECT
    DATE_FORMAT(trans_date, '%Y-%m') AS month,
    country,
    state,
    amount
  FROM Transactions
),
seq AS (
  SELECT
    month,
    country,
    COUNT(*) AS trans_count,
    SUM(CASE WHEN state = 'approved' THEN 1 ELSE 0 END) AS approved_count,
    SUM(amount) AS trans_total_amount,
    SUM(CASE WHEN state = 'approved' THEN amount ELSE 0 END) AS approved_total_amount
  FROM base
  GROUP BY month, country
)
SELECT *
FROM seq;

1174. 即时食物配送II

配送表: Delivery

复制代码
+-----------------------------+---------+
| Column Name                 | Type    |
+-----------------------------+---------+
| delivery_id                 | int     |
| customer_id                 | int     |
| order_date                  | date    |
| customer_pref_delivery_date | date    |
+-----------------------------+---------+
delivery_id 是该表中具有唯一值的列。
该表保存着顾客的食物配送信息,顾客在某个日期下了订单,并指定了一个期望的配送日期(和下单日期相同或者在那之后)。

如果顾客期望的配送日期和下单日期相同,则该订单称为 「即时订单 」,否则称为「计划订单」。

首次订单」是顾客最早创建的订单。我们保证一个顾客只会有一个「首次订单」。

编写解决方案以获取即时订单在所有用户的首次订单中的比例。保留两位小数。

结果示例如下所示:

示例 1:

复制代码
输入:
Delivery 表:
+-------------+-------------+------------+-----------------------------+
| delivery_id | customer_id | order_date | customer_pref_delivery_date |
+-------------+-------------+------------+-----------------------------+
| 1           | 1           | 2019-08-01 | 2019-08-02                  |
| 2           | 2           | 2019-08-02 | 2019-08-02                  |
| 3           | 1           | 2019-08-11 | 2019-08-12                  |
| 4           | 3           | 2019-08-24 | 2019-08-24                  |
| 5           | 3           | 2019-08-21 | 2019-08-22                  |
| 6           | 2           | 2019-08-11 | 2019-08-13                  |
| 7           | 4           | 2019-08-09 | 2019-08-09                  |
+-------------+-------------+------------+-----------------------------+
输出:
+----------------------+
| immediate_percentage |
+----------------------+
| 50.00                |
+----------------------+
解释:
1 号顾客的 1 号订单是首次订单,并且是计划订单。
2 号顾客的 2 号订单是首次订单,并且是即时订单。
3 号顾客的 5 号订单是首次订单,并且是计划订单。
4 号顾客的 7 号订单是首次订单,并且是即时订单。
因此,一半顾客的首次订单是即时的。

解答:

sql 复制代码
with base as (
select *, row_number() over(partition by customer_id order by order_date) as rn 
from Delivery),
seq as (
    select round((sum(case when customer_pref_delivery_date = order_date then 1 else 0 end) / count(*)) * 100, 2) as immediate_percentage from base where rn = 1
)
select * from seq;

176. 第二高的薪水

Employee 表:

复制代码
+-------------+------+
| Column Name | Type |
+-------------+------+
| id          | int  |
| salary      | int  |
+-------------+------+
id 是这个表的主键。
表的每一行包含员工的工资信息。

查询并返回 Employee 表中第二高的 不同 薪水 。如果不存在第二高的薪水,查询应该返回 null(Pandas 则返回 None)

查询结果如下例所示。

示例 1:

复制代码
输入:
Employee 表:
+----+--------+
| id | salary |
+----+--------+
| 1  | 100    |
| 2  | 200    |
| 3  | 300    |
+----+--------+
输出:
+---------------------+
| SecondHighestSalary |
+---------------------+
| 200                 |
+---------------------+

示例 2:

复制代码
输入:
Employee 表:
+----+--------+
| id | salary |
+----+--------+
| 1  | 100    |
+----+--------+
输出:
+---------------------+
| SecondHighestSalary |
+---------------------+
| null                |
+---------------------+

解答

sql 复制代码
SELECT MAX(salary) AS SecondHighestSalary
FROM (
  SELECT DISTINCT salary,
         DENSE_RANK() OVER (ORDER BY salary DESC) AS rk
  FROM Employee
) t
WHERE rk = 2;
(1) 子查询为空但外层用了聚合函数

SQL 的定义是:聚合函数对空输入时,返回 NULL

例子:

sql 复制代码
SELECT MAX(salary) FROM (SELECT 1 AS salary WHERE 1=0) t;

(SELECT 1 WHERE 1=0) 为空表 → MAX 没有任何值可比较 → 返回 NULL

所以这就是为什么"第二高薪水不存在"时 MAX 可以帮我们自动返回 NULL


(2)子查询为空而外层没有聚合函数

如果你直接:

sql 复制代码
SELECT salary 
FROM (SELECT salary WHERE 1=0) t;

那结果就是 0 行 (即直接返回空结果集,不会自动给你一行 NULL)。


550. 游戏玩法分析IV

Table: Activity

复制代码
+--------------+---------+
| Column Name  | Type    |
+--------------+---------+
| player_id    | int     |
| device_id    | int     |
| event_date   | date    |
| games_played | int     |
+--------------+---------+
(player_id,event_date)是此表的主键(具有唯一值的列的组合)。
这张表显示了某些游戏的玩家的活动情况。
每一行是一个玩家的记录,他在某一天使用某个设备注销之前登录并玩了很多游戏(可能是 0)。

编写解决方案,报告在首次登录的第二天再次登录的玩家的 比率四舍五入到小数点后两位。换句话说,你需要计算从首次登录后的第二天登录的玩家数量,并将其除以总玩家数。

结果格式如下所示:

示例 1:

复制代码
输入:
Activity table:
+-----------+-----------+------------+--------------+
| player_id | device_id | event_date | games_played |
+-----------+-----------+------------+--------------+
| 1         | 2         | 2016-03-01 | 5            |
| 1         | 2         | 2016-03-02 | 6            |
| 2         | 3         | 2017-06-25 | 1            |
| 3         | 1         | 2016-03-02 | 0            |
| 3         | 4         | 2018-07-03 | 5            |
+-----------+-----------+------------+--------------+
输出:
+-----------+
| fraction  |
+-----------+
| 0.33      |
+-----------+
解释:
只有 ID 为 1 的玩家在第一天登录后才重新登录,所以答案是 1/3 = 0.33

解答:

sql 复制代码
# Write your MySQL query statement below
with base as(
    select *,min(event_date) over(partition by player_id)as first from Activity
),
seq as(
    select player_id, sum(case when datediff(event_date, first) = 1 then 1 else 0 end) as rn from base
    group by player_id
)
select round(IFNULL((sum(rn) / count(distinct player_id)), 0), 2) as fraction from seq;

注意:

  1. 在使用with base as(), seq as() select from seq时,要记得每个嵌套内部的select很重要,前一个select需要包含后一个select的内容。
  2. 使用聚合函数min,max,count后结果会被压缩,如果后续要同行对最大、最小进行比较,要使用窗口函数形式的max,min...

1045. 买下所有产品的客户

Customer 表:

复制代码
+-------------+---------+
| Column Name | Type    |
+-------------+---------+
| customer_id | int     |
| product_key | int     |
+-------------+---------+
该表可能包含重复的行。
customer_id 不为 NULL。
product_key 是 Product 表的外键(reference 列)。

Product 表:

复制代码
+-------------+---------+
| Column Name | Type    |
+-------------+---------+
| product_key | int     |
+-------------+---------+
product_key 是这张表的主键(具有唯一值的列)。

编写解决方案,报告 Customer 表中购买了 Product 表中所有产品的客户的 id。

返回结果表 无顺序要求

返回结果格式如下所示。

示例 1:

复制代码
输入:
Customer 表:
+-------------+-------------+
| customer_id | product_key |
+-------------+-------------+
| 1           | 5           |
| 2           | 6           |
| 3           | 5           |
| 3           | 6           |
| 1           | 6           |
+-------------+-------------+
Product 表:
+-------------+
| product_key |
+-------------+
| 5           |
| 6           |
+-------------+
输出:
+-------------+
| customer_id |
+-------------+
| 1           |
| 3           |
+-------------+
解释:
购买了所有产品(5 和 6)的客户的 id 是 1 和 3 。

解答:

  1. WHERE DISTINCT 是非法语法DISTINCT 只能放在 SELECTCOUNT(DISTINCT ...) 里面,不能直接放在 WHERE
  2. 题目要求的是"买了所有产品的顾客",所以应该比较顾客买到的 去重后产品数Product 表里的产品总数
sql 复制代码
SELECT customer_id
FROM Customer
GROUP BY customer_id
HAVING COUNT(DISTINCT product_key) = (
    SELECT COUNT(*) FROM Product
);

解释一下:

  • GROUP BY customer_id:按顾客分组。
  • COUNT(DISTINCT product_key):统计该顾客买了多少种不同的产品。
  • 子查询 (SELECT COUNT(*) FROM Product):统计总共有多少种产品。
  • HAVING ... = ...:筛选出买全所有产品的顾客。

180. 连续出现的数字

表:Logs

复制代码
+-------------+---------+
| Column Name | Type    |
+-------------+---------+
| id          | int     |
| num         | varchar |
+-------------+---------+
在 SQL 中,id 是该表的主键。
id 是一个自增列。

找出所有至少连续出现三次的数字。

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

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

示例 1:

复制代码
输入:
Logs 表:
+----+-----+
| id | num |
+----+-----+
| 1  | 1   |
| 2  | 1   |
| 3  | 1   |
| 4  | 2   |
| 5  | 1   |
| 6  | 2   |
| 7  | 2   |
+----+-----+
输出:
Result 表:
+-----------------+
| ConsecutiveNums |
+-----------------+
| 1               |
+-----------------+
解释:1 是唯一连续出现至少三次的数字。

解答:

sql 复制代码
with base as(
    select *, row_number() over(partition by num order by id) as rn from Logs
),
seq as(
    select *, id - rn as diff from base 
),
rpq as (
    select num, count(*) as ct from seq group by num, diff having count(*) >= 3
)
select distinct num as ConsecutiveNums from rpq;

有时候不要忘记distinct


1164. 指定日期的产品价格

产品数据表: Products

复制代码
+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| product_id    | int     |
| new_price     | int     |
| change_date   | date    |
+---------------+---------+
(product_id, change_date) 是此表的主键(具有唯一值的列组合)。
这张表的每一行分别记录了 某产品 在某个日期 更改后 的新价格。

一开始,所有产品价格都为 10。

编写一个解决方案,找出在 2019-08-16 所有产品的价格。

任意顺序 返回结果表。

结果格式如下例所示。

示例 1:

复制代码
输入:
Products 表:
+------------+-----------+-------------+
| product_id | new_price | change_date |
+------------+-----------+-------------+
| 1          | 20        | 2019-08-14  |
| 2          | 50        | 2019-08-14  |
| 1          | 30        | 2019-08-15  |
| 1          | 35        | 2019-08-16  |
| 2          | 65        | 2019-08-17  |
| 3          | 20        | 2019-08-18  |
+------------+-----------+-------------+
输出:
+------------+-------+
| product_id | price |
+------------+-------+
| 2          | 50    |
| 1          | 35    |
| 3          | 10    |
+------------+-------+

解答:

sql 复制代码
with base as(
    select *, row_number()over (partition by product_id order by change_date DESC) as rn from Products where change_date <= '2019-08-16'
),
seq as (
    select product_id, new_price as price from base where rn = 1
)
select product_id, price from seq union all select distinct product_id, 10 as price from (
    select product_id, min(change_date) as mi from Products group by product_id
)t  where mi > '2019-08-16';

归纳:

想要得到某一列的最大最小值,并用其与某个数进行比较,需要先max、min(窗口或者聚合函数),然后再等值查询,或者rownumber配合ASC、DESC,然后再后续查询中使用rn=1,或者直接使用order by+limit

(1)非相关子查询 + 等值比较

使用场景全局只有一个最值(整个表的最大值/最小值),不依赖分组。

  • 特点:子查询返回单行,可以直接用 = 比较。
  • 示例:找出工资最高的员工
sql 复制代码
select *
from Employee
where salary = (select max(salary) from Employee);

(2)相关子查询

使用场景分组后的最值(每个客户/部门的最小/最大日期),外层表的条件依赖子查询。

  • 特点:子查询里需要引用外层的列,每行执行一次,保证返回单行。
  • 示例:找每个客户的首单
sql 复制代码
select *
from Delivery d1
where order_date = (
  select min(order_date)
  from Delivery d2
  where d2.customer_id = d1.customer_id
);

这里子查询里的 d2.customer_id = d1.customer_id 绑定了外层查询的 d1.customer_id

每次外层取一行,子查询就只计算该客户的最小订单日期。

也可以写成这种形式:

sql 复制代码
select *
from (
  select customer_id, min(order_date)
  from Delivery 
  group by customer_id
)t ;

(3)窗口函数 + 条件筛选

使用场景 :数据库支持窗口函数(MySQL 8+ / PostgreSQL / Oracle / SQL Server),需要按组取前几名/最值。

也可以使用max、min的窗口函数形式来获取每个组别的最值

  • 特点:写法简洁,可同时保留分组内排序信息。
  • 示例:找每个客户的首单
sql 复制代码
with t as (
  select *,
         row_number() over(partition by customer_id order by order_date) as rn
  from Delivery
)
select *
from t
where rn = 1;
  • 如果要处理并列情况,可以用 rank()dense_rank()

(4)ORDER BY + LIMIT

使用场景只需要全表范围内的最值,且只取前 N 条(不分组)。

  • 特点:简单高效,但无法直接应对「每组最值」问题。
  • 示例:找工资最高的员工(Top 1)
sql 复制代码
select *
from Employee
order by salary desc
limit 1;
  • 如果需要每组最值,就不适合,只能配合窗口函数或子查询。

对比总结
方法 适用场景 优点 局限
非相关子查询 全表唯一最值 简单 只能处理单个全局最值
相关子查询 每组最值(需要依赖外层条件) 通用,不要求窗口函数 子查询多次执行,性能较差
窗口函数 每组最值/前 N 个 高效简洁,现代 SQL 推荐 需要数据库支持窗口函数
ORDER BY + LIMIT 全表前 N 个 简洁高效 不能分组,只能处理全局最值

👉 快速记忆:

  • 全局最值 → 非相关子查询 或 order by + limit
  • 分组最值 → 相关子查询 或 窗口函数
  • 要前 N 名/并列情况 → 窗口函数最佳

1204.最后一个能进入巴士的人

表: Queue

复制代码
+-------------+---------+
| Column Name | Type    |
+-------------+---------+
| person_id   | int     |
| person_name | varchar |
| weight      | int     |
| turn        | int     |
+-------------+---------+
person_id 是这个表具有唯一值的列。
该表展示了所有候车乘客的信息。
表中 person_id 和 turn 列将包含从 1 到 n 的所有数字,其中 n 是表中的行数。
turn 决定了候车乘客上巴士的顺序,其中 turn=1 表示第一个上巴士,turn=n 表示最后一个上巴士。
weight 表示候车乘客的体重,以千克为单位。

有一队乘客在等着上巴士。然而,巴士有1000 千克 的重量限制,所以其中一部分乘客可能无法上巴士。

编写解决方案找出 最后一个 上巴士且不超过重量限制的乘客,并报告 person_name 。题目测试用例确保顺位第一的人可以上巴士且不会超重。

返回结果格式如下所示。

示例 1:

复制代码
输入:
Queue 表
+-----------+-------------+--------+------+
| person_id | person_name | weight | turn |
+-----------+-------------+--------+------+
| 5         | Alice       | 250    | 1    |
| 4         | Bob         | 175    | 5    |
| 3         | Alex        | 350    | 2    |
| 6         | John Cena   | 400    | 3    |
| 1         | Winston     | 500    | 6    |
| 2         | Marie       | 200    | 4    |
+-----------+-------------+--------+------+
输出:
+-------------+
| person_name |
+-------------+
| John Cena   |
+-------------+
解释:
为了简化,Queue 表按 turn 列由小到大排序。
+------+----+-----------+--------+--------------+
| Turn | ID | Name      | Weight | Total Weight |
+------+----+-----------+--------+--------------+
| 1    | 5  | Alice     | 250    | 250          |
| 2    | 3  | Alex      | 350    | 600          |
| 3    | 6  | John Cena | 400    | 1000         | (最后一个上巴士)
| 4    | 2  | Marie     | 200    | 1200         | (无法上巴士)
| 5    | 4  | Bob       | 175    | ___          |
| 6    | 1  | Winston   | 500    | ___          |
+------+----+-----------+--------+--------------+

解答:

PS:SQL 聚合函数(如 MAX())和普通列之间没有自动的"行对齐"关系。

不能直接写成!:

sql 复制代码
select person_name, max(total_weight) from (
    select * , sum(weight)over (order by turn) as total_weight from Queue
)t where total_weight <= 1000;

-- 结果只会返回
| person_id | person_name | weight | turn |
| --------- | ----------- | ------ | ---- |
| 5         | Alice       | 250    | 1    |
| 4         | Bob         | 175    | 5    |
| 3         | Alex        | 350    | 2    |
| 6         | John Cena   | 400    | 3    |
| 1         | Winston     | 500    | 6    |
| 2         | Marie       | 200    | 4    |

| person_name | max(total_weight) |
| ----------- | ----------------- |
| Alice       | 1000              |

-- 而不是正确输出
| person_name |
| ----------- |
| John Cena   |
(1)子查询+等值过滤
sql 复制代码
# Write your MySQL query statement below
with base as(
    select * , sum(weight)over (order by turn) as total_weight from Queue
)
select person_name from base where total_weight = (
    select max(total_weight) as mx from base where total_weight <= 1000
);

(2)ORDER BY+LIMIT
sql 复制代码
# Write your MySQL query statement below
select person_name from (
    select * , sum(weight)over (order by turn) as total_weight from Queue
)t where total_weight <= 1000 order by total_weight DESC limit 1;

1907. 按分类统计薪水

表: Accounts

复制代码
+-------------+------+
| 列名        | 类型  |
+-------------+------+
| account_id  | int  |
| income      | int  |
+-------------+------+
在 SQL 中,account_id 是这个表的主键。
每一行都包含一个银行帐户的月收入的信息。

查询每个工资类别的银行账户数量。 工资类别如下:

  • "Low Salary":所有工资 严格低于 20000 美元。
  • "Average Salary"包含 范围内的所有工资 [$20000, $50000]
  • "High Salary":所有工资 严格大于 50000 美元。

结果表 必须 包含所有三个类别。 如果某个类别中没有帐户,则报告 0

任意顺序 返回结果表。

查询结果格式如下示例。

示例 1:

复制代码
输入:
Accounts 表:
+------------+--------+
| account_id | income |
+------------+--------+
| 3          | 108939 |
| 2          | 12747  |
| 8          | 87709  |
| 6          | 91796  |
+------------+--------+
输出:
+----------------+----------------+
| category       | accounts_count |
+----------------+----------------+
| Low Salary     | 1              |
| Average Salary | 0              |
| High Salary    | 3              |
+----------------+----------------+
解释:
低薪: 有一个账户 2.
中等薪水: 没有.
高薪: 有三个账户,他们是 3, 6和 8.

解答:

sql 复制代码
select 'Low Salary' as category, sum(case when income < 20000 then 1 else 0 end) as accounts_count
from Accounts
Union all
select 'Average Salary' as category, sum(case when income >= 20000 and income <= 50000 then 1 else 0 end) as accounts_count
from Accounts
Union all
select 'High Salary' as category, sum(case when income > 50000 then 1 else 0 end) as accounts_count
from Accounts;

626. 换座位

表: Seat

复制代码
+-------------+---------+
| Column Name | Type    |
+-------------+---------+
| id          | int     |
| student     | varchar |
+-------------+---------+
id 是该表的主键(唯一值)列。
该表的每一行都表示学生的姓名和 ID。
ID 序列始终从 1 开始并连续增加。

编写解决方案来交换每两个连续的学生的座位号。如果学生的数量是奇数,则最后一个学生的id不交换。

id 升序 返回结果表。

查询结果格式如下所示。

示例 1:

复制代码
输入: 
Seat 表:
+----+---------+
| id | student |
+----+---------+
| 1  | Abbot   |
| 2  | Doris   |
| 3  | Emerson |
| 4  | Green   |
| 5  | Jeames  |
+----+---------+
输出: 
+----+---------+
| id | student |
+----+---------+
| 1  | Doris   |
| 2  | Abbot   |
| 3  | Green   |
| 4  | Emerson |
| 5  | Jeames  |
+----+---------+
解释:
请注意,如果学生人数为奇数,则不需要更换最后一名学生的座位。

解答:

(1)嵌套查询
sql 复制代码
# Write your MySQL query statement below
with base as(
    select *, lead(student, 1)over (order by id) as exchange_nxt, lag(student, 1)over (order by id) as exchange_pre from Seat
)
select id, IFNULL((case when (id % 2) != 0 then exchange_nxt else exchange_pre end), student) as student from base;
(2)CASE WHEN
sql 复制代码
# Write your MySQL query statement below
select case 
        when id % 2 = 1 and id < (select max(id) from Seat)
            then id + 1
        when id % 2 = 0 
            then id - 1
        else id
        end as id, student
    from Seat
order by id;

在 SQL 里,CASE 可以写两种形式:

a. 简单 CASE

直接对某个表达式的值做匹配:

sql 复制代码
CASE column_name
    WHEN 'A' THEN '类型1'
    WHEN 'B' THEN '类型2'
    ELSE '其他'
END
b. 搜索 CASE (最常用)

条件判断灵活,可以写比较、范围等:

sql 复制代码
CASE
    WHEN score >= 90 THEN '优秀'
    WHEN score >= 60 THEN '及格'
    ELSE '不及格'
END
c. 作为一个字段输出
sql 复制代码
SELECT name,
       CASE WHEN age < 18 THEN '未成年'
            WHEN age < 60 THEN '成年人'
            ELSE '老年人'
       END AS age_group
FROM users;
d. 在聚合函数里做条件统计
sql 复制代码
SELECT
    SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) AS success_count,
    SUM(CASE WHEN status = 'fail' THEN 1 ELSE 0 END)    AS fail_count
FROM logs;

常用于 条件计数

e. 排序(ORDER BY)
sql 复制代码
SELECT *
FROM orders
ORDER BY
  CASE 
    WHEN status = 'vip' THEN 1
    WHEN status = 'normal' THEN 2
    ELSE 3
  END;

补充:

SQL执行顺序:

  • FROM Employee
  • WHERE salary > 5000 (先筛掉工资不够的行)
  • GROUP BY dept (按部门分组)
  • COUNT(*) 统计每组人数
  • HAVING 过滤出人数 ≥3 的部门
  • SELECT 输出 dept, cnt
  • ORDER BY 按 cnt 降序
  • LIMIT 取前 5 个部门
相关推荐
我是渣哥3 小时前
Java String vs StringBuilder vs StringBuffer:一个性能优化的探险故事
java·开发语言·jvm·后端·算法·职场和发展·性能优化
THMAIL3 小时前
机器学习从入门到精通 - 机器学习调参终极手册:网格搜索、贝叶斯优化实战
人工智能·python·算法·机器学习·支持向量机·数据挖掘·逻辑回归
lytk993 小时前
矩阵中寻找好子矩阵
线性代数·算法·矩阵
珊瑚怪人3 小时前
算法随笔(一)
算法
川石课堂软件测试3 小时前
Oracle 数据库如何查询列
linux·数据库·sql·功能测试·oracle·grafana·prometheus
晚安里4 小时前
JVM相关 4|JVM调优与常见参数(如 -Xms、-Xmx、-XX:+PrintGCDetails) 的必会知识点汇总
java·开发语言·jvm·后端·算法
THMAIL4 小时前
机器学习从入门到精通 - 集成学习核武器:随机森林与XGBoost工业级应用
人工智能·python·算法·随机森林·机器学习·集成学习·sklearn
要做朋鱼燕4 小时前
【C++】迭代器详解与失效机制
开发语言·c++·算法
一支鱼4 小时前
leetcode-6-正则表达式匹配
算法·leetcode·typescript