第一单元 · 连接、建表、查询 · 第 3 / 21 节
查询、筛选、排序
选列、WHERE 条件(文本、NULL、LIKE、BETWEEN、IN)、ORDER BY 与 LIMIT;先预测结果再运行,最后做 5 项单元验收。
约 20 分钟。这一节不改数据,只练怎么从表里把想要的行和列挑出来,并按需要的顺序显示。
$ / mysql> 提示符和输出。已完成 0 / 6 步
你现在在哪个提示符
应该在 mysql>。如果回到了 PS C:\Users\...>,先 ssh root@8.148.204.152;如果停在 root@iZ7xvfx4kssqec2zzn99q9Z:~#,先 mysql-learning。
先看一眼表里的数据:
SELECT * FROM lesson01_students ORDER BY id;
+----+--------+------+--------+------------+
| id | name | age | city | joined |
+----+--------+------+--------+------------+
| 1 | 张伟 | 23 | 北京 | 2024-03-02 |
| 2 | 李娜 | 21 | 上海 | 2024-03-05 |
| 3 | 王强 | 25 | 广州 | 2024-02-20 |
| 4 | 赵敏 | 22 | NULL | 2024-03-09 |
| 5 | 陈晨 | 24 | 杭州 | 2024-03-12 |
+----+--------+------+--------+------------+
5 rows in set (0.00 sec)
如果显示 5 行,和上面一致,可以直接往下。如果第 6 行是你上一节练习加的那行,先只删它:
DELETE FROM lesson01_students WHERE id = 6;
如果显示 Empty set,说明表在、但里面没有行,回第 2 节补那段 INSERT;如果报 1146,说明表还没建,也回第 2 节。
先看全表
上面那张结果就是全表:5 行、5 列。
SELECT *里的*表示“所有列”。列多的时候不要图省事,只取你要的。FROM lesson01_students指定从哪张表取。ORDER BY id按学号排序。这里必须写,才能保证每次看到的顺序一样;不写,MySQL 返回的顺序没有承诺。
这张表能一眼看完,所以很适合练手。真实业务表可能几万行,下面的写法就是用来缩小范围的。
只取需要的列
SELECT name, city FROM lesson01_students ORDER BY id;
+--------+--------+
| name | city |
+--------+--------+
| 张伟 | 北京 |
| 李娜 | 上海 |
| 王强 | 广州 |
| 赵敏 | NULL |
| 陈晨 | 杭州 |
+--------+--------+
5 rows in set (0.00 sec)
行还是 5 行,列只剩 name 和 city。结果里的列顺序跟你写在 SELECT 后面的顺序一致。列名会直接显示成 name、city,因为没有起别名。
省掉不用的列有两个好处:屏幕清爽,传输的数据也少。初学时先把列写全,等熟练了再考虑 *。
用 WHERE 筛行
SELECT id, name, age FROM lesson01_students WHERE age >= 23 ORDER BY age DESC, id;
+----+--------+------+
| id | name | age |
+----+--------+------+
| 3 | 王强 | 25 |
| 5 | 陈晨 | 24 |
| 1 | 张伟 | 23 |
+----+--------+------+
3 rows in set (0.00 sec)
从 5 行变成 3 行。李娜 21 岁、赵敏 22 岁被筛掉了,>= 包含 23 本身,所以张伟留下了。
WHERE 后面写条件,条件为真的行才进入结果。常用的比较符:
| 写法 | 意思 |
|---|---|
= | 等于 |
<> 或 != | 不等于 |
>、>= | 大于、大于等于 |
<、<= | 小于、小于等于 |
数字直接写,文本要加英文单引号。WHERE age >= 23 里的 23 不用引号;写成 '23' 也能跑,但会让 MySQL 做类型转换,没必要。
一条查询的逻辑顺序是:先 FROM 找到表,再 WHERE 筛行,然后 SELECT 挑列,最后 ORDER BY 排序。你书写时从 SELECT 开始,数据库处理时不是这个顺序,这一点本节后面会展开。
四种常见条件
等于某个文本
SELECT name, city FROM lesson01_students WHERE city = '上海';
+--------+--------+
| name | city |
+--------+--------+
| 李娜 | 上海 |
+--------+--------+
1 row in set (0.00 sec)
只留下上海的李娜。文本比较用单引号;city = 上海 不加引号会被当成列名,报 1054。
空值
SELECT id, name, city FROM lesson01_students WHERE city IS NULL;
+----+--------+------+
| id | name | city |
+----+--------+------+
| 4 | 赵敏 | NULL |
+----+--------+------+
1 row in set (0.00 sec)
赵敏的 city 是 NULL,表示没填。判断空值必须用 IS NULL,不能写 city = NULL。NULL 参与普通比较时结果是“未知”,既不算真也不算假,所以 = NULL 永远筛不出行。反过来,非空用 IS NOT NULL。
开头或模糊匹配
SELECT id, name FROM lesson01_students WHERE name LIKE '王%';
+----+--------+
| id | name |
+----+--------+
| 3 | 王强 |
+----+--------+
1 row in set (0.00 sec)
LIKE '王%' 表示以“王”开头,后面随便什么内容。% 代表任意长度(包括 0 个字符),_ 代表正好一个字符。LIKE '%强' 是以“强”结尾,LIKE '_强' 是两个字且第二个字是“强”。
范围和列表
SELECT id, name, age FROM lesson01_students WHERE age BETWEEN 22 AND 24 ORDER BY age;
+----+--------+------+
| id | name | age |
+----+--------+------+
| 4 | 赵敏 | 22 |
| 1 | 张伟 | 23 |
| 5 | 陈晨 | 24 |
+----+--------+------+
3 rows in set (0.00 sec)
BETWEEN 22 AND 24 包含 22 和 24 两个端点,等价于 age >= 22 AND age <= 24。注意它不包含 NULL。
SELECT id, name, city FROM lesson01_students WHERE city IN ('北京', '杭州') ORDER BY id;
+----+--------+--------+
| id | name | city |
+----+--------+--------+
| 1 | 张伟 | 北京 |
| 5 | 陈晨 | 杭州 |
+----+--------+--------+
2 rows in set (0.00 sec)
IN ('北京', '杭州') 是多个“或”的简写,等价于 city = '北京' OR city = '杭州'。列表里的文本同样要加单引号。
排序与取前几行
SELECT name, age FROM lesson01_students ORDER BY age DESC LIMIT 3;
+--------+------+
| name | age |
+--------+------+
| 王强 | 25 |
| 陈晨 | 24 |
| 张伟 | 23 |
+--------+------+
3 rows in set (0.00 sec)
ORDER BY age DESC 按年龄从大到小排,DESC 是降序;不写 DESC 默认是 ASC 升序。
LIMIT 3 只取排序后的前 3 行。LIMIT 要写在 ORDER BY 后面;如果只写 LIMIT 3 不写 ORDER BY,取到的是哪 3 行没有保证,第一次和第二次可能不同。
多个排序条件用逗号隔开,比如 ORDER BY age DESC, id ASC:先按年龄降序,年龄相同的再按 id 升序。现在这 5 个人的年龄没有重复,所以第二个条件暂时看不出效果,但写上是好习惯。
说明 NULL 在排序里排在哪(可选读)
MySQL 升序排序时把 NULL 排在非空值前面,降序时排在后面。这和其他数据库不一定相同。如果某列可能为空,又在意空值的位置,先查出来看清楚,别假设。
一条查询的书写顺序和处理顺序
你写的顺序是:
SELECT 列 FROM 表 WHERE 条件 ORDER BY 列 LIMIT 行数;
数据库的逻辑处理顺序是:
FROM → WHERE → SELECT → ORDER BY → LIMIT
所以 WHERE 里不能使用 SELECT 刚起的别名,因为筛行时别名还没产生;ORDER BY 里可以用,因为排序发生在挑列之后。
常见错误
1054 Unknown column
SELECT id, cityy FROM lesson01_students;
ERROR 1054 (42S22): Unknown column 'cityy' in 'field list'
我把 city 打成了 cityy。MySQL 直接告诉你 Unknown column 'cityy'。用 DESC lesson01_students; 对一遍字段名,别靠记忆。
1064 语法错误
SELEC * FROM lesson01_students;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'SELEC * FROM lesson01_students' at line 1
SELEC 少了一个 T。1064 的通告会带一个 near '...',指出它读不懂的位置,从那里往前看:关键字拼写、少逗号、少引号、少括号,都会报 1064。
1146 表不存在
SELECT * FROM lesson01_student;
ERROR 1146 (42S02): Table 'itcast.lesson01_student' doesn't exist
表名少了一个 s。报错里的 itcast.lesson01_student 是“库名.表名”,先 SHOW TABLES LIKE 'lesson01%'; 看真实名字。
少了分号
输入到一半回车,提示符变成 ->,补 ; 或输入 \c 取消。和第 1 节一样。
中文引号
city = “上海” 里的引号不是 SQL 的字符串边界,会报 1054 或 1064。把输入法切回英文,或直接复制教程里的单引号。
练习
先写下你预测的结果(几行、哪几个人、什么顺序),再执行,最后展开对答案。
1. 年龄小于 23 的人,按年龄升序。
SELECT name, age FROM lesson01_students WHERE age < 23 ORDER BY age;
参考答案 对答案
+--------+------+
| name | age |
+--------+------+
| 李娜 | 21 |
| 赵敏 | 22 |
+--------+------+
2 rows in set (0.01 sec)
2 行:李娜 21,赵敏 22。< 23 不包含 23,所以张伟不在里面。
2. 不在北京、且年龄大于等于 23 的人,按 id 排序。
SELECT name FROM lesson01_students WHERE city <> '北京' AND age >= 23 ORDER BY id;
参考答案 对答案
+--------+
| name |
+--------+
| 王强 |
| 陈晨 |
+--------+
2 rows in set (0.00 sec)
王强和陈晨。张伟在北京被排除;李娜 21 岁不够;赵敏的城市是 NULL,city <> '北京' 对 NULL 的结果是“未知”,所以她也不会出现。想让空城市也算进来,需要写成 city IS NULL OR city <> '北京'。
3. 按加入日期从早到晚,取前 2 个人。
SELECT name, joined FROM lesson01_students ORDER BY joined LIMIT 2;
参考答案 对答案
+--------+------------+
| name | joined |
+--------+------------+
| 王强 | 2024-02-20 |
| 张伟 | 2024-03-02 |
+--------+------------+
2 rows in set (0.00 sec)
王强 2024-02-20 最早,张伟 2024-03-02 第二。日期按时间先后比较,不是按字符串长短。
本单元验收
做完下面 5 项,再进入下一单元。
- 不看笔记,从 PowerShell 进到
mysql>:ssh root@8.148.204.152→mysql-learning→ 看到mysql>。 - 执行
DESC lesson01_students;,能说出 5 个字段名,并指出id是主键、name不能为空。 - 数一下行数:
SELECT COUNT(*) AS 人数 FROM lesson01_students;
+--------+
| 人数 |
+--------+
| 5 |
+--------+
1 row in set (0.00 sec)
应该是 5。如果多了一行,检查是不是上一节练习的第 6 行没删。
- 跑一条带筛选和排序的查询:
SELECT name FROM lesson01_students WHERE age BETWEEN 21 AND 23 ORDER BY age;
+--------+
| name |
+--------+
| 李娜 |
| 赵敏 |
| 张伟 |
+--------+
3 rows in set (0.00 sec)
顺序是李娜、赵敏、张伟,按年龄升序。
- 看到
1050、1062、1146时,能各用一句话解释,并知道先SELECT或SHOW TABLES看现状,而不是删库。
做到这里,今天可以停
到这里第一单元结束。你现在有一张自己的表,会放数据,会按条件挑行、按列取数、按顺序显示。这三件事是后面查询的基础。
下一单元仍在这张 lesson01_students 上练:UPDATE 改一行、DELETE 删一行、更多数据类型、COUNT 和 AVG 这类简单统计,以及 NULL 在统计里的坑。不会换库,也不会清空你表的现有数据;页面做好后会接在这里。
说明 这些结果是怎么验证的(可选读)
本单元所有 SQL 都在 MySQL 8.0.46(和你服务器同版本)的隔离实例上逐条执行,实例使用私有数据目录和 Unix socket,不连你的阿里云学习库。
验证覆盖首次执行、重复执行、断开客户端后重连,以及 1050、1062、1146、1048、1054、1064、1046 等故意报错场景。页面里的结果表就是那次执行的原始输出,0.00 sec 是测试机耗时,你的屏幕会略有不同。