RSS
菜单
全部文章快讯开发科技深度热点

第 4 章 SELECT基础与WHERE过滤

内容摘要

如果说 DML 是"写",那 `SELECT` 就是数据库里出现频率最高的"读"。本篇带你从最基础的"查所有列"开始,逐步掌握列选择、列别名、`DISTINCT` 去重,以及 `WHERE` 子句的完整用法——包括比较运算符、逻辑组合、`IN`/`BETWEEN`/`LIKE` 三大过滤利器。这一篇是后面所有复杂查询(排序、聚合、多表连接、子查询)的地基。本示例基于 MySQL 8.0,所有 SQL 均按该版本语法书写。

📖 本篇简介:如果说 DML 是"写",那 SELECT 就是数据库里出现频率最高的"读"。本篇带你从最基础的"查所有列"开始,逐步掌握列选择、列别名、DISTINCT 去重,以及 WHERE 子句的完整用法——包括比较运算符、逻辑组合、IN/BETWEEN/LIKE 三大过滤利器。这一篇是后面所有复杂查询(排序、聚合、多表连接、子查询)的地基。本示例基于 MySQL 8.0,所有 SQL 均按该版本语法书写。

目录

  • 1 SELECT 概述与语法结构
  • 2 列选择与列别名
  • 3 DISTINCT 去重
  • 4 WHERE 过滤基础与比较运算符
  • 5 逻辑运算符 AND/OR/NOT
  • 6 IN / BETWEEN / LIKE 三大过滤
  • 7 NULL 值的特殊处理
  • 8 SELECT 常用速查表
  • 9 常见坑与注意事项
  • 10 练习题

1 SELECT 概述与语法结构

SELECT 是 SQL 里最灵活、写法最多的语句。它的完整语法树大致是这样:

-- SELECT 完整语法骨架(8.0 支持 CTE、窗口函数,本篇先不讲)
SELECT [ALL | DISTINCT] 列列表
FROM   表
[WHERE    行过滤条件]
[GROUP BY 分组]
[HAVING   分组后过滤]
[ORDER BY 排序]
[LIMIT    分页];

本篇只覆盖 列选择 + WHERE 过滤 这两块,其他子句在后续章节展开。

最小可用的 SELECT:

-- 查 students 表的所有列、所有行
SELECT * FROM students;

* 代表"所有列"。生产环境禁止用 `*`,原因有三:

1. 表加了新列,应用代码拿到的字段就变多了,可能崩溃;

2. 网络传输浪费(比如一张表有 TEXT 大字段,你根本不需要);

3. 优化器无法使用覆盖索引(后面索引章节会讲)。

正确写法是显式列出需要的列:

-- 只查需要的列
SELECT id, name, city, score FROM students;

2 列选择与列别名

2.1 字面量与表达式

SELECT 后面不只是表的列,还可以是字面量、表达式、函数调用:

-- 查学生姓名,同时查一个固定字符串、一个计算字段
SELECT
    name,
    '在读'        AS status,        -- 字面量字符串
    age * 12      AS months,        -- 表达式:年龄换算成月数
    NOW()         AS query_time     -- 函数调用
FROM students;

这种"在 SELECT 列表里做计算"的能力,是报表查询的基础。

2.2 列别名 AS

AS 用来给列起一个好读的名字。下面三种写法等价:

-- 写法 1:显式 AS(推荐,最清晰)
SELECT name AS 姓名, score AS 分数 FROM students;

-- 写法 2:省略 AS
SELECT name 姓名, score 分数 FROM students;

-- 写法 3:别名含空格/中文特殊字符时,必须用反引号或双引号包起来
SELECT name `学生姓名`, score `考试分数` FROM students;

别名一旦定义,就不能在 WHERE 里用,只能在 ORDER BY、HAVING 里用。原因是 SQL 执行顺序:

FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT

WHERE 在 SELECT 之前执行,此时别名还不存在。后面会反复用到这个执行顺序。

2.3 完全独立于表的 SELECT

MySQL 允许 SELECT 后面不写 FROM,用来做"纯计算":

-- 不查表,直接算
SELECT 1 + 1 AS sum;             -- 2
SELECT NOW() AS current_time;     -- 当前时间
SELECT VERSION() AS mysql_version;  -- 数据库版本

这种写法在调试、快速验证函数行为时非常方便。

3 DISTINCT 去重

3.1 基本用法

DISTINCT 紧跟在 SELECT 后面,对整行组合去重:

-- 查 students 表中出现过哪些城市(重复城市只显示一次)
SELECT DISTINCT city FROM students;

如果不加 DISTINCT,每个学生的城市都会出现一次,北京有 100 个学生就出现 100 次。加了之后北京只出现一次。

3.2 多列组合去重

DISTINCT 是对"后面所有列的组合"去重,不是对每一列单独去重:

-- 查"城市 + 性别"的所有组合,组合相同的只留一行
SELECT DISTINCT city, gender FROM students;

比如北京有男有女,会出现 (北京,M)、(北京,F) 两行。不能理解成"北京出现一次、M 出现一次"。

3.3 DISTINCT 与 COUNT

配合 COUNT() 可以统计"去重后的行数":

-- 统计有多少个不同的城市
SELECT COUNT(DISTINCT city) AS city_count FROM students;

注意 COUNT(*) 是数行数,COUNT(DISTINCT city) 是数 city 不重复的个数。

4 WHERE 过滤基础与比较运算符

WHERE 决定"哪些行进入结果集"。它放在 FROM 之后、GROUP BY 之前。

4.1 比较运算符

| 运算符 | 含义 | 例子 |

|---|---|---|

| = | 等于 | score = 90 |

| <> 或 != | 不等于 | age <> 18 |

| > / < | 大于 / 小于 | score > 60 |

| >= / <= | 大于等于 / 小于等于 | age >= 20 |

-- 查成绩大于 90 分的学生
SELECT name, score FROM students WHERE score > 90;

-- 查不是北京的学生
SELECT name, city FROM students WHERE city <> '北京';

字符串比较默认是不区分大小写的(取决于列的 collation,MySQL 默认 utf8mb4_general_ci,_ci = case insensitive)。如果你要区分大小写,用 BINARY 关键字:

-- 区分大小写比较
SELECT * FROM students WHERE BINARY city = 'Beijing';

4.2 算术与表达式

WHERE 里也可以写表达式:

-- 查年龄大于 20 岁(也就是出生年份早于 2005 年)的学生
SELECT name, age FROM students WHERE age > 20;

-- 查"成绩 * 10"大于 800 的(等价于成绩大于 80)
SELECT name, score FROM students WHERE score * 10 > 800;

不过这种"对列做运算"的写法通常用不到索引(后面索引章节会讲),尽量把运算放到右边常量上。

5 逻辑运算符 AND/OR/NOT

5.1 三种逻辑运算符

| 运算符 | 含义 |

|---|---|

| AND | 两边都为真才为真 |

| OR | 任意一边为真就为真 |

| NOT | 取反 |

-- 查"北京的、成绩大于 80 的男学生"
SELECT name, city, score
FROM students
WHERE city = '北京' AND score > 80 AND gender = 'M';

5.2 AND 与 OR 的优先级

AND 优先级高于 OR。下面两种写法结果完全不同:

-- 写法 A:(北京 AND 成绩>80) OR (上海)
-- 意思是:北京的高分学生,或者上海的所有学生
SELECT * FROM students
WHERE city = '北京' AND score > 80 OR city = '上海';

-- 写法 B:北京 AND (成绩>80 OR 上海)
-- 意思是:北京的、并且(成绩>80 或 城市是上海)
-- 因为 city 不可能同时是北京和上海,所以结果等价于"北京的高分学生"
SELECT * FROM students
WHERE city = '北京' AND (score > 80 OR city = '上海');

实战建议:当条件同时混着 AND 和 OR,一律用括号包起来,别依赖优先级记忆。

5.3 IN 是 OR 的简写

-- 等价写法 A:OR 串联
SELECT * FROM students WHERE city = '北京' OR city = '上海' OR city = '广州';

-- 等价写法 B:IN(推荐,更短更清晰)
SELECT * FROM students WHERE city IN ('北京', '上海', '广州');

6 IN / BETWEEN / LIKE 三大过滤

6.1 IN:集合成员判断

IN 后面跟一个列表,值在列表里就为真:

-- 查来自北上广的学生
SELECT name, city FROM students
WHERE city IN ('北京', '上海', '广州');

-- 取反:不在列表里
SELECT name, city FROM students
WHERE city NOT IN ('北京', '上海', '广州');

IN 也可以接子查询(后面第 8 章会讲),但本章先记住"后面接常量列表"的用法。

6.2 BETWEEN:区间过滤

BETWEEN ... AND ... 是闭区间,包含两端:

-- 查成绩在 60 到 90 之间的学生(含 60 和 90)
SELECT name, score FROM students
WHERE score BETWEEN 60 AND 90;

-- 等价于
WHERE score >= 60 AND score <= 90;

日期也能用:

-- 查 2025 年 9 月入学的学生
SELECT name, enroll_date FROM students
WHERE enroll_date BETWEEN '2025-09-01' AND '2025-09-30';
注意:BETWEEN 用于日期时,'2025-09-30' 实际是 2025-09-30 00:00:00,如果你列里存的是 datetime,9 月 30 号白天的数据会被漏掉。建议改成 >= '2025-09-01' AND < '2025-10-01'。

6.3 LIKE:模糊匹配

LIKE 配合两个通配符:

| 通配符 | 含义 | 例子 |

|---|---|---|

| % | 任意长度(含 0)的任意字符 | '张%' 姓张的 |

| _ | 恰好一个任意字符 | '张_' 姓张且名字 2 个字的 |

-- 姓张的学生
SELECT name FROM students WHERE name LIKE '张%';

-- 名字里包含"三"的
SELECT name FROM students WHERE name LIKE '%三%';

-- 名字恰好 2 个字、姓张的
SELECT name FROM students WHERE name LIKE '张_';

-- 取反:名字不以张开头
SELECT name FROM students WHERE name NOT LIKE '张%';

性能提醒:以 % 开头的 LIKE(比如 '%三%')用不到索引,因为不知道从哪开始。大表上这种查询会全表扫描,生产环境要警惕。后面全文索引、搜索引擎章节会讲怎么优化。

7 NULL 值的特殊处理

NULL 是 SQL 里最容易踩坑的概念。它代表"未知"或"不存在",不等于空字符串、不等于 0、不等于任何值。

7.1 判断 NULL 必须用 IS NULL

-- 错误:= NULL 永远返回 NULL(不是真),查不出任何行
SELECT * FROM students WHERE gender = NULL;

-- 正确:用 IS NULL
SELECT * FROM students WHERE gender IS NULL;

-- 取反:IS NOT NULL
SELECT * FROM students WHERE gender IS NOT NULL;

7.2 NULL 参与运算的结果

任何值与 NULL 做算术、比较,结果都是 NULL:

-- age 是 NULL 时,age + 1 还是 NULL
SELECT NULL + 1;        -- NULL
SELECT NULL = NULL;     -- NULL(不是 TRUE!)
SELECT NULL <> NULL;    -- NULL

这就是为什么 = NULL 查不到东西——它返回 NULL,被 WHERE 当成"不通过"。

7.3 NULL 与 IN/NOT IN 的坑

-- 如果子查询里有 NULL,NOT IN 会返回空结果集(经典坑)
SELECT * FROM students
WHERE city NOT IN (SELECT city FROM other_table);
-- 如果 other_table.city 有一行是 NULL,整个 NOT IN 结果是空

后面子查询章节会专门讲这个坑,先记住:NOT IN 遇到 NULL 全灭。

7.4 用 IFNULL / COALESCE 处理 NULL

-- IFNULL:如果第一个参数是 NULL,返回第二个
SELECT name, IFNULL(gender, '未知') AS gender_display FROM students;

-- COALESCE:返回第一个非 NULL 的参数(更通用)
SELECT name, COALESCE(gender, city, '未知') AS info FROM students;

8 SELECT 常用速查表

| 需求 | 推荐写法 | 备注 |

|---|---|---|

| 查所有列 | SELECT * FROM t; | 生产禁用 |

| 查指定列 | SELECT c1,c2 FROM t; | 推荐 |

| 列起别名 | SELECT c AS 别名 FROM t; | 别名只在 ORDER BY/HAVING 可用 |

| 去重 | SELECT DISTINCT c FROM t; | 对整行组合去重 |

| 去重计数 | SELECT COUNT(DISTINCT c) FROM t; | 统计不重复值个数 |

| 等值过滤 | WHERE c = v | = 不是 == |

| 不等过滤 | WHERE c <> v | 也支持 != |

| 集合过滤 | WHERE c IN (v1,v2) | 等价多个 OR |

| 区间过滤 | WHERE c BETWEEN a AND b | 闭区间 |

| 前缀匹配 | WHERE c LIKE '张%' | 能用到索引 |

| 包含匹配 | WHERE c LIKE '%三%' | 全表扫描 |

| 判断空 | WHERE c IS NULL | 不能 = NULL |

| 空值替换 | IFNULL(c, 默认值) | 或 COALESCE |

9 常见坑与注意事项

坑 1:生产环境用 `SELECT *`。

表加列后应用崩溃、网络浪费、索引用不上。务必显式列名。

坑 2:WHERE 里用列别名。

执行顺序上 WHERE 早于 SELECT,别名还不存在,会报"Unknown column"。

坑 3:AND/OR 混写不加括号。

优先级问题导致结果与预期不符。一律加括号。

坑 4:`= NULL` 查不到数据。

必须用 IS NULL。

坑 5:LIKE 以 `%` 开头导致全表扫描。

大表慎用,考虑全文索引或搜索引擎。

坑 6:BETWEEN 用于 datetime 时漏掉当天数据。

BETWEEN '2025-09-01' AND '2025-09-30' 实际只到 30 号零点,改成 < '2025-10-01'。

坑 7:NOT IN 子查询包含 NULL 时结果为空。

经典坑,后面子查询章节会展开。

坑 8:字符串比较意外区分大小写。

默认 _ci 不区分;要区分时用 BINARY。

坑 9:DISTINCT 作用于多列组合,不是单列去重。

SELECT DISTINCT a,b 是对 (a,b) 组合去重。

10 练习题

练习 1:基础列选择

题目:查询 students 表中所有学生的姓名、城市、成绩,列名显示为"姓名""城市""分数"。

<details>

<summary>参考答案</summary>

SELECT name AS 姓名, city AS 城市, score AS 分数
FROM students;

</details>

练习 2:多条件组合

题目:查询"北京或上海的、成绩大于 80 分的女学生"。

<details>

<summary>参考答案</summary>

SELECT name, city, score
FROM students
WHERE (city = '北京' OR city = '上海')
  AND score > 80
  AND gender = 'F';

</details>

练习 3:IN 与 BETWEEN 改写

题目:把 WHERE city='北京' OR city='上海' OR city='广州' 改成 IN 写法;把 WHERE score >= 60 AND score <= 90 改成 BETWEEN 写法。

<details>

<summary>参考答案</summary>

-- IN 改写
WHERE city IN ('北京', '上海', '广州');

-- BETWEEN 改写
WHERE score BETWEEN 60 AND 90;

</details>

练习 4:模糊查询

题目:查询姓名包含"张"字、且城市不是"北京"的学生。

<details>

<summary>参考答案</summary>

SELECT name, city
FROM students
WHERE name LIKE '%张%'
  AND city <> '北京';

</details>

练习 5:NULL 处理

题目:查询性别字段还没填(NULL)的学生,并把查询结果中的 gender 显示为"未填写"。

<details>

<summary>参考答案</summary>

SELECT name, IFNULL(gender, '未填写') AS gender_display
FROM students
WHERE gender IS NULL;

</details>

练习 6:去重计数

题目:统计 students 表里一共有多少个不同的城市。

<details>

<summary>参考答案</summary>

SELECT COUNT(DISTINCT city) AS city_count
FROM students;

</details>


下一篇预告:第 5 章 排序与分页

— 全文完 —回到顶部 ↑
下载推广海报

文章推广海报

《第 4 章 SELECT基础与WHERE过滤》完整推广海报
DISCUSSION

文章回复

0 条公开回复
未登录回复需要审核后公开
还没有回复,欢迎参与讨论。