欢迎光临
我们一直在努力

对于MySQL:数据操作的解析

开篇介绍:

hello 大家,那么在前面的学习中,我们学习了MySQL中的对数据库的操作,对表的操作,数据类型,那么我们前面也说了,我们遵循由外到内的学习路线,那么在学习完表的操作之后,比表还小的,也就是数据了,所以,接下来,我们就来学习一下MySQL中的,对于数据的操作,增删查改,这一部分知识点,可以说是最重要最核心的一个知识点了,所以各位,come on!

哈哈,OK,那么接下来,我们就来学习学习。

其实 MySQL 的核心操作 ——CRUD(Create 新增、Retrieve 查询、Update 更新、Delete 删除),本质上就是对 “数据库表” 的 “增、查、改、删”,就像我们日常操作 Excel 表格一样,只是换了一套更规范、更高效的 “语言”。

一、CRUD 核心概念:先搞懂 “到底在做什么”

在正式讲解语法前,我们先把底层逻辑说透 ——CRUD 是 4 个英文单词的缩写,对应 MySQL 中对数据表的四类核心操作,也是所有数据库操作的基础,没有之一。

可以用一个非常生活化的类比来理解:MySQL 的 “数据表” 就相当于我们电脑里的 Excel 工作表,CRUD 就是对这个工作表的四种核心操作:

  • Create(新增):对应 Excel 里的 “新增一行数据”,比如给学生表添加一条新学生的记录、给成绩表填一条刚考完的考试分数;
  • Retrieve(查询):对应 Excel 里的 “筛选数据、排序数据、计算数据”,这是日常使用频率最高的操作,比如查 “语文不及格的学生名单”“全班总分前 3 的同学排名”“每个学生的平均分”;
  • Update(更新):对应 Excel 里的 “修改单元格内容”,比如把学生的数学成绩从 78 分改成 80 分、把员工的部门从 “销售部” 调整到 “市场部”、把过期的手机号改成新号码;
  • Delete(删除):对应 Excel 里的 “删除整行数据”,比如删除已退学学生的记录、清理过期的考试成绩、移除重复的用户信息。

为什么不用 Excel 而用 MySQL?核心原因有三个:

  • 数据量更大:Excel 最多支持 104 万行数据,而 MySQL 可以轻松处理百万、千万甚至上亿行数据;
  • 多用户协作:Excel 只能单用户编辑,多人同时操作会冲突,而 MySQL 支持多用户并发读写,还能通过权限控制谁能看、谁能改;
  • 数据更安全:Excel 文件容易丢失、损坏,而 MySQL 有完善的备份、恢复机制,还能通过事务保证数据的一致性。
  • 二、Create(新增数据):把数据 “填” 进表里

    新增数据是和数据库交互的第一步 —— 没有数据,后续的查询、更新、删除都无从谈起。MySQL 中新增数据的核心语法是INSERT

    2.1 新增数据的核心语法结构

    先记住INSERT的通用语法框架,后续所有新增场景都是这个框架的变形,没有例外:

    INSERT [INTO] 表名 [(列1, 列2, 列3, …)] VALUES (值1, 值2, 值3, …) [, (值1, 值2, 值3, …)] …

    我们把这个语法拆成 “人话版”,每个部分都解释清楚,确保你看到就懂:

    • INSERT [INTO]:这是固定的关键字组合,作用是 “告诉数据库,我要往表里填数据了”。这里的INTO可以省略,比如写成INSERT 表名 VALUES …,效果完全一样,但加上INTO会更易读,新手建议加上;
    • 表名:必填项,指定你要往哪张表里填数据,比如students(学生表)、exam_result(成绩表)、employees(员工表),必须是已经创建好的表,否则会报错;
    • (列1, 列2, 列3, …),列其实指的就是字段,列1等待,其实就是要填写字段名:可选项,指定你要给哪些列赋值。如果不写这个部分,默认就是给表中所有列赋值,顺序必须和表创建时的列顺序一致;
    • VALUES (值1, 值2, 值3, …):必填项,指定你要填的具体数据。每组值用括号括起来,多组值之间用逗号分隔,这样就能一次性插入多行数据,注意插入数据的类型要和对应的字段的数据类型相匹配哦;
    • 结尾的分号;:MySQL 语句的结束标志,必须加,否则数据库不知道你这句话说完了。

    大家看完之后自己再写一遍:insert into 表名字 (字段名,……) values (数据,……);

    2.2 场景 1:单行数据 + 全列插入

    2.2.1 适用场景

    这种方式适合两种情况:一是临时手动补录单条数据,比如给学生表加一条新学生的信息;二是测试表结构是否正常,比如创建完表后插一条数据看看能不能成功。

    2.2.2 语法 & 完整示例

    首先,我们需要一张测试表,比如创建一张 “学生表”,包含 4 个列:

    • id:学生 ID,整数类型,无符号(不能是负数),主键(唯一标识每条记录),自增(插入时不用手动赋值,数据库自动生成);
    • sn:学号,整数类型,非空(必须填),唯一(不能重复);
    • name:姓名,字符串类型,长度 20,非空;
    • qq:QQ 号,字符串类型,长度 20,可空(可以不填)。

    创建表的代码如下:

    CREATE TABLE students (
    id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    sn INT NOT NULL UNIQUE COMMENT '学号',
    name VARCHAR(20) NOT NULL COMMENT '学生姓名',
    qq VARCHAR(20) COMMENT 'QQ号'
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

    现在,我们要往这张表里插入一条 “孙悟空” 的完整信息,用全列插入的语法:

    INSERT INTO students VALUES (101, 10001, '孙悟空', '11111');

    执行这条语句后,数据库会返回一行提示:Query OK, 1 row affected (0.03 sec),意思是 “执行成功,影响了 1 行数据,耗时 0.03 秒”。

    2.2.3 关键注意点

    全列插入的约束最多,稍微不注意就会报错,这里总结了 4 个核心注意点:

  • 值的数量必须和列的数量完全一致:学生表有 4 个列,所以VALUES后面必须跟 4 个值,不能多也不能少。如果少一个,会报错Column count doesn't match value count;如果多一个,同样会报错;
  • 值的顺序必须和列的定义顺序完全一致:表的列顺序是id→sn→name→qq,所以VALUES里的值顺序也必须是 “学生 ID→学号→姓名→QQ 号”。如果顺序搞反了,比如把学号填到了 ID 的位置,要么会因为数据类型不匹配报错,要么会插入错误的数据;
  • 空值必须用NULL表示,不能留空:如果某个列允许为空(比如qq列),而你暂时没有数据,必须用NULL填充,不能只写两个逗号(比如(100, 10000, '唐三藏', ))。正确的写法是:

    INSERT INTO students VALUES (100, 10000, '唐三藏', NULL);

  • 自增列可以手动赋值,但不推荐:id列是自增列,数据库会自动从 1 开始递增生成值。如果你手动给id赋值(比如上面的 101、100),数据库会接受这个值,但后续的自增会从你赋值的最大值 + 1 开始。比如你插入了 id=101 的记录,下一条自增的 id 会是 102,而不是 1。新手建议不要手动给自增列赋值,用 “指定列插入” 的方式更灵活。
  • 2.3 场景 2:多行数据 + 指定列插入

    2.3.1 适用场景

    这是实际开发中使用频率最高的插入方式,没有之一。它适合三种核心场景:

    • 批量插入多条数据,比如一次导入 100 条学生信息,比单条插入快 10 倍以上;
    • 不需要给所有列赋值,比如自增列(id)、默认值列、可空列,都可以省略;
    • 表结构后续可能变更,比如新增一列 “年龄”,只要新增列允许为空,原有插入语句不用修改就能继续使用。
    2.3.2 语法 & 完整示例

    还是用学生表举例,我们要同时插入 “曹孟德” 和 “孙仲谋” 两条记录,并且只给id、sn、name三个列赋值(qq列暂不填),语法如下:

    INSERT INTO students (id, sn, name) VALUES
    (102, 20001, '曹孟德'),
    (103, 20002, '孙仲谋');

    执行后,数据库会返回提示:Query OK, 2 rows affected (0.01 sec),意思是 “执行成功,影响了 2 行数据”。

    如果我们不想手动给id列赋值(利用自增特性),可以直接省略id列,语法更简单:

    INSERT INTO students (sn, name) VALUES
    (20003, '刘玄德'),
    (20004, '诸葛亮');

    执行这条语句后,数据库会自动给id列生成值:104 和 105(因为之前的最大 id 是 103),完美利用了自增列的特性。

    2.3.3 核心优势 & 避坑要点

    这种插入方式的优势太明显了

    核心优势
  • 插入效率极高:批量插入多条数据时,只需要和数据库建立一次连接,执行一次 SQL 语句。而单条插入需要建立多次连接,数据量越大,效率差距越明显。比如插入 1000 条数据,批量插入只需 0.1 秒,单条插入需要 10 秒以上;
  • 语法极度灵活:想给哪些列赋值就写哪些列,完全不用管表的列顺序。比如你可以写成(name, sn),只要VALUES里的值顺序和指定列的顺序一致就行;
  • 对表结构变更更友好:如果后续给学生表新增一列 “age”(年龄),并且设置为 “允许为空”,那么上面的插入语句不需要做任何修改,直接就能用。而全列插入的语句会因为 “列数量不匹配” 报错;
  • 避免自增列手动赋值的麻烦:自增列可以直接省略,数据库自动生成值,不用担心后续自增顺序混乱;
  • 可读性更强:指定列名后,别人看你的 SQL 语句,一眼就知道你要给哪些列赋值,不用去查表结构。
  • 避坑要点
  • 指定列的数量必须和每组值的数量完全一致:比如指定了sn、name两个列,那么每组VALUES里必须有 2 个值,不能多也不能少。如果指定了 3 个列,每组值却只有 2 个,会报错Column count doesn't match value count;
  • 值的顺序必须和指定列的顺序完全一致:比如指定列的顺序是(sn, name),那么VALUES里的值顺序必须是 “学号→姓名”,不能写成 “姓名→学号”,否则会把姓名插到学号列里,导致数据类型不匹配报错;
  • 非空列必须赋值,不能省略:比如学生表的sn和name列都是NOT NULL(非空),如果在指定列时省略了sn列,会报错Field 'sn' doesn't have a default value,意思是 “sn 列没有默认值,必须赋值”。
  • 2.4 场景 3:插入否则更新(解决 “主键 / 唯一键冲突”,避免报错)

    2.4.1 问题背景:为什么会冲突?

    这是新手插入数据时最常遇到的报错:ERROR 1062 (23000): Duplicate entry '100' for key 'PRIMARY',翻译过来就是 “主键冲突,100 这个值已经存在了”。

    冲突的根源只有两个:

    • 主键冲突:主键列的值必须唯一,比如学生表的id列是主键,已经插入了 id=100 的记录,再插一次 id=100 就会冲突;
    • 唯一键冲突:唯一键列的值也必须唯一,比如学生表的sn列是唯一键,已经插入了 sn=20001 的记录,再插一次 sn=20001 就会冲突。

    这种冲突在实际开发中太常见了:比如用户重复提交表单、批量导入数据时包含重复记录、定时任务同步数据时重复执行。

    我们需要的不是报错,而是 “有则更新,无则插入” 的逻辑 —— 这就是INSERT … ON DUPLICATE KEY UPDATE的核心作用。

    2.4.2 语法 & 完整示例

    核心语法是在普通INSERT语句后面,加上ON DUPLICATE KEY UPDATE子句,指定冲突时要更新的列:

    INSERT INTO students (id, sn, name) VALUES (100, 10010, '唐大师')
    ON DUPLICATE KEY UPDATE sn = 10010, name = '唐大师';

    我们来拆解这条语句的逻辑:

  • 数据库先尝试插入一条 id=100、sn=10010、name = 唐大师的记录;
  • 检测到 id=100 的记录已经存在(主键冲突),于是执行UPDATE子句:把这条记录的sn改成 10010,name改成唐大师;
  • 执行完成后,返回提示:Query OK, 2 rows affected (0.04 sec)—— 这里的 “2 rows affected” 是 MySQL 的特殊提示,代表 “冲突后更新了 1 行数据”。
  • 2.4.3 如何判断执行结果?(看受影响行数)

    执行INSERT … ON DUPLICATE KEY UPDATE后,通过 “受影响行数” 可以精准判断执行结果,这对程序开发非常重要,我们总结了 3 种情况:

  • 0 rows affected:表里已经有冲突数据,并且新数据和原有数据完全一致,不需要更新。比如你插入的 sn 和 name 和原有记录一样,数据库会认为 “没必要更新”,返回 0 行受影响;
  • 1 row affected:表里没有冲突数据,数据成功插入。这是最理想的情况,代表插入了一条新记录;
  • 2 rows affected:表里有冲突数据,并且数据已经被更新。这是冲突后的正常更新,代表原有记录被修改。
  • 其实语法还是很好记住的,前面就是和普通的插入语句一样,只是要在插入语句后面加上

    on duplicate key update 字段名=数据,(注意,一定要有逗号分割)……

    2.4.4 核心优势:原子操作,避免并发问题

    这里要补充一个非常重要的知识点:INSERT … ON DUPLICATE KEY UPDATE是原子操作。

    什么是原子操作?简单说就是 “要么全部成功,要么全部失败”,不会出现 “一半成功一半失败” 的情况。

    举个例子:如果不用这个语法,你需要先执行SELECT语句查有没有冲突数据,再执行INSERT或UPDATE。但在高并发场景下(比如 1000 人同时提交表单),可能会出现 “你查的时候没有,插入的时候突然有了” 的情况,导致报错。

    而用INSERT … ON DUPLICATE KEY UPDATE,这个过程由数据库内部完成,是原子操作,完全避免了并发冲突问题 —— 这也是它在实际开发中被广泛使用的核心原因。

    2.5 场景 4:替换(REPLACE,删除原有数据后插入)

    2.5.1 适用场景

    这种插入方式的使用场景比较特殊,适合 “需要完全替换冲突数据” 的情况。比如你确定要删除原有记录,用新记录替代它,并且接受 “未被覆盖的列会变成 NULL 或默认值” 的后果。

    2.5.2 语法 & 完整示例

    REPLACE的语法和INSERT几乎一样,只是把INSERT换成REPLACE:

    REPLACE INTO students (sn, name) VALUES (20001, '曹阿瞒');

    我们来拆解这条语句的逻辑:

  • 数据库先尝试插入一条 sn=20001、name = 曹阿瞒的记录;
  • 检测到 sn=20001 的记录已经存在(唯一键冲突),于是先删除原有记录(曹孟德的记录);
  • 再插入新记录(曹阿瞒的记录),未被赋值的qq列会变成 NULL。
  • 执行后,数据库会返回提示:Query OK, 2 rows affected (0.05 sec)—— 这里的 “2 rows affected” 代表 “删除了 1 行,插入了 1 行”。

    2.5.3 REPLACE vs INSERT … ON DUPLICATE KEY UPDATE(核心区别)

    这两种方式都能解决冲突,但核心逻辑完全不同,很容易混淆。我们用一张表总结它们的 5 个核心区别:

    特性REPLACEINSERT … ON DUPLICATE KEY UPDATE
    冲突处理逻辑 先删除原有记录,再插入新记录 直接更新原有记录,不删除
    未覆盖列的处理 未赋值的列会变成 NULL 或默认值 未更新的列保持原有值不变
    受影响行数 冲突时返回 2(删 1 行 + 插 1 行) 冲突时返回 2(更新 1 行)
    自增列的影响 会生成新的自增值(因为删除后重新插入) 自增值保持不变(只是更新)
    适用场景 完全替换整条记录,不怕丢失数据 部分更新记录,保留原有数据

    避坑警告:REPLACE会删除原有记录,导致未被覆盖的列数据丢失(比如qq列变成 NULL),生产环境务必慎用!除非你明确知道自己要做什么,否则优先用INSERT … ON DUPLICATE KEY UPDATE。

    2.6 新增数据的额外避坑技巧

    我们再补充 3 个容易踩的坑,以及对应的解决方案:

  • 插入中文乱码问题:如果插入的中文变成了 “???”,是因为表的字符集设置不对。创建表时一定要加上DEFAULT CHARSET=utf8mb4,这个字符集支持所有中文和表情符号;
  • 超出列长度限制报错:比如name列是VARCHAR(20),插入的姓名超过 20 个字符,会报错Data too long for column 'name'。解决方案是根据实际需求调整列长度,或者用TEXT类型存储长文本;
  • 默认值列的使用:如果某列设置了默认值(比如age列默认值是 18),插入时省略该列,数据库会自动填充默认值。比如INSERT INTO students (sn, name) VALUES (20005, '赵云'),age列会自动变成 18。
  • 三、Retrieve(查询数据):MySQL 最核心的操作,没有之一

    查询是 MySQL使用频率最高、语法最灵活、功能最强大的操作,没有之一。从简单的 “查全班成绩” 到复杂的 “查近 3 年每个月的销售额排名”,都属于查询的范畴。

    我们可以把查询的过程比作 “筛豆子”:

    • 表就是 “装豆子的筐”,里面有各种各样的豆子(数据);
    • FROM子句就是 “选筐”,指定你要筛哪个筐里的豆子;
    • WHERE子句就是 “筛子”,指定你要留下什么样的豆子(符合条件的数据);
    • SELECT子句就是 “挑豆子”,指定你要挑哪些豆子(列)出来,还能对豆子进行加工(表达式计算);
    • ORDER BY子句就是 “排豆子”,把挑出来的豆子按大小、颜色排序;
    • LIMIT子句就是 “装豆子”,只装你需要的前 N 个豆子,避免豆子太多拿不动。

    3.1 查询的核心语法结构

    先记住SELECT的通用语法框架,这是一个 “万能框架”,后续所有查询都是这个框架的扩展,没有例外:

    SELECT [DISTINCT] {* | 列1 [AS 别名1], 列2 [AS 别名2], …}
    FROM 表名1 [别名1]
    [JOIN 表名2 [别名2] ON 连接条件] — 多表查询用,新手暂时可忽略
    [WHERE 筛选条件]
    [GROUP BY 分组列1, 分组列2, …]
    [HAVING 分组筛选条件]
    [ORDER BY 排序列1 [ASC/DESC], 排序列2 [ASC/DESC], …]
    [LIMIT 分页参数];

    我们还是拆解每个部分:

    • SELECT:必填项,指定你要查询的列或表达式,是整个查询语句的核心;
    • DISTINCT:可选项,作用是 “去重”,去掉查询结果中的重复记录;
    • *:代表 “查询所有列”,新手常用,但实际开发中尽量少用;
    • AS 别名:可选项,给列或表达式起一个通俗易懂的名字,比如把chinese + math + english起名为总分;
    • FROM 表名:必填项,指定你要查询的表,是查询的 “数据来源”;
    • WHERE:可选项,筛选符合条件的行,是 “数据过滤的第一道关卡”;
    • GROUP BY:可选项,按指定列分组,用于聚合统计(比如按部门分组统计平均工资);
    • HAVING:可选项,对分组后的结果进行筛选,是 “数据过滤的第二道关卡”;
    • ORDER BY:可选项,对查询结果排序,没有这个子句,结果顺序是未定义的;
    • LIMIT:可选项,分页查询,只返回指定数量的记录,避免查询全表数据导致数据库卡死。

    上面说的很重要,很重要!!!大家要牢记。

    3.2 基础查询:查什么、从哪查

    基础查询是所有复杂查询的基础

    我们先创建一个成绩表,用于下面的示例:

    — 创建表结构
    CREATE TABLE exam_result (
    id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(20) NOT NULL COMMENT '同学姓名',
    chinese float DEFAULT 0.0 COMMENT '语文成绩',
    math float DEFAULT 0.0 COMMENT '数学成绩',
    english float DEFAULT 0.0 COMMENT '英语成绩'
    );
    — 插入测试数据
    INSERT INTO exam_result (name, chinese, math, english) VALUES
    ('张伟', 85, 92, 78),
    ('李静', 79, 86, 88),
    ('王强', 91, 77, 69),
    ('刘芳', 88, 81, 90),
    ('陈杰', 72, 68, 75),
    ('杨敏', 83, 94, 82),
    ('黄伟', 66, 79, 67),
    ('赵娜', 93, 88, 91);

    3.2.1 全列查询(SELECT *):尽量少用,只适合临时调试
    语法 & 示例

    全列查询的语法最简单,就是用*代表所有列:

    SELECT * FROM exam_result;

    执行这条语句后,数据库会返回exam_result表中的所有列和所有行数据,比如学生的 ID、姓名、语文、数学、英语成绩。

    为什么不推荐使用?

    很多新手刚学 MySQL 时,最爱用SELECT *,觉得方便快捷,但在实际开发中,强烈不推荐使用,原因有 4 个:

  • 查询性能极差:查询的列越多,数据库需要从磁盘读取的数据量就越大,传输到程序的网络开销也越大。比如表有 20 个列,你只需要 3 个列,用SELECT *会多读取 17 个列的数据,速度慢很多;
  • 可能导致索引失效:数据库的索引是为特定列设计的。如果查询的列包含索引列和非索引列,数据库可能无法使用 “覆盖索引”,必须回表查询,进一步降低效率;
  • 数据冗余,处理成本高:程序拿到大量不需要的列数据,需要额外处理,增加了内存和 CPU 开销;
  • 表结构变更会导致程序报错:如果后续给表新增一列 “排名”,程序用SELECT *查询后,可能会因为 “列数量增加” 而报错,而指定列查询的语句完全不受影响。
  • 适用场景:仅适合两种情况 —— 一是临时调试,快速查看表中的数据;二是查询表的所有列(这种场景在实际开发中极少)。

    3.2.2 指定列查询(推荐使用,性能最优)
    语法 & 示例

    指定列查询的语法是 “SELECT 列1, 列2, … FROM 表名”,比如查询成绩表中的 “姓名” 和 “英语成绩”:

    SELECT name, english FROM exam_result;

    执行后,数据库只会返回name和english两列数据,简洁高效。

    核心优势 & 小技巧

    这种查询方式的优势我们在前面已经提过,这里补充 2 个新手必知的小技巧:

  • 列的顺序可以和表定义顺序不一致:比如表的列顺序是id→name→chinese→math→english,你可以写成SELECT english, name FROM exam_result,数据库会按你指定的顺序返回结果,完全不影响使用;
  • 可以重复查询同一列:虽然实际场景很少用,但语法是允许的,比如SELECT name, name FROM exam_result,会返回两列 name 数据。
  • 3.2.3 表达式查询:对列数据进行 “加工处理”

    有时候我们需要的不是列的原始值,而是基于列的计算结果,这就是 “表达式查询” 的核心作用。表达式可以是 “无字段表达式”“单字段表达式”“多字段表达式”,我们分别讲解。

    场景 1:无字段表达式(和列无关,适合测试 / 标识)

    无字段表达式就是 “不包含任何列的表达式”,比如固定值、数学公式,适合测试或给结果加标识。

    示例:查询成绩表中的 “姓名” 和 “固定值 10”,语法如下:

    SELECT name, 10 FROM exam_result;

    执行后,每条记录都会多出一列值为 10 的数据。这种方式在实际开发中主要用于测试,比如验证查询是否能正常返回结果。

    场景 2:单字段表达式(基于单个列计算,适合简单处理)

    单字段表达式就是 “只包含一个列的表达式”,比如给英语成绩加 10 分、乘以 2,适合对单个列进行简单加工。

    示例 1:查询姓名和 “英语成绩加 10 分” 的结果,语法如下:

    SELECT name, english + 10 FROM exam_result;

    示例 2:查询姓名和 “英语成绩乘以 2” 的结果,语法如下:

    SELECT name, english * 2 FROM exam_result;

    场景 3:多字段表达式(基于多个列计算,实战最常用)

    多字段表达式就是 “包含多个列的表达式”,比如计算总分、平均分,这是实际开发中最常用的表达式类型。

    示例:查询姓名和 “总分(语文 + 数学 + 英语)”,语法如下:

    SELECT name, chinese + math + english FROM exam_result;

    执行后,数据库会自动计算每个学生的总分并返回,比在程序里计算效率高 10 倍以上 —— 因为数据库是专门做数据计算的,比程序更擅长这个工作。

    避坑要点:NULL 参与表达式的结果是 NULL

    这里要补充一个非常重要的知识点:如果表达式中的列值包含NULL,那么整个表达式的结果都是NULL。

    比如某个学生的english成绩是NULL(未考),那么chinese + math + english的结果就是NULL,而不是chinese + math。

    解决方案:用IFNULL函数把NULL转换成 0,语法如下:

    SELECT name, chinese + math + IFNULL(english, 0) FROM exam_result;

    这样,即使english成绩是NULL,也会被当作 0 计算,总分结果就正常了。

    3.2.4 别名:给查询结果 “起个好记的名字”
    问题背景:为什么需要别名?

    用表达式查询时,结果列的名称会是 “表达式本身”,比如chinese + math + english,非常不直观。别人看你的查询结果,根本不知道这列代表什么意思。

    这时候,“别名” 就派上用场了 —— 给列或表达式起一个通俗易懂的名字,比如 “总分”“平均分”“英语加分后”,可读性直接翻倍。

    语法 & 示例

    别名的语法很简单:SELECT 列/表达式 AS 别名 FROM 表名,其中AS可以省略,直接写别名即可。

    示例 1:给总分表达式起别名 “总分”,语法如下:

    SELECT name, chinese + math + english AS 总分 FROM exam_result;

    那么我们起别名时所写的字符串,是可以不用加‘’单引号的哦。

    示例 2:省略AS,直接写别名,语法更简洁:

    SELECT name, chinese + math + english 总分 FROM exam_result;

    执行后,结果列的名称会变成 “总分”,一眼就能看懂。

    核心注意点:别名不能用在 WHERE 子句中

    这是新手查询时最常遇到的报错:Unknown column '总分' in 'where clause',意思是 “WHERE 子句中找不到‘总分’这个列”。

    为什么会这样?因为 MySQL 的执行顺序是:先执行 WHERE 子句筛选数据,再执行 SELECT 子句计算别名显示。

    这一点我们要严格注意哦。

    当执行 WHERE 子句时,“总分” 这个别名还没有被计算出来,所以数据库不知道你说的 “总分” 是什么,自然会报错,那么最后显示出结果时,还是会使用别名,这一点大家需要注意一下。

    正确的做法是:在 WHERE 子句中使用原始表达式,而不是别名。比如查询 “总分 < 200 的学生”,正确语法是:

    SELECT name, chinese + math + english 总分 FROM exam_result WHERE chinese + math + english < 200;

    虽然写起来麻烦,但这是唯一正确的方式。不过别担心,别名可以用在ORDER BY子句中,因为ORDER BY的执行顺序在SELECT之后。

    3.2.5 去重查询:用DISTINCT去掉重复记录
    适用场景

    当查询结果中有大量重复记录时,用DISTINCT可以快速去掉重复记录,只保留唯一值。比如查询成绩表中的 “数学成绩”,去掉重复的分数。

    语法 & 示例

    示例 1:查询所有数学成绩,包含重复值:

    SELECT math FROM exam_result;

    执行后,结果中会有重复的 98 分(黄伟和小明都是 79 分)。

    示例 2:用DISTINCT去重,查询唯一的数学成绩:

    SELECT DISTINCT math FROM exam_result;

    执行后,重复的 79 分只会出现一次,结果更简洁。

    核心注意点:DISTINCT作用于所有指定列(不是单个列)

    新手很容易误以为DISTINCT只作用于 “紧跟它的列”,但实际上,DISTINCT作用于所有指定列的组合。

    比如查询DISTINCT name, math,数据库会判断 “姓名 + 数学成绩” 的组合是否重复,而不是只判断姓名是否重复。

    示例:查询 “姓名 + 数学成绩” 的唯一组合:

    SELECT DISTINCT name, math FROM exam_result;

    即使两个学生的数学成绩相同,但姓名不同,也会被视为不同的记录,不会被去重。

    3.3 WHERE 条件:筛选数据的 “黄金筛子”

    WHERE子句是查询的核心灵魂,它的作用是 “筛选出符合条件的行数据”,就像一个黄金筛子,只留下你需要的豆子。

    要用好WHERE子句,必须先掌握 “运算符”—— 这是筛选条件的基础。我们把运算符分为 “比较运算符” 和 “逻辑运算符” 两大类

    3.3.1 比较运算符:判断 “数据是否符合条件”

    比较运算符是用来判断 “一个值是否符合某个条件” 的符号,比如 “大于”“小于”“等于”“在某个范围内”。:

    比较运算符通俗解释适用场景示例(以 exam_result 表为例)
    > 大于 筛选大于某个值的数据 WHERE english > 80(英语成绩大于 80 分)
    >= 大于等于 筛选大于等于某个值的数据 WHERE math >= 90(数学成绩大于等于 90 分)
    < 小于 筛选小于某个值的数据 WHERE chinese < 60(语文成绩小于 60 分)
    <= 小于等于 筛选小于等于某个值的数据 WHERE english <= 100(英语成绩小于等于 100 分)
    = 等于 筛选等于某个值的数据 WHERE name = '孙悟空'(姓名是孙悟空)
    !=/<> 不等于 筛选不等于某个值的数据 WHERE math != 98(数学成绩不是 98 分)
    BETWEEN a AND b 在 [a,b] 范围内(包含 a 和 b) 筛选区间内的数据 WHERE chinese BETWEEN 80 AND 90(语文 80-90 分)
    IN (值1, 值2, …) 是括号中的任意一个值 筛选多个离散值的数据 WHERE math IN (58, 59, 98, 99)(数学成绩是这四个值之一)
    IS NULL 是 NULL 值 筛选空值数据 WHERE qq IS NULL(QQ 号为空的学生)
    IS NOT NULL 不是 NULL 值 筛选非空值数据 WHERE qq IS NOT NULL(有 QQ 号的学生)
    LIKE 模糊匹配 筛选符合模糊规则的数据 WHERE name LIKE '孙%'(姓孙的学生)

    我们对其中最常用、最容易出错的运算符进行详细讲解

    重点 1:BETWEEN a AND b—— 区间筛选,简洁高效

    BETWEEN a AND b的作用是 “筛选出大于等于 a 且小于等于 b 的数据”,等价于>=a AND <=b,但语法更简洁。

    示例:查询语文成绩在 80-90 分之间的学生,两种写法对比:

    • 用AND:SELECT name, chinese FROM exam_result WHERE chinese >= 80 AND chinese <= 90;
    • 用BETWEEN:SELECT name, chinese FROM exam_result WHERE chinese BETWEEN 80 AND 90;

    两种写法的结果完全一致,但BETWEEN的语法更简洁,可读性更强。

    避坑要点:BETWEEN a AND b中的a必须小于等于b,否则会返回空结果。比如WHERE chinese BETWEEN 90 AND 80,不会报错,但结果为空。

    重点 2:IN (值1, 值2, …)—— 多值筛选,替代多个 OR

    IN的作用是 “筛选出值等于括号中任意一个的数据”,等价于多个OR的组合,但语法更简洁,效率更高。

    示例:查询数学成绩是 58、59、98、99 分的学生,两种写法对比:

    • 用OR:SELECT name, math FROM exam_result WHERE math=58 OR math=59 OR math=98 OR math=99;
    • 用IN:SELECT name, math FROM exam_result WHERE math IN (58,59,98,99);

    当需要筛选的值超过 3 个时,IN的优势非常明显,不仅写起来快,数据库的执行效率也更高。

    避坑要点:IN括号中的值类型必须和列类型一致。比如math是整数类型,括号中不能写字符串(比如'98'),虽然 MySQL 会自动转换,但可能导致索引失效,降低查询效率。

    重点 3:LIKE—— 模糊匹配,查 “姓孙的学生”“名字带小的学生”

    LIKE是模糊匹配的核心运算符,它的关键是两个通配符:%和_,我们分别讲解它们的作用:

    • %:匹配任意多个字符(包括 0 个字符),比如'孙%'匹配 “孙悟空”“孙权”“孙” 等所有以 “孙” 开头的字符串;
    • _:匹配严格的一个字符,比如'孙_'只匹配 “孙权” 这种两个字的名字,不匹配 “孙悟空”(三个字)。

    我们用 4 个经典示例,把LIKE的用法讲透:

  • 查询姓孙的学生:用'孙%',匹配以 “孙” 开头的所有姓名:

    SELECT name FROM exam_result WHERE name LIKE '孙%';

    结果会返回 “孙悟空” 和 “孙权”;

  • 查询名字以 “谋” 结尾的学生:用'%谋',匹配以 “谋” 结尾的所有姓名:

    SELECT name FROM exam_result WHERE name LIKE '%谋';

    结果会返回 “孙仲谋”;

  • 查询名字带 “悟” 的学生:用'%悟%',匹配包含 “悟” 的所有姓名:

    SELECT name FROM exam_result WHERE name LIKE '%悟%';

    结果会返回 “孙悟空” 和 “猪悟能”;

  • 查询名字是两个字且姓孙的学生:用'孙_',只匹配两个字的姓名:

    SELECT name FROM exam_result WHERE name LIKE '孙_';

    结果只会返回 “孙权”,不会返回 “孙悟空”。

  • 避坑要点:查询含%或_的字符串,需要用转义字符\\。比如查询名字带%的学生,语法是WHERE name LIKE '%\\%%',其中\\%代表 “真实的 % 字符”,而不是通配符。

    重点 4:= vs <=>——NULL 值的判断

    这是新手最容易混淆的两个运算符,核心区别是 “是否支持 NULL 值判断”。

    • =:NULL 不安全,无法判断 NULL 值。比如NULL = NULL的结果是NULL(不是 TRUE),1 = NULL的结果也是NULL;
    • <=>:NULL 安全,可以判断 NULL 值。比如NULL <=> NULL的结果是TRUE,1 <=> NULL的结果是FALSE。

    我们用两个示例对比它们的区别:

  • 用=判断 NULL 值,结果为空:

    SELECT name FROM students WHERE qq = NULL;

    执行后,即使有学生的qq是 NULL,也不会返回任何结果;

  • 用<=>判断 NULL 值,结果正确:

    SELECT name FROM students WHERE qq <=> NULL;

    执行后,会返回所有qq为 NULL 的学生。

  • 最佳实践:判断 NULL 值时,优先用IS NULL或IS NOT NULL,比<=>更易读。比如WHERE qq IS NULL,一眼就能看懂是 “查询 QQ 号为空的学生”。

    3.3.2 逻辑运算符:组合多个条件(复杂筛选的核心)

    逻辑运算符是用来 “组合多个比较条件” 的符号,比如 “语文 > 80 且数学 > 80”“英语 < 60 或语文 < 60”“不是姓孙的学生”。

    常用的逻辑运算符有三个:AND、OR、NOT,我们用一张表总结它们的作用:

    逻辑运算符通俗解释优先级示例(以 exam_result 表为例)
    AND 多个条件都满足才返回 TRUE WHERE chinese > 80 AND math > 80(语文和数学都大于 80 分)
    OR 多个条件任意一个满足就返回 TRUE WHERE english < 60 OR chinese < 60(英语或语文不及格)
    NOT 对条件取反,TRUE 变 FALSE,FALSE 变 TRUE 最高 WHERE name NOT LIKE '孙%'(不是姓孙的学生)
    核心注意点 1:逻辑运算符的优先级(用括号改变顺序)

    逻辑运算符的优先级是:NOT > AND > OR。这意味着,当一个筛选条件中同时包含AND和OR时,数据库会先计算AND的条件,再计算OR的条件。

    比如条件WHERE chinese > 80 OR math > 80 AND english > 80,数据库会先计算math > 80 AND english > 80,再和chinese > 80做OR运算。

    如果你的需求是 “语文> 80 或数学 > 80,并且英语 > 80”,必须用括号改变优先级,语法如下:

    WHERE (chinese > 80 OR math > 80) AND english > 80;

    括号的优先级最高,数据库会先计算括号内的OR条件,再计算括号外的AND条件,确保结果符合你的预期。

    核心注意点 2:AND和OR的短路特性(提升查询效率)

    逻辑运算符有 “短路特性”,这是提升查询效率的关键:

    • AND短路:如果第一个条件为 FALSE,第二个条件不会被计算。比如WHERE 1=2 AND chinese > 80,因为1=2是 FALSE,数据库不会计算chinese > 80,直接返回 FALSE;
    • OR短路:如果第一个条件为 TRUE,第二个条件不会被计算。比如WHERE 1=1 OR chinese > 80,因为1=1是 TRUE,数据库不会计算chinese > 80,直接返回 TRUE。

    在实际开发中,把 “筛选结果少” 的条件放在AND前面,把 “筛选结果多” 的条件放在OR前面,可以显著提升查询效率。

    3.3.3 经典筛选场景实战

    理论讲得再多,不如实战来得实在。我们总结了 10 个经典的筛选场景。

    场景查询逻辑完整语法
    1. 英语不及格的学生 英语成绩 < 60 SELECT name, english FROM exam_result WHERE english < 60;
    2. 语文 80-90 分的学生 语文成绩在 80 到 90 之间 SELECT name, chinese FROM exam_result WHERE chinese BETWEEN 80 AND 90;
    3. 数学是 98 或 99 分的学生 数学成绩在 (98,99) 中 SELECT name, math FROM exam_result WHERE math IN (98,99);
    4. 姓孙的学生 姓名以孙开头 SELECT name FROM exam_result WHERE name LIKE '孙%';
    5. 语文 > 英语的学生 语文成绩 > 英语成绩 SELECT name, chinese, english FROM exam_result WHERE chinese > english;
    6. 总分 < 200 的学生 语文 + 数学 + 英语 < 200 SELECT name, chinese+math+english 总分 FROM exam_result WHERE chinese+math+english < 200;
    7. 语文 > 80 且不姓孙的学生 语文 > 80 且 姓名不是孙开头 SELECT name, chinese FROM exam_result WHERE chinese > 80 AND name NOT LIKE '孙%';
    8. 名字是两个字的学生 姓名长度为 2(用 CHAR_LENGTH 函数) SELECT name FROM exam_result WHERE CHAR_LENGTH(name) = 2;
    9. 有 QQ 号的学生 QQ 号不为 NULL SELECT name, qq FROM students WHERE qq IS NOT NULL;
    10. 总分 > 200 且语文 < 数学的学生 总分 > 200 且 语文 < 数学 SELECT name, chinese, math FROM exam_result WHERE (chinese+math+english) > 200 AND chinese < math;

    3.4 ORDER BY:给查询结果 “排个序”(让数据更有规律)

    默认情况下,查询结果的顺序是 “未定义” 的 —— 数据库怎么存储数据,就怎么返回数据,完全没有规律。

    而ORDER BY子句的作用,就是 “给查询结果按指定规则排序”,让数据更有规律,比如 “按数学成绩从高到低排序”“按总分从低到高排序”。

    3.4.1 排序的核心语法

    SELECT … FROM 表名 [WHERE …] ORDER BY 列1 [ASC/DESC], 列2 [ASC/DESC], …;

    语法拆解:

    • 列1, 列2, …:指定要排序的列,可以是原始列、表达式、别名(注意,order by就可以使用别名了哦);
    • ASC:升序排序(从上到下为从小到大),默认值,可以省略;
    • DESC:降序排序(从上到下为从大到小),必须显式指定;
    • 多列排序:优先级按书写顺序,先按列 1 排序,列 1 值相同的记录,再按列 2 排序。
    3.4.2 经典排序场景实战

    我们用 6 个经典场景,把排序的用法讲透,包括单字段排序、多字段排序、表达式排序、别名排序、NULL 值排序

    — 创建表结构
    CREATE TABLE exam_result (
    id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(20) NOT NULL COMMENT '同学姓名',
    chinese float DEFAULT 0.0 COMMENT '语文成绩',
    math float DEFAULT 0.0 COMMENT '数学成绩',
    english float DEFAULT 0.0 COMMENT '英语成绩'
    );
    — 插入测试数据
    INSERT INTO exam_result (name, chinese, math, english) VALUES
    ('唐三藏', 67, 98, 56),
    ('孙悟空', 87, 78, 77),
    ('猪悟能', 88, 98, 90),
    ('曹孟德', 82, 84, 67),
    ('刘玄德', 55, 85, 45),
    ('孙权', 70, 73, 78),
    ('宋公明', 75, 65, 30);

    场景 1:单字段升序排序(默认 ASC)

    示例:查询学生姓名和数学成绩,按数学成绩升序排序(从小到大):

    SELECT name, math FROM exam_result ORDER BY math;

    执行后,数学成绩从低到高排列,宋公明(65 分)在最前面,唐三藏和猪悟能(98 分)在最后面。

    这里要注意,order by前面不能直接就加where哦,换句话来说where后面是不能直接就加order by的,中间必须要隔个筛选条件,不然就会报错:

    那么我们加上筛选条件的话,MySQL就会先根据筛选条件筛选完数据之后,再去进行order by语句进行排序。

    场景 2:单字段降序排序(显式 DESC)

    示例:查询学生姓名和数学成绩,按数学成绩降序排序(从大到小):

    SELECT name, math FROM exam_result ORDER BY math DESC;

    执行后,数学成绩从高到低排列,唐三藏和猪悟能(98 分)在最前面,宋公明(65 分)在最后面。

    场景 3:多字段排序(先降序,后升序)

    示例:查询学生姓名、数学成绩、英语成绩,先按数学降序,再按英语升序排序:

    SELECT name, math, english FROM exam_result ORDER BY math DESC, english;
    #注意中间要用逗号隔开

    执行后,数学成绩高的记录排在前面;数学成绩相同的记录(唐三藏和猪悟能都是 98 分),按英语成绩升序排序 —— 唐三藏的英语成绩 56 分,猪悟能的英语成绩 90 分,所以唐三藏排在猪悟能前面。

    场景 4:表达式排序(按总分降序)

    示例:查询学生姓名和总分,按总分降序排序:

    SELECT name, chinese+math+english 总分 FROM exam_result ORDER BY chinese+math+english DESC;

    执行后,总分最高的猪悟能(276 分)排在最前面,总分最低的宋公明(170 分)排在最后面。

    场景 5:别名排序(更简洁,推荐使用)

    那么我们要知道,因为排序是在筛选之后才进行排序的,所以,order by是可以使用别名的哦!!!

    这个主要还是因为MySQL语句的执行顺序,我们后面会专门一篇博客来进行讲解。

    示例:查询学生姓名和总分,用别名 “总分” 排序,语法更简洁:

    SELECT name, chinese+math+english 总分 FROM exam_result ORDER BY 总分 DESC;

    执行结果和场景 4 完全一致,但语法更易读 —— 这就是别名的优势,ORDER BY的执行顺序在SELECT之后,所以可以直接用别名。

    场景 6:NULL 值排序(NULL 视为最小值)

    示例:查询学生姓名和 QQ 号,按QQ 号升序排序:

    SELECT name, qq FROM students ORDER BY qq;

    执行后,QQ 号为 NULL 的学生(唐三藏、曹孟德)排在最前面,QQ 号不为 NULL 的学生(孙悟空)排在最后面 —— 因为 MySQL 中,NULL 被视为 “比任何值都小”。

    如果按 QQ 号降序排序,NULL 值会排在最后面,语法如下:

    SELECT name, qq FROM students ORDER BY qq DESC;

    3.4.3 排序的避坑要点

    排序虽然简单,但也有 3 个核心注意事项,我们逐一说明:

  • 字符串排序是按 “字符编码” 排序,不是按 “拼音” 排序:比如查询姓名排序,SELECT name FROM exam_result ORDER BY name,结果是按字符的 Unicode 编码排序,不是按拼音排序。如果需要按拼音排序,需要用ORDER BY CONVERT(name USING gbk);
  • 排序会影响查询效率,数据量大时慎用:排序需要数据库额外消耗 CPU 和内存来整理数据顺序。当数据量超过 10 万行时,尽量通过 “索引排序”(给排序列建索引)来提升效率;
  • 不要依赖排序后的结果顺序做业务逻辑:如果没有ORDER BY子句,数据库返回的结果顺序是不确定的,可能每次查询都不一样。业务逻辑中需要有序数据时,必须显式指定ORDER BY。
  • 3.5 LIMIT:分页查询(避免查询全表数据,保护数据库)

    当表中数据量很大时(比如 100 万行),一次性查询全表数据会导致数据库卡死 

    而LIMIT子句的作用,就是 “分页查询数据”,只返回你需要的前 N 条记录,或者从第 M 条开始的 N 条记录,完美解决了 “查询全表数据” 的问题。

    而且,一般来说,limit的执行优先级,是最低的,所以我们一般也是把它放在后面的哦。

    3.5.1 分页的三种语法(推荐第三种,可读性最强)

    MySQL 支持三种LIMIT语法,我们分别讲解,最后推荐最优写法:

  • 语法 1:查询前 N 条记录

    SELECT … FROM 表名 LIMIT n;

    示例:查询成绩表的前 3 条记录:SELECT * FROM exam_result LIMIT 3;

  • 语法 2:从第 s 条开始,查询 n 条记录(不推荐,可读性差)

    SELECT … FROM 表名 LIMIT s, n;

    注意:这里的s是起始下标,从 0 开始,不是从 1 开始。比如LIMIT 3, 3代表 “从第 4 条记录开始,查询 3 条记录”;

  • 语法 3:从第 s 条开始,查询 n 条记录(推荐,可读性强)

    SELECT … FROM 表名 LIMIT n OFFSET s;

    这个语法和语法 2 的效果完全一致,但可读性更强 ——LIMIT n代表 “查询 n 条记录”,OFFSET s代表 “跳过前 s 条记录”。

  • 3.5.2 经典分页场景实战(按页查询,覆盖 90% 的业务需求)

    实际业务中,最常见的需求是 “按页查询数据”,比如 “每页显示 3 条记录,查询第 1 页、第 2 页、第 3 页”。

    我们以成绩表为例,搭配ORDER BY子句(确保分页顺序稳定),完整实现按页查询:

    • 分页参数说明:每页条数page_size=3,当前页码page_num,起始下标s=(page_num-1)*page_size;
    • 第 1 页:page_num=1,s=(1-1)*3=0,语法如下:

      SELECT id, name, math, english, chinese FROM exam_result ORDER BY id LIMIT 3 OFFSET 0;

      结果返回 id=1、2、3 的 3 条记录;

    • 第 2 页:page_num=2,s=(2-1)*3=3,语法如下:

      SELECT id, name, math, english, chinese FROM exam_result ORDER BY id LIMIT 3 OFFSET 3;

      结果返回 id=4、5、6 的 3 条记录;

    • 第 3 页:page_num=3,s=(3-1)*3=6,语法如下:

      SELECT id, name, math, english, chinese FROM exam_result ORDER BY id LIMIT 3 OFFSET 6;

      结果返回 id=7 的 1 条记录(因为表中只有 7 条记录),不足 3 条也不会报错。

    3.5.3 分页的核心注意事项

    分页查询虽然简单,我们逐一说明:

  • 分页必须搭配 ORDER BY,否则结果顺序不稳定:如果没有ORDER BY子句,数据库返回的结果顺序是不确定的,可能出现 “第 1 页和第 2 页有重复记录” 的情况。强烈建议:分页时必须搭配ORDER BY,且排序列最好是主键或唯一键(比如id),确保顺序绝对稳定;
  • 起始下标 s 不能太大,否则查询效率极低:当s超过 10 万时,LIMIT n OFFSET s的查询效率会急剧下降 —— 因为数据库需要先跳过s条记录,才能返回n条记录。解决方案:用 “游标分页”,比如WHERE id > 100000 LIMIT 3,利用主键索引提升效率;
  • 对未知表查询时,先加 LIMIT 1,避免卡死数据库:如果你不知道一张表有多少数据,先执行SELECT * FROM 未知表 LIMIT 1;,查看表结构和数据量,再决定是否查询全表;
  • COUNT (*) 统计总条数,计算总页数:要实现 “分页控件”(比如显示 “共 3 页,当前第 1 页”),需要先统计表的总条数,再计算总页数。语法如下:
    • 统计总条数:SELECT COUNT(*) FROM exam_result;(结果是 7);
    • 计算总页数:总页数=CEIL(总条数/每页条数)=CEIL(7/3)=3(CEIL是向上取整函数)。
  • 四、Update(更新数据):修改表里已有的数据(务必慎用!)

    更新数据的核心语法是UPDATE,它的作用是 “修改表里已有的数据”。但这个操作务必慎用—— 尤其是不带WHERE子句的UPDATE,会修改全表数据,造成灾难性后果。

    我们可以把UPDATE比作 “修改 Excel 表格的单元格内容”:

    • UPDATE 表名:指定你要修改哪张表;
    • SET 列=值:指定你要修改哪些列,改成什么值;
    • WHERE 条件:指定你要修改哪些行,几乎必加,否则修改全表。

    4.1 更新的核心语法结构

    UPDATE 表名
    SET 列1 = 表达式1, 列2 = 表达式2, …
    [WHERE/order by 筛选条件]
    [ORDER BY 排序条件]
    [LIMIT 限制数量];

    语法拆解:

    • SET:必填项,指定要修改的列和新值。新值可以是固定值、表达式(比如math = math + 30)、函数(比如name = UPPER(name));
    • WHERE:可选项,但实战中几乎必加,筛选要修改的行。不加WHERE会修改全表数据,生产环境中 99% 的误操作都是因为这个,注意,也可以只使用order by哦。

    where+set就是说明,我们要修改哪一行(符合where条件的那一行)的哪个字段(set后面所跟着的字段)

    • ORDER BY:可选项,对要修改的行排序,搭配LIMIT使用,比如 “修改总分倒数前三的学生”;
    • LIMIT:可选项,限制修改的行数,避免一次修改太多数据。

    注意不用加from哦。

    4.2 经典更新场景实战

    场景 1:修改单条数据(指定值更新,最基础)

    示例:把 “孙悟空” 的数学成绩从 78 分改成 80 分。

    操作步骤(安全第一,三步法)

    更新数据的安全三步法:先查、再更、再验证,确保万无一失。

  • 第一步:先查询原数据,确认要修改的行:

    SELECT name, math FROM exam_result WHERE name = '孙悟空';

    执行后,确认孙悟空的数学成绩是 78 分;

  • 第二步:执行更新操作,加上 WHERE 条件:

    UPDATE exam_result SET math = 80 WHERE name = '孙悟空';

    执行后,数据库返回提示:Query OK, 1 row affected (0.04 sec),代表修改了 1 行数据;

  • 第三步:查询修改后的数据,验证结果:

    SELECT name, math FROM exam_result WHERE name = '孙悟空';

    执行后,确认孙悟空的数学成绩已经变成 80 分。

  • 避坑要点
    • 必须加WHERE条件,且条件要精准:比如用name='孙悟空',而不是math=78,因为可能有多个学生的数学成绩是 78 分;
    • 先查询再更新:这是最安全的操作习惯,尤其是在生产环境中,永远不要直接执行UPDATE语句。
    场景 2:修改多条数据(多列同时更新,实战常用)

    示例:把 “曹孟德” 的数学成绩改成 60 分,语文成绩改成 70 分。

    语法 & 步骤
  • 先查询原数据:

    SELECT name, math, chinese FROM exam_result WHERE name = '曹孟德';

  • 执行多列更新:

    UPDATE exam_result SET math = 60, chinese = 70 WHERE name = '曹孟德';

  • 验证结果:

    SELECT name, math, chinese FROM exam_result WHERE name = '曹孟德';

  • 核心优势
    • 一次修改多列数据,效率高:比执行两次UPDATE语句快一倍;
    • 语法简洁:多个列之间用逗号分隔,清晰明了。
    场景 3:基于原值更新(表达式更新,超实用)

    示例:给 “总分倒数前三的学生” 的数学成绩加 30 分。

    语法 & 步骤

    这种场景需要搭配ORDER BY和LIMIT使用,核心是 “先排序,再限制行数,最后更新”。

  • 先查询总分倒数前三的学生:

    SELECT name, math, chinese+math+english 总分 FROM exam_result ORDER BY 总分 LIMIT 3;

    结果是宋公明、刘玄德、曹孟德;

  • 执行表达式更新:

    UPDATE exam_result SET math = math + 30 ORDER BY chinese+math+english LIMIT 3;

    注意:MySQL 不支持math += 30这种简写语法,必须写math = math + 30;

  • 验证结果:

    SELECT name, math FROM exam_result WHERE name IN ('宋公明', '刘玄德', '曹孟德');

  • 避坑要点
    • 表达式更新要注意数据类型:比如math是FLOAT类型,加 30 后不会溢出;如果是INT类型,要确保结果不超过列的最大值;
    • ORDER BY和LIMIT的顺序不能反:必须先ORDER BY排序,再LIMIT限制行数,否则会修改错误的行。
    场景 4:修改全表数据(慎用!务必先备份)

    示例:把所有学生的语文成绩改成原来的 2 倍。

    语法 & 步骤
  • 先备份表数据(生产环境必须做):

    CREATE TABLE exam_result_backup LIKE exam_result;
    INSERT INTO exam_result_backup SELECT * FROM exam_result;

  • 执行全表更新:

    UPDATE exam_result SET chinese = chinese * 2;

    执行后,数据库返回提示:Query OK, 7 rows affected (0.05 sec),代表修改了 7 行数据;

  • 验证结果:

    SELECT name, chinese FROM exam_result;

  • 避坑警告
    • 生产环境中,修改全表数据前必须备份:一旦修改错误,只能通过备份恢复数据;
    • 尽量分批次修改:如果表中有 100 万行数据,不要一次修改全表,分 10 次,每次修改 10 万行,避免锁表;
    • 在业务低峰期执行:全表更新会锁表,导致其他用户无法读写数据,最好在凌晨等低峰期执行。

    4.3 更新的核心避坑指南

    更新数据是高危操作:

  • 永远不要在生产环境中执行不带 WHERE 的 UPDATE:这是最致命的错误,没有之一。如果不小心执行了,立即执行ROLLBACK(前提是开启了事务),并恢复备份;
  • 开启事务,先测试再提交:在执行UPDATE前,先执行START TRANSACTION;开启事务,执行完后查询验证,确认正确再执行COMMIT;提交事务;如果错误,执行ROLLBACK;回滚事务。语法如下:

    START TRANSACTION; — 开启事务
    UPDATE exam_result SET math = 80 WHERE name = '孙悟空'; — 执行更新
    SELECT name, math FROM exam_result WHERE name = '孙悟空'; — 验证结果
    COMMIT; — 确认正确,提交事务
    — ROLLBACK; — 如果错误,回滚事务

  • 用主键或唯一键作为 WHERE 条件:比如WHERE id=2,而不是WHERE name='孙悟空',因为主键是唯一的,不会修改错误的行;
  • 避免更新主键或唯一键:主键是表的唯一标识,更新主键可能导致主键冲突,或破坏外键关联;
  • 注意外键约束:如果表有外键关联,更新外键列时,要确保新值在主表中存在,否则会报错Foreign key constraint fails。
  • 五、Delete(删除数据):移除表里不需要的行

    删除数据的核心语法是DELETE,它的作用是 “移除表里不需要的行数据”。这个操作比更新更危险—— 删除的数据很难恢复,尤其是不带WHERE的DELETE,会删除全表数据,造成无法挽回的损失。

    我们可以把DELETE比作 “删除 Excel 表格的整行数据”:

    • DELETE FROM 表名:指定你要删除哪张表的数据;
    • WHERE 条件:指定你要删除哪些行,几乎必加,否则删除全表;
    • ORDER BY/LIMIT:指定删除的顺序和行数,避免一次删除太多数据。

    5.1 删除的核心语法结构

    DELETE FROM 表名
    [WHERE 筛选条件]
    [ORDER BY 排序条件]
    [LIMIT 限制数量];

    语法拆解:

    • DELETE FROM:固定关键字组合,指定要删除数据的表;
    • WHERE:可选项,但实战中几乎必加,筛选要删除的行。不加WHERE会删除全表数据;
    • ORDER BY:可选项,对要删除的行排序,搭配LIMIT使用;
    • LIMIT:可选项,限制删除的行数,避免一次删除太多数据。

    5.2 经典删除场景实战

    场景 1:删除单条数据(最基础,安全第一)

    示例:删除 “孙悟空” 的考试成绩记录。

    操作步骤(安全三步法)

    删除数据的安全三步法:先查、再删、再验证,和更新数据一样,安全永远是第一位的。

  • 第一步:先查询要删除的行,确认无误:

    SELECT * FROM exam_result WHERE name = '孙悟空';

    执行后,确认要删除的记录是 id=2 的行;

  • 第二步:执行删除操作,加上 WHERE 条件:

    DELETE FROM exam_result WHERE name = '孙悟空';

    执行后,数据库返回提示:Query OK, 1 row affected (0.04 sec),代表删除了 1 行数据;

  • 第三步:查询验证,确认记录已删除:

    SELECT * FROM exam_result WHERE name = '孙悟空';

    执行后,返回空结果,代表记录已成功删除。

  • 避坑要点
    • 必须加WHERE条件,且条件要精准:比如用name='孙悟空',或更精准的id=2;
    • 不要用DELETE * FROM 表名:这种语法是错误的,DELETE后面不需要加*,直接写DELETE FROM 表名即可。
    场景 2:删除全表数据(DELETE,慎用!可回滚)

    示例:删除测试表 for_delete 的所有数据。

    操作步骤(务必先备份)
  • 第一步:创建测试表并插入数据先准备一张测试表,避免操作生产数据:

    CREATE TABLE for_delete (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(20)
    );
    INSERT INTO for_delete (name) VALUES ('A'), ('B'), ('C');

    执行后,表中有 3 条数据,id 分别为 1、2、3。

  • 第二步:备份数据(关键!)执行删除全表操作前,一定要备份:

    CREATE TABLE for_delete_backup LIKE for_delete;
    INSERT INTO for_delete_backup SELECT * FROM for_delete;

  • 第三步:执行全表删除不带 WHERE 子句的 DELETE 会删除全表数据:

    DELETE FROM for_delete;

    执行后,数据库返回提示:Query OK, 3 rows affected (0.03 sec),代表删除了 3 行数据。

  • 第四步:验证结果查询表数据,确认全表已空:

    SELECT * FROM for_delete;

    执行后返回空结果,说明数据已删除。

  • 第五步:测试自增列特性往空表中插入一条新数据:

    INSERT INTO for_delete (name) VALUES ('D');

    查询结果会发现,新数据的 id 是 4,不是 1—— 这是 DELETE 删除全表的关键特性:自增列不会重置,会从删除前的最大值 + 1 继续递增。

  • 核心特点(DELETE 删除全表)
    • 属于 DML 操作:DELETE 是数据操作语言,执行后可以通过事务回滚(前提是未提交事务)。
    • 可以筛选和限制行数:如果加上 WHERE 和 LIMIT,可以只删除部分数据,比如 DELETE FROM for_delete WHERE id < 3 LIMIT 2;。
    • 自增列不重置:如上述示例,删除后插入新数据,id 从 4 开始,因为自增计数器的数值没有被清空。
    • 效率较低:DELETE 是逐行删除数据,会记录每一行的删除日志,数据量越大,删除速度越慢。
    场景 3:截断表(TRUNCATE,彻底清空,无法回滚)

    如果需要彻底清空表并重置自增列,可以使用 TRUNCATE 语句。它和 DELETE 删除全表的逻辑完全不同,更高效,但风险也更高。

    操作步骤
  • 第一步:创建测试表并插入数据

    CREATE TABLE for_truncate (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(20)
    );
    INSERT INTO for_truncate (name) VALUES ('A'), ('B'), ('C');

    表中有 3 条数据,id 为 1、2、3。

  • 第二步:执行截断表操作

    TRUNCATE TABLE for_truncate;

    执行后,数据库返回提示:Query OK, 0 rows affected (0.01 sec)—— 这里的 0 rows affected 是因为 TRUNCATE 不逐行处理数据,直接清空表。

  • 第三步:验证结果查询表数据,确认全表已空:

    SELECT * FROM for_truncate;

  • 第四步:测试自增列特性插入一条新数据:

    INSERT INTO for_truncate (name) VALUES ('D');

    查询结果会发现,新数据的 id 是 1—— 这是 TRUNCATE 的关键特性:重置自增列,自增计数器会被重置为初始值(通常是 1)。

  • DELETE vs TRUNCATE 核心区别(必须牢记)
    特性DELETE FROM 表名(删除全表)TRUNCATE TABLE 表名(截断表)
    操作类型 DML(数据操作语言) DDL(数据定义语言)
    能否回滚 可以(事务未提交时执行 ROLLBACK) 不可以(直接修改表结构,无法回滚)
    自增列是否重置 否(从最大值 + 1 继续递增) 是(重置为初始值 1)
    能否筛选 / 限制行数 能(加 WHERE/LIMIT) 不能(只能清空全表)
    执行效率 低(逐行删除,记录日志) 高(不处理数据,直接清空表)
    适用场景 需要删除部分数据,或需回滚的场景 彻底清空测试表,且无需保留自增列的场景
    避坑警告
    • TRUNCATE 是高危操作,执行后数据无法回滚,生产环境中务必确认表名正确,且数据已备份。
    • TRUNCATE 会清空表的所有数据,但保留表结构,表的列、索引、约束等不会被删除。
    • 如果表有外键约束,TRUNCATE 会报错,需要先删除外键约束,再执行截断操作。

    5.3 Delete 的核心避坑指南

    删除数据是不可逆操作(尤其是 TRUNCATE),一旦误删,损失可能无法挽回:

  • 永远不要在生产环境执行不带 WHERE 的 DELETE:这是最致命的错误,没有之一。如果不小心执行,立即执行 ROLLBACK(前提是开启事务),并通过备份恢复数据。
  • 执行删除前,必须开启事务并验证这是最安全的操作流程,没有例外:

    START TRANSACTION; — 开启事务
    DELETE FROM exam_result WHERE name = '孙悟空'; — 执行删除
    SELECT * FROM exam_result WHERE name = '孙悟空'; — 验证是否删除正确
    COMMIT; — 确认无误,提交事务
    — ROLLBACK; — 如果误删,立即回滚

  • 优先用主键 / 唯一键作为 WHERE 条件比如 DELETE FROM exam_result WHERE id = 2;,而不是 DELETE FROM exam_result WHERE name = '孙悟空';—— 主键是唯一的,不会误删其他行。
  • 删除大量数据时,分批次执行如果要删除 10 万行数据,不要一次执行 DELETE FROM 表名 WHERE 条件;,会导致锁表,影响业务。正确做法是分批次删除:

    DELETE FROM exam_result WHERE id < 10000 LIMIT 1000; — 每次删1000行

    循环执行,直到删除完毕。

  • 注意外键约束,避免级联删除如果表有外键关联,且外键设置了 ON DELETE CASCADE(级联删除),删除主表数据时,子表的关联数据会被自动删除。例如:
    • 主表 course(课程表),子表 student(学生表),外键 courseid 关联 course.id,且设置了 ON DELETE CASCADE。
    • 删除 course 表中 id=1 的课程,student 表中所有 courseid=1 的学生数据会被自动删除。因此,执行删除前,务必确认外键的级联规则。
  • 六、进阶操作:插入查询结果 & 聚合函数 & 分组查询

    掌握了基础的 CRUD 之后,我们需要学习三个进阶操作,这些操作在实际开发中使用频率极高,是实现 “数据统计、数据去重、数据迁移” 的核心。

    6.1 插入查询结果:把一个表的查询结果插入到另一个表

    核心语法:

    INSERT INTO 目标表 [(列1, 列2, …)] SELECT 列1, 列2, … FROM 源表 [WHERE 筛选条件];

    这个语法的作用是:将源表的查询结果,直接插入到目标表中,无需手动逐条插入,效率极高。

    经典场景:删除表中的重复记录(只保留一份)

    在实际开发中,表中可能会出现重复数据(比如批量导入时重复上传),我们需要删除重复记录,只保留一份。步骤如下:

    第一步:创建原数据表并插入重复数据

    CREATE TABLE duplicate_table (id INT, name VARCHAR(20));
    INSERT INTO duplicate_table VALUES
    (100, 'aaa'), (100, 'aaa'), (200, 'bbb'), (200, 'bbb'), (200, 'bbb'), (300, 'ccc');

    表中有 6 条数据,其中 (100, 'aaa') 重复 2 次,(200, 'bbb') 重复 3 次。

    第二步:创建一张和原表结构相同的空表使用 LIKE 关键字,快速创建结构一致的空表:

    CREATE TABLE no_duplicate_table LIKE duplicate_table;

    可以看到,两个表的创建操作是一样的,这就是一个大大提高效率的方法。

    第三步:将原表去重后的结果插入新表使用 SELECT DISTINCT 去重,再插入新表:

    INSERT INTO no_duplicate_table SELECT DISTINCT * FROM duplicate_table;

    第四步:重命名表,实现原子去重通过重命名表,将新表替换原表,这个操作是原子的(瞬间完成,不会导致数据丢失):

    RENAME TABLE duplicate_table TO old_duplicate_table, no_duplicate_table TO duplicate_table;

    第五步:验证结果查询新表,确认重复数据已被删除:

    SELECT * FROM duplicate_table;

    结果会显示 3 条数据:(100, 'aaa'), (200, 'bbb'), (300, 'ccc'),完美去重。

    核心优势
    • 效率极高:直接通过数据库内部操作完成数据迁移和去重,比 “查出来再逐条插入” 快 10 倍以上。
    • 原子操作:重命名表的过程是瞬间完成的,不会出现 “数据缺失” 的情况。
    • 适用场景广:除了去重,还可以用于 “数据备份”“数据分表”“统计结果存储” 等场景。

    6.2 聚合函数:对数据进行统计计算

    聚合函数是对一组数据进行计算,返回单个值的函数,是实现 “数据统计” 的核心。

    聚合函数通俗解释核心注意点
    COUNT([DISTINCT] 列/表达式) 统计行数(数据的数量),也就是某一个字段有几行 1. COUNT(*):统计所有行,包含 NULL 值;2. COUNT(列):统计该列非 NULL 的行数;3. COUNT(DISTINCT 列):统计该列非 NULL 且不重复的行数。
    SUM([DISTINCT] 列) 计算该列的总和 1. 只对数字类型的列有效;2. 忽略 NULL 值;3. SUM(DISTINCT 列):计算去重后的总和。
    AVG([DISTINCT] 列) 计算该列的平均值 1. 只对数字类型的列有效;2. 公式:AVG(列) = SUM(列) / COUNT(列);3. 忽略 NULL 值。
    MAX(列) 计算该列的最大值 1. 对数字、字符串、日期类型都有效;2. 忽略 NULL 值;3. 字符串按 “字符编码” 比较大小。
    MIN(列) 计算该列的最小值 同 MAX(列) 的注意点。

    注意这些函数的使用都是加在select后面的哦,而不是where后面,大家详细的要看下面的示例进行理解哦,然后自己动手敲敲。

    经典聚合场景实战

    以 exam_result 成绩表和 students 学生表为例:

  • 统计班级总人数用 COUNT(*) 统计,包含 NULL 值:

    SELECT COUNT(*) FROM students;

    结果是 6,代表班级有 6 个学生。

  • 统计有 QQ 号的学生数用 COUNT(qq) 统计,忽略 NULL 值:

    SELECT COUNT(qq) FROM students;

    结果是 2,代表只有 2 个学生填了 QQ 号。

  • 统计数学成绩的去重数量用 COUNT(DISTINCT math) 统计:

    SELECT COUNT(DISTINCT math) FROM exam_result;

    结果是 7,代表数学成绩有 7 种不同的分数。

  • 统计数学成绩的总分用 SUM(math) 计算:

    SELECT SUM(math) FROM exam_result;

    结果是 673,代表所有学生的数学成绩总和是 673 分。

  • 统计所有学生的总分平均分用 AVG(表达式) 计算:

    SELECT AVG(chinese + math + english) 平均总分 FROM exam_result;

    结果是 297.5,代表班级的平均总分是 234分。

  • 查询英语成绩的最高分和最低分用 MAX(english) 和 MIN(english) 计算:

    SELECT MAX(english) 英语最高分, MIN(english) 英语最低分 FROM exam_result;

    结果是 90 和 30,代表英语最高分是 90 分,最低分是 30 分。

  • 6.3 GROUP BY:按 “类别” 分组统计数据

    先明确:GROUP BY 相关 SQL 的执行顺序

    这是理解 GROUP BY 逻辑的核心,MySQL 中包含 GROUP BY 的 SQL 语句,执行顺序固定为:

  • FROM:指定要操作的数据源表(比如 exam_result),加载原始数据;
  • WHERE:对原始数据进行筛选,只保留符合条件的记录(分组前筛选);
  • GROUP BY:将 WHERE 筛选后的结果按指定列分组,那么说白了其实这一步就是分出几个不同组的表来,是的,每一组其实就相当于一个表,这也就是mysql中的一切皆为表!
  • 聚合函数计算:对每个分组(表)执行 SUM/AVG/MAX 等聚合函数,生成分组统计结果;
  • HAVING:对分组后的统计结果(也就是上面的经过聚合函数计算后的结果)进行筛选,只保留符合条件的分组;
  • SELECT:选择要展示的列(分组列或聚合函数结果);
  • ORDER BY:对最终结果排序(可选);
  • LIMIT/OFFSET:限制结果行数或偏移(可选)。
  • 简单总结执行逻辑:先找表 → 筛原始数据 → 分组 → 算统计值 → 筛统计结果 → 选展示列 → 排序 → 限制行数。记住这个顺序,就能理解为什么 WHERE 不能用聚合函数(因为聚合函数在 GROUP BY 之后才计算),而 HAVING 可以(HAVING 在聚合函数计算后执行)。

    这里我和大家说一下,无论如何,必须要把这些顺理解透,它是我们掌握group by的重中之重。

    理解:GROUP BY 到底是干什么的?
    生活类比:老师的 “分堆统计”

    想象你是班主任,手里拿着全班的考试成绩表,现在需要做几个统计:

    • 统计每个学生 3 科的总分和平均分 → 按「学生姓名」把成绩分成 7 堆(每个学生一堆),每堆算一次总分 / 平均分;
    • 统计每门科目的平均分和最高分 → 按「科目」把成绩分成 3 堆(语文 / 数学 / 英语各一堆),每堆算一次平均分 / 最高分;
    • 统计每科及格和不及格的人数 → 先按「科目」分 3 堆,每堆再按「是否及格」分成 2 小堆,每小堆算人数。

    这个 “分堆→统计” 的过程,就是 GROUP BY 的核心逻辑:把原始数据按指定规则分成若干组,然后对每组单独进行聚合统计。

    数据源:我们的考试成绩表

    先明确我们要操作的表结构:

    idnamechinesemathenglish
    1 唐三藏 67 128 56
    2 孙悟空 87 80 77
    3 猪悟能 88 98 90
    4 曹孟德 82 84 67
    5 刘玄德 55 115 45
    6 孙权 70 73 78
    7 宋公明 75 95 30
    核心语法拆解:每个部分的作用

    GROUP BY 的完整语法如下,我们逐部分解释:

    SELECT 分组列1, 分组列2, 聚合函数1, 聚合函数2
    FROM exam_result
    [WHERE 分组前筛选条件]
    GROUP BY 分组列1, 分组列2
    [HAVING 分组后筛选条件];

    1. SELECT 子句:只能包含两种内容

    规则:SELECT 里的列,必须是 GROUP BY 后面的「分组列」,或者是「聚合函数」(比如 SUM/AVG/MAX 等)。

    为什么?

    因为分组后,每组会被压缩成一行统计结果,不再包含组内的单个原始数据。比如按「姓名」分组后,每组只有一个学生的统计值(总分 / 平均分),不能直接显示该学生某一次的数学分数(因为组内只有一个统计行,没有原始的数学分数行)。

    正确示例(按学生分组,统计总分):

    SELECT name, SUM(chinese + math + english) AS 总分
    FROM exam_result
    GROUP BY name;

    • name 是分组列 → 每个学生一行;
    • SUM (…) 是聚合函数 → 每个学生的 3 科总分。

    错误示例(包含非分组列、非聚合函数):

    SELECT name, math, SUM(chinese + math + english) AS 总分
    FROM exam_result
    GROUP BY name;

    • math 既不是分组列,也不是聚合函数 → 分组后每组只有一行,无法确定显示哪个 math 值(每个学生只有一个 math 值,这里看似没问题,但如果是按科目分组,就会出现多个 math 值,导致逻辑错误)。
    2. FROM 子句:指定数据源

    这里就是我们的考试成绩表 exam_result,用于加载原始数据,是 SQL 执行的第一步。

    3. WHERE 子句:分组前的筛选(先筛再分)

    作用:在分组之前,先对原始数据进行筛选,只保留符合条件的数据进入分组。

    限制:WHERE 里不能使用聚合函数(因为聚合函数是 GROUP BY 之后才计算的,分组前没有统计值)。

    示例:只统计「语文≥60 分」的学生的总分

    SELECT name, SUM(chinese + math + english) AS 总分
    FROM exam_result
    WHERE chinese >= 60 — 分组前筛:排除语文<60的学生(刘玄德被排除)
    GROUP BY name;

    • 原始表中「刘玄德」的语文是 55 分,不符合 WHERE 条件,所以不会进入分组,最终结果里没有他的记录。
    4. GROUP BY 子句:指定分组规则

    作用:按指定的列(或表达式)把数据分成若干组,相同值的记录会被分到同一组。

    多列分组:可以指定多个分组列,规则是「先按第一列分,再按第二列细分」,比如 GROUP BY 科目,及格状态 会先按科目分成 3 组,每组再按及格状态分成 2 小组。

    示例 1:单列分组(按学生姓名)

    SELECT name, SUM(chinese + math + english) AS 总分
    FROM exam_result
    GROUP BY name;

    • 按 name 分组,每个学生一组,每组计算一次总分。
    示例 2:多列分组(按科目 + 及格状态)

    — 先把科目转成列→行(用 UNION ALL 做列转行)
    SELECT '语文' AS 科目, chinese AS 分数 FROM exam_result
    UNION ALL
    SELECT '数学' AS 科目, math AS 分数 FROM exam_result
    UNION ALL
    SELECT '英语' AS 科目, english AS 分数 FROM exam_result

    — 再对转换后的结果分组统计
    SELECT
    科目,
    CASE WHEN 分数 >= 60 THEN '及格' ELSE '不及格' END AS 及格状态,
    COUNT(*) AS 人数
    FROM (
    SELECT '语文' AS 科目, chinese AS 分数 FROM exam_result
    UNION ALL
    SELECT '数学' AS 科目, math AS 分数 FROM exam_result
    UNION ALL
    SELECT '英语' AS 科目, english AS 分数 FROM exam_result
    ) AS subject_scores
    GROUP BY 科目, 及格状态;

    • 先把原始表的 3 科分数转成「科目 + 分数」的格式(列转行),再按「科目」和「及格状态」分组,统计每科及格 / 不及格的人数。
    5. HAVING 子句:分组后的筛选(先分再筛)

    作用:在分组之后,对聚合统计的结果进行筛选,只保留符合条件的分组。

    特点:HAVING 里可以使用聚合函数(因为分组后已经有了统计值)。

    示例:统计「总分≥230 分」的学生

    SELECT name, SUM(chinese + math + english) AS 总分
    FROM exam_result
    GROUP BY name
    HAVING 总分 >= 230; — 分组后筛:只保留总分≥230的学生

    • 分组后,每个学生的总分已经计算完成,HAVING 会筛选出总分≥230 的分组,最终结果里只有唐三藏、孙悟空、猪悟能、曹孟德 4 个学生。
    6. 聚合函数:分组统计的 “工具包”

    聚合函数是对每组数据进行统计的工具,常用的有:

    聚合函数作用(考试场景)示例
    SUM() 求和 → 计算学生 3 科总分 SUM(chinese + math + english)
    AVG() 求平均 → 计算学生 3 科平均分 AVG(chinese + math + english)
    MAX() 求最大值 → 计算学生某科最高分 MAX(math)
    MIN() 求最小值 → 计算学生某科最低分 MIN(english)
    COUNT() 计数 → 统计每科及格人数 COUNT(*)

    示例:按学生分组,统计总分、平均分、最高单科分

    SELECT
    name,
    SUM(chinese + math + english) AS 总分,
    ROUND(AVG(chinese + math + english), 1) AS 平均分,
    GREATEST(chinese, math, english) AS 最高单科分
    FROM exam_result
    GROUP BY name;

    注:MySQL 中没有 MAX 多列取值的函数,需用 GREATEST () 替代,作用是取多个列中的最大值。

    完整实战:从需求到 SQL 的完整流程
    需求:统计「语文≥60 分」的学生中,「总分≥230 分」的学生的姓名、总分和平均分
    步骤拆解:
  • 分组前筛选:用 WHERE 筛选语文≥60 分的学生(排除刘玄德);
  • 分组:用 GROUP BY name 按学生姓名分组;
  • 聚合统计:用 SUM 和 AVG 计算每个学生的总分和平均分;
  • 分组后筛选:用 HAVING 筛选总分≥230 分的学生。
  • 最终 SQL:

    SELECT
    name,
    SUM(chinese + math + english) AS 总分,
    ROUND(AVG(chinese + math + english), 1) AS 平均分
    FROM exam_result
    WHERE chinese >= 60
    GROUP BY name
    HAVING 总分 >= 230;

    执行结果:
    name总分平均分
    唐三藏 251 83.7
    孙悟空 244 81.3
    猪悟能 276 92.0
    曹孟德 233 77.7
    结合执行顺序理解这个 SQL:
  • FROM exam_result:加载考试成绩表的所有原始数据;
  • WHERE chinese >= 60:筛选出语文≥60 分的记录(排除刘玄德);
  • GROUP BY name:将筛选后的记录按姓名分组;
  • 聚合计算:对每个分组计算 SUM (总分) 和 AVG (平均分);
  • HAVING 总分 >= 230:筛选出总分≥230 的分组;
  • SELECT:选择展示姓名、总分、平均分三列;最终得到上述结果。
  • 常见坑点避坑指南
    坑点 1:SELECT 列包含非分组列 / 非聚合函数

    错误:SELECT name, math, SUM (…) FROM exam_result GROUP BY name

    ;正确:要么把 math 改成聚合函数(比如 MAX (math)),要么把 math 加入 GROUP BY(但按 name, math 分组不符合业务需求)。

    坑点 2:WHERE 里使用聚合函数

    错误:SELECT name, SUM (…) FROM exam_result WHERE SUM (…) >= 230 GROUP BY name;

    正确:把聚合函数的筛选放到 HAVING 里:HAVING SUM (…) >= 230。

    坑点 3:多列分组时,GROUP BY 列和 SELECT 列不对应

    错误:SELECT 科目,及格状态,COUNT () FROM … GROUP BY 科目;

    正确:SELECT 科目,及格状态,COUNT () FROM … GROUP BY 科目,及格状态;(SELECT 里的列必须和 GROUP BY 列完全对应)。

    坑点 4:COUNT (*) 和 COUNT (列名) 的区别
    • COUNT (*):统计组内的总记录数(包括 NULL 值);
    • COUNT (列名):统计组内该列不为 NULL 的记录数。比如统计英语成绩不为 NULL 的人数:COUNT (english);统计总人数:COUNT (*)。
    坑点 5:混淆 MAX 单列和多列的用法

    错误:MAX (chinese, math, english);

    正确:GREATEST (chinese, math, english)(MySQL 中取多列最大值用 GREATEST,最小值用 LEAST)。

    核心总结
  • 执行顺序:FROM → WHERE → GROUP BY → 聚合函数 → HAVING → SELECT → ORDER BY → LIMIT/OFFSET,这是 GROUP BY 相关 SQL 的核心逻辑,决定了 WHERE 和 HAVING 的使用边界;
  • 语法规则:SELECT 列只能是 GROUP BY 分组列或聚合函数,WHERE 用于分组前筛原始数据(无聚合函数),HAVING 用于分组后筛统计结果(可使用聚合函数);
  • 核心逻辑:GROUP BY 是 “分堆统计”,先按规则分组,再对每组执行聚合计算,最终得到分组级的统计结果。
  • 结语:以 CRUD 为舟,驶向 MySQL 的星辰大海

    亲爱的同学,当你看到这里时,相信你已经跟着这份教程,走完了 MySQL 数据操作从基础到进阶的完整旅程 —— 从最初对 CRUD 四个字母的陌生,到能熟练写出插入多行数据的语句,能精准用 WHERE 筛选出想要的数据,能小心翼翼地执行更新和删除操作,甚至能通过 GROUP BY 完成分组统计,这一路的收获,值得你为自己鼓掌。

    我们的学习始终遵循 “由外到内” 的逻辑:从数据库到表,再到表中最核心的数据,而 CRUD 正是我们与数据对话的 “通用语言”。回头看,这趟学习之旅里没有晦涩难懂的 “空中楼阁”,每一个知识点都锚定着实际的业务场景:新增数据时,我们知道了批量插入比单行插入更高效,知道了用 INSERT … ON DUPLICATE KEY UPDATE 解决主键冲突;查询数据时,我们摆脱了只会用 SELECT * 的新手误区,懂得了指定列查询的性能优势,能用 WHERE 做精准筛选,用 ORDER BY 让数据有序,用 LIMIT 保护数据库不被全表查询拖垮;更新和删除数据时,我们牢牢记住 “先查再操作、必加 WHERE 条件、开启事务验证” 的安全准则,避开了 “修改全表、误删数据” 的致命坑;进阶学习中,我们学会了用插入查询结果去重,用聚合函数做数据统计,用 GROUP BY 完成 “分堆计算”,理解了 FROM→WHERE→GROUP BY→HAVING 的执行顺序 —— 这些不是孤立的语法,而是解决实际问题的 “工具箱”,是你未来应对业务需求的核心底气。

    我知道,学习的过程里你一定有过困惑:比如第一次忘记给 NULL 值赋值导致插入报错,第一次用 WHERE 筛选 NULL 值时用了 = 而不是 IS NULL,第一次写 GROUP BY 时因为 SELECT 里加了非分组列而报错,甚至可能在测试时不小心执行了不带 WHERE 的 UPDATE,看着全表数据被修改时慌了神。但请你记住:踩坑不是学习的 “失误”,而是掌握 MySQL 的 “必经之路”。这些错误恰恰让你理解了 MySQL 的底层逻辑 —— 它不是一套死板的语法规则,而是有自己执行顺序、数据约束、安全机制的 “数据管家”:它要求值的类型与字段匹配,是为了保证数据的准确性;它要求更新删除必加 WHERE,是为了防止数据被误操作;它的聚合函数要在 GROUP BY 后执行,是为了保证统计结果的合理性。理解这些 “为什么”,远比死记 “怎么写” 更重要,因为当你懂了底层逻辑,哪怕忘记了某个语法细节,也能凭借逻辑推导出正确的写法。

    MySQL 从来不是 “看会” 的,而是 “练会” 的。这份教程里的每一个示例、每一个场景,都需要你亲手敲一遍代码:创建学生表、插入测试数据、执行查询语句、修改数据后验证结果、删除数据前先备份…… 只有当你的手指敲过这些语句,当你亲眼看到 “Query OK, 1 row affected” 的提示,当你排查过 “Column count doesn't match value count” 的报错,这些知识才会真正变成你的 “肌肉记忆”。你可以试着给自己布置一些模拟业务场景:比如 “统计一个班级各科的平均分和及格率”“清理一张表中的重复用户数据”“给不同分数段的学生更新等级标签”,把学到的 CRUD 和聚合、分组知识结合起来,你会发现,那些看似零散的语法,会在解决问题的过程中形成完整的体系。

    当然,CRUD 只是 MySQL 学习的 “第一站”。未来你还会接触到多表连接查询、索引优化、事务与锁、存储过程等更深的知识点,但请相信,今天你打下的 CRUD 基础,是解锁所有进阶内容的 “钥匙”:多表查询本质上是多表的 “联合查询 + 筛选”,索引优化是为了让 SELECT 查询更快,事务是为了保证更新 / 删除操作的原子性 —— 所有高阶知识点,都是在 CRUD 的基础上解决 “更复杂、更高效、更安全” 的问题。所以不必急于求成,把今天的知识点吃透,把每一个避坑要点记牢,比如 “永远不要在生产环境执行不带 WHERE 的 UPDATE/DELETE”“分页查询必加 ORDER BY 保证顺序稳定”“GROUP BY 的 SELECT 列只能是分组列或聚合函数”,这些看似琐碎的规则,会成为你未来工作中 “保护数据安全” 的第一道防线。

    学习编程从来不是 “一蹴而就” 的事,就像我们操作数据时要 “一步一步验证”,学习的过程也需要 “慢慢来,比较快”。可能你现在还不能立刻把这些知识应用到实际项目中,可能你还会偶尔混淆 HAVING 和 WHERE 的用法,这都没关系。重要的是,你已经推开了 MySQL 数据操作的大门,理解了 “数据是数据库的核心,而 CRUD 是与数据交互的核心” 这个本质。未来的日子里,当你遇到问题时,不妨回头看看这份教程,看看我们用 Excel 类比 CRUD 的通俗解释,看看那些标注了 “避坑要点” 的细节,看看分组查询的执行顺序 —— 这些内容会像 “指南针” 一样,帮你找回思路。

    最后,想对你说:数据库是 “数据的仓库”,而你学会的 CRUD,就是管理这个仓库的 “基本功”。无论是未来做后端开发、数据分析,还是测试运维,这些操作都会是你日常工作中最频繁接触的内容。请珍惜这份 “从 0 到 1” 的成长,也请保持对 “为什么” 的好奇 —— 为什么聚合函数不能用在 WHERE 里?为什么 REPLACE 和 INSERT … ON DUPLICATE KEY UPDATE 处理冲突的逻辑不同?为什么 NULL 值参与表达式结果还是 NULL?当你把这些 “为什么” 都弄明白,你就不再是 “只会写语法的新手”,而是 “懂逻辑的开发者”。

    愿你以 CRUD 为舟,在 MySQL 的星辰大海里稳步前行,每一次敲下的 SQL 语句,都精准、安全、高效;每一次解决的数据问题,都让你离 “优秀的开发者” 更近一步。别怕犯错,别怕慢,只要你坚持练习、持续思考,终会把这些基础的操作内化成能力,用数据解决真正的业务问题。加油,未来的你,一定会感谢今天认真学完 CRUD 的自己!

    赞(0)
    未经允许不得转载:171主机测评 » 对于MySQL:数据操作的解析
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址