MySQL 面试上机语法函数速查 · MySQL
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