第二单元 · 精准修改、安全删除、基础汇总 · 第 4 / 21 节
先查一遍,再改一行
建第二单元自己的表 lesson02_students(6 行),用同一段 WHERE 先 SELECT 再 UPDATE,读懂 Rows matched 与 Changed 的区别,并处理列名写错(1054)。
约 18 分钟。这一节建一张属于第二单元的表,然后用 UPDATE 只改一行。重点是先把条件写对、先看清楚会碰到谁,再敲回车。
$ / mysql> 提示符和输出。已完成 0 / 7 步
你现在在哪个提示符
应该在 mysql>。如果 SSH 断了,先 ssh root@8.148.204.152,再 mysql-learning,最后确认自己在 itcast:
USE itcast;
Database changed
USE 只切换当前连接用哪个库,不动数据。如果 SELECT DATABASE(); 已经显示 itcast,这句可以跳过。
为什么新建一张表,而不是改第一单元那张
第一单元的 lesson01_students 是你自己建的,行数和年龄都可能被你改过。本节要改数据,如果直接在它上面练,“应该看到什么”就对不上任何固定答案。
所以第二单元用一张新表 lesson02_students:字段和第一单元一样,行换成另一组 6 个人。第一单元那张表这一单元一个字节都不碰,想回去看原样随时可以。
先确认这张新表还没建:
SHOW TABLES LIKE 'lesson02_students';
Empty set (0.00 sec)
Empty set 说明没有同名表,可以建。如果这里显示了一行,说明你之前建过,跳到本节的「重复学习怎么办」。
顺便看一眼第一单元那张表在不在(只读,不改它):
SELECT COUNT(*) AS 人数 FROM lesson01_students;
ERROR 1146 (42S02): Table 'itcast.lesson01_students' doesn't exist
这条报错不是要你修。它只说明一件事:本节之后的所有步骤都不依赖第一单元那张表,没做过第一单元也能从这一节开始。
建表和 6 行数据
CREATE TABLE lesson02_students (
id INT PRIMARY KEY COMMENT '学号',
name VARCHAR(20) NOT NULL COMMENT '姓名',
age TINYINT UNSIGNED COMMENT '年龄',
city VARCHAR(20) COMMENT '所在城市',
joined DATE COMMENT '加入日期'
) COMMENT='本教程第二单元练习表,与视频示例无关';
Query OK, 0 rows affected (0.03 sec)
字段名和第一单元完全一样,DESC 的结果也应该一样:
DESC lesson02_students;
+--------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+------------------+------+-----+---------+-------+
| id | int | NO | PRI | NULL | |
| name | varchar(20) | NO | | NULL | |
| age | tinyint unsigned | YES | | NULL | |
| city | varchar(20) | YES | | NULL | |
| joined | date | YES | | NULL | |
+--------+------------------+------+-----+---------+-------+
5 rows in set (0.00 sec)
然后放进 6 行:
INSERT INTO lesson02_students (id, name, age, city, joined) VALUES
(1, '张伟', 23, '北京', '2024-03-02'),
(2, '李娜', 21, '上海', '2024-03-05'),
(3, '王强', 25, '广州', '2024-02-20'),
(4, '赵敏', 22, '深圳', '2024-03-09'),
(5, '陈晨', 24, '杭州', '2024-03-12'),
(6, '周舟', 26, '北京', '2024-02-28');
Query OK, 6 rows affected (0.01 sec)
Records: 6 Duplicates: 0 Warnings: 0
SELECT id, name, age, city, joined FROM lesson02_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 | 深圳 | 2024-03-09 |
| 5 | 陈晨 | 24 | 杭州 | 2024-03-12 |
| 6 | 周舟 | 26 | 北京 | 2024-02-28 |
+----+--------+------+--------+------------+
6 rows in set (0.00 sec)
这 6 行里有两处是特意安排的:id = 1 和 id = 6 都在北京,而上海只有李娜一个人。后面讲“到底改了几行”,这两处就是活教材。
改一个字之前,先把 WHERE 抄给 SELECT
UPDATE 的写法是 UPDATE 表 SET 列 = 新值 WHERE 条件。条件写错,改的就不是一行。所以这一单元的习惯是:**同一段 WHERE,先让 SELECT 跑一遍**。
SELECT id, name, age FROM lesson02_students WHERE city = '上海';
+----+--------+------+
| id | name | age |
+----+--------+------+
| 2 | 李娜 | 21 |
+----+--------+------+
1 row in set (0.00 sec)
只有李娜一行。这就说明,如果把 SELECT id, name, age 换成 UPDATE ... SET age = 24,被改的只会是她。
不写 WHERE 会怎样:UPDATE 会把它当成“这张表的每一行”。而就算写了 WHERE,条件太宽也会一次改掉好几个人。下面这条把“上海人”当成条件,如果表里有三个上海人,三个人都会被改成 24:
注意 为什么这里只给警告、不给能直接复制的命令
“忘记写 WHERE”和“WHERE 太宽”是这个操作最危险的两种写法,一条能把整张表的某一列全改掉,改完还没有回收站。教程不提供可以直接复制执行的这类语句——粘贴时少看一眼,代价就是整列数据。
自己要看后果,可以这样推:把 WHERE city = '上海' 换成 WHERE city = '北京',命令结构和上面一样,Rows matched 就会是 2。先跑 SELECT ... WHERE city = '北京',你能看到会命中的就是张伟和周舟两行。
第一次精准改一行
目标是让 id = 1 的张伟变成 30 岁。先 SELECT:
SELECT id, name, age FROM lesson02_students WHERE id = 1;
+----+--------+------+
| id | name | age |
+----+--------+------+
| 1 | 张伟 | 23 |
+----+--------+------+
1 row in set (0.00 sec)
再改:
UPDATE lesson02_students SET age = 30 WHERE id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
UPDATE 在 mysql> 里会回三行字,每一行在回答不同的问题:
Query OK, 1 row affected:语句执行成功,影响了 1 行。Rows matched: 1:WHERE 条件选中了几行。Changed: 1:其中值真正变了、确实写进去的有几行。
再查一次:
SELECT id, name, age FROM lesson02_students WHERE id = 1;
+----+--------+------+
| id | name | age |
+----+--------+------+
| 1 | 张伟 | 30 |
+----+--------+------+
1 row in set (0.00 sec)
30 进去了。前后两条 SELECT 的列一样,所以两张小表可以直接对照着看。
matched 和 changed 不是一回事
先看“一行都没选中”:
UPDATE lesson02_students SET age = 31 WHERE id = 99;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0 Changed: 0 Warnings: 0
表里没有 id = 99,所以什么都没发生。**Query OK 不等于改成功了**。看到 matched: 0,先回头核对 id,而不是继续往下。
再看“选中了,但值本来就一样”:
UPDATE lesson02_students SET city = '上海' WHERE id = 2;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 1 Changed: 0 Warnings: 0
李娜本来就在上海,把她的城市设成“上海”等于没改。matched: 1 说明人找到了,Changed: 0 说明没有必要写入。这就是它和上一条 matched: 0 的区别:一个是没找到,一个是找到了但没变化。
这也解释了一个常见疑惑:同一条 UPDATE 跑第二次,经常看到 Changed: 0。不是坏了,是值已经对了。
Changed 还可能比 matched 小:WHERE 选中了几行,其中一部分本来就已经等于新值,那几行不算改动。下一节开头的 WHERE city = '北京' 就会遇到这种情况——命中的行和真正改动的行不是一回事。
写错列名会怎样
UPDATE lesson02_students SET age = 24 WHERE citye = '北京';
ERROR 1054 (42S22): Unknown column 'citye' in 'where clause'
citye 是故意打错的列名。1054 表示找不到这个列,整条语句没有执行,一行没动。遇到它别靠记忆猜,直接看结构:
DESC lesson02_students;
+--------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+------------------+------+-----+---------+-------+
| id | int | NO | PRI | NULL | |
| name | varchar(20) | NO | | NULL | |
| age | tinyint unsigned | YES | | NULL | |
| city | varchar(20) | YES | | NULL | |
| joined | date | YES | | NULL | |
+--------+------------------+------+-----+---------+-------+
5 rows in set (0.00 sec)
是 city,少了一个 e。列名以 DESC 为准,不以记忆为准。
一次改两行:先看清,再动手
现在把北京两个人的年龄都改成 23。先看会命中谁:
SELECT id, name, age, city, joined FROM lesson02_students ORDER BY id;
+----+--------+------+--------+------------+
| id | name | age | city | joined |
+----+--------+------+--------+------------+
| 1 | 张伟 | 30 | 北京 | 2024-03-02 |
| 2 | 李娜 | 21 | 上海 | 2024-03-05 |
| 3 | 王强 | 25 | 广州 | 2024-02-20 |
| 4 | 赵敏 | 22 | 深圳 | 2024-03-09 |
| 5 | 陈晨 | 24 | 杭州 | 2024-03-12 |
| 6 | 周舟 | 26 | 北京 | 2024-02-28 |
+----+--------+------+--------+------------+
6 rows in set (0.00 sec)
北京的年龄是 30 和 26。把这一列设成 23,两行都需要改:
UPDATE lesson02_students SET age = 23 WHERE city = '北京';
Query OK, 2 rows affected (0.01 sec)
Rows matched: 2 Changed: 2 Warnings: 0
SELECT id, name, age, city, joined FROM lesson02_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 | 深圳 | 2024-03-09 |
| 5 | 陈晨 | 24 | 杭州 | 2024-03-12 |
| 6 | 周舟 | 23 | 北京 | 2024-02-28 |
+----+--------+------+--------+------------+
6 rows in set (0.00 sec)
两张表对照:age 从 30 和 26 变成 23 和 23,name、city、joined 一个字没动。这就是“改了一列、命中两行”应该长成的样子。如果这里某个人的 joined 也变了,说明 SET 后面写错了列。
本节结束时,表必须是这样:6 行,id = 1 和 id = 6 的 age 都是 23,其余 4 行分别是 21、25、22、24。下一节从这张表删掉 id = 6 那一行。
重复学习怎么办
说明 断了、重进、想重做:三种情况分开处理
一、只是断了,想接着学。 重新 ssh → mysql-learning,然后:
SELECT id, name, age FROM lesson02_students ORDER BY id;
只看两处:行数是 6 吗?id = 1 和 id = 6 的 age 都是 23 吗?都对,本节就做完了,直接去第 5 节。如果 id = 1 还是 30,说明最后那条 WHERE city = '北京' 没跑,补上即可。
二、想知道本节开始时表长什么样。 不用重建,DESC 加本节第一张全表结果就是那个画面。
三、真想从本节开头再练一遍(可选)。 先数行数,别急着删:
SELECT COUNT(*) AS 人数 FROM lesson02_students;
+--------+
| 人数 |
+--------+
| 6 |
+--------+
1 row in set (0.00 sec)
如果这里的数字是 5,说明你已经做过第 5 节(删掉了 id = 6),那就别再动这张表,直接去第 5 节看注明的接续方式。如果是 7 或更多,先用 SELECT * 看清楚多了什么,再单独删掉多的那一行。
要把年龄恢复成本节开头的 23、21、25、22、24、26,不需要删表:按 id 一行一行改回去就行。不要 DROP 整张表,不要 TRUNCATE,也别去“重置 itcast 库”——第二单元的表不归这一节管,第一单元的表更不归。
练习
先自己想,再展开对答案。
**1. 把 id = 3 的加入日期改成 2024-03-01,看一眼,再改回 2024-02-20。**
参考答案 参考答案
UPDATE lesson02_students SET joined = '2024-03-01' WHERE id = 3;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
SELECT name, joined FROM lesson02_students WHERE id = 3;
+--------+------------+
| name | joined |
+--------+------------+
| 王强 | 2024-03-01 |
+--------+------------+
1 row in set (0.00 sec)
UPDATE lesson02_students SET joined = '2024-02-20' WHERE id = 3;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
日期也要加英文单引号,格式 YYYY-MM-DD。
**2. 下面两句都会显示 Query OK。各选中几行、改了几行?先猜再看。**
UPDATE lesson02_students SET age = 30 WHERE id = 99;
UPDATE lesson02_students SET city = '上海' WHERE id = 2;
参考答案 参考答案
第一句 matched: 0, changed: 0:没找到 99 号,表没变。第二句 matched: 1, changed: 0:找到了李娜,但她已经是上海,不用改。
两句都没改到数据,却都是 Query OK。所以判断改没改,要看 Rows matched 和 Changed。
**3. 现在表有 6 行,年龄最高的是 30。再用 id = 99 去改一次年龄,会发生什么?**
UPDATE lesson02_students SET age = 30 WHERE id = 99;
参考答案 参考答案
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0 Changed: 0 Warnings: 0
什么都没有发生。Query OK 只表示语句跑通了,没有 99 号,所以既没选中也没改。想看年龄最高的人是谁、是不是 30,用汇总函数:
SELECT COUNT(*) AS 人数, MAX(age) AS 最高年龄 FROM lesson02_students;
+--------+--------------+
| 人数 | 最高年龄 |
+--------+--------------+
| 6 | 30 |
+--------+--------------+
1 row in set (0.00 sec)
MAX 返回这一列的最大值;它是汇总函数,下一节会系统讲。
**4. 为什么这一节坚持先 SELECT 再 UPDATE?它挡住的是哪一类错误?**
参考答案 参考答案
挡住“WHERE 命中的行比你以为的多”这一类。SELECT 用的是和 UPDATE 一模一样的 WHERE,先跑一遍就能看到几行、是谁;如果多出一行,损失止于一条普通的查询。
做到这里
做到这里今天可以停。你有一张第二单元自己的表,会用 WHERE 精准改一行,也认识了 matched 和 changed 的区别。
下一节在这张表上删一行:先 SELECT 看清要删的是谁,再 DELETE,然后讲清楚什么情况下删错了能退回来,什么情况下一按回车就退不回了。
说明 这一节和视频课、讲义是什么关系(可选读)
UPDATE 属于课程 DML 部分,视频里通常在 emp 表上演示。本页的 lesson02_students、6 行数据、“把北京两个人改成 23 岁”,都是本教程为动手路径新写的,不是视频或讲义里的例子,也没有复制讲义正文。
页面里的结果都在本机独立的 MySQL 8.0.46 上实跑过(与学习服务器同版本号),包括 1054 报错、matched: 0 和 changed: 0 两种情况。0.00 sec 是测试机耗时,你屏幕上可能是 0.01 sec,行内容一致。