MySQL数据库 (八) MySQL表的基本查询(下),truncate、group by、聚合函数、分组聚合统计

目录

一、Updata

set的使用

二、Delete

[1. 删除数据](#1. 删除数据)

[2. 删除整张表](#2. 删除整张表)

[3. 截断表](#3. 截断表)

[4. 去重表数据](#4. 去重表数据)

三、聚合统计

聚合函数

四、分组聚合统计

五、总结


本篇文章我们继续学习 CRUD。

一、Updata

CRUD 里的 U 代表 Update 数据更新操作,用来修改数据表中已经存在的数据。

语法为 update 表名 set 字段=表达式,字段=表达式... where筛选条件order bylimit,它的执行逻辑是先依照 where 条件查询匹配的数据,再对筛选出来的行完成字段内容的更新修改。

举例:

我们继续使用之前的成绩表 exam_result。

set的使用

继续,比如我们要 将孙悟空同学的数学成绩变更为80分:

我们的需求是把孙悟空的数学成绩修改为 80 分,先通过 select name,math from exam_result; 查看原始数据,能看到孙悟空初始数学分数是 78;再执行 update exam_result set math=80 where name='孙悟空';,语句依靠 where 精准定位姓名为孙悟空的单行数据,执行后返回 1 行受影响,再次查询,对应 math 字段从 78 更新为 80。这里我们要重点强调:当 update 不加 where 条件时,会把全表的所有字段都进行更新,就会导致极易误改全表数据,所以慎用。

继续,比如我们要 将曹孟德同学的数学成绩变更为60分,语文成绩变更为70分:

如果我们想要单次修改同一个人的多个字段,在 set 后用英文逗号分隔多组字段 = 新值即可,比如我们要将曹孟德同学的数学成绩改成 60、语文改成 70,执行 update exam_result set math=60,chinese=70 where name='曹孟德';,这条 SQL 通过 where 锁定曹孟德单条记录,一次性完成两个不同字段的数值修改,查询数据表可以看到两条字段同步更新完毕。

这里我们需要注意 set 后面多个字段赋值之间,绝对不能用 and,只能用英文逗号 , 分隔,and 不能替换逗号,替换后会语法报错。这里的逗号是用来分隔多条并列的字段赋值表达式,代表同一行数据一次性修改多个列,属于 set 子句内部的分隔符。而 and 是逻辑运算符,只用在 where 条件里用来拼接多个筛选条件、做行匹配判断,and 的生效范围在 where 后,不能放到 set 的字段赋值之间。

继续,比如我们要 将总成绩倒数前三的3位同学的数学成绩加上30分:

第一步先用查询语句定位目标数据 select name,math+chinese+english total from exam_result order by total asc limit 3;,order by total asc 按总分从小到大升序,搭配 limit 3,筛选出总分最低的宋公明、刘玄德、曹孟德三人。

MySQL 的 update 语法支持搭配 order by + limit,执行语句 update exam_result set math=math+30 order by chinese+english+math asc limit 3;。set math=math+30 代表在原有数学分数基础上加 30,MySQL 不支持 math+=30 这种简写,只能用原值参与运算写法;order by 先按总分升序排序,limit3 限定只修改排序后的前 3 行,刚好命中总分倒数三位。执行后 3 行数据被修改。

修改后再次查询 select name,math+chinese+english total from exam_result order by total limit3;,发现倒数三名变成宋公明、刘玄德、唐三藏。原因是三人数学加分 30 后总分同步上涨,原本第四名唐三藏分数低于曹孟德,顶替进倒数前三,但原始指定的 3 名学生确实已经完成加分,符合题目更新要求。

继续,比如我们要 将所有同学的语文成绩更新为原来的2倍:

注意:更新全表的语句慎用!

我们需要将全表所有人语文成绩改成原来 2 倍,MySQL 中没有 chinese*=2 运算符,规范写法:update exam_result set chinese=chinese*2;。这条 SQL没有 where 条件、没有 limit 限制,会遍历整张表 7 条数据,全部把 chinese 字段乘 2 翻倍,查询前后表格可以看到全部学生语文分数变为原值两倍。

一定要注意没有 where、没有 limit 约束的 update 会直接修改整张数据表全部数据,极易造成全表数据误改,上线前务必先用 select 校验筛选范围,确认目标数据无误后再执行 update。

二、Delete

1. 删除数据

DELETE 对应 CRUD 中的删除操作,核心作用是移除数据表内的行记录:

标准语法为 DELETE FROM table_name WHERE ...ORDER BY ...LIMIT ...,如果省略 where、order by 与 limit 所有限定条件,直接书写 delete from exam_result; 会清空整张数据表内部的全部数据,但数据表本身的字段结构、主键、字段约束等表定义信息会完整保留,想要连同表结构一起彻底销毁数据表不能使用 delete,需要使用 drop table 语法,这是 delete 和 drop 在功能本质上最关键的区分点。

继续,比如我们要 删除孙悟空同学的考试成绩

想要精准删除指定单行数据时依靠 where 条件完成范围锁定,我们需要删除孙悟空的成绩,需要先查询再执行删除,先通过 select * from exam_result where name='孙悟空'; 查看这条数据,确认待删除数据内容无误后,再执行 delete from exam_result where name='孙悟空';,这条 sql 语句依托 name 字段的等值条件精准匹配单条记录并完成删除,执行结束后再次全表查询可以发现孙悟空对应的整条数据已经从表格中消失,实际项目开发里删除操作前使用 select 先进行数据校验是规避误删的必要步骤,能提前排查 where 条件书写错误带来的风险。

继续,比如我们要 删除倒数第一的同学:

delete 语句同样可以搭配 order by 与 limit 实现按排序规则限定删除行数,我们首先借助查询语句 select name, chinese+math+english total from exam_result order by total asc limit 1;,通过总分升序排序加 limit 锁定倒数第一的学生是刘玄德,确认目标后执行 delete from exam_result order by english+math+chinese asc limit 1;,数据库会先按照总分完成全表排序,再根据 limit 的限制仅删除排序后的第一条记录,执行完毕后查表能够验证刘玄德的数据已经被成功移除。

结合前面所有案例可以总结 delete 的使用安全准则,不带任何筛选条件与行数限制的 delete 语句具备全表清空的高危特性,线上业务环境严禁随意执行,但凡需要做删除操作,优先使用 select 语句校验筛选范围,确认命中数据和预期删除内容一致后再执行删除指令,同时牢记 delete 仅操作表内数据、不改动表结构的特性,区分开 delete 删数据和 drop 删整张数据表的用法边界。

2. 删除整张表

表分为表结构和表数据,delete 语句只负责删除表内部存储的数据,不会改动表原本的字段结构、主键约束以及自增计数器。

继续,比如我们要 删除整张表数据

注意:删除整表操作要慎用!

这里我们就重新新建一张表:

使用语句 create table for_delete(id int primary key auto_increment,name varchar(20)); 建表,id 字段设置主键并且开启 auto_increment 自增属性,

插入数据:

之后通过 insert into for_delete(name) values('a'),('b'),('c'); 批量插入三条测试数据,查询全表可以看到三条数据对应的 id 依次是 1、2、3,此时数据表的自增计数器数值为 4,代表下一条新增数据会从 4 开始编号。

下面我们删除整表的数据:

此时我们在执行全表删除之前先用 show create table for_delete\g 查看表结构信息,确认 auto_increment 字段数值是 4,随后执行全表清空语句 delete from for_delete;,这条不带任何条件的 delete 会清空表里全部三条记录,再次执行 select * from for_delete; 查询表格,结果为空代表所有数据已经被删除,再次使用 show create table for_delete\g 语句查看建表语句能够发现,数据表结构是完整保留的,但是自增属性对应的计数器数值依旧是 4 没有被重置归零,这是 delete 全删数据的关键特征。

此时我们再插入数据:

数据表清空之后我们 insert into for_delete(name) values('e'); 再次插入新数据,随后执行 select * from for_delete 发现这条新数据的 id 是 4 而不是 1,紧接着继续执行 show create table for_delete\g 查看表结构可以看到 auto_increment 数值更新成 5,代表后续再新增数据会顺着 5 继续自增,所以我们就能得出结论 : 使用 delete 删除全表数据只会清空表内记录,不会重置主键自增计数器,自增数值会延续删除之前的计数继续往后累加。

3. 截断表

截断的关键字是 truncate,作用也是清空表数据,truncate 和 delete 都可以实现清空整张数据表数据的效果,但是底层执行原理、使用限制、自增处理逻辑不同。

truncate 的基础语法为 truncate table table_name,使用该命令有三条硬性规则:

  1. 第一,truncate 只能作用于整张数据表,无法像 delete 搭配 where 条件去删除局部部分数据;
  2. 第二,truncate 不走 mysql 事务机制,不会生成事务日志,执行速度远快于全表 delete,同时数据删除后无法通过事务回滚恢复;
  3. 第三,truncate 执行后会直接重置主键 auto_increment 自增计数器,这也是和 delete 最核心的区分点。

下面我们就举例验证:

为了直观验证 truncate 的特性,我们重新建表 create table for_truncate(id int primary key auto_increment,name varchar(20));,通过 desc 查看表结构确认 id 字段绑定自增属性,接着执行 insert into for_truncate(name) values('a'),('b'),('c'); 插入三条测试数据,全表查询可以看到 id 依次为 1、2、3,然后借助 show create table for_truncate\g 查看建表信息,此时数据表的 auto_increment 自增计数器数值为 4,代表下一条插入数据默认 id 为 4。

下面我们截断表:

执行截断语句 truncate for_truncate;,执行完成后通过 show tables; 能够发现数据表本身仍然存在,truncate 不会删除表结构。

再执行 select * from for_truncate; 查询,表内数据已经全部清空,再次查看建表语句可以看到,原本数值为 4 的 auto_increment 自增值被重置为零,和 delete 全删之后保留原有自增值的行为形成明显区别。

在 truncate 完成数据清空之后,再次向表内插入新数据 insert into for_truncate(name) values('e');,查询这条新增记录可以看到,新数据的 id 从 1 开始计数,也就印证了 truncate 会重置自增计数器的特点。

所以我们可以得出结论 : delete 清空全表保留原有自增数值,truncate 清空全表重置自增从 1 重新开始,生产环境中 truncate 因为不可回滚,使用前必须谨慎确认。

4. 去重表数据

我们继续举例 : 比如我们要删除表中的重复记录,使表中重复的数据只能有一份:

我们再新建一个表,首先执行 create table duplicate_table(id int,name varchar(20));,通过 desc duplicate_table; 查看表结构,字段 id、name 都没有主键与唯一约束,所以能够存入重复数据,

接着插入数据 :

接着执行 insert into duplicate_table values(100,'aaa'),(100,'aaa'),(200,'bbb'),(200,'bbb'),(200,'bbb'),(300,'ccc'); 批量插入六条数据,全表查询 select * from duplicate_table; 后可以直观看到,aaa、bbb 两组数据存在多行重复记录,ccc 为唯一数据,我们的需求是永久删掉重复行,最终每组重复数据只保留一条,从物理层面上修改原表数据。

我们之前使用 distinct 关键字也能去重,如上我们执行 select distinct * from duplicate_table; 可以在查询结果里自动剔除重复内容,只展示去重之后的三条有效数据,但 distinct 仅仅是查询时临时过滤返回结果,不会改动磁盘里的原始数据表,执行完这条查询后再次查表,duplicate_table 内部依旧保留六条重复原始数据,无法满足原地删重复、修改原表真实数据的需求,因此想要物理去重不能只用 distinct。

那如果我们要求改变表原始结构式的去重呢? 如下:

我们先依托原有含重复数据的 duplicate_table,执行 create table no_duplicate_table like duplicate_table; 创建一张结构和原表完全一致的空的新表,like 语法会完整复刻原表的字段类型、字段长度、空值规则等表结构信息,但不会同步原表里已存储的任何数据,执行 desc no_duplicate_table; 查看表结构,可以验证新表字段配置和原始重复数据表完全相同。

再创建一个空的新表:

确认原表还保留六条重复数据之后,先用 select distinct * from duplicate_table; 校验去重后的结果,这条查询可以筛选出去除重复后的三条有效记录。

接着借助 insert into no_duplicate_table select distinct * from duplicate_table; 把子查询去重后的结果批量写入新建空表,执行结束后查询 select * from no_duplicate_table;,此时新表内部就存储了去重完毕的无重复数据,完整保留了原表每条唯一的记录内容。

下面我们对这两个表进行重命名操作:

最后我们通过重命名 rename 语句完成表名替换,首先执行 rename table duplicate_table to old_duplicate_table; 把存有重复原始数据的旧表改名备份,再执行 rename table no_duplicate_table to duplicate_table; 将已经去重完毕的新表改成原来业务使用的表名。

此时再查询 select * from duplicate_table;,对外使用的表名没有变化,但底层实际已经替换成去重后的干净数据表,从使用层面实现了原表原地去重。

为什么最后是通过 rename 的方式去重呢?

采用 rename 改名替换的方式做原地去重,核心优势是不会改动上层业务的 sql 代码,程序原本访问 duplicate_table 的语句无需任何修改就能正常使用,同时旧表被改名备份可以留存原始重复数据,一旦去重出错能够快速把旧表改回原名完成数据回滚,规避直接删原表数据带来的误删风险,这也是该方案优先选用 rename 而不是直接删表的关键原因。

三、聚合统计

聚合函数

什么是聚合函数?

聚合函数是 mysql 中用来对多行数据做汇总统计的专用函数,它会把筛选出来的一整组数据经过运算后,最终只返回单行统计结果,区别于普通字段查询每行返回一条数据,常用的一共五类:count、sum、avg、max、min,并且都支持搭配 distinct 关键字,在统计前先剔除重复数据再计算。

分类:

  1. count(distinct expr):统计满足条件的数据条目总数,统计时会自动忽略字段为 null 的记录;count(distinct 字段)代表统计该字段去重之后的有效行数。
  2. sum(distinct expr):对数值型字段做累加求和,非数字类型字段使用无统计意义,搭配 distinct 会先去重再做数值相加。
  3. avg(distinct expr):计算一组数值的算术平均值,底层逻辑为总和 ÷ 有效数据条数,同样只对数字字段生效,非数字字段无法运算。
  4. max(distinct expr):查询字段里的最大值,数字、字符串都可以对比取值,distinct 不影响最大结果,仅会剔除重复数据后再筛选极值。
  5. min(distinct expr):查询字段里的最小值,适用数据类型规则和 max 保持一致。

举例 : 比如统计班级共有多少同学****

我们继续用 exam_result 这张表:

先执行 select * from exam_result; 查看全表数据,表里一共留存 5 条学生记录。

想要统计班级总人数可以使用 count(*),语句写成 select count(*) from exam_result;,最终统计结果为 5,代表总共 5 名学生,还能借助 as 给统计字段起别名,select count(*) 总数 from exam_result;,让查询结果的表头展示自定义名称,同时补充知识点,count (1)、count (常数) 效果和 count (*) 基本一致,同样用来统计整张表有效数据行数。

继续,比如我们要 统计本次考试的数学成绩分数个数

想要统计数学成绩的有效记录数量,改用 count(字段) 写法,执行 select count(math) from exam_result;,该写法会遍历 math 字段,自动忽略字段值为 null 的数据,当前表所有学生都填写了数学分数,所以统计结果依旧是 5,和 count () 结果相同,后续如果出现 math 字段为空的记录,count (math) 就会跳过 null 行,统计数值小于 count ()。

继续,比如我们要统计不重复的数学成绩

需求变更为统计不重复的数学成绩数量,我们先单独查询 select math from exam_result; 可以发现,唐三藏和猪悟能的数学分数都是 98,存在重复数值,这里需要注意 distinct 的书写位置,错误写法 select distinct count(math) as res from exam_result; 是先统计出行数 5,再对数字 5 去重,结果仍旧是 5,无法实现去重统计;正确语法是把 distinct 写在 count 的括号内部 select count(distinct math) as res from exam_result;,先对 math 字段整体去重再统计条数,剔除一组重复 98 之后,最终有效不重复成绩一共 4 个,结果返回 4。

继续,比如我们要统计 数学成绩总分

首先使用 sum 函数完成数学总分统计,执行 select sum(math) from exam_result;,sum 会对 math 字段所有有效数值累加,最终算出数学总分 454。

那我们也能顺便得出平均分:

我们也可以借助 sum 和 count 手动运算求取平均分,写法 select sum(math)/count(*) from exam_result;,用总分除以总人数得到数学平均分 90.8​​​。

套用相同逻辑 select sum(english)/count(*) from exam_result; 能够算出英语平均分 64.2,这种手动相除的方式可以直观理解平均分底层运算逻辑。

当然我们也可以统计不及格得人数:

聚合函数也能够搭配 where 条件提前过滤数据,再做统计,用来筛选指定范围的数据量。想要统计数学不及格人数就执行 select count(*) from exam_result where math<60;,筛选后无满足条件的数据,统计结果为 0;统计英语不及格则用select count(*) from exam_result where english<60;,筛选出两条记录,最终结果是 2,where 会在聚合运算前先剔除不符合条件的行,只对剩余数据做汇总。

也有更简单的求平均分的方法:

avg 是 mysql 内置求平均值的聚合函数,替代手动 sum 除法,select avg(math) from exam_result; 直接得到数学平均分 90.8,和 sum 除法计算结果一致。

同时支持多字段相加后再求平均,select avg(english+chinese+math) 平均分 from exam_result;,先把单条记录三科分数求和,再对全部总分求取平均,算出三科整体平均分 303。

继续,比如我们要 返回****> 70********分以上的数学最低分****

min、max 搭配 where 实现带筛选的极值查询,需求是查找 70 分以上数学成绩里的最低分,先通过 select math from exam_result where math>70; 筛选出全部符合条件的数学分数,再嵌套 min 聚合 select min(math) from exam_result where math>70;,最终筛选出符合条件里的最小值 73,where 优先过滤数据,聚合只作用在筛选完毕的结果集上。

补充执行顺序规则,整条 sql 的执行顺序是先通过 from 确定数据表,再用 where 筛选有效数据,最后对筛选完的数据执行聚合运算,因此聚合只能作用在经过 where 过滤后的数据集中,未经过筛选的单行明细数据无法直接和聚合结果拼接查询,这也是聚合查询不能随意混杂普通字段的核心原因。

四、分组聚合统计

group by 分组子句的核心作用是按照指定字段拆分数据组,再配合聚合函数实现分组聚合统计。

语法格式为 select 字段1,聚合函数(字段2) from 数据表 group by 分组字段;,分组会把分组字段取值相同的行归集为一组,每组最终只生成一条聚合结果,日常统计部门、岗位维度数据都会依托 group by 实现。

下面我们直接举例,这里我们已经准备好了相关的表及信息了,本次案例使用经典 scott 测试库,包含 dept 部门表、emp 员工表、salgrade 薪资等级三张业务表 :

dept 和 emp 是主从表的关系 但是并没有通过外键约束表示

先梳理三张数据表的表结构与关联逻辑,dept 部门表存储部门编号、部门名称、部门地点,主键是 deptno 部门编号;emp 员工表存储员工编号、姓名、岗位、入职薪资、奖金、所属部门 deptno,依靠 deptno 和 dept 形成主从关联;salgrade 薪资等级表划分薪资档位,记录每个等级对应的最低工资、最高工资,三张表通过编号字段建立业务关联,建表脚本先判断库、表是否存在再执行删除与新建,统一使用 utf8 字符集。

下面我们将这些信息数据都导入到我们的命令行中:

此时我们就能看到这个库了:

数据导入完成后,先用 show databases; 查看全库列表,能确认 scott 已经成功创建,之后分别使用 desc 表名查看三张核心数据表的字段结构,desc emp 查看员工表字段定义,包含员工编号、姓名、岗位、薪资、奖金、部门编号等字段;desc dept 查看部门表,存储部门编号、部门名称、地点;desc salgrade 查看薪资等级表,划分等级、薪资上下限,再通过select * from 表名分别查询全量原始数据,核对 emp14 条员工、dept4 个部门、salgrade5 档薪资等级的数据和初始化脚本一致。

下面我们就可以分组查询了:

需求1 : 如何显示每个部门的平均工资和最高工资:

首先我们要对这些员工按照部门进行分组 ,使用 group by deptno 分组,以部门编号作为分组依据,数据库会自动把 deptno 数值相同的所有员工归集为同一个分组,在 select 后搭配 max(sal) 统计单组最高工资、avg(sal) 计算分组平均薪资,完整语句 select deptno,max(sal) 最高,avg(sal) 平均 from emp group by deptno;,执行后数据按 10、20、30 三个部门分成三组,40 号部门暂无员工因此不会出现在结果里,最终分别输出各组的极值与平均值。

我们再补充一下分组的性质:

使用 group by 之前要先梳理业务需求,确认统计目标是按维度拆分数据再聚合,只有需要分组汇总数据时才适合选用分组语法,单条明细查询不需要使用 group by。以 group by deptno为例,分组会依据 deptno 字段里不同的数据值完成拆分,数据表中 deptno 相同的所有行会被划入同一个分组。

同一个分组内部,用来分组的字段(deptno)数值完全一致,正因分组字段值统一,整组多条明细数据才能够被聚合函数压缩成单行统计结果,这也是分组搭配聚合生效的底层条件。从逻辑层面理解,group by等价于把一张完整数据表,按照分组条件拆分成多张逻辑上独立的子表,物理上原表数据不会改动。

拆分出各个逻辑子表之后,聚合函数会分别针对每一张拆分后的子表单独运算,最后把所有分组的统计结果整合输出,最终一条分组对应一条统计结果,这就是分组聚合的完整执行逻辑,对应前面案例就是按部门拆分子表后,分别计算每个子表的最高薪资、平均薪资。

需求2 : 显示每个部门的每种岗位的平均工资和最低工资

这里我们需要多字段联合分组,统计每个部门、每个岗位的平均薪资与最低工资,分组语法写作 group by deptno,job,代表优先按照部门编号分组,在同一个部门内部再依据岗位二次拆分分组,逻辑上类似树形分层拆分,分组维度越精细,最终输出的数据条目就越多。 完整 sql 为select deptno,job,avg(sal) 平均,min(sal) 最低 from emp group by deptno,job;,执行后一共生成 9 组统计结果,每条数据对应单一部门下单一岗位的薪资汇总数据。

**分组执行逻辑遵循先拆分分组,后聚合运算,**数据库先根据 deptno+job 把全表数据拆分成多组,同组内部门与岗位完全一致,再分别对每组薪资字段执行 avg、min 聚合运算;分组字段越多、划分粒度越细,拆分出来的分组数量就越多,明细条目随之增多。

我们继续:

这里我们想加上每个员工的名称再进行分组,为什么报错了?

因为我们在分组时,比如先按照部门编号分组,那此时每个组内的员工的部门编号一定是相同的,然后我们继续按照岗位再次分组,那此时相同岗位的员工就会被压缩聚合在一起,压缩聚合后才能统计出他们中的平均和最低薪资,那此时最后统计出的员工中的部门编号和岗位一定是一样的,所以才能压缩,可是这时我们突然又想按照名字来分组,因为每个人的名字是不一样的,所以肯定就冲突了,自然就无法成功聚合分组。

所以这里我们总结一下 select 与 group by 字段的匹配规则:select 后面能出现的字段只有两类,一是写在 group by 后的分组字段,二是被 avg、max、min 等聚合函数包裹的字段;普通明细字段没有写入 group by 时,禁止直接裸写在 select 查询列中。

需求3 : 显示平均工资低于2000的部门和它的平均工资

需求为筛选平均薪资低于 2000 的部门与对应平均薪资,整个运算拆分为两步,第一步先通过分组算出全量部门平均薪资:

我们先执行 select deptno,avg(sal) deptavg from emp group by deptno;,依靠 group by deptno 按部门分组,avg(sal) 完成分组聚合,得到 10、20、30 三个部门各自的平均薪资数据,此时聚合计算已经全部完成。

下面我们就需要对聚合好的结果进行判断了,怎么判断? having

第二步则是借助 having 关键字对聚合之后的结果做条件过滤,完整语句 select deptno, avg(sal) deptavg from emp group by deptno having deptavg<2000;,sql 执行顺序为先分组聚合,再用 having 筛选分组结果,最终仅保留 30 号部门,其平均薪资 1566.67 满足小于 2000 的条件。having 一般搭配 group by 使用,筛选对象是分组聚合完毕的数据。

那 having 和 where 的区别是什么呢?

区分 where 和 having 的核心差异,二者虽然都是条件筛选,但执行阶段完全不同。where 在分组之前生效,用来过滤原始数据表的行数据,无法识别聚合别名、聚合结果。having在分组之后生效,筛选聚合统计结果。

这里我们将 where 和 having 的顺序进行一下替换:

因为 where 在分组之前生效,用来过滤原始数据表的明细行,无法识别聚合别名、聚合结果;因此 where deptavg< 2000 直接语法报错,deptavg 是聚合后生成的字段,原始表不存在该列。having 在分组之后生效,筛选聚合统计结果,整张表本身也可以视作是一个组,所以 having 也能筛选原始字段,比如select * from emp having ename='SMITH'; 可以正常执行,但业务规范上原始行过滤优先使用 where。

下面我们将二者综合使用一下 比如我们要统计剔除员工' SMITH'之后的平均工资低于2000的部门和它的平均工资

where 与 having 组合使用,需求改为剔除 SMITH 员工后,筛选岗位平均薪资低于 2000 的数据,语句 select deptno,job,avg(sal) myavg from emp where ename != 'SMITH' group by deptno,job having myavg < 2000;,执行顺序依次是:from 确定数据表 → where过滤删掉 SMITH 这条原始数据 → group by按部门+岗位分组 → avg 完成分组聚合 → having筛选聚合值小于 2000 的分组,最终输出四条符合条件的统计数据。

总结整条 sql 固定执行次序:from→where→group by→聚合运算→having→select 展示字段,原始行过滤交给 where,分组后聚合结果的过滤交给 having,严格遵守分工可以避免语法报错。

补充建立一个认知:

我们要跳出只有磁盘落地、物理真实存在的数据表才算表的固有认知,在 mysql 的逻辑设计理念里遵循一切皆表的核心思想,除了通过 create 语句创建、实际存储在磁盘中的物理数据表之外,sql 执行过程里 where 筛选生成的中间临时数据集、group by 分组聚合之后生成的结果集、select 查询最终返回的结果集合,在逻辑层面全都可以视作一张表,这些逻辑表同样拥有行列结构,能够沿用单表的 CRUD 相关语法规则去做二次筛选、聚合等操作,正是基于这个设计思路,只要熟练掌握单表的增删改查使用逻辑,后续遇到多表联查、子查询、嵌套查询等各类复杂 sql 场景时,都可以把关联查询拆解成多张逻辑单表分步处理,用统一的单表处理思路逐层拆解需求,因此哪怕后续接触多表查询,底层的查询处理逻辑也和单表保持一致,只需要理清多张逻辑表之间的关联条件,就能沿用已经掌握的单表语法完成各类复杂的数据统计与筛选工作。
所以 "MySQL一切皆表"
from 是整个查询的起点,数据库先根据 from 加载数据源表,生成初始逻辑数据表;紧接着执行 on,在多表关联场景下依靠 on 的关联条件筛选两张表的匹配数据,随后执行 join 完成多表拼接,生成拼接后的中间逻辑表;where 在多表拼接结束后执行,针对明细原始行做条件过滤,剔除不满足条件的数据;经过 where 过滤后的数据进入 group by 阶段,按照指定字段拆分数据分组,同时完成分组内聚合函数运算;having 紧跟分组之后,专门对聚合生成的分组结果做筛选,过滤不符合统计阈值的分组;select 在聚合筛选完毕后执行,确定最终需要展示的查询字段、字段别名;distinct 依托 select 的结果集做去重处理,剔除整行完全重复的数据;order by 在去重结束后,按照指定字段对结果全局排序;最后 limit 截取排序后的结果,限定最终返回的数据行数。with 作为公用表表达式,会在整条 sql 正式查询逻辑运行之前提前解析生成临时表,优先级高于 from。

五、总结

这篇文章系统讲解了SQL中的CRUD操作,重点介绍了Update和Delete的使用方法,以及聚合统计和分组查询。主要内容包括:

  1. Update操作:详细说明了Update语法、执行逻辑和注意事项,强调不加where条件会误改全表数据,并通过多个示例演示了单字段更新、多字段更新、条件更新等用法。
  2. Delete操作:讲解了Delete的基本语法,比较了Delete与Truncate的区别,特别指出Delete会保留自增计数器而Truncate会重置。还介绍了如何安全删除数据以及表数据去重的实现方法。
  3. 聚合函数:介绍了count、sum、avg、max、min等聚合函数的用法,演示了如何结合where条件进行统计计算。
  4. 分组查询:详细讲解了group by的使用方法,包括单字段分组、多字段分组,以及having子句对分组结果的过滤,并与where条件进行了对比。
  5. 执行顺序:总结了SQL语句的固定执行顺序:from→where→group by→聚合运算→having→select。

文章通过大量实例演示了各种操作的实际应用,强调了数据操作的安全性注意事项,并指出"MySQL一切皆表"的核心设计理念。

谢谢大家的观看!

相关推荐
乐世东方客1 小时前
备份脚本记录(binlog文件+mysql+mongo)
android·数据库·mysql
暴力求解1 小时前
MySQL---数据类型
数据库·mysql
Nturmoils1 小时前
分页别写太顺手,LIMIT 背后还有排序和边界
数据库·后端
小饕1 小时前
RAG学习之【向量数据库】Milvus 从入门到精通:索引、检索、混合搜索一篇打通(RAG 必备)
数据库·人工智能·学习·milvus
kisdiem2 小时前
RAG ENGINEERING · 中文教程从文档到可靠答案
数据库
SilentSamsara2 小时前
向量数据库实战:Chroma/Milvus/Qdrant 选型与语义搜索应用
开发语言·数据库·人工智能·python·青少年编程·milvus
沪漂阿龙3 小时前
LangChain 系列之Agent:从固定流程到模型自主决策
服务器·数据库·langchain
zh_xuan3 小时前
PC端操作SQLite数据库
数据库·c++·sqlite
MXsoft6183 小时前
**采集节点主备模:保障监控系统自身高可用**
数据库