MySQL 面试上机语法函数速查 · MySQL

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