WHERE 条件与排序分页
WHERE 是 SQL 的灵魂——它决定查询返回哪些行。一个 SELECT 加上合适的 WHERE,能从百万行数据里精准捞出你要的那几行。配合 ORDER BY 排序、LIMIT 分页,基本能应付 80% 的查询场景。
1. 比较运算符
WHERE 最基础的用法是比较运算:=、<>、<、>、<=、>=。
-- 比较运算符:=、<> (或 !=)、<、>、<=、>=
SELECT * FROM students WHERE age = 20; -- 等于
SELECT * FROM students WHERE age <> 20; -- 不等于(标准写法)
SELECT * FROM students WHERE age != 20; -- 不等于(MySQL 容忍,非标准)
SELECT * FROM students WHERE age > 20; -- 大于
SELECT * FROM students WHERE age >= 20; -- 大于等于
-- 注意:SQL 标准的"不等于"是 <>,不是 !=
-- 字符串比较按字典序(注意大小写和编码)2. 逻辑运算符 AND / OR / NOT
多个条件用 AND、OR、NOT 组合。混用时务必加括号——AND 优先级高于 OR,不写括号含义会变:
-- 逻辑运算符:AND、OR、NOT
SELECT * FROM students
WHERE age >= 18 AND age <= 25; -- 年龄在 18-25 之间(两个都满足)
SELECT * FROM students
WHERE city = '北京' OR city = '上海'; -- 北京或上海(任一满足)
SELECT * FROM students
WHERE NOT age < 18; -- 年龄不小于 18
-- ⚠️ 优先级:AND 比 OR 高,混用要用括号
SELECT * FROM students
WHERE (city = '北京' OR city = '上海') AND age >= 18;
-- 不加括号会被解析成:city='北京' OR (city='上海' AND age>=18)
-- 含义完全变了!3. BETWEEN 范围查询
查"在某个范围内"的值,用 BETWEEN ... AND ...,比写两个 >= AND <= 简洁:
-- BETWEEN ... AND ...:范围查询(包含两端,闭区间)
SELECT * FROM students WHERE age BETWEEN 18 AND 25;
-- 等价于 age >= 18 AND age <= 25
-- 日期范围(MySQL 标准写法)
SELECT * FROM orders
WHERE created_at BETWEEN '2024-01-01 00:00:00' AND '2024-12-31 23:59:59';
-- NOT BETWEEN:不在范围内
SELECT * FROM students WHERE age NOT BETWEEN 18 AND 25;
-- 注意:BETWEEN 的边界是【闭区间】,两端都包含
-- 时间范围查询时,结束时间要写到 23:59:59 或用 < 下一天关键点:BETWEEN 是闭区间(两端都包含);日期范围查询的结束时间要么写到 23:59:59,要么改用 created_at < '2025-01-01'(下一天开区间)。
4. IN 集合查询
判断一个值是否在指定集合里,用 IN。等价于多个 OR,但更简洁:
-- IN:在指定集合里(任意一个匹配就返回)
SELECT * FROM students
WHERE name IN ('小明', '小红', '小刚');
SELECT * FROM students
WHERE class_id IN (1, 2, 3, 5);
-- 等价于多个 OR,但 IN 更简洁、可读性好
-- name = '小明' OR name = '小红' OR name = '小刚'
-- NOT IN:不在集合里
SELECT * FROM students WHERE class_id NOT IN (1, 2);
-- ⚠️ NOT IN 的坑:如果列表里有 NULL,结果会出乎意料
SELECT * FROM students WHERE class_id NOT IN (1, 2, NULL);
-- 这条查询【不会返回任何行】!
-- 因为 class_id <> NULL 的结果是 NULL(未知),不是 true
-- 解决:先过滤掉 NULL,或用 NOT EXISTS注意 NOT IN 的坑:集合里有 NULL 时结果会异常。因为 x <> NULL 的结果是 NULL(不是 true),整条查询会一行都不返回。生产环境遇到这种情况,要么先过滤 NULL,要么改用 NOT EXISTS。
5. LIKE 模糊匹配
字符串模糊匹配用 LIKE 配合两个通配符:%(任意长度)和 _(单个字符)。
-- LIKE:模糊匹配(配合通配符)
-- % 匹配任意长度(包括 0)的字符
-- _ 匹配【单个】字符
SELECT * FROM students WHERE email LIKE '%@example.com'; -- 以 @example.com 结尾
SELECT * FROM students WHERE name LIKE '小%'; -- 以"小"开头
SELECT * FROM students WHERE name LIKE '%明%'; -- 含"明"字
SELECT * FROM students WHERE name LIKE '小_'; -- "小" + 任意一个字符(两字)
-- NOT LIKE:不匹配
SELECT * FROM students WHERE name NOT LIKE '小%';
-- ⚠️ LIKE 的性能陷阱
-- 前缀匹配 '小%' 【可能】用上索引(取决于数据库)
-- 但 '%明%' 一定用不上索引,会全表扫描!
-- 大数据量场景,考虑全文索引(FULLTEXT INDEX)或 Elasticsearch性能警告:'%关键词%' 这种前后都带 % 的查询一定用不上索引,会全表扫描。百万级表上这种查询会很慢。解决方案:用全文索引(FULLTEXT INDEX)、Elasticsearch、或者前端用搜索建议(自动补全)减少查询次数。
6. NULL 的特殊处理
NULL 是 SQL 里最容易踩坑的概念。它不是 0、不是空字符串,而是"未知 / 不存在"。判 NULL 必须用 IS NULL,不能用 = NULL:
-- NULL 是 SQL 里的特殊值,表示"未知 / 不存在 / 没填"
-- ⚠️ 判 NULL 不能用 = 或 !=,必须用 IS NULL / IS NOT NULL
-- ❌ 错误:永远查不出 NULL 行!
SELECT * FROM students WHERE email = NULL;
-- ✅ 正确
SELECT * FROM students WHERE email IS NULL;
SELECT * FROM students WHERE email IS NOT NULL;
-- NULL 的运算结果还是 NULL(任何数和 NULL 运算都"传染")
SELECT 1 + NULL; -- 结果:NULL
SELECT NULL = NULL; -- 结果:NULL(不是 true!)
-- 实战:用 COALESCE 把 NULL 替换成默认值
SELECT name, COALESCE(nickname, name, '匿名') AS display_name FROM users;
-- nickname 不为 NULL 就用 nickname,否则用 name,再否则用"匿名"记住一条:任何数和 NULL 运算结果都是 NULL。1 + NULL = NULL,NULL = NULL 的结果也是 NULL(不是 true)。这也是为什么 WHERE x <> NULL 查不出任何行。
7. ORDER BY 排序
默认情况下 SELECT 返回的行顺序是不可预测的(取决于数据库内部存储)。要稳定顺序,必须用 ORDER BY:
-- ORDER BY:排序
-- ASC 升序(默认,从小到大)
-- DESC 降序(从大到小)
SELECT name, age FROM students ORDER BY age; -- 默认升序
SELECT name, age FROM students ORDER BY age ASC; -- 显式升序
SELECT name, age FROM students ORDER BY age DESC; -- 降序
-- 多列排序:先按第一列,再按第二列(只在第一列相同时生效)
SELECT name, class_id, age FROM students
ORDER BY class_id ASC, age DESC;
-- 先按班级升序,班级相同的再按年龄降序
-- 用列别名或列位置排序
SELECT name, age * 2 AS double_age FROM students ORDER BY double_age DESC;
SELECT name, age FROM students ORDER BY 2 DESC; -- 2 表示第 2 列(age)
-- NULL 在排序里的位置(各库不同)
-- MySQL:NULL 视为最小值,ASC 时排最前,DESC 时排最后
-- PostgreSQL:NULL 视为最大值,ASC 时排最后
-- 用 NULLS FIRST / NULLS LAST 显式控制(PostgreSQL/Oracle)多列排序的规则是先按第一列,再按第二列(只在第一列相同时第二列才生效)。NULL 在不同数据库排序位置不同,正式生产代码要显式处理。
8. LIMIT 与分页
查询返回太多行会撑爆内存和带宽。LIMIT 用来限制返回行数,配合 OFFSET 实现分页:
-- LIMIT:限制返回的行数(MySQL/PostgreSQL/SQLite)
SELECT * FROM students ORDER BY age DESC LIMIT 3; -- 只取前 3 行
SELECT * FROM students ORDER BY age DESC LIMIT 10; -- Top 10
-- 分页:LIMIT 每页大小 OFFSET 跳过的行数
-- 第 1 页:每页 10 条
SELECT * FROM students ORDER BY id LIMIT 10 OFFSET 0;
-- 第 2 页
SELECT * FROM students ORDER BY id LIMIT 10 OFFSET 10;
-- 第 3 页
SELECT * FROM students ORDER BY id LIMIT 10 OFFSET 20;
-- MySQL 简写:LIMIT offset, count
SELECT * FROM students ORDER BY id LIMIT 20, 10;
-- SQL Server / Oracle 语法不同
-- SQL Server:SELECT TOP 10 ... 或 OFFSET FETCH
-- Oracle:WHERE ROWNUM <= 10 或 FETCH FIRST 10 ROWS ONLY
-- ⚠️ 深分页性能问题:OFFSET 1000000 LIMIT 10 会扫 1000010 行
-- 生产环境常改用 WHERE id > last_id ORDER BY id LIMIT 10 游标分页深分页问题:OFFSET 越大,数据库要跳过的行越多,查询越慢。OFFSET 1000000 LIMIT 10 实际要扫描 1000010 行。生产环境常改用游标分页(WHERE id > 上次最大id ORDER BY id LIMIT 10),性能恒定。
SELECT 语句的完整执行顺序
SQL 写起来是 SELECT ... FROM ... WHERE ... ORDER BY ... LIMIT,但数据库内部执行顺序是另一回事:
- FROM 先确定从哪张表(包括 JOIN)。
- WHERE 过滤行。
- GROUP BY 分组(下一篇详讲)。
- HAVING 过滤组。
- SELECT 选择列、计算表达式。
- ORDER BY 排序。
- LIMIT 截取。
理解这个顺序能解释很多疑惑——比如为什么 WHERE 里不能用 SELECT 里定义的别名(因为执行时还没算到 SELECT)。
小结
WHERE + ORDER BY + LIMIT 是查询的"三件套",熟练后你能解决 80% 的查询需求。下一篇我们看 SQL 最强大的特性之一:JOIN 多表连接。
← 上一篇 DML 数据操作
下一篇 JOIN 连接 →