CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
SQL 与关系型数据库/课时/WHERE 过滤与排序
RawBlame
课时目录10 节

SQL 与关系型数据库

  1. 01关系型数据库与 SQL 是什么
  2. 02SELECT 基本查询
  3. 03WHERE 过滤与排序
  4. 04JOIN 多表联结
  5. 05聚合与 GROUP BY
  6. 06INSERT、UPDATE、DELETE
  7. 07表设计与主键外键
  8. 08索引与查询性能
  9. 09事务基础
  10. 10综合练习与本站数据模型

WHERE 过滤与排序

条件、LIKE、IN、ORDER BY、LIMIT。

30 分钟

WHERE 过滤与排序

这一课你将能做到

  1. 用比较、逻辑、IN、BETWEEN、LIKE、IS NULL 组合出精确的过滤条件
  2. 用 ORDER BY 做单列/多列、升序/降序排序,并理解 NULL 在排序里的位置
  3. 用 LIMIT + OFFSET 实现分页,并知道深分页为什么会慢

前置:第一课的 sql_practice 库已就绪,本课语句都在这个库里执行。

为什么要学

上一课 SELECT 解决「要哪些列」,这一课 WHERE 解决「要哪些行」。真实业务里几乎没有「查全表」的接口:商品列表按价格区间筛、订单按状态筛、搜索框按关键词模糊匹配——全是 WHERE。排序和分页也是列表接口的标配,写成什么样直接决定查询走不走索引(第 8 课见分晓)。

先建立图像

WHERE 就是一张筛网:每一行被单独拎出来过一遍条件,条件为真(TRUE)的留下,为假(FALSE)的丢掉,算出来「不知道」(UNKNOWN)的也丢掉。

study_record 17 行
      │ WHERE completed = 1
      ▼
  16 行(大鹏那条 completed=0 被筛掉)
      │ ORDER BY score DESC
      ▼
  顺序重排
      │ LIMIT 3
      ▼
  只剩 3 行

关键:NULL 和任何值比较(包括 NULL = NULL)结果都是 UNKNOWN,不是 TRUE——这就是「为什么 = NULL 查不到东西」的根源。

跟着做

步骤 1:单条件比较

SELECT title, price FROM course WHERE price > 100;
-- 3 行:Java 基础入门 199 / Java 面向对象进阶 299 / MySQL 性能优化实战 399

SELECT title, status FROM course WHERE status = 'published';
-- 5 行

运算符:=、!=(等价于标准写法 <>)、>、>=、<、<=。字符串比较默认不区分大小写(取决于排序规则,本库是 utf8mb4_unicode_ci)。

步骤 2:多条件 AND / OR,括号保平安

-- AND:同时满足
SELECT title FROM course WHERE category = 'java' AND status = 'published';
-- 2 行

-- OR 不加括号的经典翻车:
SELECT title, category, level, price
FROM course
WHERE category = 'database' AND level = 'advanced' OR price = 0;
-- 得到 4 行!AND 优先级高于 OR,后半个条件把所有 0 元课都放进来了

-- 加括号才是原意:
SELECT title, category, level, price
FROM course
WHERE category = 'database' AND (level = 'advanced' OR price = 0);
-- 3 行:SQL 与关系型数据库 / MySQL 性能优化实战 / Redis 缓存入门

规则很简单:AND 和 OR 混用,永远加括号,写给自己也写给读代码的人。

步骤 3:IN / NOT IN —— 多值等值判断

SELECT name, city FROM student WHERE city IN ('深圳', '杭州');
-- 3 行:老陈、小林、小夏

SELECT title FROM course WHERE category NOT IN ('java');
-- 5 行

IN 等价于一串 = 用 OR 连接,但更短、更好优化。注意一个深坑:NOT IN (子查询) 时,子查询结果里只要有一个 NULL,整个 NOT IN 永远返回空——本课先记结论,JOIN 课教 NOT EXISTS 替代。

步骤 4:BETWEEN 区间,注意闭区间和日期边界

SELECT title, price FROM course WHERE price BETWEEN 50 AND 200;
-- 2 行:前端页面基础 99 / Java 基础入门 199(50 和 200 都包含在内)

日期区间的经典坑:'2026-06-10' 会被解析成 '2026-06-10 00:00:00',当天上午的记录全部漏掉:

SELECT title, created_at FROM course
WHERE created_at BETWEEN '2026-06-01' AND '2026-06-10';
-- 只有 4 行!course 5 是 06-10 10:00 创建的,被「00:00:00」边界吃掉了

-- 正确的半开区间写法(左闭右开):
SELECT title, created_at FROM course
WHERE created_at >= '2026-06-10 00:00:00'
  AND created_at <  '2026-06-11 00:00:00';

步骤 5:LIKE 模糊匹配

SELECT title FROM course WHERE title LIKE '%Java%';   -- 2 行(包含 Java)
SELECT slug  FROM course WHERE slug  LIKE 'java-%';   -- 2 行(前缀匹配)
SELECT slug  FROM course WHERE slug  LIKE 'java-_';   -- 1 行:java-oop(_ 占 1 个字符)
  • % 匹配任意长度,_ 匹配单个字符
  • LIKE 'java-%' 前缀确定,可以走索引;LIKE '%Java%' 前缀不确定,走不了(第 8 课用 EXPLAIN 验证)

步骤 6:IS NULL —— 判空唯一正确姿势

SELECT id, student_id, lesson_id, completed_at
FROM study_record
WHERE completed_at IS NULL;
-- 1 行:id=12,大鹏学了但没完成的那条

SELECT COUNT(*) FROM study_record WHERE completed_at IS NOT NULL;  -- 16

WHERE completed_at = NULL 永远返回空集,因为 NULL 和任何值的比较都是 UNKNOWN。判空只能用 IS NULL / IS NOT NULL。

步骤 7:ORDER BY 排序

SELECT title, price FROM course
WHERE status = 'published'
ORDER BY price DESC;         -- 降序:399 → 199

-- 多列排序:先按 category 升序,同分类内按 price 降序
SELECT category, title, price FROM course ORDER BY category ASC, price DESC;

-- ASC 是默认值,可省略
SELECT name, balance FROM student ORDER BY balance;   -- 0 → 500

NULL 在排序里的位置(MySQL 规则):升序时 NULL 排最前面,降序时排最后面。想强制控制用 ORDER BY completed_at IS NULL, completed_at(先把 NULL 沉底)。

步骤 8:LIMIT / OFFSET 分页

SELECT id, name FROM student ORDER BY id LIMIT 3;             -- 第 1 页:1,2,3
SELECT id, name FROM student ORDER BY id LIMIT 3 OFFSET 3;    -- 第 2 页:4,5,6
-- LIMIT 3 = LIMIT 3 OFFSET 3 的简写

通用公式:第 n 页、每页 pageSize 条 → LIMIT pageSize OFFSET (n-1)*pageSize。前提是必须先 ORDER BY 一个稳定列(通常是主键或带索引的时间列),否则翻页会出现重复和漏行。

讲清楚要点

  1. 三值逻辑是 WHERE 的地基:TRUE / FALSE / UNKNOWN。只有 TRUE 的行留下;NULL 参与比较产生 UNKNOWN,行被丢弃。所有「查不到明明存在的数据」的问题,先怀疑 NULL。
  2. AND 优先级高于 OR:混用必须加括号,没有例外。
  3. IN 用于多值等值,BETWEEN 用于闭区间;日期比较用半开区间 >= 当天00:00 AND < 次日00:00 最不容易漏。
  4. LIKE 的索引友好度:前缀匹配 LIKE 'x%' 能走索引;中缀/后缀 '%x%'、'%x' 走不了。这决定了搜索功能该不该用 LIKE 实现。
  5. ORDER BY 可以用 SELECT 别名(它跑在 SELECT 之后),多列排序按书写顺序依次生效,每列可独立指定 ASC/DESC。
  6. 分页必须配合稳定排序;OFFSET 越大扫描丢弃越多,深分页是性能杀手——优化思路在第 8 课后你会更有体感,先记住「翻到很后面就慢」。

你可能会懵的地方

  • <> 和 != 用哪个? 等价。<> 是 SQL 标准,跨数据库都能用;!= 是 MySQL 等支持的简写。团队统一一种即可。

  • 为什么 WHERE col != 5 查不出 col 为 NULL 的行? NULL != 5 结果是 UNKNOWN,不是 TRUE。要包含 NULL 行得显式写 (col != 5 OR col IS NULL)。

  • 0 和 NULL 一样吗? 完全不同。本练习库里 completed = 0 表示「开始了没完成」,score = NULL 表示「还没打分」。混为一谈,统计口径就错了。

  • LIMIT 不加 ORDER BY 会怎样? 返回哪三行不确定。看似每次都一样,换执行计划或并发后就漂移。分页永远带 ORDER BY。

  • WHERE 里能写聚合吗(WHERE COUNT(*) > 3)? 不能,聚合发生在分组阶段,WHERE 在它之前。这类条件下一课用 HAVING 写。

错法对照

错法问题正解
WHERE col = NULLUNKNOWN,永远空集col IS NULL
AND / OR 混用不加括号条件组合与预期不符每处混用都加括号
日期 BETWEEN '06-01' AND '06-10'吃掉 10 日全天>= '06-10 00:00:00' AND < '06-11 00:00:00'
NOT IN (子查询) 遇 NULL永远空集NOT EXISTS(JOIN 课展开)
LIKE 中缀匹配当搜索用全表扫描前缀匹配或接入搜索引擎/全文索引
分页不带 ORDER BY翻页重复/漏行先排序再 LIMIT

卡点急救

  1. You have an error in your SQL syntax:检查关键字顺序(WHERE 在 ORDER BY 之前)、字符串是否漏单引号、括号是否配对。
  2. Empty set 但数据明明有:三步排查——值两边有没有不可见空格;是不是 NULL 比较;大小写/类型是否对上(字符串列传了数字会隐式转换)。
  3. 查询巨慢:先 LIMIT 10 验证逻辑对不对,再谈性能;把 EXPLAIN 放在语句最前面看走没走索引(第 8 课详解)。
  4. 排序结果「不稳定」:排序列有重复值(如多个 price 相同)。补一列唯一值做次级排序,如 ORDER BY price DESC, id。

面试常问

  • NULL 在 SQL 里怎么比较?为什么 = NULL 不行? NULL 参与任何比较都返回 UNKNOWN(三值逻辑),只有 IS NULL 能判定。聚合函数(COUNT(col)、AVG)也会忽略 NULL,这是相关高频追问。

  • 分页是怎么实现的?深分页为什么慢? LIMIT n OFFSET m;OFFSET 越大,数据库要扫描并丢弃的行越多。优化方向:只翻有限页、用 WHERE id > 上一页最大id LIMIT n 的游标分页、或覆盖索引 + 延迟关联。

  • WHERE 和 HAVING 的区别? WHERE 过滤行(分组前,不能用聚合),HAVING 过滤组(分组后,可以聚合)。下一课展开。

  • 字符串模糊搜索有什么方案? 前缀匹配走索引;任意位置匹配用全文索引(FULLTEXT)或外置搜索引擎(ES),LIKE '%x%' 在数据量大时不可接受。

自测三问

  1. 写条件:价格在 100~300 之间、或分类是 frontend 的已发布课程。
  2. completed_at 可能为 NULL 的列,怎么统计「已完成且有完成时间」的记录数?
  3. 每页 10 条,第 5 页的 LIMIT/OFFSET 怎么写?

小结一句话

WHERE 用「比较 + 逻辑 + IN/BETWEEN/LIKE/IS NULL」挑行,ORDER BY + LIMIT 负责顺序和分页;NULL 永远用 IS NULL 判,AND/OR 混用永远加括号。

延伸

  • MySQL 比较函数与运算符
  • 下一课:JOIN 多表联结——数据拆在多张表里,怎么一次查回来。
加载本节练习…
← 上一课SELECT 基本查询下一课 →JOIN 多表联结