MySQL不看视频 · SSH 实操

第一单元 · 连接、建表、查询 · 第 3 / 21 节

查询、筛选、排序

选列、WHERE 条件(文本、NULL、LIKE、BETWEEN、IN)、ORDER BY 与 LIMIT;先预测结果再运行,最后做 5 项单元验收。

6 个步骤 · 进度只存本机

约 20 分钟。这一节不改数据,只练怎么从表里把想要的行和列挑出来,并按需要的顺序显示。

SSH / Linux 终端MySQL 终端输出示例(只读)复制按钮只复制命令,不含 $ / mysql> 提示符和输出。

已完成 0 / 6 步

你现在在哪个提示符

应该在 mysql>。如果回到了 PS C:\Users\...>,先 ssh root@8.148.204.152;如果停在 root@iZ7xvfx4kssqec2zzn99q9Z:~#,先 mysql-learning。

先看一眼表里的数据:

MySQL 终端sql
SELECT * FROM lesson01_students ORDER BY id;
输出示例(只读,不要粘贴执行)text不可复制
+----+--------+------+--------+------------+
| 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 行是你上一节练习加的那行,先只删它:

MySQL 终端sql
DELETE FROM lesson01_students WHERE id = 6;

如果显示 Empty set,说明表在、但里面没有行,回第 2 节补那段 INSERT;如果报 1146,说明表还没建,也回第 2 节。

先看全表

上面那张结果就是全表:5 行、5 列。

  • SELECT * 里的 * 表示“所有列”。列多的时候不要图省事,只取你要的。
  • FROM lesson01_students 指定从哪张表取。
  • ORDER BY id 按学号排序。这里必须写,才能保证每次看到的顺序一样;不写,MySQL 返回的顺序没有承诺。

这张表能一眼看完,所以很适合练手。真实业务表可能几万行,下面的写法就是用来缩小范围的。

只取需要的列

MySQL 终端sql
SELECT name, city FROM lesson01_students ORDER BY id;
输出示例(只读,不要粘贴执行)text不可复制
+--------+--------+
| name   | city   |
+--------+--------+
| 张伟   | 北京   |
| 李娜   | 上海   |
| 王强   | 广州   |
| 赵敏   | NULL   |
| 陈晨   | 杭州   |
+--------+--------+
5 rows in set (0.00 sec)

行还是 5 行,列只剩 name 和 city。结果里的列顺序跟你写在 SELECT 后面的顺序一致。列名会直接显示成 name、city,因为没有起别名。

省掉不用的列有两个好处:屏幕清爽,传输的数据也少。初学时先把列写全,等熟练了再考虑 *。

用 WHERE 筛行

MySQL 终端sql
SELECT id, name, age FROM lesson01_students WHERE age >= 23 ORDER BY age DESC, id;
输出示例(只读,不要粘贴执行)text不可复制
+----+--------+------+
| 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 开始,数据库处理时不是这个顺序,这一点本节后面会展开。

四种常见条件

等于某个文本

MySQL 终端sql
SELECT name, city FROM lesson01_students WHERE city = '上海';
输出示例(只读,不要粘贴执行)text不可复制
+--------+--------+
| name   | city   |
+--------+--------+
| 李娜   | 上海   |
+--------+--------+
1 row in set (0.00 sec)

只留下上海的李娜。文本比较用单引号;city = 上海 不加引号会被当成列名,报 1054。

空值

MySQL 终端sql
SELECT id, name, city FROM lesson01_students WHERE city IS NULL;
输出示例(只读,不要粘贴执行)text不可复制
+----+--------+------+
| id | name   | city |
+----+--------+------+
|  4 | 赵敏   | NULL |
+----+--------+------+
1 row in set (0.00 sec)

赵敏的 city 是 NULL,表示没填。判断空值必须用 IS NULL,不能写 city = NULL。NULL 参与普通比较时结果是“未知”,既不算真也不算假,所以 = NULL 永远筛不出行。反过来,非空用 IS NOT NULL。

开头或模糊匹配

MySQL 终端sql
SELECT id, name FROM lesson01_students WHERE name LIKE '王%';
输出示例(只读,不要粘贴执行)text不可复制
+----+--------+
| id | name   |
+----+--------+
|  3 | 王强   |
+----+--------+
1 row in set (0.00 sec)

LIKE '王%' 表示以“王”开头,后面随便什么内容。% 代表任意长度(包括 0 个字符),_ 代表正好一个字符。LIKE '%强' 是以“强”结尾,LIKE '_强' 是两个字且第二个字是“强”。

范围和列表

MySQL 终端sql
SELECT id, name, age FROM lesson01_students WHERE age BETWEEN 22 AND 24 ORDER BY age;
输出示例(只读,不要粘贴执行)text不可复制
+----+--------+------+
| 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。

MySQL 终端sql
SELECT id, name, city FROM lesson01_students WHERE city IN ('北京', '杭州') ORDER BY id;
输出示例(只读,不要粘贴执行)text不可复制
+----+--------+--------+
| id | name   | city   |
+----+--------+--------+
|  1 | 张伟   | 北京   |
|  5 | 陈晨   | 杭州   |
+----+--------+--------+
2 rows in set (0.00 sec)

IN ('北京', '杭州') 是多个“或”的简写,等价于 city = '北京' OR city = '杭州'。列表里的文本同样要加单引号。

排序与取前几行

MySQL 终端sql
SELECT name, age FROM lesson01_students ORDER BY age DESC LIMIT 3;
输出示例(只读,不要粘贴执行)text不可复制
+--------+------+
| 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 排在非空值前面,降序时排在后面。这和其他数据库不一定相同。如果某列可能为空,又在意空值的位置,先查出来看清楚,别假设。

一条查询的书写顺序和处理顺序

你写的顺序是:

输出示例(只读,不要粘贴执行)text不可复制
SELECT 列 FROM 表 WHERE 条件 ORDER BY 列 LIMIT 行数;

数据库的逻辑处理顺序是:

输出示例(只读,不要粘贴执行)text不可复制
FROM → WHERE → SELECT → ORDER BY → LIMIT

所以 WHERE 里不能使用 SELECT 刚起的别名,因为筛行时别名还没产生;ORDER BY 里可以用,因为排序发生在挑列之后。

常见错误

1054 Unknown column

MySQL 终端sql
SELECT id, cityy FROM lesson01_students;
输出示例(只读,不要粘贴执行)text不可复制
ERROR 1054 (42S22): Unknown column 'cityy' in 'field list'

我把 city 打成了 cityy。MySQL 直接告诉你 Unknown column 'cityy'。用 DESC lesson01_students; 对一遍字段名,别靠记忆。

1064 语法错误

MySQL 终端sql
SELEC * FROM lesson01_students;
输出示例(只读,不要粘贴执行)text不可复制
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 表不存在

MySQL 终端sql
SELECT * FROM lesson01_student;
输出示例(只读,不要粘贴执行)text不可复制
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 的人,按年龄升序。

MySQL 终端sql
SELECT name, age FROM lesson01_students WHERE age < 23 ORDER BY age;
参考答案 对答案
输出示例(只读,不要粘贴执行)text不可复制
+--------+------+
| name   | age  |
+--------+------+
| 李娜   |   21 |
| 赵敏   |   22 |
+--------+------+
2 rows in set (0.01 sec)

2 行:李娜 21,赵敏 22。< 23 不包含 23,所以张伟不在里面。

2. 不在北京、且年龄大于等于 23 的人,按 id 排序。

MySQL 终端sql
SELECT name FROM lesson01_students WHERE city <> '北京' AND age >= 23 ORDER BY id;
参考答案 对答案
输出示例(只读,不要粘贴执行)text不可复制
+--------+
| name   |
+--------+
| 王强   |
| 陈晨   |
+--------+
2 rows in set (0.00 sec)

王强和陈晨。张伟在北京被排除;李娜 21 岁不够;赵敏的城市是 NULL,city <> '北京' 对 NULL 的结果是“未知”,所以她也不会出现。想让空城市也算进来,需要写成 city IS NULL OR city <> '北京'。

3. 按加入日期从早到晚,取前 2 个人。

MySQL 终端sql
SELECT name, joined FROM lesson01_students ORDER BY joined LIMIT 2;
参考答案 对答案
输出示例(只读,不要粘贴执行)text不可复制
+--------+------------+
| name   | joined     |
+--------+------------+
| 王强   | 2024-02-20 |
| 张伟   | 2024-03-02 |
+--------+------------+
2 rows in set (0.00 sec)

王强 2024-02-20 最早,张伟 2024-03-02 第二。日期按时间先后比较,不是按字符串长短。

本单元验收

做完下面 5 项,再进入下一单元。

  1. 不看笔记,从 PowerShell 进到 mysql>:ssh root@8.148.204.152 → mysql-learning → 看到 mysql>。
  2. 执行 DESC lesson01_students;,能说出 5 个字段名,并指出 id 是主键、name 不能为空。
  3. 数一下行数:
MySQL 终端sql
SELECT COUNT(*) AS 人数 FROM lesson01_students;
输出示例(只读,不要粘贴执行)text不可复制
+--------+
| 人数   |
+--------+
|      5 |
+--------+
1 row in set (0.00 sec)

应该是 5。如果多了一行,检查是不是上一节练习的第 6 行没删。

  1. 跑一条带筛选和排序的查询:
MySQL 终端sql
SELECT name FROM lesson01_students WHERE age BETWEEN 21 AND 23 ORDER BY age;
输出示例(只读,不要粘贴执行)text不可复制
+--------+
| name   |
+--------+
| 李娜   |
| 赵敏   |
| 张伟   |
+--------+
3 rows in set (0.00 sec)

顺序是李娜、赵敏、张伟,按年龄升序。

  1. 看到 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 是测试机耗时,你的屏幕会略有不同。