☰
MySQL单表操作实战:从建表到增删改查的完整指南
2026/10/18 1:46:40 网站建设 项目流程

1. 为什么从单表开始:一张"小名著"人物表能解决所有入门问题

1.1 单表操作是所有SQL语句的地基

前阵子一位刚开始学数据库的朋友跟我诉苦:看教程看到联表查询就彻底懵了。我直接跟他说,你急什么,多表查询是后面的事,MySQL入门阶段最该做的就是把单表操作吃透。单表操作顾名思义,就是对一张独立的表做增删改查,所有动作都发生在这一个表上。别小看这一步,你以后在业务代码里看到的那些看似复杂的SQL,剥掉外壳之后,核心仍然是"对表里的一行行数据做筛选、排序、统计和加工"。

拿做饭打比方,单表操作就是洗菜、切菜、热锅。你连菜都切不利索,就别急着研究宴席怎么摆盘。数据库再花哨,最终落点都是四件事:把想要的行从表里查出来,把新行放进去,把不对的行改掉,把不要的行删掉。SELECT、INSERT、UPDATE、DELETE、WHERE、ORDER BY、LIMIT、GROUP BY这些关键字,全部都能在单表场景里先练明白。这四件事做扎实了,后面学JOIN、子查询、索引优化,都是水到渠成的事。

这篇文章就是这个系列的第一篇,核心是两件事:把SQL语句的基本分类和单表增删改查讲透,然后拿一套"名著"风格的小数据做练习。零基础可以跟着一步步敲,用过MySQL但没系统梳理过的人,也能从中查漏补缺。

1.2 用来练手的人物档案表

为了让练习不枯燥,我这次自己做了一张"迷你名著"人物表。虚构了一部架空古典小说《山河拾遗录》,把里面出场的主要角色整理成一张人物档案表。用带情境的数据练SQL有个明显好处:每个查询都是有意义的。比如"找出武力值最高的前五名""统计哪个门派人多",而不是面对一堆无意义的数字发呆。

这张表叫 characters,存放12个角色的完整信息,字段包括姓名、性别、年龄、门派、称号、武学类别、武力值、生日和建档时间。数据预览如下:

idnamegenderagesecttitleskill_typepower_levelbirthday
1沈青梧女25青梧剑派青梧剑仙剑术881999-03-03
2沈白水女27青梧剑派白水剑客剑术761997-08-14
3陆悬河男29悬河书斋悬河先生书法化形821995-07-15
4陆小泠女23听雨楼小泠姑娘音波功692001-12-05
5叶惊蝉女22听雨楼惊蝉客暗器792002-05-21
6燕孤鸿男35北境孤鸿堡孤鸿堡主长枪911990-01-09
7云渺女27青云观渺渺真人阵法841997-09-01
8岳凌霜男29凌霜阁凌霜剑客剑术861995-12-02
9萧听雨男40听雨楼听雨楼主掌法891985-06-18
10苏晚照女24悬河书斋晚照书童书法化形582000-08-11
11秦不悔男26青云观不悔道人阵法811998-10-10
12温如言女21凌霜阁如言琴师音波功632003-11-23

先说明一句,年龄、生日都是我拍脑袋定的设定值,练SQL而已,不用拿历法较真。把数据整体扫一遍你会发现几个有意思的点:有人同姓,比如沈青梧和沈白水;有人同门,听雨楼一下子就占了三位;有人武力值特别高,燕孤鸿91分一骑绝尘;也有人还是萌新,苏晚照只有58分。这些细节都是后面练习题的素材。

2. 动手前先分清:SQL四大类语句到底各管什么

2.1 DDL、DML、DQL、DCL的分工

很多人学SQL一上来就背关键字,背完就忘,因为脑子里没有分类框架。其实SQL语句按功能分四大类,搞清楚这个分类,你看到一条语句就能判断它属于哪个环节,也自然知道"我该用哪句"。

分类中文含义常见语句管的事
DDL数据定义语言CREATE、ALTER、DROP定义和修改表结构,比如建表、加字段、删表
DML数据操纵语言INSERT、UPDATE、DELETE对表里的数据做增、改、删
DQL数据查询语言SELECT查询数据,日常工作中用得最多
DCL数据控制语言GRANT、REVOKE管理用户权限,入门期了解即可

你可以这么理解:DDL是"装修房子",决定房子有几间屋、窗户开在哪、门朝哪边开;DML是"往屋里摆家具、换家具、扔家具";DQL是"看看屋里现在到底有什么";DCL则是"决定谁能进这间屋"。单表入门阶段,我们重点操练的是DDL里的CREATE TABLE、DML里的INSERT/UPDATE/DELETE,以及DQL里的SELECT。DCL暂时只用眼睛看一遍,知道有这回事就行。

2.2 连接数据库与建库:开始前的小准备

在写任何SQL之前,要先能进到MySQL环境里。命令行下最常用的连接方式是:

mysql -u root -p

输入密码后进入MySQL交互终端,看到mysql>提示符就说明连上了。新手记住三个"探路"命令就够了:

SHOW DATABASES; -- 看看目前有哪些数据库 SELECT VERSION(); -- 确认当前MySQL版本 SHOW CHARACTER SET; -- 查看支持的字符集

练手最好单独建一个库,别把练习数据混进工作相关的库。建库语句我习惯这么写:

CREATE DATABASE IF NOT EXISTS classic_practice DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;

IF NOT EXISTS 的意思是"如果这个库不存在才创建",防止重复执行时报错。字符集直接指定 utf8mb4,这一条非常关键。utf8mb4是MySQL当前的中文标配字符集,支持完整的中文和各类特殊字符。你如果偷懒不写,很可能建出来的库是 latin1 或者旧的 utf8,后面插入中文就会出现乱码或者"???"。这个问题我会在第8章专门展开讲。

建好库之后,用 USE 切进去:

USE classic_practice;

之后你写的所有建表、插数据、查询语句,默认都在这个库里执行。确认一下当前执行环境是哪个库,也是很重要的习惯,否则你可能在错误的库里建了一堆表,后面越查越乱。

3. 建表不只是写字段:类型和约束决定了你后面能怎么查

3.1 完整的建表语句长什么样

这是本篇最核心的一段SQL,建议对着敲一遍,不要直接复制,一个个字段打出来印象完全不一样:

CREATE TABLE characters ( id INT UNSIGNED AUTO_INCREMENT COMMENT '角色编号', name VARCHAR(20) NOT NULL COMMENT '角色姓名', gender ENUM('男','女') NOT NULL COMMENT '性别', age TINYINT UNSIGNED NOT NULL COMMENT '年龄', sect VARCHAR(30) NOT NULL COMMENT '所属门派', title VARCHAR(30) DEFAULT NULL COMMENT '称号', skill_type VARCHAR(20) NOT NULL COMMENT '武学类别', power_level TINYINT UNSIGNED NOT NULL COMMENT '武力值', birthday DATE DEFAULT NULL COMMENT '生日', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建档时间', PRIMARY KEY (id), UNIQUE KEY uk_name (name) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='《山河拾遗录》人物档案表';

执行成功之后,可以用SHOW CREATE TABLE characters;查看MySQL实际保存的表定义,也可以DESC characters;看字段清单。两条命令在排查建表问题时非常有用。

3.2 每个字段类型为什么这么选

逐段拆解类型选择背后的逻辑,这是很多教程不会讲的。

id 用 INT UNSIGNED AUTO_INCREMENT。INT是常规整数,UNSIGNED表示非负,把取值范围全部留给正数。AUTO_INCREMENT是自增,由数据库自动编号,插入数据时不用管它。为什么不用姓名当主键?因为姓名理论上可能重复,而且角色一旦改名,所有关联引用都得跟着改。一个与业务无关、永远不变的编号,才是最稳定的主键。

name 用 VARCHAR(20) NOT NULL。VARCHAR是可变长字符串,存几个字就占几个字的空间,不像CHAR那样固定占满。长度20对中文名完全够用。这里有个新手常犯的毛病:无脑写 VARCHAR(255)。字段长度越长,行存储和索引的成本越高,够用就好。

gender 用 ENUM('男','女')。枚举类型会在数据库层面限制这个字段只能填'男'或'女',填别的直接报错,好处是防止脏数据。坏处是以后如果要加第三个取值,得用 ALTER TABLE 修改枚举定义。如果你觉得麻烦,用 VARCHAR(2) 也行,只是少了这层约束。入门阶段我建议用ENUM,顺便把"约束"这个概念理解掉。

age 和 power_level 用 TINYINT UNSIGNED。年龄、武力值这种小范围整数,没必要用INT。TINYINT UNSIGNED取值0到255,完全够用还省空间。这是初学者最容易忽略的:数据库选型里,"够用且最小"是一种习惯。

birthday 用 DATE。日期必须用专门的日期类型,不要拿字符串硬存。用DATE之后,你可以直接比较大小、算日期差、按日期排序;用字符串存,这些操作都会变得非常别扭,还得先转换格式。

created_at 用 DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP。记录档案创建时间,默认取当前时间,插入时无需手动赋值。这类"审计字段"在很多业务表里都是标配。

3.3 主键、唯一键、非空与默认值:约束背后的设计逻辑

建表时那几行约束纯粹是为了"管住数据"。PRIMARY KEY(id) 是主键,一个表只能有一个主键,它唯一标识一行,MySQL会自动为主键建索引,按主键查询走的是最快路径。UNIQUE KEY uk_name(name) 是唯一键,保证姓名不重复,从业务上讲两个角色重名确实容易混乱。NOT NULL 表示该字段不能为空,一个角色不可能没有年龄;DEFAULT NULL 表示允许为空,比如称号,有些人确实没有称号。DEFAULT CURRENT_TIMESTAMP 则是"不填就自动带当前时间"。

这里有个经验之谈:约束越清晰,后面的脏数据越少。我见过太多表设计阶段图省事,什么约束都不加,结果到了数据分析阶段,天天花时间清洗垃圾数据。建表时多花两分钟想清楚"这个字段允许空吗、能不能重复、取值范围是什么",后面省的是成倍的时间。

4. 写入数据:INSERT的两种写法与五个常见翻车现场

4.1 单行写入与多行批量写入

表建好之后,第一步就是灌数据。最基础的单行插入长这样:

INSERT INTO characters (name, gender, age, sect, title, skill_type, power_level, birthday) VALUES ('沈青梧', '女', 25, '青梧剑派', '青梧剑仙', '剑术', 88, '1999-03-03');

注意两点。第一,字段列表里没有写 id 和 created_at,因为一个自增、一个有默认值,插入时可以被跳过。第二,VALUES 后面的每个值,顺序必须和前面字段列表一一对应。字符串和日期都必须用单引号括起来,这是SQL的硬性要求。

一条条插太慢,MySQL支持一条INSERT插入多行,用逗号分隔即可。两张表的数据我一次灌进去:

INSERT INTO characters (name, gender, age, sect, title, skill_type, power_level, birthday) VALUES ('沈青梧', '女', 25, '青梧剑派', '青梧剑仙', '剑术', 88, '1999-03-03'), ('沈白水', '女', 27, '青梧剑派', '白水剑客', '剑术', 76, '1997-08-14'), ('陆悬河', '男', 29, '悬河书斋', '悬河先生', '书法化形', 82, '1995-07-15'), ('陆小泠', '女', 23, '听雨楼', '小泠姑娘', '音波功', 69, '2001-12-05'), ('叶惊蝉', '女', 22, '听雨楼', '惊蝉客', '暗器', 79, '2002-05-21'), ('燕孤鸿', '男', 35, '北境孤鸿堡', '孤鸿堡主', '长枪', 91, '1990-01-09'), ('云渺', '女', 27, '青云观', '渺渺真人', '阵法', 84, '1997-09-01'), ('岳凌霜', '男', 29, '凌霜阁', '凌霜剑客', '剑术', 86, '1995-12-02'), ('萧听雨', '男', 40, '听雨楼', '听雨楼主', '掌法', 89, '1985-06-18'), ('苏晚照', '女', 24, '悬河书斋', '晚照书童', '书法化形', 58, '2000-08-11'), ('秦不悔', '男', 26, '青云观', '不悔道人', '阵法', 81, '1998-10-10'), ('温如言', '女', 21, '凌霜阁', '如言琴师', '音波功', 63, '2003-11-23');

写完跑一句SELECT COUNT(*) FROM characters;,返回12就说明全部进去了。多行插入相比一行行写,省的不只是打字时间。生产环境下需要批量导入数据时,一次INSERT多行的性能优势非常明显,能减少大量客户端与数据库之间的交互开销。

4.2 写入时的五个常见翻车现场

插数据阶段最容易翻车的几个点,我挨个说一遍:

第一,字符串没加引号或者用了双引号。SQL标准里字符串用单引号,虽然MySQL默认也接受双引号,但养成单引号的习惯最稳妥,免得以后换到其他数据库时踩坑。数字类型的值可以不加引号,但为了统一,我通常除了纯数值都加引号。

第二,违反非空约束。比如 age 没填,表定义里明确写了 NOT NULL,MySQL会直接报错:Field 'age' doesn't have a default value。字段列表和值数量对不上时,会报 Column count doesn't match value count at row 1。所以写INSERT时,字段列表一定列清楚,别偷懒。

第三,违反唯一键。name 字段有唯一键约束,如果你第二次插入'沈青梧',会报 Duplicate entry '沈青梧' for key 'uk_name'。看到这个报错不要慌,它恰好说明约束在起作用,数据库帮你挡住了重复数据。

第四,手工指定自增id。新手偶尔会写 VALUES (1, '沈青梧', ...),把id也填进去。MySQL允许这么做,但有个隐患:下次不指定id插入时,自增序号会从你填过的最大值往后继续跳,有时候直接跳出一大截,让不明所以的人以为"序号怎么缺了"。练习阶段尽量别手动填自增列。

第五,中文变成问号。这就是建库时没指定 utf8mb4 的后果。如果已经踩了,补救方式是把库、表、字段的字符集都改掉,再重新插入数据,具体写法在第8章。插完数据后一定要回头看一眼实际情况,执行SELECT * FROM characters;把整张表打出来,确认和预期一致,这一步才算闭环。

5. 查询是重头戏:SELECT加WHERE把表玩出花来

5.1 基础投影、别名与去重

SELECT是SQL里出场率最高的语句。先认识它的基本结构:查哪些列,从哪张表查,加什么过滤条件,按什么排序,取多少行。

最简单的查全表是SELECT * FROM characters;,星号代表所有列。但工作中我强烈建议写列名,不要动不动 SELECT *。写列名一是输出清晰,二是减少数据传输。比如只查姓名、门派、武力值:

SELECT name, sect, power_level FROM characters;

想给查询结果的列换一个显示名,用 AS 起别名。别名不影响原表数据,只是让结果更易读。中英文别名都可以,中文别名记得加引号或者直接使用:

SELECT name AS 姓名, power_level AS 战力 FROM characters;

去重用 DISTINCT 关键字。比如我想知道这张表里一共涉及几个门派:

SELECT DISTINCT sect FROM characters;

返回青梧剑派、悬河书斋、听雨楼、北境孤鸿堡、青云观、凌霜阁,正好6个门派。注意DISTINCT是对整行去重,单独某一列重复不算。

5.2 条件筛选:比较、逻辑、模糊、区间

WHERE 是整个查询的灵魂。先看比较运算符:

SELECT name, power_level FROM characters WHERE power_level > 85;

这条查所有武力值大于85的角色,返回沈青梧88、燕孤鸿91、岳凌霜86、萧听雨89。大于、小于、不等于的写法都类似:>、<、>=、<=、=、!= 和 <> 都表示不等于,MySQL两种都认。

多条件组合用 AND、OR、NOT。比如查"青梧剑派或者凌霜阁的女角色":

SELECT name, sect, gender FROM characters WHERE (sect = '青梧剑派' OR sect = '凌霜阁') AND gender = '女';

括号一定不能省。不加括号的话,AND的优先级比OR高,整条语句会被理解成"青梧剑派的所有人,再加上凌霜阁的女角色",结果完全不一样。这是新手高频踩坑点,必须记牢。

区间查询用 BETWEEN,比如查武力值在80到90之间(含边界)的角色:

SELECT name, power_level FROM characters WHERE power_level BETWEEN 80 AND 90;

集合匹配用 IN,上面两个门派的条件用IN写更清爽:

SELECT name, sect FROM characters WHERE sect IN ('青梧剑派', '凌霜阁');

模糊匹配用 LIKE,配合两个通配符:% 代表任意多个字符,_ 代表任意单个字符。查所有称号里带"剑"字的角色:

SELECT name, title FROM characters WHERE title LIKE '%剑%';

返回沈青梧(青梧剑仙)、沈白水(白水剑客)、岳凌霜(凌霜剑客)三行。查姓"沈"的角色就是name LIKE '沈%'。查"两个字的名字"则用name LIKE '__',也就是两个下划线。这个技巧平时用得不算多,但面试题里经常出现,建议亲手跑一次加深印象。

NULL的判断要单独拎出来讲。NULL在SQL里是"未知",不能用等号判断。你写WHERE title = NULL永远查不出数据,必须写成:

SELECT name FROM characters WHERE title IS NULL; SELECT name FROM characters WHERE title IS NOT NULL;

我们这张表恰好所有角色都有称号,所以第一条返回空结果,第二条返回全部12个角色。这个坑新手必踩,没有例外。想真正理解它,可以自己UPDATE一行把title改成NULL再分别试。

5.3 排序与限量:ORDER BY和LIMIT的配合

查询结果的默认顺序是没有保证的,想按某种规则排列就用 ORDER BY。武力值从高到低:

SELECT name, power_level FROM characters ORDER BY power_level DESC;

DESC是降序,ASC是升序,默认就是ASC,可以省略。想要多列排序就逗号分隔,比如先按门派排,同门派内按武力值降序:

SELECT name, sect, power_level FROM characters ORDER BY sect ASC, power_level DESC;

LIMIT 用来限量取行。取武力值最高的前3名:

SELECT name, power_level FROM characters ORDER BY power_level DESC LIMIT 3;

这里要特别注意书写顺序:WHERE 先过滤,ORDER BY 排序,LIMIT 最后限量。顺序写错MySQL直接报语法错误。LIMIT 后面还可以接两个参数,比如LIMIT 2, 5表示"跳过前2行,取接下来5行",分页场景常用。入门阶段先掌握单参数写法就够。

6. 改与删:UPDATE/DELETE的安全红线

6.1 UPDATE的精确打击

数据不是一成不变的,改数据用UPDATE。基本语法:

UPDATE characters SET power_level = 90 WHERE name = '沈青梧';

这句话的意思是,把 name 等于'沈青梧'的那一行的武力值改成90。SET 后面可以跟多个字段,用逗号分隔:

UPDATE characters SET power_level = power_level + 5, title = '青梧剑圣' WHERE name = '沈白水';

power_level = power_level + 5是"在原值基础上加5",非常常用的写法。SET里的字段顺序无所谓,多个字段更新是原子的,要么全部成功,要么全部失败。

这里必须划一条红线:UPDATE 一定要带WHERE。不带WHERE等于"无差别修改表中所有行"。我早期真实干过这种事,本来想把某个角色的武力值清零,结果漏写了WHERE,一整表数据全被清零,最后只能从备份恢复。那之后我养成了一个习惯:执行UPDATE之前,先跑一遍同样条件的SELECT,确认"我打算改哪些行",再执行UPDATE。

6.2 DELETE与TRUNCATE的区别

删数据用DELETE,语法和UPDATE类似,WHERE同样是安全红线:

DELETE FROM characters WHERE name = '温如言';

不带WHERE的DELETE FROM characters;会把整表数据清空,但表结构仍然存在。

DELETE和另一个"清空"命令TRUNCATE要区分开,它们的区别在实战中非常重要:

对比项DELETETRUNCATE
能否带WHERE能,精确删除指定行不能,只能整表清空
是否走事务走,可以回滚不走,执行后不可回滚
自增计数保留原计数,继续往后排自增计数重置
速度逐行删,相对慢重建表,速度快

一句话总结:DELETE是"选择性删行",TRUNCATE是"整表推倒重来"。入门阶段绝大多数情况你只需要DELETE。TRUNCATE和DROP(直接连表带结构一起删掉)这两类操作,我建议在任何非练习环境里都要极其谨慎,执行前反复确认。

6.3 保护数据的三条军规

结合我自己的教训,改删操作前请遵守这三条,能救你无数次。

第一,先SELECT再UPDATE/DELETE。用相同WHERE条件先查出结果集,肉眼确认影响行数和目标数据,再执行改删。

第二,没有WHERE的UPDATE/DELETE一律视为非法操作。每次写完这两类语句,都回头看一眼有没有WHERE。我的个人习惯是:先把WHERE写好,再回去补前面的语句。顺序反过来写,最不容易漏。

第三,重要操作放在事务里。MySQL的InnoDB引擎支持事务,可以这样操作:

BEGIN; UPDATE characters SET power_level = power_level + 10 WHERE name = '叶惊蝉'; SELECT * FROM characters WHERE name = '叶惊蝉'; -- 确认结果 ROLLBACK; -- 不对就回滚 -- COMMIT; -- 确认无误再提交

BEGIN 开始一个事务,执行完发现不对可以 ROLLBACK 回滚,确认没问题再 COMMIT 提交。练习环境多ROLLBACK几次没坏处,这是你对"后悔药"的熟悉过程,也是未来处理真实数据时的保命技能。

7. 名著小练习:五道题由易到难,附完整解题思路

7.1 热身题:门派检索与武力排行

题目一:查询武力值大于85的角色,只要姓名、门派、武力值三列,按武力值从高到低排序,最多取前5行。

先拆解需求:过滤条件是"武力值大于85",排序是"武力值降序",限量是"最多5行"。SQL如下:

SELECT name, sect, power_level FROM characters WHERE power_level > 85 ORDER BY power_level DESC LIMIT 5;

执行结果只有4行:燕孤鸿91、萧听雨89、沈青梧88、岳凌霜86。为什么"取前5"只出了4行?因为武力值大于85的角色一共就4个。LIMIT 5的意思是"最多返回5行",不是"必须返回5行"。这个细节理解了,以后写分页就不会搞错。

题目二:查询听雨楼和凌霜阁两个门派中,武力值不低于80的角色。

这里有两个筛选维度,门派用IN,武力值用>=,两个条件用AND连接:

SELECT name, sect, power_level FROM characters WHERE sect IN ('听雨楼', '凌霜阁') AND power_level >= 80;

结果只有两行:萧听雨89、岳凌霜86。注意听雨楼另外两位陆小泠(69)、叶惊蝉(79),以及凌霜阁的温如言(63),都因为武力值不足80被正确过滤掉了。这道题考的就是多条件组合时AND的用法,以及条件之间的括号配合。

7.2 进阶题:模糊匹配与多条件组合

题目三:找出称号中含有"剑"字的角色,以及姓名中姓氏为"沈"的角色,两个条件满足其一即可,输出姓名、称号、门派。

"满足其一"就是OR。称号含"剑"字是title LIKE '%剑%',姓沈即name LIKE '沈%':

SELECT name, title, sect FROM characters WHERE title LIKE '%剑%' OR name LIKE '沈%';

结果:沈青梧、沈白水、岳凌霜三行。这里再提醒一次,OR两侧的条件一定要各自写完整,别省写成title LIKE '%剑%' OR '沈%'。这种偷懒写法会把第二个条件当成普通字符串,永远为真,查出来就是整表数据。这种错误特别隐蔽,查一次坑一次。

题目四:查询年龄在25岁到35岁之间、且不是青梧剑派的角色,按年龄升序排列。

区间用BETWEEN,排除门派用 !=。年龄升序用 ORDER BY age ASC,ASC可省略。SQL:

SELECT name, age, sect FROM characters WHERE age BETWEEN 25 AND 35 AND sect != '青梧剑派' ORDER BY age ASC;

结果依次是:秦不悔26、云渺27、陆悬河29、岳凌霜29、燕孤鸿35。为什么沈青梧和沈白水不在里面?因为两人都属青梧剑派,被第二个条件排除了。为什么萧听雨不在里面?因为他40岁,超出区间。一个条件一个条件地核对,是练习时最有效的验证方式。

7.3 综合题:统计聚合和分组合并

题目五:统计每个门派的人数,按人数从多到少排序。

这道题涉及两个新东西:聚合函数COUNT用来计数,GROUP BY用来按门派分组。这属于单表操作里"从明细到汇总"的升级,入门篇先尝个鲜:

SELECT sect, COUNT(*) AS cnt FROM characters GROUP BY sect ORDER BY cnt DESC;

结果:听雨楼3人,青梧剑派、悬河书斋、青云观、凌霜阁各2人,北境孤鸿堡1人。COUNT(*) 是统计每组行数,AS cnt 给结果列起别名,方便后面的 ORDER BY 引用。看完这个结果,门派规模排名一目了然。聚合函数后面还有SUM、AVG、MAX、MIN,等这个系列下一篇再展开讲。

五道题全部跑通之后,建议自己再编几个问题,比如"平均武力值最高的门派是哪个""有没有角色生日在同一个月"。练习的意义不在于题海,而在于把每条SQL里的每个关键字都弄明白为什么这么写。

8. 实战中踩过的坑:每一条都是真金白银的教训

8.1 漏写WHERE:一次让全表归零的乌龙

这个坑前面提过,但值得单独拿出来说,因为它太典型了。当时我维护一张测试表,想更新某一行的状态,SQL写了一半被同事叫走,回来接着写,结果直接执行了一句不带WHERE的UPDATE,整表数据瞬间被覆盖。万幸是测试环境,要是发生在生产环境,后果不敢想。

从那以后我的固定动作是:凡是UPDATE和DELETE,写完先完整读一遍,重点看WHERE;执行前心里默念一遍"WHERE在哪"。另外,能用事务就先开事务,出问题直接ROLLBACK。这些习惯看着繁琐,关键时刻真的是保命符。

8.2 字符集没指定:中文全部变成问号

刚用MySQL那会儿,我建库图省事,直接CREATE DATABASE test;就完事。插入中文数据时,存进去的全是???,查出来也是???。原因就是库的默认字符集不支持中文。解决办法在第2章提过:建库时明确指定DEFAULT CHARACTER SET utf8mb4。

如果已经建错库,补救也不复杂:

ALTER DATABASE classic_practice CHARACTER SET utf8mb4; ALTER TABLE characters CONVERT TO CHARACTER SET utf8mb4;

但要注意,CONVERT TO 会重写表数据,线上大表执行时必须挑业务低峰期。更重要的是,这类问题最好在建库那一刻就规避,后面改字符集多出来的工作量,都是当初省那两分钟的代价。

8.3 NULL判断、保留字、自增值:三个新手极易忽略的隐性坑

第一个是NULL判断。WHERE title = NULL查不出任何结果,必须用 IS NULL。原因在于NULL是"未知值",任何和NULL的等值比较结果都是"未知",WHERE只保留结果为真的行。所以你要记住:NULL不等于0,也不等于空字符串,它是一种"未填写"的状态。我建议你动手把某一行的title改成NULL,再分别跑 IS NULL 和 =NULL 两条语句,亲眼看差别,比背十遍都管用。

第二个是保留字。SQL有些关键字不能直接用作表名、列名,比如order、group、desc、rank。真要用,可以用反引号包起来,写成`order`,但更推荐直接换一个不冲突的名字。新手最容易踩的是把表起名叫 order,然后每次查询都报语法错误,还以为是SQL写错了。建议所有字段名、表名起名时先过一遍"是不是SQL保留字",省得给自己埋雷。

第三个是自增列跳号。AUTO_INCREMENT的值不会因为删了几行就补回去。你删掉id=5,再插入新行,新行id是13而不是5,这是正常行为,别指望数字连续。想要业务上的连续编号是另外的场景,不能依赖自增主键。我见过有人因为"id怎么不连续"把数据翻来覆去查了半天,理解自增机制之后,这根本不是问题。

最后再分享一个我个人的习惯:练习完一张表,别急着扔。把当时的建表语句、练习SQL和踩坑记录存成一个文本,下次遇到类似问题直接翻。学数据库没有太多捷径,就是把基础操作反复练扎实,把每次报错都当成学习材料读。这张《山河拾遗录》人物表从建表到增删改查全部跑过关,你对MySQL单表操作的基本功,就算正式立住了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询