LeetCode MySQL 50
50题
速刷顺序
| 分组 | 题量 | 重点 |
|---|---|---|
| 选择 | 5 | 基础筛选、去重、排序、字符串长度 |
| 连接 | 9 | LEFT JOIN、自连接、笛卡尔积补零 |
| 聚合函数 | 8 | GROUP BY、比例、条件聚合、首日留存 |
| 排序和分组 | 7 | 去重计数、最大唯一值、集合覆盖 |
| 高级查询和连接 | 7 | CASE、窗口函数、默认值、累计和 |
| 子查询 | 7 | 子查询、UNION ALL、Top N、移动窗口 |
| 常用字符串函数 | 7 | 字符串函数、正则、删除重复行 |
50 题清单
链接全部指向国内站 leetcode.cn,点题名直接跳转。
选择
1757. 可回收且低脂的产品
- 题目链接:1757. 可回收且低脂的产品(简单)
- 题意概述:从
Products中找出低脂且可回收的商品。 - 常见表:
Products(product_id, low_fats, recyclable) - 考点:
WHERE多条件筛选。
代码块收起展开
SELECT product_id
FROM Products
WHERE low_fats = 'Y'
AND recyclable = 'Y';584. 寻找用户推荐人
- 题目链接:584. 寻找用户推荐人(简单)
- 题意概述:找出推荐人不是
2的客户,推荐人为空也要保留。 - 常见表:
Customer(id, name, referee_id) - 考点:
NULL不能用<>直接覆盖。
代码块收起展开
SELECT name
FROM Customer
WHERE referee_id <> 2
OR referee_id IS NULL;595. 大的国家
- 题目链接:595. 大的国家(简单)
- 题意概述:找出面积大或人口多的国家。
- 常见表:
World(name, continent, area, population, gdp) - 考点:
OR条件。
代码块收起展开
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、按结果列排序。
代码块收起展开
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()。
代码块收起展开
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。
代码块收起展开
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) - 考点:基础内连接。
代码块收起展开
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。
代码块收起展开
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) - 考点:日期相差一天的自连接。
代码块收起展开
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 配对。
代码块收起展开
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。
代码块收起展开
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补零。
代码块收起展开
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) - 考点:员工表自连接后分组。
代码块收起展开
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。
代码块收起展开
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) - 考点:取模、排序。
代码块收起展开
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) - 考点:区间连接、加权平均。
代码块收起展开
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) - 考点:连接后聚合。
代码块收起展开
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) - 考点:分母用总用户数子查询。
代码块收起展开
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) - 考点:表达式平均、条件比例。
代码块收起展开
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、条件聚合。
代码块收起展开
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) - 考点:先定位每个客户首单,再算比例。
代码块收起展开
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) - 考点:首日子查询、次日留存。
代码块收起展开
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. 每位教师所教授的科目种类的数量
- 题目链接:2356. 每位教师所教授的科目种类的数量(简单)
- 题意概述:统计每位教师教授的不同科目数。
- 常见表:
Teacher(teacher_id, subject_id, dept_id) - 考点:
COUNT(DISTINCT ...)。
代码块收起展开
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) - 考点:日期区间、去重用户。
代码块收起展开
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) - 考点:分组取最小年份,再回表筛选。
代码块收起展开
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。
代码块收起展开
SELECT class
FROM Courses
GROUP BY class
HAVING COUNT(student) >= 5;1729. 求关注者的数量
- 题目链接:1729. 求关注者的数量(简单)
- 题意概述:统计每个用户的粉丝数,按用户 id 升序。
- 常见表:
Followers(user_id, follower_id) - 考点:分组计数。
代码块收起展开
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()。
代码块收起展开
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) - 考点:集合覆盖,购买的不同商品数等于商品总数。
代码块收起展开
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())。
代码块收起展开
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) - 考点:主标记或单部门判断。
代码块收起展开
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。
代码块收起展开
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 自连接。
代码块收起展开
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) - 考点:每组取指定日期前最后一条记录,默认值。
代码块收起展开
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) - 考点:窗口函数累计和。
代码块收起展开
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固定输出类别。
代码块收起展开
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) - 考点:子查询判断经理是否仍存在。
代码块收起展开
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。
代码块收起展开
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合并,平局按字典序。
代码块收起展开
(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 日窗口。
代码块收起展开
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拉平成一列后计数。
代码块收起展开
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) - 考点:两个分组条件同时过滤。
代码块收起展开
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) - 考点:相关子查询计算有多少个更高的不同工资。
代码块收起展开
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) - 考点:
UPPER、LOWER、LEFT、SUBSTRING。
代码块收起展开
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) - 考点:单词边界正则。
代码块收起展开
SELECT patient_id, patient_name, conditions
FROM Patients
WHERE conditions REGEXP '(^| )DIAB1';196. 删除重复的电子邮箱
- 题目链接:196. 删除重复的电子邮箱(简单)
- 题意概述:删除重复邮箱,只保留同邮箱最小 id 的记录。
- 常见表:
Person(id, email) - 考点:自连接删除。
代码块收起展开
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。
代码块收起展开
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 ...)。
代码块收起展开
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) - 考点:日期过滤后分组求和。
代码块收起展开
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) - 考点:正则匹配完整字符串。
代码块收起展开
SELECT user_id, name, mail
FROM Users
WHERE mail REGEXP '^[A-Za-z][A-Za-z0-9_.-]*@leetcode[.]com$';高频模板
1. 保留左表全部记录
代码块收起展开
SELECT a.*, b.col
FROM A a
LEFT JOIN B b
ON a.id = b.id;2. 找左表有、右表没有
代码块收起展开
SELECT a.*
FROM A a
LEFT JOIN B b
ON a.id = b.id
WHERE b.id IS NULL;3. 分组后筛选
代码块收起展开
SELECT group_col, COUNT(*) AS cnt
FROM T
GROUP BY group_col
HAVING COUNT(*) >= 5;4. 条件聚合
代码块收起展开
SELECT
SUM(status = 'ok') AS ok_count,
ROUND(AVG(status = 'ok'), 2) AS ok_rate
FROM T;5. 每组取第一条
代码块收起展开
SELECT *
FROM T
WHERE (group_id, sort_col) IN (
SELECT group_id, MIN(sort_col)
FROM T
GROUP BY group_id
);6. 累计和
代码块收起展开
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 的顺序:
代码块收起展开
SELECT ...
FROM ...
JOIN ... ON ...
WHERE ...
GROUP BY ...
HAVING ...
ORDER BY ...
LIMIT ...逻辑执行顺序:
| 顺序 | 阶段 | 作用 |
|---|---|---|
| 1 | FROM / JOIN | 找数据源并连接 |
| 2 | WHERE | 分组前过滤行 |
| 3 | GROUP BY | 分组 |
| 4 | 聚合函数 | 计算 COUNT/SUM/AVG/MAX/MIN |
| 5 | HAVING | 分组后过滤 |
| 6 | SELECT | 选择和计算输出列 |
| 7 | DISTINCT | 去重 |
| 8 | ORDER BY | 排序 |
| 9 | LIMIT | 截断结果 |
2. 基础 SELECT
代码块收起展开
SELECT col1, col2
FROM table_name
WHERE condition
ORDER BY col1 DESC, col2 ASC
LIMIT 10 OFFSET 20;常用写法:
代码块收起展开
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。
代码块收起展开
WHERE col IS NULL
WHERE col IS NOT NULL常用兜底:
代码块收起展开
SELECT IFNULL(score, 0) AS score
FROM T;
SELECT COALESCE(col1, col2, 'default') AS value
FROM T;4. JOIN
INNER JOIN
只保留两边都匹配的行。
代码块收起展开
SELECT a.id, b.name
FROM A a
JOIN B b
ON a.id = b.a_id;LEFT JOIN
保留左表全部行,右表没有匹配则补 NULL。
代码块收起展开
SELECT a.id, b.name
FROM A a
LEFT JOIN B b
ON a.id = b.a_id;反连接
找 A 中存在但 B 中不存在的数据。
代码块收起展开
SELECT a.*
FROM A a
LEFT JOIN B b
ON a.id = b.a_id
WHERE b.a_id IS NULL;CROSS JOIN
生成所有组合,常用于补全”每个学生每门课”这种矩阵。
代码块收起展开
SELECT s.student_id, sub.subject_name
FROM Students s
CROSS JOIN Subjects sub;5. GROUP BY 和聚合
代码块收起展开
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 过滤分组结果:
代码块收起展开
SELECT class
FROM Courses
GROUP BY class
HAVING COUNT(*) >= 5;去重计数:
代码块收起展开
SELECT user_id,
COUNT(DISTINCT product_id) AS product_count
FROM Orders
GROUP BY user_id;6. 条件表达式
IF
代码块收起展开
SELECT IF(score >= 60, 'pass', 'fail') AS result
FROM Exam;CASE
代码块收起展开
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 使用。
代码块收起展开
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. 子查询
标量子查询
代码块收起展开
SELECT product_id
FROM Product
WHERE price > (
SELECT AVG(price)
FROM Product
);IN 子查询
代码块收起展开
SELECT customer_id
FROM Customer
WHERE product_key IN (
SELECT product_key
FROM Product
);EXISTS
代码块收起展开
SELECT c.customer_id
FROM Customer c
WHERE EXISTS (
SELECT 1
FROM Orders o
WHERE o.customer_id = c.customer_id
);每组取最早/最晚
代码块收起展开
SELECT *
FROM Sales
WHERE (product_id, year) IN (
SELECT product_id, MIN(year)
FROM Sales
GROUP BY product_id
);9. CTE
CTE 用来把复杂查询拆成可读步骤。
代码块收起展开
WITH first_login AS (
SELECT player_id, MIN(event_date) AS first_date
FROM Activity
GROUP BY player_id
)
SELECT *
FROM first_login;10. 窗口函数
窗口函数不会把多行合成一行,它是在每一行旁边追加计算结果。
排名
代码块收起展开
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() | 并列 | 不跳 |
分组排名
代码块收起展开
SELECT department_id,
name,
salary,
DENSE_RANK() OVER (
PARTITION BY department_id
ORDER BY salary DESC
) AS rnk
FROM Employee;累计和
代码块收起展开
SELECT turn,
person_name,
SUM(weight) OVER (ORDER BY turn) AS total_weight
FROM Queue;前后行
代码块收起展开
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;移动窗口
代码块收起展开
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) |
首字母大写:
代码块收起展开
SELECT CONCAT(UPPER(LEFT(name, 1)), LOWER(SUBSTRING(name, 2))) AS fixed_name
FROM Users;分组拼接并去重排序:
代码块收起展开
SELECT sell_date,
GROUP_CONCAT(DISTINCT product ORDER BY product SEPARATOR ',') AS products
FROM Activities
GROUP BY sell_date;12. 正则 REGEXP
代码块收起展开
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|b | a 或 b |
匹配空格分隔的病症代码:
代码块收起展开
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) |
常用日期格式:
代码块收起展开
SELECT DATE_FORMAT(trans_date, '%Y-%m') AS month
FROM Transactions;连续日期比较:
代码块收起展开
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 | 取模 |
代码块收起展开
SELECT ROUND(AVG(score), 2) AS avg_score
FROM Exam;15. UNION
UNION 会去重,UNION ALL 不去重。练习题里需要保留计数时通常用 UNION ALL。
代码块收起展开
SELECT requester_id AS id
FROM RequestAccepted
UNION ALL
SELECT accepter_id AS id
FROM RequestAccepted;固定输出三类:
代码块收起展开
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:
代码块收起展开
DELETE p1
FROM Person p1
JOIN Person p2
ON p1.email = p2.email
AND p1.id > p2.id;更新:
代码块收起展开
UPDATE Employee
SET salary = salary * 1.1
WHERE department_id = 1;17. 常见题型模板
找第二高
代码块收起展开
SELECT (
SELECT DISTINCT salary
FROM Employee
ORDER BY salary DESC
LIMIT 1 OFFSET 1
) AS SecondHighestSalary;每组 Top 3,允许并列
代码块收起展开
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;窗口函数版:
代码块收起展开
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;购买所有商品
代码块收起展开
SELECT customer_id
FROM Customer
GROUP BY customer_id
HAVING COUNT(DISTINCT product_key) = (
SELECT COUNT(*)
FROM Product
);首日次日留存
代码块收起展开
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;区间匹配价格
代码块收起展开
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;连续三行相同
代码块收起展开
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,再 IFNULL 或 COUNT(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. 背诵优先级
SELECT FROM WHERE GROUP BY HAVING ORDER BY LIMITLEFT JOIN、反连接、CROSS JOINCOUNT/SUM/AVG与COUNT(DISTINCT ...)CASE WHEN、IFNULL、条件聚合- 日期函数:
DATEDIFF、DATE_FORMAT、DATE_ADD、DATE_SUB - 字符串函数:
CHAR_LENGTH、CONCAT、SUBSTRING、GROUP_CONCAT - 窗口函数:
ROW_NUMBER、DENSE_RANK、SUM() OVER - 正则:
REGEXP
延伸阅读
日期
代码块收起展开
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) -- 日字符串
代码块收起展开
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) -- 分组拼接空值和条件
代码块收起展开
IFNULL(a, b) -- a 是 NULL 时用 b
COALESCE(a, b, c) -- 返回第一个非 NULL
IF(condition, a, b) -- 二选一
CASE WHEN ... THEN ... END -- 多条件数值与聚合
代码块收起展开
ROUND(x, 2) -- 四舍五入
CEIL(x) -- 向上取整
FLOOR(x) -- 向下取整
ABS(x) -- 绝对值
COUNT(*) -- 行数
COUNT(DISTINCT x) -- 去重计数
SUM(x) -- 求和
AVG(x) -- 平均