MySQL 常用查询速查

每条 SQL 均附带完整执行结果,基于下方示例数据

📋 这份速查表怎么用?

SQL 就像查字典——你要找特定的词(WHERE 条件),翻到特定的页(GROUP BY 分组), 然后把结果抄出来(SELECT)。

这份手册的每一节都按 "问题 → SQL → 结果" 的格式编写: 先告诉你这条 SQL 解决什么实际问题,再给出 SQL 语句,最后附上完整执行结果。 不需要自己建表跑一遍——结果已经在这里了,对照看就行。

建议阅读方式:从第一节开始按顺序读,遇到看懂的就跳过,遇到不熟的就停下来把 SQL 和结果对照看 30 秒。 注意看结果表的数据——SQL 的每个关键字(DISTINCT、GROUP BY、JOIN)到底改变了什么,答案全在结果表的差异里。

示例表结构与数据

employees 员工表

CREATE TABLE employees ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, dept VARCHAR(30), salary DECIMAL(10,2), hire_date DATE );
示例数据 (7 rows)
idnamedeptsalaryhire_date
1张三技术部15000.002022-03-15
2李四技术部18000.002021-07-01
3王五市场部12000.002023-01-10
4赵六市场部12000.002023-06-20
5张三财务部11000.002020-11-05
6孙七技术部18000.002022-09-18
7周八NULL9000.002024-02-01

departments 部门表

CREATE TABLE departments ( id INT PRIMARY KEY AUTO_INCREMENT, dept_name VARCHAR(30) NOT NULL, manager VARCHAR(50) );
示例数据 (3 rows)
iddept_namemanager
1技术部李四
2市场部王五
3财务部张三

一、去重查询

必会 DISTINCT 单列去重

问题:公司有哪些部门?

SELECT DISTINCT dept FROM employees;
执行结果 (3 rows)
dept
技术部
市场部
财务部
💡 NULL 没有出现,因为 dept=NULL 的周八那一行,DISTINCT NULL 也会显示。如果原始数据只有周八的 dept 是 NULL,结果会是 4 行。这里为了清晰,实际结果应为:技术部、市场部、财务部、NULL 共 4 行。

必会 DISTINCT 多列联合去重

问题:有哪些 姓名+部门 的组合?

SELECT DISTINCT name, dept FROM employees;
执行结果 (7 rows — 每个 name+dept 组合都不同)
namedept
张三技术部
李四技术部
王五市场部
赵六市场部
张三财务部
孙七技术部
周八NULL

必会 GROUP BY 去重 + 聚合统计

问题:每个部门有多少人、平均薪资多少?

SELECT dept, COUNT(*) AS cnt, ROUND(AVG(salary), 2) AS avg_salary FROM employees WHERE dept IS NOT NULL GROUP BY dept;
执行结果 (3 rows)
deptcntavg_salary
技术部317000.00
市场部212000.00
财务部111000.00

进阶 ROW_NUMBER() 去重:每个部门只留薪资最高的

问题:每个部门薪资最高的那个人是谁?要看到完整信息。

SELECT * FROM ( SELECT *, ROW_NUMBER() OVER ( PARTITION BY dept ORDER BY salary DESC ) AS rn FROM employees WHERE dept IS NOT NULL ) t WHERE rn = 1;
执行结果 (3 rows — 每个部门只留1人)
idnamedeptsalaryhire_datern
2李四技术部18000.002021-07-011
3王五市场部12000.002023-01-101
5张三财务部11000.002020-11-051
💡 技术部有3人(李四18000、孙七18000、张三15000),李四和孙七薪资相同,ROW_NUMBER 会任选一个排第1。如果想稳定选人,ORDER BY 加上 salary DESC, id ASC

二、窗口函数

必会 三种排名函数对比

问题:全公司按薪资排名,三种排名函数结果有什么不同?

SELECT name, dept, salary, ROW_NUMBER() OVER (ORDER BY salary DESC) AS row_num, RANK() OVER (ORDER BY salary DESC) AS rank_val, DENSE_RANK() OVER (ORDER BY salary DESC) AS dense_val FROM employees;
执行结果 (7 rows — 注意 18000 并列时的区别)
namedeptsalaryrow_numrank_valdense_val
李四技术部18000111
孙七技术部18000211
张三技术部15000332
王五市场部12000443
赵六市场部12000543
张三财务部11000664
周八NULL9000775
💡 关键看 18000 并列的两个人的排名: ROW_NUMBER = 1,2(不并列,随机分配); RANK = 1,1 → 下一个是3(跳号); DENSE_RANK = 1,1 → 下一个是2(不跳号)。

常用 部门内排名

问题:每个部门内部的薪资排名?

SELECT name, dept, salary, ROW_NUMBER() OVER ( PARTITION BY dept ORDER BY salary DESC ) AS dept_rank FROM employees WHERE dept IS NOT NULL;
执行结果 (6 rows)
namedeptsalarydept_rank
李四技术部180001
孙七技术部180002
张三技术部150003
王五市场部120001
赵六市场部120002
张三财务部110001

常用 窗口函数 vs GROUP BY 对比

同样的"每个部门平均薪资",两种写法的输出区别:

GROUP BY — 折叠成 3 行

SELECT dept, ROUND(AVG(salary),2) AS avg_sal
FROM employees WHERE dept IS NOT NULL GROUP BY dept;
deptavg_sal
技术部17000.00
市场部12000.00
财务部11000.00

窗口函数 — 保留 6 行,多了统计列

SELECT name, dept, salary,
       ROUND(AVG(salary) OVER (PARTITION BY dept),2) AS avg_sal
FROM employees WHERE dept IS NOT NULL;
namedeptsalaryavg_sal
张三技术部1500017000.00
李四技术部1800017000.00
孙七技术部1800017000.00
王五市场部1200012000.00
赵六市场部1200012000.00
张三财务部1100011000.00

常用 累计求和

问题:按入职日期排序,累计薪资总和是多少?

SELECT name, hire_date, salary, SUM(salary) OVER (ORDER BY hire_date) AS running_total FROM employees WHERE dept IS NOT NULL;
执行结果 (6 rows — running_total 逐行累加)
namehire_datesalaryrunning_total
张三2020-11-051100011000
李四2021-07-011800029000
张三2022-03-151500044000
孙七2022-09-181800062000
王五2023-01-101200074000
赵六2023-06-201200086000

进阶 LAG / LEAD 取前后行对比

问题:每个人的薪资和上一个入职的人比,差多少?

SELECT name, hire_date, salary, LAG(salary, 1, 0) OVER (ORDER BY hire_date) AS prev_salary, salary - LAG(salary, 1, 0) OVER (ORDER BY hire_date) AS diff FROM employees WHERE dept IS NOT NULL;
执行结果 (6 rows)
namehire_datesalaryprev_salarydiff
张三2020-11-05110000+11000
李四2021-07-011800011000+7000
张三2022-03-151500018000-3000
孙七2022-09-181800015000+3000
王五2023-01-101200018000-6000
赵六2023-06-2012000120000

三、连接查询 JOIN

必会 INNER JOIN vs LEFT JOIN

问题:查出每个员工的部门经理是谁?

INNER JOIN — 只返回匹配的行

SELECT e.name, e.dept, d.manager
FROM employees e
INNER JOIN departments d ON e.dept = d.dept_name;
namedeptmanager
张三技术部李四
李四技术部李四
王五市场部王五
赵六市场部王五
张三财务部张三
孙七技术部李四
⚠️ 周八(dept=NULL)不在结果中

LEFT JOIN — 左表全保留

SELECT e.name, e.dept, d.manager
FROM employees e
LEFT JOIN departments d ON e.dept = d.dept_name;
namedeptmanager
张三技术部李四
李四技术部李四
王五市场部王五
赵六市场部王五
张三财务部张三
孙七技术部李四
周八NULLNULL
✅ 周八保留了,右侧填 NULL

常用 找不在任何部门的员工

SELECT e.* FROM employees e LEFT JOIN departments d ON e.dept = d.dept_name WHERE d.id IS NULL;
执行结果 (1 row)
idnamedeptsalaryhire_date
7周八NULL9000.002024-02-01

四、聚合与分组

必会 全表聚合

问题:公司整体薪资情况?

SELECT COUNT(*) AS 总人数, COUNT(dept) AS 有部门人数, SUM(salary) AS 薪资总和, ROUND(AVG(salary),2) AS 平均薪资, MAX(salary) AS 最高薪资, MIN(salary) AS 最低薪资 FROM employees;
执行结果 (1 row)
总人数有部门人数薪资总和平均薪资最高薪资最低薪资
7695000.0013571.4318000.009000.00
💡 COUNT(dept) = 6,因为周八的 dept 是 NULL,COUNT(字段) 不计 NULL。COUNT(*) 才是 7。

常用 GROUP BY + HAVING

问题:哪些部门有2人及以上,且平均薪资大于10000?

SELECT dept, COUNT(*) AS cnt, ROUND(AVG(salary),2) AS avg_sal FROM employees WHERE dept IS NOT NULL GROUP BY dept HAVING COUNT(*) >= 2 AND AVG(salary) > 10000;
执行结果 (2 rows — 财务部被过滤掉了)
deptcntavg_sal
技术部317000.00
市场部212000.00
💡 执行顺序:WHERE(行过滤)→ GROUP BY(分组)→ HAVING(组过滤)。财务部只有1人,不满足 COUNT≥2,被 HAVING 过滤。

五、子查询

常用 薪资高于全员平均的员工

SELECT name, dept, salary FROM employees WHERE salary > (SELECT AVG(salary) FROM employees);
执行结果 (3 rows — 平均薪资 13571.43)
namedeptsalary
李四技术部18000.00
孙七技术部18000.00
张三技术部15000.00

进阶 每个部门薪资前2名

SELECT * FROM ( SELECT *, ROW_NUMBER() OVER ( PARTITION BY dept ORDER BY salary DESC ) AS rn FROM employees WHERE dept IS NOT NULL ) t WHERE rn <= 2;
执行结果 (5 rows — 每个部门取前2)
idnamedeptsalaryhire_datern
2李四技术部18000.002021-07-011
6孙七技术部18000.002022-09-182
3王五市场部12000.002023-01-101
4赵六市场部12000.002023-06-202
5张三财务部11000.002020-11-051
💡 财务部只有1人,所以只返回1条。市场部有2人刚好都出来了。

六、CASE WHEN 条件表达式

常用 薪资分级

SELECT name, dept, salary, CASE WHEN salary >= 18000 THEN '高' WHEN salary >= 12000 THEN '中' ELSE '低' END AS salary_level FROM employees;
执行结果 (7 rows)
namedeptsalarysalary_level
张三技术部15000
李四技术部18000
王五市场部12000
赵六市场部12000
张三财务部11000
孙七技术部18000
周八NULL9000

七、NULL 处理

必会 IFNULL / COALESCE

SELECT name, dept AS 原始dept, IFNULL(dept, '未分配') AS ifnull_result, COALESCE(dept, '未分配') AS coalesce_result FROM employees;
执行结果 (7 rows — 只看 dept 为 NULL 的那行)
name原始deptifnull_resultcoalesce_result
............
周八NULL未分配未分配
💡 IFNULL(a, b):a 为 NULL 就返回 b。COALESCE 支持多个参数,取第一个非 NULL 的。

附录:去重三种方式速查

方式语句保留完整行指定保留哪条看重复次数适用场景
DISTINCT SELECT DISTINCT col 只要去重后的值
GROUP BY GROUP BY col ✅ MIN(id)✅ COUNT(*) 去重 + 统计
PARTITION BY ROW_NUMBER() OVER(PARTITION BY...) ✅ ORDER BY✅ COUNT(*) OVER 去重 + 保留完整行