适用版本:MySQL 8.0.x
很多人刚学 MySQL 时,最先接触的查询语句通常是:
SELECT * FROM employees;
这当然是开始,但远远不是查询的重点。真正进入业务开发后,你面对的往往不是“把整张表查出来”,而是:
- 查询最近 7 天注册且状态正常的用户
- 找出工资高于部门平均值的员工
- 只返回第 2 页、按更新时间倒序排列的数据
- 统计某类商品中库存不足且仍在售的记录
- 根据不同状态输出不同的展示文案
这时,单表查询就不再只是 SELECT + WHERE 这么简单,而是会涉及条件组合、空值处理、排序分页、表达式计算、字符串匹配、条件映射等一整套能力。
这篇文章专门讲清楚 单表查询的进阶写法。目标不是堆砌语法,而是让你真正理解:当只有一张表时,如何把查询写得准确、可读、稳定、便于维护。
一、为什么单表查询依然很重要
有些初学者会觉得,多表关联才“高级”,单表查询只是入门内容。其实恰恰相反:
- 大多数列表页、详情页、后台筛选页,本质上都从单表查询开始。
- 复杂查询的很多问题,根源并不在 JOIN,而在基础条件写得不严谨。
- 单表查询写不好,后面写 JOIN、子查询、聚合时更容易混乱。
你可以把单表查询理解为 SQL 查询能力的“地基”。
常见业务里,下面这些需求都属于单表查询范畴:
| 场景 | 典型需求 |
|---|---|
| 后台列表页 | 按状态、时间、关键字筛选并分页 |
| 用户中心 | 查询当前用户最近登录记录 |
| 商品管理 | 查询库存小于阈值且上架中的商品 |
| 风控系统 | 查询命中某类规则的订单 |
| 数据校验 | 查询字段为空、格式异常、取值越界的数据 |
也就是说,只要你还在和一张表打交道,单表查询就始终是高频技能。
二、示例表结构
为了让后续 SQL 更容易理解,先约定一张员工表示例:
CREATE TABLE employees (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
emp_no VARCHAR(20) NOT NULL,
name VARCHAR(50) NOT NULL,
gender CHAR(1) NOT NULL,
department_id BIGINT NOT NULL,
job_title VARCHAR(100) NOT NULL,
salary DECIMAL(10,2) NOT NULL,
bonus DECIMAL(10,2) DEFAULT NULL,
hire_date DATE NOT NULL,
status VARCHAR(20) NOT NULL,
email VARCHAR(100) DEFAULT NULL,
performance_score INT DEFAULT NULL,
updated_at DATETIME NOT NULL
);
后面的例子大多围绕这张表展开。实际业务表结构不一样没有关系,关键是把查询思路学会。
三、先别急着 SELECT *
初学时最容易养成的习惯,就是无论什么场景都先写:
SELECT * FROM employees;
这在练习阶段没问题,但在真实项目里通常不推荐。
1. 为什么不建议滥用 SELECT *
主要有几个原因:
- 返回了不需要的列,增加网络传输和解析成本
- 字段变更不透明,表新增字段后,程序端可能被动收到额外数据
- 不利于阅读,别人很难一眼看出你到底依赖哪些字段
- 某些覆盖索引场景下,会影响查询性能
更好的写法是明确列名:
SELECT id, emp_no, name, job_title, salary
FROM employees;
2. 什么时候 SELECT * 还能接受
也不是说永远不能用,而是要分场景:
- 临时调试 SQL
- 本地学习练习
- 明确就是要返回整行全部数据,且表字段较稳定
但在正式业务代码、存储过程、报表 SQL 中,优先建议显式写出需要的列。
四、WHERE 条件过滤:进阶查询的核心
如果说 SELECT 决定“查什么列”,那么 WHERE 决定的就是“查哪些行”。
1. 常见比较运算符
| 运算符 | 含义 | 示例 |
|---|---|---|
= |
等于 | status = 'ACTIVE' |
<> 或 != |
不等于 | status <> 'LEFT' |
> |
大于 | salary > 20000 |
>= |
大于等于 | hire_date >= '2025-01-01' |
< |
小于 | performance_score < 60 |
<= |
小于等于 | salary <= 10000 |
示例:查询在职且工资大于 15000 的员工。
SELECT id, name, salary, status
FROM employees
WHERE status = 'ACTIVE'
AND salary > 15000;
2. 逻辑运算:AND、OR、NOT
业务条件通常不会只有一个,因此逻辑运算必不可少。
SELECT id, name, department_id, salary
FROM employees
WHERE department_id = 10
AND salary >= 12000
AND status = 'ACTIVE';
如果要查“技术部或产品部”的员工:
SELECT id, name, department_id
FROM employees
WHERE department_id = 10
OR department_id = 20;
更推荐写成 IN,语义更清晰:
SELECT id, name, department_id
FROM employees
WHERE department_id IN (10, 20);
3. 一定要注意括号优先级
下面这条 SQL 很容易写错:
SELECT id, name, department_id, status
FROM employees
WHERE department_id = 10
OR department_id = 20
AND status = 'ACTIVE';
由于 AND 的优先级高于 OR,它实际等价于:
WHERE department_id = 10
OR (department_id = 20 AND status = 'ACTIVE')
如果你的真实意图是“部门为 10 或 20,且员工状态为在职”,就必须加括号:
SELECT id, name, department_id, status
FROM employees
WHERE (department_id = 10 OR department_id = 20)
AND status = 'ACTIVE';
经验建议:只要
AND和OR混用,就主动加括号,不要依赖记忆中的优先级。
五、范围查询:BETWEEN、IN、时间区间
1. BETWEEN ... AND ...
适合连续区间。
SELECT id, name, salary
FROM employees
WHERE salary BETWEEN 10000 AND 20000;
注意:BETWEEN 是闭区间,也就是包含 10000 和 20000。
2. IN (...)
适合有限枚举值查询。
SELECT id, name, status
FROM employees
WHERE status IN ('ACTIVE', 'PROBATION', 'ON_LEAVE');
3. 时间范围建议写成“左闭右开”
很多人查询某天的数据时会写:
WHERE updated_at BETWEEN '2026-06-01 00:00:00' AND '2026-06-01 23:59:59'
这不是不能用,但更稳妥的写法通常是左闭右开:
SELECT id, name, updated_at
FROM employees
WHERE updated_at >= '2026-06-01 00:00:00'
AND updated_at < '2026-06-02 00:00:00';
这样做的好处:
- 边界更清晰
- 避免毫秒、微秒精度导致遗漏
- 更适合按天、按月、按小时做切片查询
六、空值判断:NULL 是单表查询里的高频坑
1. NULL 不能用 = 比较
下面的写法是错误思路:
SELECT id, name, bonus
FROM employees
WHERE bonus = NULL;
正确写法是:
SELECT id, name, bonus
FROM employees
WHERE bonus IS NULL;
查询奖金不为空:
SELECT id, name, bonus
FROM employees
WHERE bonus IS NOT NULL;
2. 为什么 NULL 特别容易踩坑
因为 NULL 在 SQL 里表示“未知”,不是普通值。它和任何值比较,结果通常都不是 TRUE。
3. 常见场景示例
查询没有填写邮箱的员工:
SELECT id, name, email
FROM employees
WHERE email IS NULL;
查询有绩效分且绩效低于 60 的员工:
SELECT id, name, performance_score
FROM employees
WHERE performance_score IS NOT NULL
AND performance_score < 60;
如果不先处理 NULL,你可能误以为“低于 60 分的都查出来了”,其实空值行根本不会参与正常比较。
七、模糊匹配:LIKE、通配符与使用边界
1. LIKE 的基本用法
| 写法 | 含义 |
|---|---|
LIKE '张%' |
以“张”开头 |
LIKE '%经理' |
以“经理”结尾 |
LIKE '%产品%' |
包含“产品” |
LIKE 'A_1' |
_ 表示单个字符 |
示例:
SELECT id, name, job_title
FROM employees
WHERE job_title LIKE '%工程师%';
2. 前导 % 可能影响索引利用
像下面这种写法:
WHERE name LIKE '%明%'
在很多场景下不利于走普通索引,因为前缀不确定。相对来说,以下写法更容易利用前缀匹配能力:
WHERE name LIKE '杨%'
这并不是说不能用 %关键字%,而是要知道:
- 它适合中小数据量或低频查询
- 数据量大时,可能要考虑全文索引或搜索引擎方案
3. 正则匹配
MySQL 8.0 可以使用正则表达式函数,例如:
SELECT id, name, email
FROM employees
WHERE REGEXP_LIKE(email, '^[A-Za-z0-9._%+-]+@example\\.com$');
这种写法表达力更强,但通常也更重,不适合滥用在高并发核心链路上。
八、结果排序:ORDER BY 决定结果是否稳定
很多人以为查询结果天然有顺序,其实不是。如果你不写 ORDER BY,数据库没有义务按你想象中的顺序返回。
1. 基本排序
按工资从高到低:
SELECT id, name, salary
FROM employees
ORDER BY salary DESC;
按部门升序、工资降序:
SELECT id, name, department_id, salary
FROM employees
ORDER BY department_id ASC, salary DESC;
2. 分页场景必须关注“排序稳定性”
下面这种分页 SQL 很常见:
SELECT id, name, salary
FROM employees
ORDER BY salary DESC
LIMIT 10 OFFSET 20;
问题在于:如果很多员工工资相同,那么跨页时结果可能不稳定。更稳妥的写法是补一个唯一键作为次排序条件:
SELECT id, name, salary
FROM employees
ORDER BY salary DESC, id DESC
LIMIT 10 OFFSET 20;
3. 不要把“主键顺序”误认为天然排序
即使今天你发现结果“看起来总是按 id 排”,也不能依赖这个现象。只有显式写了 ORDER BY,顺序才是可定义的。
九、分页查询:LIMIT 很简单,但并不等于随便用
1. 基本写法
SELECT id, name, status
FROM employees
ORDER BY id DESC
LIMIT 10;
获取第 3 页、每页 10 条:
SELECT id, name, status
FROM employees
ORDER BY id DESC
LIMIT 10 OFFSET 20;
2. 大偏移量分页的问题
如果写成:
LIMIT 20 OFFSET 100000
数据库通常仍然需要先扫描并跳过前面很多行,代价会逐渐升高。
3. 更适合高性能翻页的思路:基于游标或主键翻页
例如按主键倒序翻页:
SELECT id, name, status
FROM employees
WHERE id < 50000
ORDER BY id DESC
LIMIT 10;
这种方式也常被叫做“游标分页”或“基于上次位置翻页”,在滚动加载、超大表列表查询中更常见。
十、列别名、表达式与查询结果整理
很多时候,我们并不只是把原始字段拿出来,而是希望在查询阶段顺手把结果整理得更适合展示或计算。
1. 使用列别名
SELECT id,
name AS employee_name,
salary AS monthly_salary
FROM employees;
2. 在查询中做简单计算
SELECT id,
name,
salary,
IFNULL(bonus, 0) AS bonus_amount,
salary * 12 + IFNULL(bonus, 0) AS annual_package
FROM employees;
3. 用 CASE 做条件映射
这是单表查询里非常实用的一类技巧。
SELECT id,
name,
status,
CASE status
WHEN 'ACTIVE' THEN '在职'
WHEN 'PROBATION' THEN '试用期'
WHEN 'ON_LEAVE' THEN '休假中'
WHEN 'LEFT' THEN '已离职'
ELSE '未知状态'
END AS status_text
FROM employees;
4. 条件打标示例
SELECT id,
name,
salary,
CASE
WHEN salary >= 30000 THEN '高薪'
WHEN salary >= 15000 THEN '中高薪'
ELSE '常规'
END AS salary_level
FROM employees;
这类写法在报表、导出、后台运营分析里非常常见。
十一、去重查询:DISTINCT 什么时候该用,什么时候别乱用
查询有哪些部门:
SELECT DISTINCT department_id
FROM employees;
查询有哪些职位:
SELECT DISTINCT job_title
FROM employees;
DISTINCT 的使用建议
- 适合:明确要“唯一值列表”的场景
- 不适合:把它当成“查重问题的万能补丁”
有些人发现结果重复,就先加个 DISTINCT,这往往掩盖了真正的问题。虽然单表场景下重复数据可能来自原始脏数据,但在更复杂查询中,盲目去重常常是在掩盖 JOIN 或建模问题。
十二、常见坑:单表查询最容易出错的几个地方
1. NULL 用 = 判断
错误示例:
WHERE email = NULL
正确写法:
WHERE email IS NULL
2. AND、OR 混用不加括号
这是条件查询错误率非常高的一类问题。只要条件稍微复杂,就加括号。
3. 时间边界写得不严谨
比如查某天数据时使用 23:59:59 作为结束值,在有更高精度时间字段时,可能遗漏记录。建议优先采用“左闭右开”。
4. 分页没有明确排序
没有 ORDER BY 的分页,本质上是不稳定分页。
5. 隐式类型转换
例如把数字列拿去和字符串比较:
WHERE salary = '15000'
MySQL 通常会尝试自动转换,但这会让 SQL 可读性下降,也可能引发索引利用和结果理解问题。能写成数值就不要写成字符串。
6. 滥用函数包裹列
比如:
WHERE DATE(updated_at) = '2026-06-01'
这种写法看起来简洁,但在很多场景下不利于索引利用。更推荐改写成范围判断:
WHERE updated_at >= '2026-06-01 00:00:00'
AND updated_at < '2026-06-02 00:00:00'
十三、实践建议:把单表查询写得更稳一点
1. 先明确“筛选条件、排序条件、返回字段”
写 SQL 前,不妨先在脑子里回答三个问题:
- 我要筛掉哪些数据?
- 我要按什么规则排序?
- 我最终真的需要哪些列?
这会让你的 SQL 明显更整洁。
2. 复杂条件主动换行
例如:
SELECT id, name, department_id, salary, status
FROM employees
WHERE status = 'ACTIVE'
AND department_id IN (10, 20, 30)
AND hire_date >= '2025-01-01'
AND salary >= 12000
ORDER BY salary DESC, id DESC
LIMIT 20;
换行后的 SQL 更容易检查,也更方便团队协作。
3. 优先在 SQL 层完成简单数据整理
像别名、空值填补、状态映射、简单计算,完全可以在 SQL 层做好,不要把所有整理逻辑都堆到应用代码里。
4. 养成“先小范围验证再扩大条件”的习惯
特别是删除前确认、导出前确认、批量处理前确认时,先用 SELECT 把条件验证清楚,是很重要的习惯。
十四、小结
单表查询看起来基础,但真正写好并不简单。这篇文章重点覆盖了以下内容:
- 为什么单表查询是 SQL 能力的基础
- 为什么正式场景下不建议滥用
SELECT * WHERE、AND、OR、IN、BETWEEN的正确使用方式NULL判断、模糊匹配、排序分页的常见写法CASE、别名、表达式、DISTINCT的实际用途- 单表查询里最容易踩的坑,以及更稳妥的实践建议
如果把查询能力比作搭建房子,那么单表查询就是地基。只有把条件过滤、空值处理、排序分页这些能力练扎实,后面学习 JOIN、子查询、聚合统计时,思路才不会乱。
📝 版权声明:本文为原创技术博客,转载请注明出处。
如文章中存在错误或不准确之处,欢迎在评论区指正,感谢您的阅读与支持!