LeetCode MySQL 50

50题

速刷顺序

分组题量重点
选择5基础筛选、去重、排序、字符串长度
连接9LEFT JOIN、自连接、笛卡尔积补零
聚合函数8GROUP BY、比例、条件聚合、首日留存
排序和分组7去重计数、最大唯一值、集合覆盖
高级查询和连接7CASE、窗口函数、默认值、累计和
子查询7子查询、UNION ALL、Top N、移动窗口
常用字符串函数7字符串函数、正则、删除重复行

50 题清单

链接全部指向国内站 leetcode.cn,点题名直接跳转。

#分组题目难度
1选择1757. 可回收且低脂的产品简单
2选择584. 寻找用户推荐人简单
3选择595. 大的国家简单
4选择1148. 文章浏览 I简单
5选择1683. 无效的推文简单
6连接1378. 使用唯一标识码替换员工ID简单
7连接1068. 产品销售分析 I简单
8连接1581. 进店却未进行过交易的顾客简单
9连接197. 上升的温度简单
10连接1661. 每台机器的进程平均运行时间简单
11连接577. 员工奖金简单
12连接1280. 学生们参加各科测试的次数简单
13连接570. 至少有5名直接下属的经理中等
14连接1934. 确认率中等
15聚合函数620. 有趣的电影简单
16聚合函数1251. 平均售价简单
17聚合函数1075. 项目员工 I简单
18聚合函数1633. 各赛事的用户注册率简单
19聚合函数1211. 查询结果的质量和占比简单
20聚合函数1193. 每月交易 I中等
21聚合函数1174. 即时食物配送 II中等
22聚合函数550. 游戏玩法分析 IV中等
23排序和分组2356. 每位教师所教授的科目种类的数量简单
24排序和分组1141. 查询近30天活跃用户数简单
25排序和分组1070. 产品销售分析 III中等
26排序和分组596. 超过 5 名学生的课简单
27排序和分组1729. 求关注者的数量简单
28排序和分组619. 只出现一次的最大数字简单
29排序和分组1045. 买下所有产品的客户中等
30高级查询和连接1731. 每位经理的下属员工数量简单
31高级查询和连接1789. 员工的直属部门简单
32高级查询和连接610. 判断三角形简单
33高级查询和连接180. 连续出现的数字中等
34高级查询和连接1164. 指定日期的产品价格中等
35高级查询和连接1204. 最后一个能进入巴士的人中等
36高级查询和连接1907. 按分类统计薪水中等
37子查询1978. 上级经理已离职的公司员工简单
38子查询626. 换座位中等
39子查询1341. 电影评分中等
40子查询1321. 餐馆营业额变化增长中等
41子查询602. 好友申请 II :谁有最多的好友中等
42子查询585. 2016年的投资中等
43子查询185. 部门工资前三高的所有员工困难
44常用字符串函数1667. 修复表中的名字简单
45常用字符串函数1527. 患某种疾病的患者简单
46常用字符串函数196. 删除重复的电子邮箱简单
47常用字符串函数176. 第二高的薪水中等
48常用字符串函数1484. 按日期分组销售产品简单
49常用字符串函数1327. 列出指定时间段内所有的下单产品简单
50常用字符串函数1517. 查找拥有有效邮箱的用户简单

选择

1757. 可回收且低脂的产品

  • 题目链接:1757. 可回收且低脂的产品(简单)
  • 题意概述:从 Products 中找出低脂且可回收的商品。
  • 常见表:Products(product_id, low_fats, recyclable)
  • 考点:WHERE 多条件筛选。
代码块SQL · 4 行收起展开
SELECT product_id
FROM Products
WHERE low_fats = 'Y'
  AND recyclable = 'Y';

584. 寻找用户推荐人

  • 题目链接:584. 寻找用户推荐人(简单)
  • 题意概述:找出推荐人不是 2 的客户,推荐人为空也要保留。
  • 常见表:Customer(id, name, referee_id)
  • 考点:NULL 不能用 <> 直接覆盖。
代码块SQL · 4 行收起展开
SELECT name
FROM Customer
WHERE referee_id <> 2
   OR referee_id IS NULL;

595. 大的国家

  • 题目链接:595. 大的国家(简单)
  • 题意概述:找出面积大或人口多的国家。
  • 常见表:World(name, continent, area, population, gdp)
  • 考点:OR 条件。
代码块SQL · 4 行收起展开
SELECT name, population, area
FROM World
WHERE area >= 3000000
   OR population >= 25000000;

1148. 文章浏览 I

  • 题目链接:1148. 文章浏览 I(简单)
  • 题意概述:找出看过自己文章的作者。
  • 常见表:Views(article_id, author_id, viewer_id, view_date)
  • 考点:DISTINCT、按结果列排序。
代码块SQL · 4 行收起展开
SELECT DISTINCT author_id AS id
FROM Views
WHERE author_id = viewer_id
ORDER BY id;

1683. 无效的推文

  • 题目链接:1683. 无效的推文(简单)
  • 题意概述:找出内容长度超过 15 的推文。
  • 常见表:Tweets(tweet_id, content)
  • 考点:CHAR_LENGTH()
代码块SQL · 3 行收起展开
SELECT tweet_id
FROM Tweets
WHERE CHAR_LENGTH(content) > 15;

连接

1378. 使用唯一标识码替换员工ID

  • 题目链接:1378. 使用唯一标识码替换员工ID(简单)
  • 题意概述:用员工唯一编号替换普通员工 id,没有唯一编号时显示 NULL
  • 常见表:Employees(id, name)EmployeeUNI(id, unique_id)
  • 考点:保留全部员工,用 LEFT JOIN
代码块SQL · 4 行收起展开
SELECT u.unique_id, e.name
FROM Employees e
LEFT JOIN EmployeeUNI u
  ON e.id = u.id;

1068. 产品销售分析 I

  • 题目链接:1068. 产品销售分析 I(简单)
  • 题意概述:展示每条销售记录对应的商品名、年份和价格。
  • 常见表:Sales(sale_id, product_id, year, quantity, price)Product(product_id, product_name)
  • 考点:基础内连接。
代码块SQL · 4 行收起展开
SELECT p.product_name, s.year, s.price
FROM Sales s
JOIN Product p
  ON s.product_id = p.product_id;

1581. 进店却未进行过交易的顾客

  • 题目链接:1581. 进店却未进行过交易的顾客(简单)
  • 题意概述:统计只访问但没有交易的客户访问次数。
  • 常见表:Visits(visit_id, customer_id)Transactions(transaction_id, visit_id, amount)
  • 考点:反连接 LEFT JOIN ... IS NULL
代码块SQL · 7 行收起展开
SELECT v.customer_id,
       COUNT(*) AS count_no_trans
FROM Visits v
LEFT JOIN Transactions t
  ON v.visit_id = t.visit_id
WHERE t.transaction_id IS NULL
GROUP BY v.customer_id;

197. 上升的温度

  • 题目链接:197. 上升的温度(简单)
  • 题意概述:找出比前一天温度更高的日期 id。
  • 常见表:Weather(id, recordDate, temperature)
  • 考点:日期相差一天的自连接。
代码块SQL · 5 行收起展开
SELECT w1.id
FROM Weather w1
JOIN Weather w2
  ON DATEDIFF(w1.recordDate, w2.recordDate) = 1
WHERE w1.temperature > w2.temperature;

1661. 每台机器的进程平均运行时间

  • 题目链接:1661. 每台机器的进程平均运行时间(简单)
  • 题意概述:每台机器的平均处理时间等于同一进程结束时间减开始时间。
  • 常见表:Activity(machine_id, process_id, activity_type, timestamp)
  • 考点:同表按 start/end 配对。
代码块SQL · 9 行收起展开
SELECT a1.machine_id,
       ROUND(AVG(a2.timestamp - a1.timestamp), 3) AS processing_time
FROM Activity a1
JOIN Activity a2
  ON a1.machine_id = a2.machine_id
 AND a1.process_id = a2.process_id
 AND a1.activity_type = 'start'
 AND a2.activity_type = 'end'
GROUP BY a1.machine_id;

577. 员工奖金

  • 题目链接:577. 员工奖金(简单)
  • 题意概述:找出奖金小于 1000 或没有奖金记录的员工。
  • 常见表:Employee(empId, name, supervisor, salary)Bonus(empId, bonus)
  • 考点:LEFT JOIN 后处理 NULL
代码块SQL · 6 行收起展开
SELECT e.name, b.bonus
FROM Employee e
LEFT JOIN Bonus b
  ON e.empId = b.empId
WHERE b.bonus < 1000
   OR b.bonus IS NULL;

1280. 学生们参加各科测试的次数

  • 题目链接:1280. 学生们参加各科测试的次数(简单)
  • 题意概述:列出每个学生每门科目的参加考试次数,没有参加则为 0。
  • 常见表:Students(student_id, student_name)Subjects(subject_name)Examinations(student_id, subject_name)
  • 考点:CROSS JOIN 造完整组合,再 LEFT JOIN 补零。
代码块SQL · 11 行收起展开
SELECT s.student_id,
       s.student_name,
       sub.subject_name,
       COUNT(e.subject_name) AS attended_exams
FROM Students s
CROSS JOIN Subjects sub
LEFT JOIN Examinations e
  ON s.student_id = e.student_id
 AND sub.subject_name = e.subject_name
GROUP BY s.student_id, s.student_name, sub.subject_name
ORDER BY s.student_id, sub.subject_name;

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

  • 题目链接:570. 至少有5名直接下属的经理(中等)
  • 题意概述:找出直属下属至少 5 人的经理。
  • 常见表:Employee(id, name, department, managerId)
  • 考点:员工表自连接后分组。
代码块SQL · 6 行收起展开
SELECT m.name
FROM Employee e
JOIN Employee m
  ON e.managerId = m.id
GROUP BY m.id, m.name
HAVING COUNT(*) >= 5;

1934. 确认率

  • 题目链接:1934. 确认率(中等)
  • 题意概述:计算每个用户确认动作占全部确认请求的比例,没有请求则为 0。
  • 常见表:Signups(user_id, time_stamp)Confirmations(user_id, time_stamp, action)
  • 考点:布尔表达式求平均、IFNULL
代码块SQL · 6 行收起展开
SELECT s.user_id,
       ROUND(IFNULL(AVG(c.action = 'confirmed'), 0), 2) AS confirmation_rate
FROM Signups s
LEFT JOIN Confirmations c
  ON s.user_id = c.user_id
GROUP BY s.user_id;

聚合函数

620. 有趣的电影

  • 题目链接:620. 有趣的电影(简单)
  • 题意概述:找出 id 为奇数且描述不是 boring 的电影,并按评分降序。
  • 常见表:Cinema(id, movie, description, rating)
  • 考点:取模、排序。
代码块SQL · 5 行收起展开
SELECT id, movie, description, rating
FROM Cinema
WHERE id % 2 = 1
  AND description <> 'boring'
ORDER BY rating DESC;

1251. 平均售价

  • 题目链接:1251. 平均售价(简单)
  • 题意概述:按商品计算加权平均售价,没有销量则为 0。
  • 常见表:Prices(product_id, start_date, end_date, price)UnitsSold(product_id, purchase_date, units)
  • 考点:区间连接、加权平均。
代码块SQL · 7 行收起展开
SELECT p.product_id,
       IFNULL(ROUND(SUM(u.units * p.price) / SUM(u.units), 2), 0) AS average_price
FROM Prices p
LEFT JOIN UnitsSold u
  ON p.product_id = u.product_id
 AND u.purchase_date BETWEEN p.start_date AND p.end_date
GROUP BY p.product_id;

1075. 项目员工 I

  • 题目链接:1075. 项目员工 I(简单)
  • 题意概述:计算每个项目员工平均工作年限。
  • 常见表:Project(project_id, employee_id)Employee(employee_id, name, experience_years)
  • 考点:连接后聚合。
代码块SQL · 6 行收起展开
SELECT p.project_id,
       ROUND(AVG(e.experience_years), 2) AS average_years
FROM Project p
JOIN Employee e
  ON p.employee_id = e.employee_id
GROUP BY p.project_id;

1633. 各赛事的用户注册率

  • 题目链接:1633. 各赛事的用户注册率(简单)
  • 题意概述:计算每场比赛参加用户占总用户数的百分比。
  • 常见表:Users(user_id, user_name)Register(contest_id, user_id)
  • 考点:分母用总用户数子查询。
代码块SQL · 5 行收起展开
SELECT r.contest_id,
       ROUND(COUNT(DISTINCT r.user_id) * 100 / (SELECT COUNT(*) FROM Users), 2) AS percentage
FROM Register r
GROUP BY r.contest_id
ORDER BY percentage DESC, r.contest_id ASC;

1211. 查询结果的质量和占比

  • 题目链接:1211. 查询结果的质量和占比(简单)
  • 题意概述:按查询名计算质量均值和低评分占比。
  • 常见表:Queries(query_name, result, position, rating)
  • 考点:表达式平均、条件比例。
代码块SQL · 6 行收起展开
SELECT query_name,
       ROUND(AVG(rating / position), 2) AS quality,
       ROUND(AVG(rating < 3) * 100, 2) AS poor_query_percentage
FROM Queries
WHERE query_name IS NOT NULL
GROUP BY query_name;

1193. 每月交易 I

  • 题目链接:1193. 每月交易 I(中等)
  • 题意概述:按月份和国家统计交易数、通过数、总金额、通过金额。
  • 常见表:Transactions(id, country, state, amount, trans_date)
  • 考点:DATE_FORMAT、条件聚合。
代码块SQL · 8 行收起展开
SELECT DATE_FORMAT(trans_date, '%Y-%m') AS month,
       country,
       COUNT(*) AS trans_count,
       SUM(state = 'approved') AS approved_count,
       SUM(amount) AS trans_total_amount,
       SUM(IF(state = 'approved', amount, 0)) AS approved_total_amount
FROM Transactions
GROUP BY month, country;

1174. 即时食物配送 II

  • 题目链接:1174. 即时食物配送 II(中等)
  • 题意概述:计算每个客户首单中即时配送订单的百分比。
  • 常见表:Delivery(delivery_id, customer_id, order_date, customer_pref_delivery_date)
  • 考点:先定位每个客户首单,再算比例。
代码块SQL · 7 行收起展开
SELECT ROUND(AVG(order_date = customer_pref_delivery_date) * 100, 2) AS immediate_percentage
FROM Delivery
WHERE (customer_id, order_date) IN (
    SELECT customer_id, MIN(order_date)
    FROM Delivery
    GROUP BY customer_id
);

550. 游戏玩法分析 IV

  • 题目链接:550. 游戏玩法分析 IV(中等)
  • 题意概述:计算玩家首次登录次日仍登录的比例。
  • 常见表:Activity(player_id, device_id, event_date, games_played)
  • 考点:首日子查询、次日留存。
代码块SQL · 9 行收起展开
SELECT ROUND(COUNT(a2.player_id) / COUNT(a1.player_id), 2) AS fraction
FROM (
    SELECT player_id, MIN(event_date) AS first_login
    FROM Activity
    GROUP BY player_id
) a1
LEFT JOIN Activity a2
  ON a1.player_id = a2.player_id
 AND DATEDIFF(a2.event_date, a1.first_login) = 1;

排序和分组

2356. 每位教师所教授的科目种类的数量

代码块SQL · 4 行收起展开
SELECT teacher_id,
       COUNT(DISTINCT subject_id) AS cnt
FROM Teacher
GROUP BY teacher_id;

1141. 查询近30天活跃用户数

  • 题目链接:1141. 查询近30天活跃用户数(简单)
  • 题意概述:统计指定日期前 30 天内每天活跃用户数。
  • 常见表:Activity(user_id, session_id, activity_date, activity_type)
  • 考点:日期区间、去重用户。
代码块SQL · 5 行收起展开
SELECT activity_date AS day,
       COUNT(DISTINCT user_id) AS active_users
FROM Activity
WHERE activity_date BETWEEN DATE_SUB('2019-07-27', INTERVAL 29 DAY) AND '2019-07-27'
GROUP BY activity_date;

1070. 产品销售分析 III

  • 题目链接:1070. 产品销售分析 III(中等)
  • 题意概述:找出每个商品第一次销售年份对应的销售记录。
  • 常见表:Sales(sale_id, product_id, year, quantity, price)
  • 考点:分组取最小年份,再回表筛选。
代码块SQL · 10 行收起展开
SELECT product_id,
       year AS first_year,
       quantity,
       price
FROM Sales
WHERE (product_id, year) IN (
    SELECT product_id, MIN(year)
    FROM Sales
    GROUP BY product_id
);

596. 超过 5 名学生的课

  • 题目链接:596. 超过 5 名学生的课(简单)
  • 题意概述:找出至少有 5 名学生的课程。
  • 常见表:Courses(student, class)
  • 考点:HAVING
代码块SQL · 4 行收起展开
SELECT class
FROM Courses
GROUP BY class
HAVING COUNT(student) >= 5;

1729. 求关注者的数量

  • 题目链接:1729. 求关注者的数量(简单)
  • 题意概述:统计每个用户的粉丝数,按用户 id 升序。
  • 常见表:Followers(user_id, follower_id)
  • 考点:分组计数。
代码块SQL · 5 行收起展开
SELECT user_id,
       COUNT(follower_id) AS followers_count
FROM Followers
GROUP BY user_id
ORDER BY user_id;

619. 只出现一次的最大数字

  • 题目链接:619. 只出现一次的最大数字(简单)
  • 题意概述:找出只出现一次的最大数字;不存在则返回 NULL
  • 常见表:MyNumbers(num)
  • 考点:先筛唯一值,再 MAX()
代码块SQL · 7 行收起展开
SELECT MAX(num) AS num
FROM (
    SELECT num
    FROM MyNumbers
    GROUP BY num
    HAVING COUNT(*) = 1
) t;

1045. 买下所有产品的客户

  • 题目链接:1045. 买下所有产品的客户(中等)
  • 题意概述:找出购买过所有商品的客户。
  • 常见表:Customer(customer_id, product_key)Product(product_key)
  • 考点:集合覆盖,购买的不同商品数等于商品总数。
代码块SQL · 7 行收起展开
SELECT customer_id
FROM Customer
GROUP BY customer_id
HAVING COUNT(DISTINCT product_key) = (
    SELECT COUNT(*)
    FROM Product
);

高级查询和连接

1731. 每位经理的下属员工数量

  • 题目链接:1731. 每位经理的下属员工数量(简单)
  • 题意概述:统计每位经理的直属汇报人数和汇报员工平均年龄。
  • 常见表:Employees(employee_id, name, reports_to, age)
  • 考点:自连接、ROUND(AVG())
代码块SQL · 9 行收起展开
SELECT m.employee_id,
       m.name,
       COUNT(e.employee_id) AS reports_count,
       ROUND(AVG(e.age)) AS average_age
FROM Employees m
JOIN Employees e
  ON m.employee_id = e.reports_to
GROUP BY m.employee_id, m.name
ORDER BY m.employee_id;

1789. 员工的直属部门

  • 题目链接:1789. 员工的直属部门(简单)
  • 题意概述:找出每个员工的主部门;如果员工只有一个部门,则该部门就是主部门。
  • 常见表:Employee(employee_id, department_id, primary_flag)
  • 考点:主标记或单部门判断。
代码块SQL · 9 行收起展开
SELECT employee_id, department_id
FROM Employee
WHERE primary_flag = 'Y'
   OR employee_id IN (
       SELECT employee_id
       FROM Employee
       GROUP BY employee_id
       HAVING COUNT(*) = 1
   );

610. 判断三角形

  • 题目链接:610. 判断三角形(简单)
  • 题意概述:判断三条边是否可以构成三角形。
  • 常见表:Triangle(x, y, z)
  • 考点:CASE WHEN
代码块SQL · 6 行收起展开
SELECT x, y, z,
       CASE
           WHEN x + y > z AND x + z > y AND y + z > x THEN 'Yes'
           ELSE 'No'
       END AS triangle
FROM Triangle;

180. 连续出现的数字

  • 题目链接:180. 连续出现的数字(中等)
  • 题意概述:找出连续出现至少 3 次的数字。
  • 常见表:Logs(id, num)
  • 考点:连续 id 自连接。
代码块SQL · 8 行收起展开
SELECT DISTINCT l1.num AS ConsecutiveNums
FROM Logs l1
JOIN Logs l2
  ON l2.id = l1.id + 1
JOIN Logs l3
  ON l3.id = l1.id + 2
WHERE l1.num = l2.num
  AND l2.num = l3.num;

1164. 指定日期的产品价格

  • 题目链接:1164. 指定日期的产品价格(中等)
  • 题意概述:查询指定日期每个商品的价格;该日期前没有改价记录则价格为 10。
  • 常见表:Products(product_id, new_price, change_date)
  • 考点:每组取指定日期前最后一条记录,默认值。
代码块SQL · 17 行收起展开
SELECT p.product_id,
       IFNULL(x.new_price, 10) AS price
FROM (
    SELECT DISTINCT product_id
    FROM Products
) p
LEFT JOIN (
    SELECT product_id, new_price
    FROM Products
    WHERE (product_id, change_date) IN (
        SELECT product_id, MAX(change_date)
        FROM Products
        WHERE change_date <= '2019-08-16'
        GROUP BY product_id
    )
) x
  ON p.product_id = x.product_id;

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

  • 题目链接:1204. 最后一个能进入巴士的人(中等)
  • 题意概述:按上车顺序累计重量,找出不超过 1000 的最后一人。
  • 常见表:Queue(person_id, person_name, weight, turn)
  • 考点:窗口函数累计和。
代码块SQL · 9 行收起展开
SELECT person_name
FROM (
    SELECT person_name,
           SUM(weight) OVER (ORDER BY turn) AS total_weight
    FROM Queue
) t
WHERE total_weight <= 1000
ORDER BY total_weight DESC
LIMIT 1;

1907. 按分类统计薪水

  • 题目链接:1907. 按分类统计薪水(中等)
  • 题意概述:按低、中、高三档收入统计账户数,三档都要输出。
  • 常见表:Accounts(account_id, income)
  • 考点:UNION ALL 固定输出类别。
代码块SQL · 18 行收起展开
SELECT 'Low Salary' AS category,
       COUNT(*) AS accounts_count
FROM Accounts
WHERE income < 20000

UNION ALL

SELECT 'Average Salary' AS category,
       COUNT(*) AS accounts_count
FROM Accounts
WHERE income BETWEEN 20000 AND 50000

UNION ALL

SELECT 'High Salary' AS category,
       COUNT(*) AS accounts_count
FROM Accounts
WHERE income > 50000;

子查询

1978. 上级经理已离职的公司员工

  • 题目链接:1978. 上级经理已离职的公司员工(简单)
  • 题意概述:找出工资低于 30000 且经理已离职的员工。
  • 常见表:Employees(employee_id, name, manager_id, salary)
  • 考点:子查询判断经理是否仍存在。
代码块SQL · 8 行收起展开
SELECT employee_id
FROM Employees
WHERE salary < 30000
  AND manager_id NOT IN (
      SELECT employee_id
      FROM Employees
  )
ORDER BY employee_id;

626. 换座位

  • 题目链接:626. 换座位(中等)
  • 题意概述:相邻学生交换座位,最后一个奇数座位不变。
  • 常见表:Seat(id, student)
  • 考点:CASE 生成新 id。
代码块SQL · 8 行收起展开
SELECT CASE
           WHEN id % 2 = 1 AND id = (SELECT MAX(id) FROM Seat) THEN id
           WHEN id % 2 = 1 THEN id + 1
           ELSE id - 1
       END AS id,
       student
FROM Seat
ORDER BY id;

1341. 电影评分

  • 题目链接:1341. 电影评分(中等)
  • 题意概述:返回评分次数最多的用户,以及 2020 年 2 月平均评分最高的电影。
  • 常见表:Movies(movie_id, title)Users(user_id, name)MovieRating(movie_id, user_id, rating, created_at)
  • 考点:两个查询用 UNION ALL 合并,平局按字典序。
代码块SQL · 18 行收起展开
(SELECT u.name AS results
 FROM MovieRating mr
 JOIN Users u
   ON mr.user_id = u.user_id
 GROUP BY mr.user_id, u.name
 ORDER BY COUNT(*) DESC, u.name ASC
 LIMIT 1)

UNION ALL

(SELECT m.title AS results
 FROM MovieRating mr
 JOIN Movies m
   ON mr.movie_id = m.movie_id
 WHERE mr.created_at BETWEEN '2020-02-01' AND '2020-02-29'
 GROUP BY mr.movie_id, m.title
 ORDER BY AVG(mr.rating) DESC, m.title ASC
 LIMIT 1);

1321. 餐馆营业额变化增长

  • 题目链接:1321. 餐馆营业额变化增长(中等)
  • 题意概述:按日期计算最近 7 天的营业额总和及日均金额。
  • 常见表:Customer(customer_id, name, visited_on, amount)
  • 考点:先按天汇总,再做 7 日窗口。
代码块SQL · 25 行收起展开
WITH daily AS (
    SELECT visited_on,
           SUM(amount) AS amount
    FROM Customer
    GROUP BY visited_on
),
rolling AS (
    SELECT visited_on,
           SUM(amount) OVER (
               ORDER BY visited_on
               ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
           ) AS amount,
           ROUND(AVG(amount) OVER (
               ORDER BY visited_on
               ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
           ), 2) AS average_amount
    FROM daily
)
SELECT visited_on, amount, average_amount
FROM rolling
WHERE visited_on >= (
    SELECT DATE_ADD(MIN(visited_on), INTERVAL 6 DAY)
    FROM daily
)
ORDER BY visited_on;

602. 好友申请 II :谁有最多的好友

  • 题目链接:602. 好友申请 II :谁有最多的好友(中等)
  • 题意概述:统计 requester 和 accepter 两侧的好友数,找好友最多的人。
  • 常见表:RequestAccepted(requester_id, accepter_id, accept_date)
  • 考点:UNION ALL 拉平成一列后计数。
代码块SQL · 12 行收起展开
SELECT id,
       COUNT(*) AS num
FROM (
    SELECT requester_id AS id
    FROM RequestAccepted
    UNION ALL
    SELECT accepter_id AS id
    FROM RequestAccepted
) t
GROUP BY id
ORDER BY num DESC
LIMIT 1;

585. 2016年的投资

  • 题目链接:585. 2016年的投资(中等)
  • 题意概述:求满足指定条件的 2016 投保额总和:2015 投保额有重复,坐标位置唯一。
  • 常见表:Insurance(pid, tiv_2015, tiv_2016, lat, lon)
  • 考点:两个分组条件同时过滤。
代码块SQL · 14 行收起展开
SELECT ROUND(SUM(tiv_2016), 2) AS tiv_2016
FROM Insurance
WHERE tiv_2015 IN (
    SELECT tiv_2015
    FROM Insurance
    GROUP BY tiv_2015
    HAVING COUNT(*) > 1
)
AND (lat, lon) IN (
    SELECT lat, lon
    FROM Insurance
    GROUP BY lat, lon
    HAVING COUNT(*) = 1
);

185. 部门工资前三高的所有员工

  • 题目链接:185. 部门工资前三高的所有员工(困难)
  • 题意概述:找出每个部门工资排名前三的员工,工资并列算同一名次。
  • 常见表:Employee(id, name, salary, departmentId)Department(id, name)
  • 考点:相关子查询计算有多少个更高的不同工资。
代码块SQL · 12 行收起展开
SELECT d.name AS Department,
       e.name AS Employee,
       e.salary AS Salary
FROM Employee e
JOIN Department d
  ON e.departmentId = d.id
WHERE (
    SELECT COUNT(DISTINCT e2.salary)
    FROM Employee e2
    WHERE e2.departmentId = e.departmentId
      AND e2.salary > e.salary
) < 3;

常用字符串函数

1667. 修复表中的名字

  • 题目链接:1667. 修复表中的名字(简单)
  • 题意概述:把姓名改成首字母大写、其余小写。
  • 常见表:Users(user_id, name)
  • 考点:UPPERLOWERLEFTSUBSTRING
代码块SQL · 4 行收起展开
SELECT user_id,
       CONCAT(UPPER(LEFT(name, 1)), LOWER(SUBSTRING(name, 2))) AS name
FROM Users
ORDER BY user_id;

1527. 患某种疾病的患者

  • 题目链接:1527. 患某种疾病的患者(简单)
  • 题意概述:找出病症列表中包含以 DIAB1 开头的病症代码的患者。
  • 常见表:Patients(patient_id, patient_name, conditions)
  • 考点:单词边界正则。
代码块SQL · 3 行收起展开
SELECT patient_id, patient_name, conditions
FROM Patients
WHERE conditions REGEXP '(^| )DIAB1';

196. 删除重复的电子邮箱

  • 题目链接:196. 删除重复的电子邮箱(简单)
  • 题意概述:删除重复邮箱,只保留同邮箱最小 id 的记录。
  • 常见表:Person(id, email)
  • 考点:自连接删除。
代码块SQL · 5 行收起展开
DELETE p1
FROM Person p1
JOIN Person p2
  ON p1.email = p2.email
 AND p1.id > p2.id;

176. 第二高的薪水

  • 题目链接:176. 第二高的薪水(中等)
  • 题意概述:返回第二高工资,没有则返回 NULL
  • 常见表:Employee(id, salary)
  • 考点:子查询包住 LIMIT,保证无结果时返回一行 NULL
代码块SQL · 6 行收起展开
SELECT (
    SELECT DISTINCT salary
    FROM Employee
    ORDER BY salary DESC
    LIMIT 1 OFFSET 1
) AS SecondHighestSalary;

1484. 按日期分组销售产品

  • 题目链接:1484. 按日期分组销售产品(简单)
  • 题意概述:按日期统计售出商品种类数,并按字典序拼接商品名。
  • 常见表:Activities(sell_date, product)
  • 考点:GROUP_CONCAT(DISTINCT ... ORDER BY ...)
代码块SQL · 6 行收起展开
SELECT sell_date,
       COUNT(DISTINCT product) AS num_sold,
       GROUP_CONCAT(DISTINCT product ORDER BY product SEPARATOR ',') AS products
FROM Activities
GROUP BY sell_date
ORDER BY sell_date;

1327. 列出指定时间段内所有的下单产品

  • 题目链接:1327. 列出指定时间段内所有的下单产品(简单)
  • 题意概述:找出 2020 年 2 月订购量至少 100 的商品。
  • 常见表:Products(product_id, product_name, product_category)Orders(product_id, order_date, unit)
  • 考点:日期过滤后分组求和。
代码块SQL · 8 行收起展开
SELECT p.product_name,
       SUM(o.unit) AS unit
FROM Products p
JOIN Orders o
  ON p.product_id = o.product_id
WHERE o.order_date BETWEEN '2020-02-01' AND '2020-02-29'
GROUP BY p.product_id, p.product_name
HAVING SUM(o.unit) >= 100;

1517. 查找拥有有效邮箱的用户

  • 题目链接:1517. 查找拥有有效邮箱的用户(简单)
  • 题意概述:找出合法邮箱用户:用户名以字母开头,只能含字母、数字、下划线、点、短横线,域名为 @leetcode.com
  • 常见表:Users(user_id, name, mail)
  • 考点:正则匹配完整字符串。
代码块SQL · 3 行收起展开
SELECT user_id, name, mail
FROM Users
WHERE mail REGEXP '^[A-Za-z][A-Za-z0-9_.-]*@leetcode[.]com$';

高频模板

1. 保留左表全部记录

代码块SQL · 4 行收起展开
SELECT a.*, b.col
FROM A a
LEFT JOIN B b
  ON a.id = b.id;

2. 找左表有、右表没有

代码块SQL · 5 行收起展开
SELECT a.*
FROM A a
LEFT JOIN B b
  ON a.id = b.id
WHERE b.id IS NULL;

3. 分组后筛选

代码块SQL · 4 行收起展开
SELECT group_col, COUNT(*) AS cnt
FROM T
GROUP BY group_col
HAVING COUNT(*) >= 5;

4. 条件聚合

代码块SQL · 4 行收起展开
SELECT
    SUM(status = 'ok') AS ok_count,
    ROUND(AVG(status = 'ok'), 2) AS ok_rate
FROM T;

5. 每组取第一条

代码块SQL · 7 行收起展开
SELECT *
FROM T
WHERE (group_id, sort_col) IN (
    SELECT group_id, MIN(sort_col)
    FROM T
    GROUP BY group_id
);

6. 累计和

代码块SQL · 3 行收起展开
SELECT id,
       SUM(amount) OVER (ORDER BY id) AS running_amount
FROM T;

MySQL 上机语法函数速查

用途:笔试、上机、LeetCode SQL 题速查。
默认版本:MySQL 8.x,可使用 CTE 和窗口函数。若平台是 MySQL 5.7,窗口函数和 WITH 需要改写成子查询或自连接。

1. 查询执行顺序

写 SQL 的顺序:

代码块SQL · 8 行收起展开
SELECT ...
FROM ...
JOIN ... ON ...
WHERE ...
GROUP BY ...
HAVING ...
ORDER BY ...
LIMIT ...

逻辑执行顺序:

顺序阶段作用
1FROM / JOIN找数据源并连接
2WHERE分组前过滤行
3GROUP BY分组
4聚合函数计算 COUNT/SUM/AVG/MAX/MIN
5HAVING分组后过滤
6SELECT选择和计算输出列
7DISTINCT去重
8ORDER BY排序
9LIMIT截断结果

2. 基础 SELECT

代码块SQL · 5 行收起展开
SELECT col1, col2
FROM table_name
WHERE condition
ORDER BY col1 DESC, col2 ASC
LIMIT 10 OFFSET 20;

常用写法:

代码块SQL · 14 行收起展开
SELECT DISTINCT user_id
FROM Orders;

SELECT *
FROM Products
WHERE price BETWEEN 10 AND 100;

SELECT *
FROM Users
WHERE name LIKE 'A%';

SELECT *
FROM Users
WHERE id IN (1, 2, 3);

3. NULL 判断

NULL 表示未知,不能用 = NULL<> NULL

代码块SQL · 2 行收起展开
WHERE col IS NULL
WHERE col IS NOT NULL

常用兜底:

代码块SQL · 5 行收起展开
SELECT IFNULL(score, 0) AS score
FROM T;

SELECT COALESCE(col1, col2, 'default') AS value
FROM T;

4. JOIN

INNER JOIN

只保留两边都匹配的行。

代码块SQL · 4 行收起展开
SELECT a.id, b.name
FROM A a
JOIN B b
  ON a.id = b.a_id;

LEFT JOIN

保留左表全部行,右表没有匹配则补 NULL

代码块SQL · 4 行收起展开
SELECT a.id, b.name
FROM A a
LEFT JOIN B b
  ON a.id = b.a_id;

反连接

找 A 中存在但 B 中不存在的数据。

代码块SQL · 5 行收起展开
SELECT a.*
FROM A a
LEFT JOIN B b
  ON a.id = b.a_id
WHERE b.a_id IS NULL;

CROSS JOIN

生成所有组合,常用于补全”每个学生每门课”这种矩阵。

代码块SQL · 3 行收起展开
SELECT s.student_id, sub.subject_name
FROM Students s
CROSS JOIN Subjects sub;

5. GROUP BY 和聚合

代码块SQL · 8 行收起展开
SELECT department_id,
       COUNT(*) AS employee_count,
       AVG(salary) AS avg_salary,
       MAX(salary) AS max_salary,
       MIN(salary) AS min_salary,
       SUM(salary) AS total_salary
FROM Employee
GROUP BY department_id;

WHERE 过滤原始行,HAVING 过滤分组结果:

代码块SQL · 4 行收起展开
SELECT class
FROM Courses
GROUP BY class
HAVING COUNT(*) >= 5;

去重计数:

代码块SQL · 4 行收起展开
SELECT user_id,
       COUNT(DISTINCT product_id) AS product_count
FROM Orders
GROUP BY user_id;

6. 条件表达式

IF

代码块SQL · 2 行收起展开
SELECT IF(score >= 60, 'pass', 'fail') AS result
FROM Exam;

CASE

代码块SQL · 7 行收起展开
SELECT name,
       CASE
           WHEN income < 20000 THEN 'Low Salary'
           WHEN income <= 50000 THEN 'Average Salary'
           ELSE 'High Salary'
       END AS category
FROM Accounts;

7. 条件聚合

MySQL 中布尔表达式可当作 1/0 使用。

代码块SQL · 6 行收起展开
SELECT
    COUNT(*) AS total_count,
    SUM(state = 'approved') AS approved_count,
    ROUND(AVG(state = 'approved'), 2) AS approved_rate,
    SUM(IF(state = 'approved', amount, 0)) AS approved_amount
FROM Transactions;

8. 子查询

标量子查询

代码块SQL · 6 行收起展开
SELECT product_id
FROM Product
WHERE price > (
    SELECT AVG(price)
    FROM Product
);

IN 子查询

代码块SQL · 6 行收起展开
SELECT customer_id
FROM Customer
WHERE product_key IN (
    SELECT product_key
    FROM Product
);

EXISTS

代码块SQL · 7 行收起展开
SELECT c.customer_id
FROM Customer c
WHERE EXISTS (
    SELECT 1
    FROM Orders o
    WHERE o.customer_id = c.customer_id
);

每组取最早/最晚

代码块SQL · 7 行收起展开
SELECT *
FROM Sales
WHERE (product_id, year) IN (
    SELECT product_id, MIN(year)
    FROM Sales
    GROUP BY product_id
);

9. CTE

CTE 用来把复杂查询拆成可读步骤。

代码块SQL · 7 行收起展开
WITH first_login AS (
    SELECT player_id, MIN(event_date) AS first_date
    FROM Activity
    GROUP BY player_id
)
SELECT *
FROM first_login;

10. 窗口函数

窗口函数不会把多行合成一行,它是在每一行旁边追加计算结果。

排名

代码块SQL · 6 行收起展开
SELECT name,
       salary,
       ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn,
       RANK() OVER (ORDER BY salary DESC) AS rnk,
       DENSE_RANK() OVER (ORDER BY salary DESC) AS dense_rnk
FROM Employee;

区别:

函数并列时排名是否跳号
ROW_NUMBER()不并列,强行编号不跳
RANK()并列跳号
DENSE_RANK()并列不跳

分组排名

代码块SQL · 8 行收起展开
SELECT department_id,
       name,
       salary,
       DENSE_RANK() OVER (
           PARTITION BY department_id
           ORDER BY salary DESC
       ) AS rnk
FROM Employee;

累计和

代码块SQL · 4 行收起展开
SELECT turn,
       person_name,
       SUM(weight) OVER (ORDER BY turn) AS total_weight
FROM Queue;

前后行

代码块SQL · 5 行收起展开
SELECT id,
       num,
       LAG(num, 1) OVER (ORDER BY id) AS prev_num,
       LEAD(num, 1) OVER (ORDER BY id) AS next_num
FROM Logs;

移动窗口

代码块SQL · 6 行收起展开
SELECT visited_on,
       SUM(amount) OVER (
           ORDER BY visited_on
           ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
       ) AS seven_day_amount
FROM DailySales;

11. 字符串函数

函数作用示例
CHAR_LENGTH(s)字符数CHAR_LENGTH(content) > 15
LENGTH(s)字节数中文会大于字符数
CONCAT(a,b)拼接CONCAT(first, last)
LEFT(s,n)左 n 个字符LEFT(name, 1)
RIGHT(s,n)右 n 个字符RIGHT(phone, 4)
SUBSTRING(s,pos)从 pos 开始截取SUBSTRING(name, 2)
UPPER(s)转大写UPPER(code)
LOWER(s)转小写LOWER(name)
TRIM(s)去首尾空格TRIM(name)
REPLACE(s,a,b)替换REPLACE(phone, '-', '')
GROUP_CONCAT()分组拼接GROUP_CONCAT(name ORDER BY name)

首字母大写:

代码块SQL · 2 行收起展开
SELECT CONCAT(UPPER(LEFT(name, 1)), LOWER(SUBSTRING(name, 2))) AS fixed_name
FROM Users;

分组拼接并去重排序:

代码块SQL · 4 行收起展开
SELECT sell_date,
       GROUP_CONCAT(DISTINCT product ORDER BY product SEPARATOR ',') AS products
FROM Activities
GROUP BY sell_date;

12. 正则 REGEXP

代码块SQL · 3 行收起展开
SELECT *
FROM Users
WHERE mail REGEXP '^[A-Za-z][A-Za-z0-9_.-]*@leetcode[.]com$';

常用符号:

正则含义
^字符串开头
$字符串结尾
.任意字符
[abc]a/b/c 中任意一个
[A-Za-z]任意英文字母
[0-9]任意数字
*前一个模式出现 0 次或多次
+前一个模式出现 1 次或多次
a|ba 或 b

匹配空格分隔的病症代码:

代码块SQL · 3 行收起展开
SELECT *
FROM Patients
WHERE conditions REGEXP '(^| )DIAB1';

13. 日期时间函数

函数作用示例
DATE_FORMAT(d, fmt)格式化日期DATE_FORMAT(trans_date, '%Y-%m')
DATEDIFF(d1,d2)日期差DATEDIFF(today, yesterday) = 1
DATE_ADD(d, INTERVAL n DAY)加日期DATE_ADD(day, INTERVAL 6 DAY)
DATE_SUB(d, INTERVAL n DAY)减日期DATE_SUB(day, INTERVAL 29 DAY)
YEAR(d)YEAR(order_date)
MONTH(d)MONTH(order_date)
DAY(d)DAY(order_date)

常用日期格式:

代码块SQL · 2 行收起展开
SELECT DATE_FORMAT(trans_date, '%Y-%m') AS month
FROM Transactions;

连续日期比较:

代码块SQL · 5 行收起展开
SELECT w1.id
FROM Weather w1
JOIN Weather w2
  ON DATEDIFF(w1.recordDate, w2.recordDate) = 1
WHERE w1.temperature > w2.temperature;

14. 数值函数

函数作用
ROUND(x, n)四舍五入保留 n 位
CEIL(x)向上取整
FLOOR(x)向下取整
ABS(x)绝对值
MOD(a,b)a % b取模
代码块SQL · 2 行收起展开
SELECT ROUND(AVG(score), 2) AS avg_score
FROM Exam;

15. UNION

UNION 会去重,UNION ALL 不去重。练习题里需要保留计数时通常用 UNION ALL

代码块SQL · 5 行收起展开
SELECT requester_id AS id
FROM RequestAccepted
UNION ALL
SELECT accepter_id AS id
FROM RequestAccepted;

固定输出三类:

代码块SQL · 11 行收起展开
SELECT 'Low Salary' AS category, COUNT(*) AS accounts_count
FROM Accounts
WHERE income < 20000
UNION ALL
SELECT 'Average Salary' AS category, COUNT(*) AS accounts_count
FROM Accounts
WHERE income BETWEEN 20000 AND 50000
UNION ALL
SELECT 'High Salary' AS category, COUNT(*) AS accounts_count
FROM Accounts
WHERE income > 50000;

16. DELETE 和 UPDATE

删除重复邮箱,只保留最小 id:

代码块SQL · 5 行收起展开
DELETE p1
FROM Person p1
JOIN Person p2
  ON p1.email = p2.email
 AND p1.id > p2.id;

更新:

代码块SQL · 3 行收起展开
UPDATE Employee
SET salary = salary * 1.1
WHERE department_id = 1;

17. 常见题型模板

找第二高

代码块SQL · 6 行收起展开
SELECT (
    SELECT DISTINCT salary
    FROM Employee
    ORDER BY salary DESC
    LIMIT 1 OFFSET 1
) AS SecondHighestSalary;

每组 Top 3,允许并列

代码块SQL · 12 行收起展开
SELECT d.name AS Department,
       e.name AS Employee,
       e.salary AS Salary
FROM Employee e
JOIN Department d
  ON e.departmentId = d.id
WHERE (
    SELECT COUNT(DISTINCT e2.salary)
    FROM Employee e2
    WHERE e2.departmentId = e.departmentId
      AND e2.salary > e.salary
) < 3;

窗口函数版:

代码块SQL · 15 行收起展开
WITH ranked AS (
    SELECT d.name AS Department,
           e.name AS Employee,
           e.salary AS Salary,
           DENSE_RANK() OVER (
               PARTITION BY e.departmentId
               ORDER BY e.salary DESC
           ) AS rnk
    FROM Employee e
    JOIN Department d
      ON e.departmentId = d.id
)
SELECT Department, Employee, Salary
FROM ranked
WHERE rnk <= 3;

购买所有商品

代码块SQL · 7 行收起展开
SELECT customer_id
FROM Customer
GROUP BY customer_id
HAVING COUNT(DISTINCT product_key) = (
    SELECT COUNT(*)
    FROM Product
);

首日次日留存

代码块SQL · 9 行收起展开
SELECT ROUND(COUNT(a2.player_id) / COUNT(a1.player_id), 2) AS fraction
FROM (
    SELECT player_id, MIN(event_date) AS first_login
    FROM Activity
    GROUP BY player_id
) a1
LEFT JOIN Activity a2
  ON a1.player_id = a2.player_id
 AND DATEDIFF(a2.event_date, a1.first_login) = 1;

区间匹配价格

代码块SQL · 7 行收起展开
SELECT p.product_id,
       IFNULL(ROUND(SUM(u.units * p.price) / SUM(u.units), 2), 0) AS average_price
FROM Prices p
LEFT JOIN UnitsSold u
  ON p.product_id = u.product_id
 AND u.purchase_date BETWEEN p.start_date AND p.end_date
GROUP BY p.product_id;

连续三行相同

代码块SQL · 6 行收起展开
SELECT DISTINCT l1.num AS ConsecutiveNums
FROM Logs l1
JOIN Logs l2 ON l2.id = l1.id + 1
JOIN Logs l3 ON l3.id = l1.id + 2
WHERE l1.num = l2.num
  AND l2.num = l3.num;

18. 上机易错点

易错点正确处理
NULL 判断IS NULL / IS NOT NULL
聚合后筛选HAVING,不是 WHERE
保留无匹配记录LEFT JOIN,再 IFNULLCOUNT(right_col)
百分比先乘 100,最后 ROUND(..., 2)
字符长度CHAR_LENGTH(),不要用 LENGTH() 判断中文字符数
分组拼接GROUP_CONCAT(DISTINCT col ORDER BY col SEPARATOR ',')
Top N 并列DENSE_RANK() 或相关子查询统计不同高值数
固定类别即使为 0 也要输出UNION ALL 或维表 LEFT JOIN
NOT IN 遇到 NULL子查询可能有 NULL 时优先用 NOT EXISTS
日期区间BETWEEN '2020-02-01' AND '2020-02-29' 包含两端

19. 背诵优先级

  1. SELECT FROM WHERE GROUP BY HAVING ORDER BY LIMIT
  2. LEFT JOIN、反连接、CROSS JOIN
  3. COUNT/SUM/AVGCOUNT(DISTINCT ...)
  4. CASE WHENIFNULL、条件聚合
  5. 日期函数:DATEDIFFDATE_FORMATDATE_ADDDATE_SUB
  6. 字符串函数:CHAR_LENGTHCONCATSUBSTRINGGROUP_CONCAT
  7. 窗口函数:ROW_NUMBERDENSE_RANKSUM() OVER
  8. 正则:REGEXP

延伸阅读

日期

代码块PLAINTEXT · 8 行收起展开
DATEDIFF(a, b)                  -- 相差天数
TIMESTAMPDIFF(DAY, a, b)        -- 按指定单位计算差值
DATE_FORMAT(date, '%Y-%m')      -- 格式化日期
DATE_ADD(date, INTERVAL 1 DAY)  -- 日期加法
DATE_SUB(date, INTERVAL 1 DAY)  -- 日期减法
YEAR(date)                      -- 年
MONTH(date)                     -- 月
DAY(date)                       -- 日

字符串

代码块PLAINTEXT · 11 行收起展开
CHAR_LENGTH(str)                -- 字符数
LENGTH(str)                     -- 字节数
CONCAT(a, b)                    -- 拼接
LEFT(str, n)                    -- 左 n 个字符
RIGHT(str, n)                   -- 右 n 个字符
SUBSTRING(str, start, len)      -- 截取
UPPER(str)                      -- 大写
LOWER(str)                      -- 小写
TRIM(str)                       -- 去两端空格
REPLACE(str, a, b)              -- 替换
GROUP_CONCAT(str)               -- 分组拼接

空值和条件

代码块PLAINTEXT · 4 行收起展开
IFNULL(a, b)                    -- a 是 NULL 时用 b
COALESCE(a, b, c)               -- 返回第一个非 NULL
IF(condition, a, b)             -- 二选一
CASE WHEN ... THEN ... END      -- 多条件

数值与聚合

代码块PLAINTEXT · 8 行收起展开
ROUND(x, 2)                     -- 四舍五入
CEIL(x)                         -- 向上取整
FLOOR(x)                        -- 向下取整
ABS(x)                          -- 绝对值
COUNT(*)                        -- 行数
COUNT(DISTINCT x)               -- 去重计数
SUM(x)                          -- 求和
AVG(x)                          -- 平均