查询进阶:排序、过滤与分页

在前一篇中,我们掌握了基本的 CRUD 操作,能够对数据进行增、查、改、删。但现实中的数据量往往很大,我们很少会一次性取出整张表的所有行,而是需要按照条件筛选、按特定顺序排列,并只返回一部分结果。这篇博客就来深入 SELECT 查询的三大进阶技能:WHERE 过滤、ORDER BY 排序和 LIMIT 分页。

读完本文你将能:

  • 灵活运用 WHERE 子句进行精确和模糊查询
  • 理解比较、逻辑、范围、模糊匹配等多种过滤方式
  • 掌握 ORDER BY 实现升序、降序及多列排序
  • 使用 LIMITOFFSET 实现分页
  • 理解 SELECT 语句的执行顺序
  • 实战:实现博客文章列表的分页查询

1. 过滤数据:WHERE 子句

WHERE 子句用于指定查询条件,只返回满足条件的行。它紧跟在 FROM 子句之后。

1.1 比较运算符

最常见的是使用比较运算符对列的值进行筛选:

运算符 含义
= 等于
<>!= 不等于
< 小于
<= 小于等于
> 大于
>= 大于等于

示例 (假设有 articles 表,后面实战会创建):

sql 复制代码
-- 查询作者为 'alice' 的所有文章
SELECT * FROM articles WHERE author = 'alice';

-- 查询阅读量大于 1000 的文章
SELECT title, views FROM articles WHERE views > 1000;

-- 查询不是草稿状态的文章(假设 status=0 表示草稿)
SELECT * FROM articles WHERE status <> 0;

对于 NULL 值的判断,不能使用 = NULL ,必须使用 IS NULLIS NOT NULL

sql 复制代码
-- 查询没有填写摘要的文章
SELECT * FROM articles WHERE summary IS NULL;

-- 查询有封面图的文章
SELECT * FROM articles WHERE cover_image IS NOT NULL;

1.2 逻辑运算符:AND、OR、NOT

组合多个条件:

sql 复制代码
-- 查询 'alice' 写的且阅读量大于 500 的文章
SELECT * FROM articles
WHERE author = 'alice' AND views > 500;

-- 查询作者是 'alice' 或 'bob' 的文章
SELECT * FROM articles
WHERE author = 'alice' OR author = 'bob';

-- 查询不是 'alice' 写的文章
SELECT * FROM articles
WHERE NOT author = 'alice';

优先级NOT > AND > OR。建议使用括号明确分组,避免歧义:

sql 复制代码
-- 查询阅读量大于 1000 且作者是 'alice' 或 'bob'
SELECT * FROM articles
WHERE views > 1000 AND (author = 'alice' OR author = 'bob');

1.3 范围查询:BETWEEN 和 IN

  • BETWEEN ... AND ...:匹配某个范围内的值(包含边界)。
  • IN (...):匹配列表中的任意值。
sql 复制代码
-- 查询阅读量在 500 到 1000 之间的文章
SELECT title, views FROM articles
WHERE views BETWEEN 500 AND 1000;

-- 等价于:WHERE views >= 500 AND views <= 1000

-- 查询作者是 'alice'、'bob'、'carol' 之一的文章
SELECT * FROM articles
WHERE author IN ('alice', 'bob', 'carol');

注意:BETWEEN 对于日期类型同样适用。

sql 复制代码
SELECT * FROM articles
WHERE publish_date BETWEEN '2025-01-01' AND '2025-03-31';

1.4 模糊匹配:LIKE

LIKE 用于模式匹配,通常配合通配符:

  • % 匹配任意多个字符(包括0个)
  • _ 匹配单个任意字符
sql 复制代码
-- 查询标题以 'MySQL' 开头的文章
SELECT * FROM articles
WHERE title LIKE 'MySQL%';

-- 查询标题包含 '索引' 的文章
SELECT * FROM articles
WHERE title LIKE '%索引%';

-- 查询作者第二个字母是 'l' 的文章(如 alice, blake)
SELECT * FROM articles
WHERE author LIKE '_l%';

LIKE 对大小写的敏感性取决于列的字符集校对规则。默认的 utf8mb4_general_cici 表示 case insensitive)是不区分大小写的。如果需要区分,可使用 BINARY 关键字或修改列的校对规则为 utf8mb4_bin

性能提醒LIKE '%xxx%' 这种前缀通配会导致全表扫描,在数据量大时应谨慎使用,可以考虑全文索引(FULLTEXT)。

1.5 正则表达式:REGEXP

除了 LIKE,MySQL 还支持正则表达式匹配,功能更强大:

sql 复制代码
-- 查询标题以数字结尾的文章
SELECT * FROM articles WHERE title REGEXP '[0-9]$';
-- 查询作者名包含 'a' 或 'e' 的文章
SELECT * FROM articles WHERE author REGEXP 'a|e';

正则匹配默认不区分大小写,如需区分可使用 REGEXP BINARY


2. 排序数据:ORDER BY

查询结果默认顺序是不确定的(通常按照插入顺序或主键顺序,但这不能保证)。使用 ORDER BY 可以指定排序规则。

2.1 基本排序

sql 复制代码
SELECT title, views FROM articles ORDER BY views;          -- 升序(默认)
SELECT title, views FROM articles ORDER BY views ASC;      -- 明确升序
SELECT title, views FROM articles ORDER BY views DESC;     -- 降序

2.2 多列排序

可以指定多个排序条件,优先级从左到右:

sql 复制代码
-- 先按作者升序排列,同一作者的按阅读量降序排列
SELECT author, title, views
FROM articles
ORDER BY author ASC, views DESC;

2.3 与 WHERE 结合

ORDER BY 应放在 WHERE 之后:

sql 复制代码
SELECT * FROM articles
WHERE status = 1
ORDER BY publish_date DESC;

3. 分页查询:LIMIT 与 OFFSET

当结果集很大时,我们需要分页展示。MySQL 使用 LIMIT 子句限制返回的行数。

3.1 LIMIT 基本用法

sql 复制代码
-- 返回前 10 篇文章
SELECT * FROM articles LIMIT 10;

3.2 带偏移量的分页

语法:LIMIT 偏移量, 行数LIMIT 行数 OFFSET 偏移量

sql 复制代码
-- 返回第 11 到 20 条(即第二页,每页 10 条)
SELECT * FROM articles LIMIT 10, 10;
-- 等价写法:
SELECT * FROM articles LIMIT 10 OFFSET 10;

偏移量基于 0,即 OFFSET 0 代表第一行。

3.3 分页性能注意

LIMIT 结合大 OFFSET 时,MySQL 实际上会扫描并丢弃前面的所有行,可能导致性能问题。常见的优化方式包括使用覆盖索引、记录上一页的最大 ID 进行条件查询等,这些我们将在索引优化部分详细讨论。


4. SELECT 语句的执行顺序

理解 SELECT 各子句的执行顺序,可以帮助你写出正确的 SQL,避免"别名在 WHERE 中不可用"等错误。

一个完整的 SELECT 语句如下(省略了一些暂未学到的子句):

sql 复制代码
SELECT [DISTINCT] 列1, 列2, ...
FROM 表名
[WHERE 条件]
[GROUP BY 列]
[HAVING 分组过滤]
[ORDER BY 列 [ASC|DESC]]
[LIMIT [偏移,] 行数];

逻辑执行顺序(非物理读取顺序):

  1. FROM:确定数据来源
  2. WHERE:过滤行
  3. GROUP BY:分组
  4. HAVING:过滤分组
  5. SELECT:选择列(包括别名在此阶段生效)
  6. ORDER BY:排序(可以使用 SELECT 中的别名)
  7. LIMIT:限制返回行数

因此,你不能在 WHERE 子句中使用 SELECT 中定义的别名,因为 WHERE 执行时别名尚未被定义。但可以在 ORDER BY 中使用别名,因为 ORDER BYSELECT 之后执行。


5. 实战:实现博客文章列表的分页查询

我们模拟一个博客系统的文章表,并用之前学到的过滤、排序和分页来查询。

5.1 创建文章表并插入测试数据

sql 复制代码
CREATE TABLE articles (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(200) NOT NULL,
    author VARCHAR(50) NOT NULL,
    views INT UNSIGNED DEFAULT 0,
    status TINYINT UNSIGNED DEFAULT 1 COMMENT '1:发布 0:草稿',
    publish_date DATETIME DEFAULT CURRENT_TIMESTAMP,
    summary TEXT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

INSERT INTO articles (title, author, views, status, publish_date, summary) VALUES
('MySQL 安装指南', 'alice', 1520, 1, '2025-03-01 10:00:00', 'Windows 与 Linux 安装详解'),
('数据库与表的诞生', 'alice', 980, 1, '2025-03-02 12:30:00', '创建数据库及数据类型'),
('CRUD 入门', 'bob', 2100, 1, '2025-03-03 09:15:00', '数据的增删改查'),
('查询进阶', 'bob', 850, 1, '2025-03-04 14:00:00', '排序、过滤与分页'),
('索引初探', 'carol', 3200, 1, '2025-03-05 16:45:00', '让查询快起来'),
('事务入门', 'dave', 670, 1, '2025-03-06 08:20:00', '数据一致性的保证'),
('JDBC 编程', 'alice', 430, 1, '2025-03-07 11:10:00', 'Java 连接 MySQL'),
('连接池技术', 'carol', 590, 1, '2025-03-08 13:55:00', '提升应用性能'),
('MySQL 架构解析', 'dave', 2800, 1, '2025-03-09 17:30:00', '架构与存储引擎'),
('InnoDB 物理结构', 'dave', 1800, 1, '2025-03-10 10:05:00', '表空间与页'),
('锁机制详解', 'bob', 1500, 1, '2025-03-11 07:50:00', '表锁与行锁'),
('MVCC 原理', 'carol', 2200, 1, '2025-03-12 20:00:00', '无锁读的艺术');

5.2 查询需求与 SQL

需求 1:查询"alice"写的所有已发布文章,按阅读量降序

sql 复制代码
SELECT title, views, publish_date
FROM articles
WHERE author = 'alice' AND status = 1
ORDER BY views DESC;

需求 2:搜索标题包含"MySQL"的文章,按发布日期升序,只显示前5条

sql 复制代码
SELECT title, author, publish_date
FROM articles
WHERE title LIKE '%MySQL%'
ORDER BY publish_date ASC
LIMIT 5;

需求 3:实现文章列表分页,每页 5 条,请求第 2 页

sql 复制代码
SELECT title, author, views, publish_date
FROM articles
WHERE status = 1
ORDER BY publish_date DESC
LIMIT 5 OFFSET 5;   -- 第2页:跳过前5条,取后5条

需求 4:查询阅读量在 1000 到 2000 之间且作者不是 'bob' 的文章

sql 复制代码
SELECT title, author, views
FROM articles
WHERE views BETWEEN 1000 AND 2000
  AND author <> 'bob';

5.3 扩展练习

  1. 查询作者名以 c 开头的所有文章,并按标题降序排列。
  2. 查询发布日期在 2025-03-05 之后的文章,按阅读量升序,取前 3 条。
  3. 将之前我们创建的用户表 users 拿出来,自己设计几个查询条件试试。

6. 小结

本文深入介绍了 SELECT 查询的三大利器:

  • WHERE:通过比较、逻辑、范围、模糊匹配筛选行,使用 IS NULL 处理空值。
  • ORDER BY:对结果集进行单列或多列排序。
  • LIMIT:限制返回行数,结合 OFFSET 实现分页。

我们还了解了 SELECT 各子句的逻辑执行顺序,这对于编写复杂查询非常重要。最后,通过创建文章表并完成多个查询,实际感受了过滤、排序和分页的组合使用。

下一篇将是第一阶段的项目实战篇,我们将综合前四篇文章的知识,搭建一个本地图书管理系统的数据库,动手设计表、插入数据并编写查询。敬请期待!

思考题

  1. 如果查询条件 WHERE author = 'alice' AND status = 1 中,author 列有索引而 status 列没有,索引能起作用吗?(提示:思考索引的最左匹配原则,先留个悬念)
  2. LIMIT 100000, 20 为什么可能会很慢?你有什么优化思路?
  3. 尝试用 EXPLAIN 查看上述查询的执行计划(EXPLAIN SELECT ...),看看 typerows 等字段有什么不同。

参考资料


相关推荐
2301_781571421 小时前
c++怎么在写入文本文件时自动将所有的制表符统一转换为四格空格【实战】
jvm·数据库·python
iuvtsrt1 小时前
如何在 Go 中为权威 DNS 服务器实现持久化 DNS 记录存储
jvm·数据库·python
2301_812539671 小时前
Redis怎样在Spring中执行批量Pipeline指令
jvm·数据库·python
lifewange1 小时前
HBase 增删改查(CRUD)完整操作指南
数据库·python·hbase
woxihuan1234561 小时前
Redis怎样定位每秒被高频访问的热点键
jvm·数据库·python
sleepcattt1 小时前
Spring-全面详解(学习总结)
数据库·sql·spring·spring事务·spring详解
Volunteer Technology1 小时前
Spring AI MCP案例
java·开发语言·数据库
神明9311 小时前
CSS 背景图滑动切换:纯 CSS 实现右进左出轮播效果
jvm·数据库·python
星栈1 小时前
投影挂了怎么办?我的 CQRS 三层容错方案
数据库·后端·开源