MySQL之表的增删查改

MySQL之表的增删查改

🌟🌟hello,各位读者大大们你们好呀🌟🌟

🚀🚀系列专栏:【MySQL的学习】

📝📝本篇内容:表的增删查改;Create;Retrieve;where条件;结果排序;筛选分页结果;Update;Delete;截断表;插入查询结果;聚合函数;group by子句的使用

⬆⬆⬆⬆上一篇:MySQL之表的约束

💖💖作者简介:轩情吖,请多多指教(>> •̀֊•́ ) ̖́-

1.表的增删查改

CRUD : Create(创建), Retrieve(读取),Update(更新),Delete(删除)

2.Create

其实在我们之前讲的很多内容,其实都已经使用过这些操作了,这边只是正式的引入这些内容,然后还有一些其他跟额外的内容要说明。

bash 复制代码
insert[into] table_name
[(column [, column] ...)]
VALUES (value_list) [, (value_list)] ...
value_list: value, [, value] ...

insert中的into其实不是必须要写的,只不过写上会更好一点

我们插入的方式主要也是有这些特性:全列插入和指定列插入或者多行数据和单行数据

但是当我们插入时插入否则更新可能会由于主键或者唯一键对应的值已经存在而导致插入失败,因此我们可以在插入失败时进行更新

bash 复制代码
insert... on duplicate key update
column = value [, column = value] ...
  • 0 row affected: 表中有冲突数据,但冲突数据的值和 update 的值相等,MySQL 不会执行实际更新,所以影响行数为 0
  • 1 row affected: 表中没有冲突数据,数据被插入,执行 insert操作,插入一条新记录,所以影响行数为 1。
  • 2 row affected: 表中有冲突数据,并且数据已经被更新,执行 UPDATE 操作,更新了一条记录,MySQL 会返回影响行数为 2(这是 MySQL 的特殊计数规则)

通过 MySQL 函数获取受到影响的数据行数

bash 复制代码
select row_count();

除了这种方法,我们也可以直接替换

  • 1 row affected:主键或者唯一键 没有冲突,则直接插入;
  • 2 row affected: 主键或者唯一键 如果冲突,则删除后再插入;

3. Retrieve

这部分主要是就是查看我们的数据

详细SQL语句待会说,我们先创建一个表

bash 复制代码
-- 创建表结构
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);
Query OK, 7 rows affected (0.00 sec)
Records: 7 Duplicates: 0 Warnings: 0

指定列的顺序不需要按定义表的顺序来,其实我们select查询字段为表达式,因此它是可以计算的

bash 复制代码
select column [AS] alias_name [...] from table_name;  指定别名

在我们的数学成绩中还有重复的成绩,其实我们也可以使用distinct来去重

bash 复制代码
select distinct math from exam_result; 去重

3.2.where条件

where其实可以理解为和我们编程语言中的if一样,是个判断条件

比较运算符

运算符 说明
>, >=, <, <= 大于,大于等于,小于,小于等于
= 等于,NULL 不安全,例如 NULL = NULL 的结果是 NULL
<=> 等于,NULL 安全,例如 NULL <=> NULL 的结果是 TRUE(1)
!=, <> 不等于
BETWEEN a0 AND a1 范围匹配,[a0, a1],如果 a0 <= value <= a1,返回 TRUE(1)
IN (option, ...) 如果是 option 中的任意一个,返回 TRUE(1)
IS NULL 是 NULL
IS NOT NULL 不是 NULL
LIKE 模糊匹配。% 表示任意多个(包括 0 个)任意字符;_ 表示任意一个字符

逻辑运算符

运算符 说明
AND 多个条件必须都为 TRUE(1),结果才是 TRUE(1)
OR 任意一个条件为 TRUE(1),结果为 TRUE(1)
NOT 条件为 TRUE(1),结果为 FALSE(0)

其实很多内容跟编程语言中是差不多的,这边重点要关注的是NULL,之前就说过,NULL就是NULL,代表什么都没有,它并不是0或者是空字符串,因此在比较的时候要用<=>,使用单纯的=,结果还是NULL


接下来使用几个案例来看看这些运算符的使用

英语不及格的同学及英语成绩 ( < 60)

语文成绩在 [80, 90] 分的同学及语文成绩

数学成绩是 58 或者 59 或者 98 或者 99 分的同学及数学成绩

姓孙的同学 及 孙某同学

语文成绩好于英语成绩的同学

总分在 200 分以下的同学

一句话总结:别名是在 SELECT 阶段才生效的,而 WHERE 阶段在它之前,所以不能在 WHERE 里直接用 SELECT 里定义的别名。这主要是WHERE 的作用是 "筛选行",只保留符合条件的数据。如果先执行 SELECT(计算别名、拼接字段等),再执行 WHERE,就会对全表数据做无意义的计算,再筛选,效率极低。

语文成绩 > 80 并且不姓孙的同学

孙某同学,否则要求总成绩 > 200 并且 语文成绩 < 数学成绩 并且 英语成绩 > 80

NULL 的查询

3.2.结果排序

bash 复制代码
-- ASC 为升序(从小到大)
-- DESC 为降序(从大到小)
-- 默认为 ASC
SELECT ... FROM table_name [WHERE ...]
ORDER BY column [ASC|DESC], [...];

注意:没有 ORDER BY 子句的查询,返回的顺序是未定义的,永远不要依赖这个顺序

还是老样子,我们使用几个例子来演示这个排序是怎么用的

同学及数学成绩,按数学成绩升序显示

NULL的排序


查询同学各门成绩,依次按 数学降序,英语升序,语文升序的方式显示

多列排序的核心是「优先级递减」:只有前一列的值完全相同,才会触发后一列的排序规则。

** 查询姓孙的同学或者姓曹的同学数学成绩,结果按数学成绩由高到低显示**

** 查询同学及总分,由高到低**

为什么这边又可以使用total别名了呢?关键点:ORDER BY 可以使用 SELECT 中定义的别名,是因为它在 SELECT 之后执行;而 WHERE 子句在 SELECT 之前执行,所以不能在 WHERE 里直接使用别名。ORDER BY 是对 "最终要返回给用户的结果集" 排序,而结果集的列(包括别名)是 SELECT 定义的,必须等 SELECT 计算出所有列 / 别名后,才能基于这些值排序。并且ORDER BY 是 "重量级操作"(排序需要消耗大量资源),放在最后执行,只需要对 SELECT 处理后的、用户需要的少量数据排序,而非全表排序。

3.3.筛选分页结果

bash 复制代码
-- 起始下标为 0
-- 从 s 开始,筛选 n 条结果
select ... from table_name [where ...] [order by ...] limit s, n;

-- 从 0 开始,筛选 n 条结果
select ... from table_name [where ...] [order by ...] limit n;

-- 从 s 开始,筛选 n 条结果,比第二种用法更明确,建议使用
select ... from table_name [where ...] [order by ...] limit n offset s;

建议:对未知表进行查询时,最好加一条 LIMIT 1,避免因为表中数据过大,查询全表数据导致数据库卡死

limit的执行顺序是在最后的,我们的排序是需要有数据才行,而limit只有数据准备好了,才能显示,limit的本质功能就是"显示"。

但是对于排序字段是原始列的场景,数据库会在 WHERE 筛选后、SELECT 提取列之前先排序(逻辑上等价于 "先排序再选列"),但这只是引擎的性能优化,语法层面的执行顺序依然是 SELECT → ORDER BY。

4.Update

bash 复制代码
UPDATE table_name SET column = expr [, column = expr ...]
[WHERE ...] [ORDER BY ...] [LIMIT ...]

对查询到的结果进行列值更新,因此也可以把它看成是"查找"的一种

我们直接来看案例

将孙悟空同学的数学成绩变更为 80 分

将曹孟德同学的数学成绩变更为 60 分,语文成绩变更为 70 分

将总成绩倒数前三的 3 位同学的数学成绩加上 30 分

数据更新,不支持 math += 30 这种语法

将所有同学的语文成绩更新为原来的 2 倍

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

5.Delete

bash 复制代码
DELETE FROM table_name [WHERE ...] [ORDER BY ...] [LIMIT ...]

删除其实和前面的SQL语句很类似,我们直接来看案例

删除孙悟空同学的考试成绩


删除整张表数据

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


我们可以看到,我们的自增数字已经来到了4,如果我此时删除会发生什么呢?到时候再进行插入是继续按照auto_increment还是重新开始呢?

可以发现,我们删除后,auto_increment没有发生变化,继续插入后,它会按照auto_increment来继续。

5.1截断表

bash 复制代码
TRUNCATE [TABLE] table_name;

注意:这个操作慎用

  1. 只能对整表操作,不能像 DELETE 一样针对部分数据操作;
  2. 实际上 MySQL 不对数据操作,所以比 DELETE 更快,但是TRUNCATE在删除数据的时候,并不经过真正的事物,所以无法回滚
  3. 会重置 AUTO_INCREMENT 项

接下来我们进行删除和再次插入,看看我们的auto_increment会发生什么

并且我们可以注意到截断整表数据,注意影响行数是 0,所以实际上没有对数据真正操作,它其实没有经过事务和日志,而是直接删除的

我们的MySQL其实会对我们的操作什么的会进行记录,比如bin log提供了持久化方式:记录历史SQL语句或者记录数据本身,通过 bin log 可以恢复到某个时间点的数据;redo log能保证即使数据库崩溃,重启后也能通过 redo log 恢复未刷盘的数据;undo log用于回滚。

truncate 不可回滚:因为不生成 undo log,且执行时强制提交事务,即使在事务中执行 truncate,也无法通过 rollback 恢复数据;

bin log 仅恢复操作,不恢复数据:如果误执行 truncate,能通过 bin log 知道 "何时执行了 truncate",但无法从 bin log 恢复被清空的数据(需依赖备份);

redo log 保证操作不丢:truncate 的物理变更会写入 redo log,避免数据库崩溃后表状态异常。

6.插入查询结果

我们其实也可以通过select查询后的结果进行插入,也就是插入和查询操作结合。

案例:删除表中的的重复复记录,重复的数据只能有一份

思路:假设我们有一个重复数据的表,我们先创建一个新的表,然后去重查询旧表中的数据并插入到新表中,最后将两个表重命名

在数据库操作中强调 "原子性",核心是为了避免操作过程中出现数据不一致、业务异常或数据丢失------ 尤其是像 "去重替换表" 这种涉及表结构变更的核心操作,原子性是保证系统稳定的关键。我们的rename就是为了达到这个目标,为了等一切就绪了,然后统一放入、更新、生效等。

7.聚合函数

函数 说明
COUNT([DISTINCT] expr) 返回查询到的数据的数量
SUM([DISTINCT] expr) 返回查询到的数据的总和,不是数字没有意义
AVG([DISTINCT] expr) 返回查询到的数据的平均值,不是数字没有意义
MAX([DISTINCT] expr) 返回查询到的数据的最大值,不是数字没有意义
MIN([DISTINCT] expr) 返回查询到的数据的最小值,不是数字没有意义

聚合函数是 SQL 中用于对一组数据进行计算并返回单个结果的函数,核心作用是 "汇总统计数据"。

我们来看案例

统计班级共有多少同学


统计本次考试的数学成绩分数个数

统计数学成绩总分

统计数学成绩小于60分的

统计平均总分

返回英语最高分

返回 > 70 分以上的数学最低分

8. group by子句的使用

在select中使用group by 子句可以对指定列进行分组查询

bash 复制代码
select column1, column2, .. from table group by column;

案例:

准备工作,创建一个雇员信息表(来自oracle 9i的经典测试表)

EMP员工表

DEPT部门表

SALGRADE工资等级表

bash 复制代码
-- 创建部门表
create table dept (
    deptno int(2) not null comment '部门编号',
    dname varchar(14) comment '部门名称',
    loc varchar(13) comment '部门所在地点',
    primary key (deptno)  -- 主键:部门编号
) engine=innodb default charset=utf8mb4 comment='部门表';

-- 插入测试数据
insert into dept (deptno, dname, loc) 
values 
(10, 'ACCOUNTING', 'NEW YORK'),
(20, 'RESEARCH', 'DALLAS'),
(30, 'SALES', 'CHICAGO'),
(40, 'OPERATIONS', 'BOSTON');
bash 复制代码
-- 创建工资等级表
create table salgrade (
    grade int comment '工资等级',
    losal int comment '该等级最低工资',
    hisal int comment '该等级最高工资'
) engine=innodb default charset=utf8mb4 comment='工资等级表';

-- 插入测试数据
insert into salgrade (grade, losal, hisal) 
values 
(1, 700, 1200),
(2, 1201, 1400),
(3, 1401, 2000),
(4, 2001, 3000),
(5, 3001, 9999);
bash 复制代码
-- 创建员工表
create table emp (
    empno int(4) not null comment '员工编号',
    ename varchar(10) comment '员工姓名',
    job varchar(9) comment '职位',
    mgr int(4) comment '直属领导编号',
    hiredate date comment '入职日期',
    sal decimal(7,2) comment '月薪',
    comm decimal(7,2) comment '奖金',
    deptno int(2) comment '所属部门编号',
    primary key (empno),  -- 主键:员工编号
    foreign key (deptno) references dept(deptno)  -- 外键:关联部门表
) engine=innodb default charset=utf8mb4 comment='员工表';

-- 插入测试数据
insert into emp (empno, ename, job, mgr, hiredate, sal, comm, deptno) 
values 
(7369, 'SMITH', 'CLERK', 7902, '1980-12-17', 800.00, null, 20),
(7499, 'ALLEN', 'SALESMAN', 7698, '1981-02-20', 1600.00, 300.00, 30),
(7521, 'WARD', 'SALESMAN', 7698, '1981-02-22', 1250.00, 500.00, 30),
(7566, 'JONES', 'MANAGER', 7839, '1981-04-02', 2975.00, null, 20),
(7654, 'MARTIN', 'SALESMAN', 7698, '1981-09-28', 1250.00, 1400.00, 30),
(7698, 'BLAKE', 'MANAGER', 7839, '1981-05-01', 2850.00, null, 30),
(7782, 'CLARK', 'MANAGER', 7839, '1981-06-09', 2450.00, null, 10),
(7788, 'SCOTT', 'ANALYST', 7566, '1987-04-19', 3000.00, null, 20),
(7839, 'KING', 'PRESIDENT', null, '1981-11-17', 5000.00, null, 10),
(7844, 'TURNER', 'SALESMAN', 7698, '1981-09-08', 1500.00, 0.00, 30),
(7876, 'ADAMS', 'CLERK', 7788, '1987-05-23', 1100.00, null, 20),
(7900, 'JAMES', 'CLERK', 7698, '1981-12-03', 950.00, null, 30),
(7902, 'FORD', 'ANALYST', 7566, '1981-12-03', 3000.00, null, 20),
(7934, 'MILLER', 'CLERK', 7782, '1982-01-23', 1300.00, null, 10);



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

我们分组的目的是为了在分组之后,方便进行聚合统计,其中的分组也就是deptno,把其中相同的分到一组进行聚合压缩,组内一定是相同的;聚合统计指的是avg(sal)和max(sal)。其实分组就是把一组按照条件拆成了多个组,进行各自的组内统计,但是从逻辑上来讲,分组可以理解为"分表",就是把一张表按照条件在逻辑上拆成多个子表,然后分别对各自的子表进行聚合统计

分组就是指定列名,实际分组是用上图中圈出来的该列不同行数据进行分组!

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

这个例子其实是分组分了两次,第一次分组是先deptno分组,接下来在deptno分组中再进行job分组

在早期的 MySQL 版本中,GROUP BY 会隐式地对结果集进行排序(按分组列排序)。

但在现代的 MySQL(尤其是 8.0 及以后版本)中,优化器为了提升性能,默认会取消这个隐式排序的步骤。它会选择最高效的方式来完成分组,这就导致返回的行顺序是不确定的,可能是基于存储引擎的物理存储顺序,也可能是哈希表的遍历顺序。

显示平均工资低于2000的部门和它的平均工资

操作思路:

第一步:统计出来每个部门的平均工资(结果先聚合出来)

第二步:再进行判断,对聚合的结果进行判断

having是对聚合后的统计数据,条件筛选,having经常和group by搭配使用,作用是对分组进行筛选,作用有些像where。

当我们想要将name也显示出来时是不行的,这主要是没有办法对ename显示是因为按 deptno 分组后,每个部门会有多条员工记录,数据库无法确定应该返回哪个员工的名字,这会导致结果不确定。

接下来该谈谈我们的where和having问题了,它们两个有什么区别呢?

同样的我们举个例子,以前面的例子为基础

显示平均工资低于2000的部门的每种岗位和它的平均工资和岗位,SMITH不参与统计

我们可以看到我们的where和having的条件筛选的阶段是不同的,where是对具体的任意列进行条件筛选,而having是对分组聚合后的结果进行条件筛选。并且它们的优先级也是不同的,如下图

当然我们的having也可以像where一样使用,但是不推荐,它能使用的原因是可以理解为having把这个表当成了分组聚合后的分表,因此当然可以使用。

不要单纯的认为,只有磁盘上表格结构导入到 mysql,真实存在的表,才叫做表

中间筛选出来的,包括最终结果,在我看来,全部都是逻辑上的表!"MySQL 一切皆表"

未来只要我们能够处理好单表的 CURD,所有的 sql 场景,我们全部都能用统一的方式进行
🌸🌸MySQL之表的增删查改大概就讲到这里啦,博主后续会继续更新更多Qt的相关知识,干货满满,如果觉得博主写的还不错的话,希望各位小伙伴不要吝啬手中的三连哦!如有小伙伴需要Qt的安装包可以私信我,你们的支持是博主坚持创作的动力!💪💪

相关推荐
卤蛋七号1 小时前
springboot整合validation详细教程
后端
robotx1 小时前
安卓15开机动画结束流程简单分析
android
2301_793804691 小时前
C++与硬件交互编程
开发语言·c++·算法
XiaoLeisj1 小时前
Android 模块化与组件化工程实战:从子模块库化、Gradle 配置统一到 ARouter 解耦跨模块页面通信与 Fragment 解耦集成
android·gradle·模块化·arouter
神奇小汤圆1 小时前
为什么redis不能跨机房部署哨兵模式
后端
锦木烁光1 小时前
Flowable 实战:从架构解耦到多状态动态查询的高性能重构方案
前端·后端
梨落秋霜1 小时前
Python入门篇【正则表达式】
python·mysql·正则表达式
csbysj20202 小时前
jEasyUI 转换 HTML 表格为数据网格
开发语言
None3212 小时前
【NestJs】Websocket 通关指南:从入门到实战
后端·node.js