返回首页

05|单表查询进阶

适用版本:MySQL 8.0.x

很多人刚学 MySQL 时,最先接触的查询语句通常是:

SELECT * FROM employees;

这当然是开始,但远远不是查询的重点。真正进入业务开发后,你面对的往往不是“把整张表查出来”,而是:

  • 查询最近 7 天注册且状态正常的用户
  • 找出工资高于部门平均值的员工
  • 只返回第 2 页、按更新时间倒序排列的数据
  • 统计某类商品中库存不足且仍在售的记录
  • 根据不同状态输出不同的展示文案

这时,单表查询就不再只是 SELECT + WHERE 这么简单,而是会涉及条件组合、空值处理、排序分页、表达式计算、字符串匹配、条件映射等一整套能力。

这篇文章专门讲清楚 单表查询的进阶写法。目标不是堆砌语法,而是让你真正理解:当只有一张表时,如何把查询写得准确、可读、稳定、便于维护


一、为什么单表查询依然很重要

有些初学者会觉得,多表关联才“高级”,单表查询只是入门内容。其实恰恰相反:

  1. 大多数列表页、详情页、后台筛选页,本质上都从单表查询开始
  2. 复杂查询的很多问题,根源并不在 JOIN,而在基础条件写得不严谨
  3. 单表查询写不好,后面写 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. 逻辑运算:ANDORNOT

业务条件通常不会只有一个,因此逻辑运算必不可少。

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';

经验建议:只要 ANDOR 混用,就主动加括号,不要依赖记忆中的优先级。


五、范围查询:BETWEENIN、时间区间

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. ANDOR 混用不加括号

这是条件查询错误率非常高的一类问题。只要条件稍微复杂,就加括号。

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 前,不妨先在脑子里回答三个问题:

  1. 我要筛掉哪些数据?
  2. 我要按什么规则排序?
  3. 我最终真的需要哪些列?

这会让你的 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 *
  • WHEREANDORINBETWEEN 的正确使用方式
  • NULL 判断、模糊匹配、排序分页的常见写法
  • CASE、别名、表达式、DISTINCT 的实际用途
  • 单表查询里最容易踩的坑,以及更稳妥的实践建议

如果把查询能力比作搭建房子,那么单表查询就是地基。只有把条件过滤、空值处理、排序分页这些能力练扎实,后面学习 JOIN、子查询、聚合统计时,思路才不会乱。


📝 版权声明:本文为原创技术博客,转载请注明出处。

如文章中存在错误或不准确之处,欢迎在评论区指正,感谢您的阅读与支持!

上一篇

04|数据类型与约束设计

下一篇

07|多表查询与 JOIN 详解