MySQL学习笔记 - 基础部分

MySQL基础部分

一、基本认识

1.1 数据库的作用:

1.2 数据库的相关概念:

1.3 SQL的优点:

二、非关系型数据库

三、关系型数据库(RDBMS)

3.1 设计规则:

3.2 表的关联关系:

3.2.1 一对一关联:

3.2.2 一对多关联:

3.2.3 多对多关联:

3.2.4自我关联:

3.3 优势:

四、一条数据存储的过程

五、安装和配置MySQL

5.1 启动MySQL服务:

5.2 字符集相关的设置:

5.3 命令行客户端的字符集问题:

5.4 图形化用户界面SQLyog

六、常用的MySQL命令

七、MySQL的语法规范

八、MySQL中的数据类型

8.1 常见的数据类型:

8.2 常见数据类型的属性: 

九、常见的约束constraint

9.1 含义与语法:

 9.2 分类(六大约束):

9.2.1 约束的创建时机、约束的分类

9.2.2 主键约束

9.2.3 列级/表级约束

9.2.4 复合约束

9.2.5 主键约束和唯一可空的约束的对比:

9.2.6 外键约束foreign key

9.3 创建表时字段添加约束的通用写法:

9.4 约束的添加/删除

9.4.1 修改表时添加约束:

9.4.2 修改表时删除约束

9.5 标识列 / 自增长列(必须是键列)

9.5.1 含义:

9.5.2 特点:

9.5.3 创建表时设置标识列

9.5.4 添加/删除标识列:

MySQL8的新特性 - 自增变量的持久化:

十、SQL语句的执行顺序

十一、DDL语言的学习

11.1 库的相关操作:

11.2 表的相关操作:

11.2.1 表的创建:

11.2.2 表的复制:

11.2.3 表的修改:

11.2.4 表的删除:

11.2.5 清空表:

十二、DQL语言的学习

12.1 基础查询:

12.1.1 基础查询的语法和特点:

12.1.2 显示当前所有的数据库

12.1.3 跳到myemployees数据库中

12.1.4 查询表中的单/多个/所有字段

12.1.5 查询常量值、常量表达式、函数

12.1.6 起别名:as 或者 空格

12.1.7 去重

12.1.8 连接字符串

12.1.9 显示表中的所有列,并用逗号隔开

12.2 条件查询:

12.2.1 条件查询的语法和特点:

12.2.2 按条件表达式筛选:

12.2.3 按逻辑表达式筛选:

12.2.4 模糊查询:

12.3 排序查询:

12.3.1 排序查询的语法与特点:

12.3.2 常见的一些操作:

12.4 常见函数:

12.4.1 单行函数:(可以相互嵌套)

12.4.2 分组函数:(不能嵌套使用)

12.5 分组查询:

12.5.1 分组前/后筛选(where / having):

12.5.2 按函数或表达式分组:

12.5.3 按多个字段进行分组:

12.6 多表查询:又称关联查询 或 连接查询

12.6.1 连接查询的分类:

12.6.2 sql92标准:

12.6.3 sql99标准:

12.6.4 总结:

12.7 子查询:

12.7.1 分类:

12.7.2 特点:

12.7.3 按子查询出现的位置分类:

12.8 分页查询:

12.9 联合查询:

十三、DML语言的学习

13.1 DDL和DML的区别

13.2 DML的相关操作

13.2.1 插入语句

13.2.2 修改语句 

13.2.3 删除语句

13.3 MySQL8的新特性:计算列

十四、常见的数据库对象:

14.1 视图

14.1.1 含义:

14.1.2 特点:

14.1.3 创建视图语法:

14.1.4 修改视图语法:

14.1.5 删除视图的语法

14.1.6 视图的更新:

14.1.7 使用的场景:

14.1.8 视图和表的对比:

14.2 存储过程与存储函数

14.2.1 含义:

14.2.2 优点:

14.2.3 存储过程和函数的对比:

14.2.4 存储过程

14.2.5 存储函数

十五、变量、流程控制和游标

15.1 变量

15.1.1 系统变量:

15.1.2 自定义变量:

15.1.3 会话用户变量和局部变量的对比:

15.2 流程控制

15.2.1 分支结构:

15.2.2 循环结构:

15.3 游标(光标)

15.3.1 使用游标的步骤:

15.3.2 使用范例

15.3.3 总结:

十六、触发器

16.1 触发器的引入:

16.2 触发器的介绍:

16.3 触发器的创建:

16.3.1 说明:

16.3.2 同时给两张表添加相同的记录:

16.3.3 将一张表中删除的记录存在另一张表中:

16.3.4 在插入数据前,检查数据是否满足条件,不满足则添加失败

16.4 删除触发器:

16.5 查看触发器的定义:

16.6 优缺点:

十七、MySQL8.0新增特性

17.1 窗口函数:

17.1.1 窗口函数的介绍:

17.1.2 窗口函数的分类:

17.1.3 举例:

17.1.4 总结:

17.2 公用表表达式:

17.2.1 介绍:

17.2.2 分类:

17.2.3 总结:


一、基本认识

1.1 数据库的作用:

1)持久化的保存数据到本地硬盘上加以“固化”,这通常需要通过各种关系数据库来完成,当然也可以存储在磁盘文件中;

 2)可以实现结构化查询,方便管理;

 

1.2 数据库的相关概念:

1)DB(数据库database):存储数据的“仓库”,保存了一系列有组织的数据;

2)DBMS(数据库管理系统`Database Management System`):数据库是通过DBMS创建和操作的容器;

常见的数据库管理软件:MySQL、Oracle(收费)、DB2(适用于处理海量的数据,常用于银行系统中)、sqlServer(只能安装在windows环境下);

DBMS的分类:

  • 基于共享文件系统的,如access等
  • 基于客户机-服务器的,如MySQL、Qracle、SqlServer等;
  • SQL(结构化查询语言(structured query language)):专门用来于数据库通信的语言;

总结:MySQL数据库服务器中安装了`MySQL DBMS`,使用`MySQL DBMS`并通过SQL来管理和操作DB。

1.3 SQL的优点:

1)不是某个特定数据库供应商专有的语言,几乎所有的数据库管理系统DBMS都支持SQL;

2)一种强有力的语言,灵活使用其语言元素,可以进行非常复杂和高级的数据库操作;


二、非关系型数据库

键值型数据库:Redis,使用场景是作为内存缓存

文档型数据库MongoDB、搜索引擎数据库Elasticsearch/Solr、列式数据库HBase、图形数据库InfoGrid

列式数据库:相对于行式数据库而存在,Oracle、MYSQL、SQL Server等数据库都是采用行式存储(Row-based)。相对于行式存储,列式存储的优势在于能大大降低系统的I/O,适合于分布式文件系统,不足是功能相对有限。


三、关系型数据库(RDBMS)

3.1 设计规则:

1)关系型数据库的典型数据结构就是数据表,且这些数据表的组成是结构化的;

2)将数据放在表中,表再存放在库中;

3)一个数据库可以有多张表,每个表都有自己的名字,用来标识自己;且表名具有唯一性;

4)表具有一些特性,这些特性定义了数据在表中如何存储,类似于“类”的设计;行式存储的,每行类似于 “对象”,一行数据称为一个实体/一条记录;表由一个或多个列组成,每列类似于 “属性”,一列数据称为一个字段

3.2 表的关联关系:

表与表之间的数据是有关系的。现实世界中,各种实体以及实体间的各种联系均用关系模型来表示。

四种关联关系:一对一关联、一对多关联、多对多关联、自我关联

3.2.1 一对一关联:

实际的开发中应用的并不多,因此一对一可以创建成一张表;

两种建表原则

  • 外键唯一:主表的主键和从表的外键(唯一),形成主外键关系,外键唯一;
  • 外键是主键:主表的主键和从表的主键,形成主外键关系;

3.2.2 一对多关联:

实例场景:客户表和订单表、分类表和商品表、部门表和员工表;

一对多的建表原则在从表创建一个字段,并将该字段作为外键指向主表的主键;

3.2.3 多对多关联:

将两个表的主键都插入到中间表中,该中间表常称为联接表;

3.2.4 自我关联:

同一张表中,两列的数据是有关系的;

3.3 优势:

  • 复杂查询:支持多表查询;

  • 事务支持:使得安全性很高的数据访问得以实现。


四、一条数据存储的过程

存储数据时处理数据的第一步,只有正确地把数据存储起来,我们才能进行有效地处理和分析。

MySQL中,一个完整地数据存储过程总共4步,如下图:

 从系统架构的层次看,MySQL数据库系统从小到大依次是,数据库服务器、数据库、数据表、数据表的行与列


五、安装和配置MySQL

(57条消息) mysql数据库安装(详细)体会!的博客-CSDN博客mysql安装

5.1 启动MySQL服务:

任务管理器/服务/MySQL/启动即可 或者通过用命令行启动的方式实现:

 (mysql最终将数据也是以文件形似保存在磁盘中,路径(可以通过my.ini配置文件查看):# Path to the database root datadir="C:/ProgramData/MySQL/MySQL Server 5.5/Data/"

5.2 字符集相关的设置:

5.3 命令行客户端的字符集问题:

在命令行客户端无法正常显示中文,原因:服务器端认为你的命令行客户端的字符集是utf8,而实际上你的命令行客户端的字符集是GBK

解决方案:设置当前命令行客户端的字符集是gbk。

5.4 图形化用户界面SQLyog


六、常用的MySQL命令

insert / delete / update / select:增删改查

select version();   # 查看mysql的版本
# 或者直接在命令行窗口C:\Users\29371>mysql --version

# 导入sql数据
source 路径+文件名.sql

show databases;      # 显示当前所有的数据库
use test;   # 跳到database_name:test数据库中
show tables;         # 显示当前所在数据库中的所有表
select database();   # 查看当前在哪个数据库中

# 在当前数据库中,创建表
create table stuinfo(
id int,
name varchar()
);
show tables;
desc stuinfo;   -- 查看表的结构

# 向表中插入数据
insert into stuinfo (id,name) values(1, "tom");  
insert into stuinfo (id,name) values(2, "jary");  
select * from stuinfo;   // 查看表中的数据

# 修改表中的数据
update stuinfo set name="wuli" where id=1;

# 删除表中的数据
delete from stuinfo where id=1;

七、MySQL的语法规范

1)不区分大小写,建议关键字大写,表名、列名小写;

2)标识符命名的规则:

  • 数据库名、表名不得超过30个字符,变量名限制29个;
  • 必须只能包含A-Z、a-z、0-9、_共63个字符;
  • 数据库名、表名、字段名等对象名中间不要包含空格;
  • 同一个MySQL软件中,数据库不能同名;同一个库中,表不能重名;同一个表中,字段不能重名;
  • 如果字段名和MySQL的保留字、数据库系统或常用方法冲突,使用时需要用单引号引起来;
  • 多表查询时,同一个字段在不同的表中,要保持数据类型的一致;

2)每条命令下,采用”分号结尾“;

3)每条命令根据需要,可以进行缩进 或者 换行;

4)注释:# ... 或者 -- ...;多行注释:/* ... */;

5)mysql中尽量使用' '" "也可以使用但不推荐;

6)MySQL提供了一个标准的SQL模式匹配,可以使用“_”来匹配任意单个字符,"%"可以用来匹配任意数量(包含0个字符)的字符

# 查询字符串第二个字符是_,第三个字符是'a'
select * from employees 
where last_name like '_\_a%';   # 或 where last_name like '_$_a%' escape '$';

7)空值不是0或空字符,参与计算时必须通过ifnull(字段名,0),来指定当字段名是整型且为空时值为0;

8)sql中可以使用的运算符号包括:

  • 算数运算符:+、-、*、/(div)、%(mod);
  • 比较运算符:=/<=>安全等于、!=、>、>=、<、<=;
  • 非符号比比较运算符:null、isnull()、is not null;in (set)、not in (set);between ... and ...;least()、greatest();like;
  • 逻辑运算符:and / &&、or / ||、not / !、xor;
  • 位运算符:&、|、~、>>、<<、^;

        运算符的优先级:数字越大,优先级越高;

 9)sql中字符+数值,会发生隐式类型转换,如果转换成功会将字符中的数字+数值,否则会置字符为0;


八、MySQL中的数据类型

使用规范:任何字段如果是非负数,必须是unsigned无符号型;

默认所有类型的值都可以为null,且空字符串和0都不是null;

8.1 常见的数据类型:

类型类型举例
整型tinyint、smallint、mediumint、int/integer(推荐使用)、bigint
浮点型float、double
定点数类型decimal(推荐使用)
位类型bit(位数)
日期时间类型year、time、date、datetime(推荐使用)、timestamp
文本字符串类型char(简短且固定长度)、可变长度varchar(推荐使用);可变长度:tinytext、text、mediumtext、longtext
枚举类型enum:用于保存枚举
聚合类型set:用于保存集合
二进制字符串类型binary、varbinary:用于保存较短的二进制;tinyblob、blob(保存较长的二进制数据)、mediumblob、longblob
json类型json对象、json数组
空间数据类型

单值类型:point、linestring、polygon、geometry

多值类型:multipoint、multilinestring、multipolygon、geometrycollection

8.2 常见数据类型的属性: 

约束的关键字含义
null数据列可为null
not null数据列不允许为null
default默认值
primary key主键
auto_increment自动递增,适用于整型
unsigned无符号
character set 字符集名指定一个字符集

整型:

 特点:

1)如果不设置无符号还是有符号,默认是有符号。通过`type unsigned`可以将无符号变为有符号;

2)如果输入的数值超过了整型的范围,会报`out of range`的错误,并且插入临界值;

3)如果不设置长度,会有默认长度;

4)长度代表了显示的最大宽度。通过`type zerofill`可以将,没有用到的位数在左边填充0并显示;

create table test_int (
	id int(7) zerofill,
    id2 int(11) unsigned,
    id3 int
);
insert into test_int values(123, 123, 123);

select * from test_int;

小数:

 特点:

1)`float(M, D)、double(M, D)、dec(M, D)、decimal(M, D)`中,M代表整数部分的位数+小数部分的位数,D代表小数部分的位数;

2)如果超出范围,则添加临界值;

3)M、D都可以省略;但`decimal`默认M=10、D=0,而float、double会根据插入数据的精度进行决定;

4)精度:定点型>浮点型;

5)浮点数类型的无符号数取值范围,只相当于有符号数取值范围的一半(即只相当于有符号数取值范围中大于0的部分):mysql中浮点数的存储格式是:符号S、尾数M、阶码E组成,其中无论浮点数有无符号,符号位都有数据;

6)浮点数与定点数的对比:浮点数:取值范围大,但不精准;定点数:取值范围相对较小,但精准更高(因定点数在MySQL内部是以‘字符串’的形式进行存储的);

7)实际开发中,一般都用decimal;如果存储的数据范围超过decimal的范围,建议将数据拆分成整数和小数分开存储;

总结:所选择的类型越简单越好,节省空间;要进行相等判断时,尽量采用decimal来存储小数;

create table test_float (
	f1 float(5, 2),
    f2 double(5, 2),
    f3 decimal(5, 2)
);

insert into test_float values(123.456,123.456,123.456),(123.4,123.4,123.4),(1233.4,1243.4,1234.4)  

select * from test_float;

字符型:

较短的文本(char、varchar)、较长的文本(text、blob(较大的二进制文件))

 特点:

1)M代表的是字符串中,最大的字符数;

2)char、varchar的对比:

varchar不会预先分配内存,每次存储时都有额外的计算,得到长度并占一个byte用于存储长度;

类型特点空间上时间上使用场景
char(M)固定长度浪费存储空间效率高存储不大,速度要求高;简短且固定长度;
varchar(M)可变长度节省存储空间效率低非char的情况

3)char中M可以省略,varchar中M是不可省略的

Enum枚举类型:

说明:要求插入的值必须是列表中指定的值之一;没有指明not null时,插入null也是有效的;

create table test_enum(
	i enum('1','2',"23")
);
insert into test_enum values('1'),("23"),('3');

Set类型:

说明:与Enum类似,但Set类型一次可以选取多个成员,而Enum只能选择一个;set用来保存集合,即出现重复时会自动的剔除掉;

create table test_set(
	i set('1','2','23')
);
insert into test_enum values('1,2'),('1,23,1'),('3');  # 出现重复时,会自动剔除重复的

日期型: 

 特点:

1)year只保存年;date只保存日期;time只保存时间;

2)datetime、timestamp都只保存日期+时间;但datetime不受时区的影响,datestamp受时区的影响; 

总结:使用最多的还是datetime,但对于有计算要求的还需要用时间戳timestamp来存储;


九、常见的约束constraint

9.1 含义与语法:

        一种限制,用于限制表中字段的数据,为了保证表数据得准确性和可靠性。SQL通过约束的形式对表数据进行额外的条件限制,主要从下列四个方面考虑:

  • 实体完整性Entity Integrity:即同一张表中,不能存在相同且无法区分的记录;
  • 域完整性Domain Integrity:即字段的值必须符合一定的范围;
  • 引用完整性Referential Integrity:即多表查询时,要能进行正确的查找;
  • 用户自定义的完整性User defined Integrity;
create table 表名(
	字段名 字段类型 列级约束,
    表级约束
)

 9.2 分类(六大约束):

约束的关键字作用
not null(非空约束)用于保证该字段的值不为空
default(默认值约束)用于保证该字段有默认值
primary key(主键约束)用于保证该字段的值非空且唯一,即是非空约束和唯一性约束的组合,等价于unique + not null
unique(唯一性约束)用于保证该字段的值具有唯一性,但可出现多个null
foreign key(外键约束)用于限制两个表的关系,用于保证该字段的值必须来自于主表的关系列的值
check(检查约束)mysql 5.7不支持,但mysql 8.0支持

注意:建表时,加not null default '' 或 default 0,可以避免出现null值而影响计算 或 降低效率。 

9.2.1 约束的创建时机、约束的分类

添加约束的时机:创建表时、修改表时;

约束的添加分类:列级约束、表级约束;

9.2.2 主键约束

  • 一张表中,只能有一个主键约束,但一个主键约束可以对应一列或者多列(复合主键)
  • MySQL中主键约束名,总是primary,即自定义主键约束名无用;
  • 当创建主键约束时,系统默认会在所在的列或者列组合上建立对应的主键索引(能够通过主键查询,则根据主键查询,且效率更高);
  • 如果删除主键约束 ,则主键约束对应的索引会自动删除;
  • 如果主键约束的整型列同时是自增长列,则给主键添加元素时,添加0/null则会自动的给该列最大值加一作为当前行的主键值(推荐),添加特定的其余值则会排序后添加到相应的行位置其余行位置会改变(不推荐);

9.2.3 列级/表级约束

列级约束:六大约束都支持,只是外键/check约束在mysql 5.7中没有效果

表级约束:除了非空约束not null、默认值约束default,其他的都支持

列级约束和表级约束的区别:

  • 位置:列级约束在列的后面,表级约束在所有列的后面;
  • 支持的约束类型:列级约束支持所有语法但外键/check没有效果,表级约束不支持默认和非空约束;
  • 是否能起约束名:列级约束不可以,表级约束可以但对主键没有效果(主要用于表级约束给外键起别名);

9.2.4 复合约束

列级约束的组合:列级约束,可以在一个字段上追加多个,中间用逗号隔开,且没有顺序要求

复合唯一性约束(表级约束):

create table user(
	id int,
    `name` varchar(15),
    `password` varchar(25)
    
    # 复合唯一性约束(表级约束)
    constraint uk_user_name_pwd unique(`name`,`password`);
);

insert into user values(1,'tom','abc');   
insert into user values(1,'tom1','abc');  # 添加成功
insert into user values(1,'tom','abc');   # 添加失败

注意:如果是多列组合的复合主键约束,那么这些列都不允许为空值,并且组合的值不允许重复。

9.2.5 主键约束和唯一可空的约束的对比:

主键唯一
唯一性
是否允许为空×
一个表中能否有多个×
是否允许组合√(不推荐)√(不推荐)

9.2.6 外键约束foreign key

作用:限制某个表的某个字段的引用完整性。

使用规范:

  • 该约束是添加在从表中的,用于引用主表中主键列或唯一列的值(因为被参考的列必须是唯一的);
  • 插入数据时,先插入主表(父表),再插入从表(子表);删除数据时,先删除从表,再删除主表
  • 主表的被引用列 和 从表的外键列要求类型一致,意义一样,名称无要求;
  • 创建外键时,外键的默认名不是列名而是自动生成的,也可以指定外键约束名
  • 从表中,可以有多个外键约束
  • 当创建外键约束时,系统默认会在所在的列上建立对应的普通索引(即列名),但根据外键约束名的查询效率高
  • 删除外键约束后,必须手动删除对应的索引

        总结:外键约束的约束关系是针对主表和从表双方的;添加外键约束后,主表的修改和删除、从表的添加和删除受约束;

使用外键时的约束等级:

  • cascade方式:在父表update/delete记录时,同步update/delete掉子表的匹配记录;
  • set null方式:在父表上update/delete时,将子表上匹配记录的会被置空为null,但要注意子表的外键列不能为not null;
  • no action方式:如果子表中有匹配的记录,则不允许对父表对应候选键进行update/delete操作;
  • restrict方式:同no action,都是立即检查外键约束;
  • set default方式:父表有变更时,子表将外键列设置为一个默认的值,但Innodb不能识别;

注意:如果没有指定等级,就相当于restrict方式。

级联删除、级联置空、级联更新和级联删除置空:

alter table 表名 drop foreign key 外键名;
# 传统的方式添加外键
alter table 表名 add constraint 外键名 foreign key(从表的外键字段) references 被引用的主表名(主表中相应的字段名);
# 用传统的方式添加的外键,删除时需要“先删从表再删主表”;

# 级联删除
alter table 表名 add constraint 外键名 foreign key(从表的外键字段) references 被引用的主表名(主表中相应的字段名) on delete cascade;
delete from 主表名 where 筛选条件;
# 用级联删除的方式,创建的外键,删除主表时,会直接改变从表;

# 级联置空
alter table 表名 add constraint 外键名 foreign key(从表的外键字段) references 被引用的主表名(主表中相应的字段名) on delete set null;
delete from 主表名 where 筛选条件;
# 用级联置空的方式,创建的外键,将主表对应的位置置空/删除的同时,从表中相应的位置会被置空;

# 级联更新和级联删除置空
alter table 表名 add constraint 外键名 foreign key(从表的外键字段) references 被引用的主表名(主表中相应的字段名) on update cascade on delete set null;   # 父表更新时,子表自动匹配并同步更新;父表删除时,子表会置空;

对于外键约束,最好采用:on update cascade on delete restrict的方式。

create table 从表名 (
    字段名 字段类型,

    # 表级约束
    foreign key(字段名) references 主表名(主表中对应的字段) on update cascade on delete restrict
);

总结:

  1. 如果两张表存在关联关系,它们之间并不一定需要外键约束。
  2. 建和不建外键约束的区别?建立外键约束后,对表的相关操作(创建表、删除表、添加、修改、删除)都会受到语法层面的限制。
  3. 外键约束和级联更新更适合于单机低开发不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度;外键约束就不能跨引擎使用;

9.3 创建表时字段添加约束的通用写法

DROP TABLE IF EXISTS stuinfo; 
CREATE TABLE stuInfo(
    # 主键约束
    id INT PRIMARY KEY,
    # 唯一非空约束
    stuName VARCHAR(20) NOT NULL,
    # 检查约束
    sex CHAR(1),
    seat INT UNIQUE,   # 唯一可空约束
    age INT DEFAULT 18,   # 默认约束
    majorID INT,

    # 表级约束
    CONSTRAINT fk_major_id FOREIGN KEY(majorID) REFERENCES major(id)  # 引用主表major的id主键列,并给从表stuinfo的majorID列创建外键约束
);

注意:建表时,一般为了避免出现null值,需要加 not null default '' 或 default 0 等约束。因null值比较特殊,在遇到运算符时,通常会返回null值,导致运算出问题;null值的存在,也会影响效率,导致索引的效果不是很好。

举例:

CREATE TABLE major(
    id INT PRIMARY KEY,
    majorName VARCHAR(20)
)

# 列级约束(对一个字段可以同时添加多个约束)
DROP TABLE IF EXISTS stuinfo; 
CREATE TABLE stuInfo(
    # 主键约束
    id INT PRIMARY KEY,
    # 唯一非空约束
    stuName VARCHAR(20) NOT NULL,
    # 检查约束
    sex CHAR(1) CHECK(sex='男' OR sex='女'),
    seat INT UNIQUE,   # 唯一可空约束
    age INT DEFAULT 18,   # 默认约束
    # 外键约束
    majorID INT REFERENCES major(id) 
);
DESC stuinfo;  # 查看表的结构
SHOW INDEX FROM stuinfo;  # 查看表中所有的索引,包括外键、主键、唯一

# 表级约束
DROP TABLE IF EXISTS stuInfo; 
CREATE TABLE stuInfo(
    id INT,
    stuName VARCHAR(20),
    sex CHAR(1),
    seat INT,   
    age INT,  
    majorID INT,
     # 主键约束
    CONSTRAINT pk PRIMARY KEY(id),    
    # 唯一可空约束
    CONSTRAINT uq UNIQUE(seat),        
    # check约束
    CONSTRAINT ck CHECK(sex='男' OR sex='女'),  
    # 外键约束
    CONSTRAINT fk_major_id FOREIGN KEY(majorID) REFERENCES major(id)  
);
DESC stuinfo; 
SHOW INDEX FROM stuinfo;  

9.4 约束的添加/删除

9.4.1 修改表时添加约束:

# 添加列级约束
alter table 表名 modify column 字段 字段类型 列级约束;

# 添加表级约束
alter table 表名 add [constraint 外键名] 表级约束(字段);
#添加外键
alter table 表名 add [constraint 外键名] foreign key(从表的字段) references(主表的字段);

9.4.2 修改表时删除约束

# 删除非空约束
alter table 表名 modify column 字段名 字段类型 null;

# 删除默认值约束
alter table 表名 modify column 字段名 字段类型;

# 删除主键约束
alter table 表名 drop primary key;

# 删除唯一性约束
# 1)在添加唯一性约束的列上,会自动创建唯一索引
# 2)删除唯一约束只能通过删除唯一索引的方式
# 3)创建唯一性约束时,未指定名称,如果是单列则默认与列名同名,如果是组合列则默认与第一列列名同名
alter table 表名 drop index 字段名;

# 删除外键约束
alter table 表名 drop foreign key 字段名;

9.5 标识列 / 自增长列(必须是键列)

9.5.1 含义:

不可以手动的插入值,系统会提供默认的序列值;

9.5.2 特点:

  • 一张表最多有一个自增长列;
  • 当需要产生唯一标识符或顺序值时,可设置自增长列;
  • 因自增长列约束的列值不能重复,故必须是键列(主键列 或 唯一键列)
  • 自增长约束的列的数据类型,必须是整型
  • 如果自增长列指定了0和null,会在当前最大值的基础上自增;如果自增长列手动指定了具体值,直接赋值为具体的值
  • 标识列可以设置起始值和步长起始值:通过手动插入值设置,默认是1;步长:通过set auto_increment_increment = 3设置,默认是1;

9.5.3 创建表时设置标识列

# 语法:
CREATE TABLE 表名(
    # 设置该字段为标识列(自增长列,auto_increment)且该字段必须是一个key
	字段名 字段类型 约束 AUTO_INCREMENT,   
	...
);
DROP TABLE IF EXISTS tab_identity;
CREATE TABLE tab_identity(
    # 设置id为标识列(自增长列,auto_increment)
	id INT PRIMARY KEY AUTO_INCREMENT,   
	name_ VARCHAR(20)
);
INSERT INTO tab_identity(id,name_) VALUES(NULL,"john"),(NULL,"lili"),(NULL,"nini");
TRUNCATE TABLE tab_identity;
INSERT INTO tab_identity(id,name_) VALUES(10,"john"),(NULL,"lili"),(NULL,"nini");
SELECT * FROM tab_identity;

# 查看自增长的系统变量
SHOW VARIABLES LIKE "%auto_increment%";
# 设置自增长列的步长
SET auto_increment_increment=3;
INSERT INTO tab_identity(id,name_) VALUES(NULL,"lili"),(NULL,"nini");
SELECT * FROM tab_identity;

9.5.4 添加/删除标识列:

# 修改表并添加标识列
alter table tab_identity modify column id int primary key auto_increment;

# 修改表时删除标识列
alter table tab_identity modify column id int;

MySQL8的新特性 - 自增变量的持久化:

1)MySQL 8.0之前:

自增主键auto_increment的值,如果大于max(primary key)+1,在MySQL重启后,由于自增主键没有持久化,会重置auto_increment = max(primary key)+1。

create table test_auto_increment(
    id int primary key auto_increment
);
​
# 查看不同情况下,表中id字段的值
set auto_increment_increment=1;    # 设置自增长列的步长为1
insert into test_auto_increment values(0),(0),(0),(0);   # 1,2,3,4
delete from test_auto_increment where id=4;    # 1,2,3 
insert into test_auto_increment values(0);     # 1,2,3,5
delete from test_auto_increment where id=5;     # 1,2,3;此时auto_increment=6
# 重启服务器后,由于mysql5.7中自增主键没有持久化,会重置auto_increment= max(primary key)+1=4
insert into test_auto_increment values(0);     # 1,2,3,4

总结:在MySQL 5.7系统中,对于自增主键的分配规则,是由InnoDB数据字典内部的一个计数器来决定的,而该计数器只在内存中维护,并不会持久化到磁盘中。故重启数据库服务器时,该计数器会被初始化为max(primary key)+1

2)MySQL 8.0中

将自增长列的计数器持久化在了重做日志中,每次计数器的改变都会将其写入日志中。如果数据库发生重启,InnoDB会根据日志中的信息来初始化计数器的内存值;

# 重启服务器后,由于mysql8.0中自增主键有持久化,会记录auto_increment=6,并从当前值开始重新自增
insert into test_auto_increment values(0);     # 1,2,3,6

十、SQL语句的执行顺序


十一、DDL语言的学习

DDL :Data Define Language

主要用来做库和表的管理创建create、删除alter、修改drop;这些语句定义了不同的数据库、表、视图、索引等数据库对象,还可以用来创建、删除、修改数据库和数据表的结构

DDL中对库和表的操作,在事务中,无法实现回滚,要慎用;

11.1 库的相关操作:

# 库的创建语法:
create database 库名
create database 库名 character set 'gbk' 
CREATE DATABASE IF NOT EXISTS stuinfo character set 'utf8';  # 推荐使用
# 如果stuinfo数据库不存在,则创建成功;否则,创建失败,但不报错。

show databases; # 查看当前连接中,存在的所有数据库
use 库名;  # 可以用来切换数据库
select database();  # 查询当前使用的数据库
show tables;  # 查询当前数据库下,存在的所有数据表

# 修改库的字符集
ALTER DATABASE stuinfo CHARACTER SET 'utf8';

# 库的删除
DROP DATABASE IF EXISTS stuinfo;

注意:database创建后不能修改名字。但一些可视化工具可以通过建新库,并把原库中所有表复制到新库中,再删除原库来完成的。

11.2 表的相关操作:

11.2.1 表的创建:

# 表的创建语法:
#CREATE TABLE 表名(
#	列名1 列的类型【(长度)约束】
#	列名2 列的类型【(长度)约束】
#	列名3 列的类型【(长度)约束】
#	...
#);
CREATE TABLE IF NOT EXISTS book(
	id INT,
	book_name VARCHAR(20),
	price DOUBLE,
	author_id INT,
	printdate DATETIME, # 出版日期
	loan_stuID INT
);
DESC book;    # 显示表的详细信息:

11.2.2 表的复制:

1)仅仅复制表的结构:

CREATE TABLE book_copy LIKE bookinfo;

2)拷贝表的部分/所有数据:

# 可以基于现有的表,创建新的表
create table employees_copy
as 
select employee_id,last_name,salary from employees;

# 拷贝现有的表
create table employees_copy
as 
select * from employees;

11.2.3 表的修改:

数据库的字段名的修改代价很大,会发生数据的拷贝,所以字段名的修改要慎重。

# 表的修改:修改列名、修改列的类型或约束、添加新的列、删除列、修改表名
# 1) 修改列名:
ALTER TABLE book CHANGE COLUMN printdate pubDate DATETIME;
# 2)修改列的类型或约束
ALTER TABLE book MODIFY COLUMN pubDate TIMESTAMP;
# 3)添加新列
ALTER TABLE book ADD COLUMN pageNum INT(20) 【first|After 列名:添加到哪一行】;
# 4)删除列
ALTER TABLE book DROP COLUMN pageNum;
# 5)修改表名
ALTER TABLE book RENAME TO bookInfo;

11.2.4 表的删除:

DROP TABLE IF EXISTS book;

11.2.5 清空表:

清空表中的所有数据,但保留表的结构。

truncate table employees_copy;

十二、DQL语言的学习

DQL :Data Query Language,主要用来查询数据

12.1 基础查询:

12.1.1 基础查询的语法和特点:

语法:select 查询列表 from 表名
特点:
1、查询列表可以是:表中的字段、常量值、表达式、函数
2、查询的结果是一个虚拟的表格

12.1.2 显示当前所有的数据库

# 显示当前所有的数据库
SHOW DATABASES; 

12.1.3 跳到myemployees数据库中

# 跳到myemployees数据库中
USE myemployees;    
select database();  # 查看当前在哪个数据库中

12.1.4 查询表中的单/多个/所有字段

# 查询表中的单个字段
select last_name from employees;

#ifnull(字段名,0),即如果字段名为空,则将其赋值为零
select ifnull(commission_pct, 0), commission_pct from employees;

#isnull(字段名),即如果字段名为空,则返回零
select isnull(commission_pct), commission_pct from employees;


# 查询表中的多个字段
select first_name, last_name, email from employees;


# 查询表中的所有字段
select * from employees;

12.1.5 查询常量值、常量表达式、函数

# 查询常量值
select 100;
select 'John';

# 查询常量表达式
select 100*98;
select 100%98;

# 查询函数
select version();

# 显示表中的结构
DESC departments;  

12.1.6 起别名:as 或者 空格

#select 100%98 As 结果;
select last_name As 姓 from employees;
select 100%98 结果;
select last_name 姓 from employees;
SELECT last_name "out.put" FROM employees;   # 别名中有特殊字符要加双引号

12.1.7 去重

SELECT DISTINCT department_id FROM employees;

12.1.8 连接字符串

SELECT CONCAT(last_name,' ',first_name) AS 姓名 FROM employees;

12.1.9 显示表中的所有列,并用逗号隔开

SELECT CONCAT(`first_name`,',',`last_name`,',',`email`,',',`phone_number`,',',`job_id`,',',`salary`,',',IFNULL(commission_pct,0))
FROM employees;

12.2 条件查询:

12.2.1 条件查询的语法和特点:

select 查询列表 from 表名 where 筛选条件

12.2.2 按条件表达式筛选:

# 条件运算符:>、<、=/<=>安全等于、!=/<>、>=、<=
SELECT * FROM employees WHERE salary>2000;

12.2.3 按逻辑表达式筛选:

# 逻辑运算符:&&/and、||/or、!/not
SELECT * FROM employees WHERE salary>2000 AND salary<10000;

12.2.4 模糊查询:

like:一般需要结合通配符使用,用于判断字符型/数值型;

通配符:%代表任意多个字符;_代表任意单个字符;

between and、in、is null、is not null;

# like、between ... and ...、in、is null、is not null、通配符
SELECT * FROM employees WHERE salary BETWEEN 2000 AND 10000;     #等价于...<=salary<=...

select * from employees where last_name like '%t%';

select last_name,salary from employees where last_name like '___T%'; #查询第四个字符是T的last_name

select last_name,salary from employees where last_name like '_$_T%' ESCAPE '$'; 
#指定该字符是通配符

select last_name, job_id from employees where job_id in('IT_PROG',"MK_MAN","PU_MAN");

#安全等于: <=>
SELECT last_name, job_id, salary FROM employees WHERE salary <=> 12000;  

select last_name, job_id, salary*12*(1+ifnull(commission_pct,0)) As 年薪 from employees;

12.3 排序查询:

12.3.1 排序查询的语法与特点:

order by子句,一般放在查询语句的最后,limit语句除外;

order by子句中,可以支持单个字段(一级排序)、多个字段(多级排序)、表达式、函数、别名等;

select 查询列表 from 表 【where 筛选条件】order by 排序列表 asc/desc
#其中,asc表升序,desc表降序,默认是升序

12.3.2 常见的一些操作:

select last_name, salary from employees order by salary desc;  #其中,asc表升序,desc表降序,默认是升序

select * from employees where department_id>30 order by hiredate desc;   # 一级排序   

select 
	last_name, salary*12*(1+ifnull(commission_pct,0)) As 年薪 
from 
	employees 
order by 
	年薪 desc;       
	
SELECT 
	last_name, salary FROM employees 
WHERE
	salary NOT BETWEEN 8000 AND 17000
ORDER BY 
	salary DESC;
	
select last_name, length(last_name) 字段长度, salary from employees order by 字段长度 desc;

SELECT last_name, department_id, salary FROM employees ORDER BY salary DESC, department_id ASC;  #当salary相同时,按照department_id进行升序

SELECT *, LENGTH(email) FROM employees 
ORDER BY salary DESC, LENGTH(email) ASC;   # 二级排序

12.4 常见函数:

类似于c++/go中的方法,将一组逻辑语句封装在方法体中,对外暴露方法名;

优点:1、隐藏了实现细节;2、提高了代码的重用性;

从定义的角度,函数可以分类为:内置函数(系统内置的通用函数)、自定义函数;

单行函数:ifnull()、isnull()、concat();字符函数、数学函数、日期函数、其他函数;流程控制函数等;

分组函数(多行函数/聚合函数):做统计使用,又称统计函数、聚合函数、组函数。

# 语法:
select 函数名(实参列表);

12.4.1 单行函数:(可以相互嵌套)

1、字符函数

1)ascii():获取字符串中第一个字符的ascii码;

2)length():获取参数值的字节数;char_length():字符的长度;

3)concat():拼接字符串;

4)upper()、lower():可以用来将字符串的首字母变大小写;

5)substr() / substring():注意sql的索引时从1开始的;

# 截取从index开始之后的所有字符
select substr(字符串, index) As output 

# 截取从index开始之后的size个字符
select substr(字符串, index, size) As output 

6)instr():返回字符串第一次出现的索引,找不到则返回0;

# 返回字符串str_第一次出现在str中的索引,找不到则返回0
select instr(str, str_) As output 

7)trim():用字符串修剪给定的字符串;

SELECT LENGTH(TRIM("  背景  ")) AS output;   # 一个中文字符占三个字节;结果是6;

SELECT TRIM("aa" FROM "aaa里aaaa啊啊aaa") AS output;    # 结果是,a里aaaa啊啊a
SELECT TRIM("aa" FROM "aaaa里aaaa啊啊aaaa") AS output;  # 结果是,里aaaa啊啊

8)lpad(str, len, padstr) / rpad():用指定字符左/右填充指定长度,用来实现左右对齐;

select LPAD("123", 6, "7") As output;
SELECT RPAD("123", 6, "7") AS output;

9)replace(str,from_str,to_str):替换

2、数学函数

1)abs():取绝对值;

2)round(x):对x进行四舍五入;

3)ceil():向上取整,返回>=该参数的最小整数;floor():向下取整,返回<=该参数的最小整数;

4)truncate(x,y):截断小数x为y位小数

select truncate(1.4999,1) as output;

5)取余mod()

select mod(10, 3)  # a - (a / b) * b

6)指数exp(x)、pow(x,y),对数ln(x)/log(x)、log10()、log2()

7)进制之间的转换:bin()、hex()、oct()、conv(x,进制1,进制2)

8)format(小数,保留的小数点的位数)函数,用于小数点后位数的格式化。如果位数<=0,则只保留整数部分。

3、日期函数

1)返回当前的日期 和 时间

select Now();   # 返回当前的日期 和 时间
select curdate(); # 返回当前的日期 
SELECT curtime(); # 返回当前的时间

2)获取指定部分的时间,年year(date)、月month(date)、日day(date)、时hour(date)、分minute(date)、秒second(date)

3)将日期格式的字符串转换为指定格式的日期

select str_to_date("1998-03-23","%Y-%m-%d");

SELECT hiredate FROM employees WHERE hiredate=STR_TO_DATE("03-23-1998", "%m-%d-%Y");

3)将日期转换为指定的字符串

SELECT DATE_FORMAT(NOW(), "%Y年%m月%d日");

4)返回两个日期相差的天数

select datediff(日期1, 日期2);

4、流程控制函数

1)if()函数:实现了if-else的效果

select if(10<2, "小", "大");

select last_name, commission_pct, if(commission_pct is null, "non-commission","commission") from employees;

# ifnull()是if()函数的特殊情况:
select last_name, commission_pct, ifnull(commission_pct, 0) from employees;
select last_name, commission_pct, if(commission_pct is not null, commission_pct, 0) from employees;

2)case()函数:实现了switch-case / if-else的效果

# 语法:
case 要判断的字段或表达式
when 常量1 then 要显示的值1或语句1;
when 常量2 then 要显示的值2或语句2;
...
else 要显示的值n或语句n;
end


SELECT salary, department_id,
CASE department_id
WHEN 30 THEN salary*1.1
WHEN 40 THEN salary*1.2
WHEN 50 THEN salary*1.3
ELSE salary 
END AS new_salary
FROM employees;

SELECT salary,
CASE 
WHEN salary>10000 THEN 'A'
WHEN salary>5000 THEN 'B'
WHEN salary>2000 THEN 'C'
ELSE 'D'
END AS salary_grade
FROM employees;

12.4.2 分组函数:(不能嵌套使用)

        用作统计使用,又称统计函数、聚合函数、组函数。它是对一组数据进行汇总的函数,输入的是一组数据的集合,输出的是单个值

常见的分组函数:sum()、avg()、max()、min()、count()计算非空值的个数

1)sum()、avg():一般用于处理数值型;

2)max()、min()、count():可以处理任何类型,如数值类型、字符串类型、日期时间类型等;

 注意:

  • 这些函数在使用时,会自动忽略null空值;
  • 可以和distinct搭配使用,实现去重后再计算;
  • 和分组函数一起查询的字段,要求是group by后的字段
  • where不能使用分组函数作为筛选条件,而having可以;
  • select后可以使用分组函数,作为一个查询字段;
select avg(salary) as avg_salary, min(salary) as min_salary, max(salary) as max_salary from employees;

select sum(distinct salary) from employees;       # 去重后求和

select count(distinct salary) from employees;     # 去重后求和
select count(*) from employees;                   # 统计非空行的行数

# 查询公司的平均奖金率
select sum(commission_pct) / count(ifnull(commission_pct,0)) from employees;  
# 查询公司中有奖金的员工的平均奖金率
select avg(commission_pct) from employees;


# 查询不同部门,不同工种的平均工资
select departemnt_id, job_id, avg(salary)
from employees
group by department_id, job_id;

使用时,count(1)、count(*)、count(字段名),谁更好?

  • 如果该表的存储引擎使用的是MyISAM引擎,该引擎内部维护着一个计数器来记录行数,故三者的使用效果相同,都是O(1);
  • 使用Innodb存储引擎的表,用count(*) / count(1),会直接读行数,复杂度是O(n);总体上,count(*)=count(1)>count(字段名)

注意:count(*)/count(1):只要存在该条记录,则计数加一;count(字段名):只有该字段非空时,计数才加一。

12.4.3 查询mysql信息的函数:

1)version():查询mysql的版本;

2)database():查询当前的所在的数据库;

3)charset(value):返回自变量value的字符集;

4)collation(value):返回字符串value的比较规则;

12.4.4 其他函数:

1)format(value,n):返回对数字value格式化四舍五入后保留n位小数;

2)conv(value,进制1,进制2):将value从进制1转换到进制2;

3)benchmark(n,expr):将表达式expr执行n次;

4)convert(value using char_code):将value所使用的字符编码修改为char_code编码;

12.5 分组查询:

可以使用group by子句将表中的数据分成若干组;

 注意:where一定放在from后。考虑到执行顺序和性能问题,故能用分组前筛选,则尽量用分组前筛选。

SELECT MAX(salary), job_id FROM employees GROUP BY job_id;  # 查询每个工种的最高工资

SELECT COUNT(*), location_id FROM departments GROUP BY location_id;  # 查询每个位置上的部门个数


# 查询部门id为10、20、30、40这4个部门中最高工资比10000高的部门信息
# 方式1:使用where先筛选一部分,再分组后having筛选(推荐使用,效率更高;与sql语句执行顺序有关)
select department_id, max(salary)
from employees
where department_id in (10,20,30,40)
group by department_id
having max(salary)>10000;

# 方式2:直接使用having进行分组后筛选
select department_id, max(salary)
from employees
group by department_id
having max(salary)>10000 and department_id in (10,20,30,40);

12.5.1 分组前/后筛选(where / having):

 1)分组前筛选:

#查询有奖金的每个领导手下员工的最高工资
SELECT MAX(salary), manager_id 
FROM employees 
where commission_pct is not null
GROUP BY manager_id;  

2)分组后筛选:

# 查询哪个部门员工数大于2
select count(*),department_id 
from employees 
group by department_id 
having count(*)>2;

where和having的对比:

  • where可以直接使用表中的字段作为筛选条件,但不能使用分组函数作为筛选条件having必须要与group by配合使用,可以把分组函数和分组字段作为筛选条件
  • 如果需要通过连接从关联表中获取需要的数据,where最先筛选后连接,而have是先连接后筛选
优点缺点
where先筛选数据再关联,执行效率高不能使用分组中的计算函数进行筛选
having可以使用分组中的计算函数在最后的结果集中进行筛选,执行效率低

12.5.2 按函数或表达式分组:

select count(*), length(last_name) 
from employees 
group by length(last_name) 
having count(*)>5;

12.5.3 按多个字段进行分组:

# 按多个字段进行分组:查询每个部门、每个工种的员工的平均工资;并在分组前筛选掉部门为空数据,最后按照平均工资进行降序
SELECT AVG(salary),department_id,job_id 
FROM  employees
WHERE department_id IS NOT NULL
GROUP BY department_id, job_id
having avg(salary)>10000   # 分组后筛选掉,平均工资低于1万的数据
ORDER BY AVG(salary) DESC;

12.6 多表查询:又称关联查询 或 连接查询

        查询到字段来自于多个表时,需要添加有效的连接条件(即如果是n个表的查询,则需要n-1个连接条件)(有效的连接条件,能避免出现笛卡尔乘积现象)。从sql优化的角度讲,建议在多表查询时,尽量在select后的字段前指明该字段来自哪个表,以便节省sql查询时间。

12.6.1 连接查询的分类:

1)按年代分类:sql92标准(只支持内连接),sql99标准(支持内连接、外连接(左连接和右连接)和交叉连接);

2)按功能分类:

  • 内连接inner join 表名 on 连接条件:等值连接、非等值连接、自连接
  • 外连接left|right|full join 表名 on 连接条件:左外连接、右外连接、全外连接(mysql不支持);
  • 交叉连接cross join ...:会出现笛卡尔乘积现象(如果多表查询时,存在有效的连接条件,则能避免出现笛卡尔乘积现象);

12.6.2 sql92标准:

单/多表等值连接:多表等值连接的结果是多表的交集部分;n表连接至少需要n-1个连接条件;多表连接可以搭配之前所有的子句使用,筛选、分组、排序等;

SELECT `name` FROM beauty;
SELECT `name`,`boyName` FROM beauty,boys; #存在笛卡尔乘积现象:当查询多个表时,没有添加有效的连接条件,导致多个表所有行实现了完全连接;

# 等值连接
SELECT `name`,`boyName` FROM beauty,boys WHERE beauty.`boyfriend_id`=boys.`id`;

SELECT last_name,department_name 
FROM employees,departments
WHERE employees.`department_id` = departments.`department_id`;  #连接条件

#如果为表起了别名,就必须使用表的别名来进行限定
SELECT last_name,e.job_id,job_title
FROM employees AS e, jobs AS j    #为表起别名
WHERE e.`job_id`=j.`job_id`;

#添加筛选条件
SELECT last_name, department_name
FROM employees AS e, departments AS d
WHERE e.`department_id`=d.`department_id` AND e.`commission_pct` IS NOT NULL;

# 查询每个城市的部门个数
select count(*) 部门数, city 
from departments d, locations l
where d.`location_id`=l.`location_id`
group by city;

#三表连接
SELECT last_name, department_name, city
FROM employees e,departments d, locations l
WHERE e.`department_id`=d.`department_id` AND l.`location_id`=d.`location_id`

非等值连接:

# 查询员工工资和工资级别
SELECT last_name, salary, grade_level 
FROM employees e, job_grades jg 
WHERE salary BETWEEN jg.`lowest_sal` AND jg.`highest_sal`;

自连接:将同一张表,看做不同的两个表,通过连接这两张表实现自连接;

# 查询员工(员工表)和自己的上级(领导表)
SELECT e.`employee_id`, e.`last_name`, m.`employee_id`, m.`last_name`
FROM employees AS e, employees AS m
WHERE e.`manager_id`=m.`employee_id`;

12.6.3 sql99标准:

select 查询列表
from 表1 别名   
#内连接:inner join,外连接:左外left 【outer】join,右外right【outer】join,full 【outer】join,交叉连接:cross join
【连接类型】join 表2 on 连接条件
【where 筛选条件】
【having 分组后筛选】
【order by 用于排序的字段】

内连接:

1)等值连接:

# 查询员工名、部门名
SELECT last_name, department_name 
FROM employees AS e
INNER JOIN departments AS d ON e.`department_id`=d.`department_id`; 

# 查询部门个数>3的城市名和部门个数
SELECT city,COUNT(*) AS 部门数  
FROM departments AS d 
INNER JOIN locations AS l ON d.`location_id`=l.`location_id`
GROUP BY l.`city`
HAVING 部门数>3;

# 查询员工名、部门名、工种名、并按部门名排序
SELECT last_name, department_name, e.job_id
FROM employees AS e
INNER JOIN departments AS d ON e.`department_id`=d.`department_id`
INNER JOIN jobs AS j ON e.`job_id`=j.`job_id`
ORDER BY department_name;

2)非等值连接:

# 查询员工的工种、工资等级
SELECT last_name, salary, job_title, grade_level
FROM employees AS e
INNER JOIN jobs AS j ON e.`job_id`=j.`job_id`
JOIN job_grades AS jg ON e.`salary` BETWEEN jg.`lowest_sal` AND jg.`highest_sal`;

# 查询工资等级个数>10,并且按照工资级别降序
SELECT COUNT(*), grade_level
FROM employees AS e
INNER JOIN job_grades AS jg ON e.`salary` BETWEEN jg.`lowest_sal` AND jg.`highest_sal`
GROUP BY grade_level
HAVING COUNT(*)>10
ORDER BY grade_level DESC;

3)自连接:

#查询(姓名中包含字符k的)员工的名字和上级的名字
SELECT e.`employee_id`, e.`last_name`, m.`employee_id`, m.`last_name`
FROM employees AS e
INNER JOIN employees AS m ON e.`manager_id`=m.`employee_id`
WHERE e.`last_name` LIKE "%k%";

外连接(左外、右外、全外连接):

        应用场景:用于查询一个表中有,而另一个表中没有的;一般在需求中,出现了“所有的”字眼,则需要使用外连接进行查询;

        外连接的查询结果为主表中的所有记录 + 

  • 如果从表中有和它匹配的,则显示匹配的值;
  • 如果从表中没有和它匹配的,则显示null;外连接查询结果=内连接结果+主表中有而从表中没有的记录;

主表 和 从表的界定:左外连接:left join左边的是主表;右外连接:right join右边的是主表;

全外连接 = 内连接的结果 + 表1中有但表2中没有的 + 表2中有但表1中没有的;

SELECT beauty.name, boys.*
FROM beauty          # 左外连接
LEFT OUTER JOIN boys ON beauty.`boyfriend_id`=boys.`id`
#where boys.`boyName` is not null
ORDER BY id ASC;

SELECT beauty.name, boys.*   
FROM boys            # 右外连接
RIGHT OUTER JOIN beauty ON beauty.`boyfriend_id`=boys.`id`
WHERE boys.`boyName` IS NOT NULL
ORDER BY id ASC;

# 查询哪个部门没有员工
SELECT d.*, e.`employee_id`
FROM departments AS d 
LEFT OUTER JOIN employees AS e ON d.`department_id`=e.`department_id`
WHERE e.`employee_id` IS NULL;

交叉连接:

# 交叉连接:等价于笛卡尔乘积
SELECT b.*, bo.*   
FROM beauty AS b
CROSS JOIN boys AS bo; 

12.6.4 总结:

        sql99实现连接条件和筛选条件的分离,支持的查询方式较多,可读性高,故更推荐使用sql99语法;但需要严格控制连接表的数量,多表连接就相当于嵌套for循环一样,非常消耗资源,会让严重降低SQL查询性。在许多大型的DBMS中,也都会由最大连接表的限制。一般禁止3个以上的join出现。

/*分组查询的练习:*/
# 查询所有部门的名字、location_id,员工数量和平均工资,并按平均工资降序排列
SELECT de.`department_name`,de.`location_id`,COUNT(*),AVG(e.`salary`)
FROM departments AS de
INNER JOIN employees AS e ON de.`department_id`=e.`department_id`
GROUP BY de.`department_name`,de.`location_id`; 

# count(*)会将所有的部门和地方索引,等分组成的不同的“实体”,然后进行计数并计算每组的平均工资
#但并没有丢弃不存在员工的实体,导致了实体中没有员工但计数是1;通过count(e.`employee_id`)可以将没有员工的实体计数为0;

SELECT de.`department_name`,de.`location_id`,COUNT(e.`employee_id`),AVG(e.`salary`)
FROM departments AS de
LEFT JOIN employees AS e ON de.`department_id`=e.`department_id`
GROUP BY de.`department_name`,de.`location_id`;

12.7 子查询:

        出现在其他语句中的select语句,称为子查询或内查询;外部的查询语句,称为主查询或外查询。总之,从子查询中获取一个结果集,然后再对该结果集进行主查询的过程。

12.7.1 分类:

  • 按子查询出现的位置(除group by/limit后不能使用子查询)分类:select后面(仅支持标量子查询) 、from后面(支持表子查询)、where/having后面(支持标量子查询、列子查询、行子查询)、exists后面(相关子查询:内查询被多次执行,更直观的反映是相关子查询的子查询中用到了主查询中的表)、join(支持表子查询)、order by(支持行子查询);

        注意:相关子查询的需求:查询工资大于本部门平均工资的员工信息;不相关子查询的需求:查询工资大于本公司平均工资的员工信息。

# 查询员工中,工资大于本部门平均工资的员工的last_name,salary和其department_id
# 方式一:相关子查询
select last_name, salary, department_id
from employees e1
where e1.salary > (
                  # 该子查询会被多次执行
                  select avg(salary)
                  from employees e2
                  where e1.department_id = e2.department_id
                  );

# 方式二:from中声明子查询
select e1.last_name, e1.salary, e1.department_id
from employees e1, (
                  # 该子查询会被多次执行
                  select e2.department_id, avg(e2.salary) as avg_sal
                  from employees e2
                  group by e2.department_id
                  ) as e2_dep_avg_sal
where e1.department_id = e2_dep_avg_sal.department_id
and e1.avg_salary > e2_dep_avg_sal.salary;


# 查询每个部门下的部门人数大于5的部门名称
select department_name
from departments as d
where 5 < (
           select count(*)
           from employees as e
           where d.department_id=e.department_id
           );
  • 按返回的结果集中行列数分类:标量子查询(结果集只有一行一列)、行子查询(结果集有一列多行)、列子查询(结果集有多列)、表子查询(结果集一般为多行多列);

12.7.2 特点:

  • 子查询,放在条件的右侧的小括号中;
  • 标量子查询(返回一行一列),需搭配单行比较操作符(>、<、>=、<=、=、!=)使用;
  • 多行子查询(返回多行),需搭配着多行比较操作符(in/not in:等于列表中的任意一个、any/some:和子查询返回的某一个值比较(需结合单行比较操作符)all:和子查询返回的所有值比较(需结合单行比较操作符))使用;
  • 执行顺序:先执行子查询,再执行主查询

12.7.3 按子查询出现的位置分类:

1、where/having后面的子查询:

1)标量子查询(子查询结果是一行一列)

# 查询工资高于Abel的员工信息
SELECT *
FROM employees 
WHERE salary > (
	SELECT salary 
	FROM employees 
	WHERE last_name='Abel'
);

# 查询job_id与141号员工相同,salary比143号员工多的员工姓名,job_id和工资
SELECT last_name, job_id, salary
FROM employees 
WHERE job_id = (
	SELECT job_id
	FROM employees
	WHERE employee_id = 141
) AND salary > (
	SELECT salary 
	FROM employees 
	WHERE employee_id=143
);

# 查询最低工资大于50号部门最低工资的部门id和其最低工资
SELECT MIN(salary), department_id
FROM employees 
GROUP BY department_id
HAVING MIN(salary)>(
	SELECT MIN(salary)
	FROM employees
	WHERE department_id=50
);

2)行子查询(子查询的结果是一列多行)

# 查询location_id是1400或1700的部门中的所有员工的姓名
SELECT last_name
FROM employees 
WHERE department_id IN(
	SELECT department_id
	FROM departments
	WHERE location_id IN(1400,1700)
);

# 查询其他工种中,员工工资比‘IT_PROG’部门任一工资低的,员工姓名,编号,工种,工资
SELECT last_name, employee_id, job_id, salary 
FROM employees 
WHERE salary<ANY(   
	SELECT DISTINCT salary
	FROM employees
	WHERE job_id='IT_PROG'
) AND job_id != 'IT_PROG';

3)列子查询(子查询的结果是多列)

# 查询员工编号最小并且工资最高的员工信息
/*
SELECT *
FROM employees 
WHERE employee_id=(      # 查询员工编号最小并且工资最高的员工信息
	SELECT MIN(employee_id)
	FROM employees
) AND salary=(
	SELECT MAX(salary)
	FROM employees
);
*/
SELECT * 
FROM employees 
WHERE (employee_id, salary)=(
	SELECT MIN(employee_id),MAX(salary)
	FROM employees
);

2、select后面的子查询:

# 查询每个部门的员工个数
SELECT d.*, (
	SELECT COUNT(*)
	FROM employees
	WHERE department_id=d.`department_id`
) 员工个数
FROM departments AS d;

3、from后面的子查询:将子查询的结果充当一张表,要求必须起别名。

# 查询每个部门的平均工资的工资等级
SELECT avg_salary.*, jg.`grade_level`
FROM (
	SELECT AVG(salary) AS avgsal, department_id 
	FROM employees AS e
	GROUP BY e.department_id
) AS avg_salary
INNER JOIN job_grades AS jg ON avg_salary.avgsal 
BETWEEN jg.`lowest_sal` AND jg.`highest_sal`
WHERE department_id IS NOT NULL;

4、exists后面的子查询(相关子查询,所有使用到in的查询语句, 都可以尝试用exists改写):

如果在子查询中不存在满足条件的行:①条件返回false;②继续在子查询中查找。

如果在子查询中存在满足条件的行:①条件返回true;②退出当前的子查询,从主查询中拿到新的一行数据并继续在子查询中查找。

# 查询没有女朋友的男生名单
SELECT * 
FROM boys AS bo
WHERE NOT EXISTS (    
	SELECT boyfriend_id
	FROM beauty AS b
	WHERE bo.`id`=b.`boyfriend_id`
);
/*
SELECT *
FROM boys 
WHERE boys.`id` NOT IN (
	SELECT DISTINCT boyfriend_id
	FROM beauty
);
*/

# 查询有员工的部门
SELECT department_name
FROM departments AS d
WHERE EXISTS (     
	SELECT * 
	FROM employees AS e
	WHERE e.`department_id`=d.department_id
);
/* # 用in来等价实现
SELECT d.department_name
FROM departments AS d
WHERE d.`department_id` IN(
	SELECT DISTINCT e.department_id
	FROM employees AS e
	WHERE e.`department_id` IS NOT NULL
);
*/


# 查询公司管理者的employee_id、last_name、job_id、department_id信息
# 方式一:自连接
select distinct mgr.employee_id, mgr.last_name, mgr.job_id, mgr.department_id
from employees emp join employees mgr
on emp.employee_id=mgr.manager_id

# 方式二:exists后的相关子查询
select distinct emp.employee_id, emp.last_name, emp.job_id, emp.department_id
from employees emp
where exists (
             select *
             from employees as mgr
             where emp.employee_id=mgr.manager_id
             )


# 查询departments表中,不存在于employees表中的部门department_id和department_name
# 方式一:右外连接
select d.department_id, d.department_name
from employees as e right join departments as d
on e.department_id=d.department_id
where e.department_id is null;

# 方式二:not exists后的相关子查询
select department_id, department_name
from departments as d
where not exists (
                 select * 
                 from employees as e
                 where d.department_id=e.department_id
                 )

5、join后面的表子查询

# 查询各部门中工资比本部门平均工资高的员工的员工号、姓名和工资
SELECT e.employee_id, e.last_name, e.salary, e.`department_id`, ags_de.`ags`
FROM employees AS e
INNER JOIN (
	SELECT department_id, AVG(salary) AS ags
	FROM employees
	GROUP BY department_id
) AS ags_de ON ags_de.`department_id`=e.`department_id`
WHERE e.salary > ags_de.`ags`;

6、order by后面的行子查询

# 查询员工的id、salary,并按照department_name排序
select employee_id, salary
from employees as e
order by (
         select department_name
         from departments as d
         where e.department_id = d.department_id
         );

 自连接和子查询的对比:如果既可以使用自连接,又可以使用子查询,一般建议使用自连接

分析:子查询实际上是通过对未知表进行查询后的条件判断;自连接是通过已知的自身数据表进行条件判断,因此在大部分DBMS中都对自连接进行了优化,故都可以使用时,推荐使用自连接

总结:

  • 如果子查询相对较简单,则建议从外往里写。比较复杂,则建议从里往外写;
  • 如果是相关子查询,则通常是从外往里写。

12.8 分页查询:

        当要查询的信息一页无法完全显示,需要分页提交sql请求。另外,在没有索引的情况下,通过使用limit,可以极大的提高查询效率。

# 语法:
limit offset,size
  • offset是要显示的条目的起始索引(从零开始);
  • size是要显示的所有条目数;
  • 一般的分页查询:limit (page-1)*size, size;
# 查询前五条员工信息
SELECT * FROM employees 
LIMIT 0,5;   # 如果从零开始索引,0往往可以省略

# 找出书名中字数最多的一本书,不包含空格
select *
from books
order by char_length(replace(`name`,' ','')) desc
limit 0,1;
# 查询平均工资最低的部门信息和该部门的平均工资
SELECT d.*
FROM departments AS d
INNER JOIN (
	SELECT AVG(salary), department_id
	FROM employees
	GROUP BY department_id
	ORDER BY AVG(salary)
	LIMIT 0,1
) AS avg_de ON d.`department_id`=avg_de.`department_id`;

# 查询平均工资最高的部门manager的详细信息:last_name,department_id,email,salary
SELECT last_name, e.department_id, email, salary 
FROM employees AS e
INNER JOIN departments AS d ON e.`employee_id`=d.`manager_id`   # 得到所有部门manager的详细信息
WHERE d.`department_id`=(
	SELECT department_id
	FROM employees 
	GROUP BY department_id
	ORDER BY AVG(salary) DESC
	LIMIT 0,1
);

12.9 联合查询:

union联合查询,会将多条查询语句的结果合并成一个结果,即并集;

应用场景:要查询的结果来自于多表,且多个表之间没有直接的连接关系,但要查询的信息一致;

特点:

  • 要求多条查询的查询列数一致;
  • 要求多条查询语句的查询顺序是一致的;
  • union关键字默认是去重的,union all可以不去重的显示;
SELECT * FROM employees WHERE email LIKE "%a%" 
UNION
SELECT * FROM employees WHERE department_id>10;
/*
SELECT * FROM employees 
WHERE email LIKE "%a%" OR department_id>10;
*/

十三、DML语言的学习

DML :Data Manipulation Language,用来添加、删除、更新表中的数据,并检查数据完整性。

13.1 DDL和DML的区别

  • DDL的操作一旦执行,则不能回滚。因为执行完DDL操作后,必会执行一次commit提交,即是无法控制的且主动提交的,不可回滚。
  • DML的操作默认情况下,一旦执行,也是不可回滚。如果执行DML操作前,执行了'set autocommit = false',则之后执行的DML操作可以实现回滚。

13.2 DML的相关操作

13.2.1 插入语句

# 语法:
# 插入方式一:
insert into 表明(列名...) values(值1...)
# 1、列名可以颠倒顺序;
# 2、列名可以缺省但相应的值也应该缺省;
# 3、如果省略整个列名,则默认是所有列,故值就必须按照顺序将所有列对应的值按照顺序给出;

# 插入方式二:
insert into 表名 set 列名=值,...

# 两种插入方式的比较:
# 1、方式一支持一次性插入多行,但方式二不支持;
# 2、方式一支持子查询,但方式二不支持;
# ,故推荐使用方式一
# 插入方式一:
INSERT INTO beauty(`id`,`name`,`sex`,`borndate`,`phone`,`photo`,`boyfriend_id`)
VALUES(13,"nini","female","1999-10-21","1299999933",NULL,2);

INSERT INTO beauty(`id`,`name`,`sex`,`borndate`,`phone`,`boyfriend_id`)
VALUES(14,"mimi","female","1999-10-21","12999999337",3);

# 插入方式二:
INSERT INTO beauty SET id=16,`name`="sasa",sex="female",borndate="1292-12-11";

13.2.2 修改语句 

# 修改单表的记录
# 语法:update 表名 set列=新值,... where 筛选条件;
# 执行顺序是:update -> where -> set

# 修改多表的记录 sql99
update 表1 别名
inner/left/right join 表2 别名 on 连接条件
set 列=值,...
where 筛选条件;
# 单表修改
UPDATE beauty SET phone="12999999333" 
WHERE id=13 OR id=15;

# 多表修改
UPDATE beauty AS b    
INNER JOIN boys AS bo ON bo.`id`=b.`boyfriend_id`
SET b.`phone`="111"
WHERE bo.`boyName`="张无忌";

SELECT *
FROM beauty AS b
LEFT JOIN boys AS bo ON bo.`id`=b.`boyfriend_id`;

UPDATE beauty AS b
LEFT JOIN boys AS bo ON bo.`id`=b.`boyfriend_id`
SET b.`boyfriend_id`=0
WHERE b.id IS NULL;

13.2.3 删除语句

# 1、语法(删除会删除整行):单表删除、多表删除和删除整个表
# 1)单表删除:delete from 表名 where 筛选条件;
# 2)多表删除:将多表中,满足筛选条件的记录一同删除:(sql99)
    delete 表1的别名 表2的别名
    from 表1 别名
    inner/left/right join 表2 别名 on 连接条件
    where 筛选条件
    【limit 待删除的条目数】;
# 3)删除整个表:truncate table 表名;

# 2、'delete from 表名'与'truncate table 表名'的区别:
# 1)如果用delete删除后,再插入数据,自增长列的值从断点开始;用truncate删除后,再插入数据,自增长列的值从1开始;
# 2)delete删除后有返回值(返回受影响的值的个数),truncate删除后没有返回值;
# 3)delete删除后可以回滚,truncate删除后不能回滚;
# 4)delete可以添加筛选条件,而truncate不可以;

13.3 MySQL8的新特性:计算列

某一列的值是通过表中其余两列的值得到的,不需要手动植入和计算。

create tables if not exists test_calcolumn(
	id int,
	a int,
	b int,
    # c即是计算列
	c int generate always (a+b) virtual
);

insert into test_calcolumn(id, a, b) values(1,10,20);

select * from test_calcolumn;

十四、常见的数据库对象:

对象描述
表 table表是存储数据的逻辑单元,以行列的形式存在,列是字段、行是记录
数据字典就是系统表,存放数据库相关信息的表。系统表,通常是由数据库系统维护,只可查看不可修改。
约束 constraint执行数据校验的规则,用于保证数据完整性的规则。
索引 index用于提高查询性能,相当于书的目录
视图 view一个或者多个数据表里的数据的逻辑显示单元,视图并不存储数据,是执行过程中动态生成的虚拟表。本质上,视图就是存储起来的select的语句
存储过程 procedure用于完成一次完整的业务处理(能对底层表进行更新),没有返回值,但可通过传出参数将多个值传给调用环境。
存储函数 function用于完成一次特定的计算(能对底层表进行更新),具有一个返回值
触发器 trigger相当于一个事件监听器,当数据库发生特定事件后,触发器被触发,完成相应的处理。

14.1 视图

14.1.1 含义:

        视图,是从5.0.1版本后提供的新功能,是一种虚拟存在的表,行和列的数据来自定义视图的查询中使用的表,并且视图是在执行时动态生成的只保存了sql逻辑,不保存查询结果,故占用内存很少本质上:视图就是存储起来的select的语句

14.1.2 特点:

优点:

  • 重用sql语句;
  • 简化复杂的sql操作,不必知道具体的操作细节;
  • 控制数据的访问,保护数据,提高安全性;

不足:

  •  在实际数据表的基础上创建的视图,如果实际数据表的结构变化了,我们就需要及时对相关的视图进行维护。如果存在嵌套视图,则维护成本会大大提升。
  • 多层嵌套视图,会导致维护性和可读性的降低。

14.1.3 创建视图语法:

create [or replace] view 视图名
as 
查询语句;  
# 创建视图,每个部门的平均工资
CREATE VIEW avgs_dep
AS 
SELECT de.department_name, AVG(e.salary) FROM employees AS e
INNER JOIN departments AS de ON e.`department_id`=de.`department_id`
GROUP BY e.department_id
ORDER BY AVG(e.salary) DESC;

# 查询平均工资最高的部门名和平均工资
select * from avgs_dep limit 0,1;

14.1.4 修改视图语法:

# 方式一:
create or replace view 视图名
as 
查询语句;

# 方式二:
alter view 视图名
as 
查询语句;

14.1.5 删除视图的语法

drop view if exists 视图名1,视图名2,...

举例:

# 创建视图empsm_dep,要求查询部门的最高工资高于12000的部门信息
CREATE OR REPLACE VIEW empsm_dep
AS 
SELECT ms_depID.ms, d.* FROM departments AS d
INNER JOIN (
	SELECT MAX(salary) AS ms,department_id
	FROM employees 
	GROUP BY department_id
	HAVING ms>12000
) AS ms_depID ON ms_depID.`department_id`=d.`department_id`;

14.1.6 视图的更新:

1. 更新时,使用insert、delete、update与普通表用法相同更新视图,也会对原始表(基表)产生影响

2. 视图的可更新性和视图中查询的定义有关系,以下类型是不能更新的:

  • 包含分组函数、distinct、group by、having、union或者union all;
  • 常量视图;
  • select中包含子查询;
  • join;
  • from 一个不能更新的视图;
  • where子句的子查询引用了from子句中的表;

14.1.7 使用的场景:

  • 多个地方用到同样的查询结果;
  • 该查询结果使用的sql语句较复杂;
  • 一般大型的项目使用,便于数据安全的问题;

14.1.8 视图和表的对比:

创建语法的关键字是否实际占用了物理空间使用
视图create view只保存了sql逻辑一般不能进行增删改主要用来查
create table保存了数据增删改查

14.2 存储过程与存储函数

存储过程和存储函数:类似于c++中的方法,都能够将复杂的sql逻辑封装在一起,并预先存储在数据库服务器中,需要使用时客户端只需要向服务器端发送请求,服务器就会执行预先存储好的存储过程和函数全部执行,并返回结果给客户端;

14.2.1 含义:

一组预先编译好的sql语句的集合,可以理解成批处理语句;

14.2.2 优点:

  • 提高代码的复用性;
  • 简化操作;
  • 减少了编译次数并且减少了和数据库服务器的连接次数,提高了效率;
  • 减少了网络传输量(客户端不需要把所有的sql语句通过网络发给服务器),同时这也提高了数据查询的安全性

14.2.3 存储过程和函数的对比:

关键字调用语法返回值应用场景
存储过程procedurecall 存储过程名(参数);可以设置0或多个out|inout模式的参数,作为输出一般用于更新
存储函数functionselect 函数(参数);只能有一个一般用于查询并返回一个结果,且结果只能是一个值

        此外,存储函数可以放在查询语句中,但存储过程不行;存储过程,能够执行对表的更新和事务的操作,这些功能存储函数并不具备。

14.2.4 存储过程

存储过程的创建和调用、销毁:

# 创建语法:
delimiter $
create procedure 存储过程名(参数模式(in|out|inout) 参数名 参数类型...)
begin
	存储过程体(一组合法的sql语句);
end $
delimiter ;
/*
注意: 1)参数列表包含三个部分:参数模式(in|out|inout) 参数名 参数类型;
      2)参数模式:
          in:该参数可以作为输入,即需要传入值
          out:该参数可以作为输出,即该参数可以作为返回值
          inout:该参数既可以作为输入又可以作为输出,即既需要传入值又可以返回值
      注意:in、out、inout都可以在一个存储体中带多个
      3)如果存储过程体中,仅仅只有一句话,则 begin end 可以省略
      4)默认情况下,存储过程体中每条sql语句的结尾都必须加分号,但end后需要新的结束符,可通过delimiter重新设置,
      	语法:delimiter 结束标志
*/
         
# 调用语法:
call 存储过程名(实参列表);

# 销毁存储过程:
drop procedure if exists 过程名;

不同的参数列表:

1、空参列表(无参数、无返回):

SELECT * FROM book_copy2;  
TRUNCATE TABLE book_copy2;  # 清空表并重新计数
DROP TABLE book_copy2;      # 删除表

SHOW VARIABLES;      # 查看会话变量

CREATE TABLE book_copy2(   # 创建带有主键的表book_copy2
	id INT PRIMARY KEY, 
	book_name VARCHAR(10), 
	loan_stuID INT
);
 
DESC book_copy2;    # 查看表的结构

DROP PROCEDURE IF EXISTS mySP;   # 销毁存储过程名

DELIMITER $                  # 定义结束标志
CREATE PROCEDURE mySP()      # 定义存储过程
BEGIN
	SET auto_increment_increment=2;

	ALTER TABLE book_copy2 MODIFY COLUMN id INT AUTO_INCREMENT;
	ALTER TABLE book_copy2 MODIFY COLUMN loan_stuID INT UNIQUE;	
	
	INSERT INTO book_copy2(id, book_name, loan_stuID)
	VALUES(5,"哈利.波特",2),(NULL,"三体2",8),(NULL,"三体1",7),(NULL,"三体3",5);
END $ 
DELIMITER ;    

CALL mySP();                  # 调用存储过程

2、带in模式的参数列表(有参数、无返回):

CREATE TABLE admin(     # 创建admin表,并插入两行数据
	id INT PRIMARY KEY AUTO_INCREMENT,
	userName VARCHAR(10),
	log_password VARCHAR(10)
);
SET auto_increment_increment=1;
SHOW VARIABLES;
INSERT INTO admin(id,userName,log_password) VALUES(1,"lili","8888"),(NULL,"nini","6666");
SELECT * FROM admin;
# 创建带in模式的参数列表的存储过程
DELIMITER $
CREATE PROCEDURE mySP2(IN userName VARCHAR(10), IN log_password VARCHAR(10))
BEGIN 
	DECLARE result INT DEFAULT 0;   # 创建自定义的整型局部变量,默认是0
	
	SELECT COUNT(*) INTO result
	FROM admin
	WHERE admin.userName=userName AND admin.log_password=log_password;
	
	SELECT IF(result>0,"log in successfully","log in failure");
END $
DELIMITER ;

# 调用存储过程
CALL mySP2("lili","8888");

3、带out模式的参数列表(无参数、有返回):

DELIMITER $  # 定义结束标志符
CREATE PROCEDURE mySP(IN girlName VARCHAR(10), OUT boyName VARCHAR(10), OUT userCP INT)
BEGIN 
	SELECT bo.boyName,bo.userCP INTO boyName,userCP
	FROM beauty AS b
	INNER JOIN boys AS bo ON b.boyfriend_id=bo.id
	WHERE b.name=girlName;
END $
DELIMITER ;

DROP PROCEDURE IF EXISTS mySP;  # 销毁存储过程mySP

SET @bName := "";    # 声明并初始化,自定义的用户变量
SET @bCP := "";   
CALL mySP("热巴", @bName, @bCP);
SELECT @bName,@bCP;

4、带inout模式的参数列表(有参数、有返回):

DELIMITER $   # 定义结束标志符
CREATE PROCEDURE mySP3(INOUT a INT, INOUT b INT)   
BEGIN  
	SET a = a * a;    # 参数列表中的变量,在存储体中,可以充当局部变量
	SET b = b * b;
END $
DELIMITER ;

DROP PROCEDURE IF EXISTS mySP3;   # 销毁存储过程mySP
SET @n1=10;   # 自定义的用户变量
SET @n2=20;
CALL mySP3(@n1,@n2);
SELECT @n1,@n2; 

查看存储过程的信息:

show create procedure 存储过程名;

举例:

DELIMITER $
CREATE PROCEDURE mySP4(IN date_time DATETIME, OUT strDate VARCHAR(20))
BEGIN 
	SELECT DATE_FORMAT(date_time, "%y年%m月%d日") INTO strDate;
END $
DELIMITER ;    

DROP PROCEDURE IF EXISTS mySP4;

CALL mysp4(NOW(), @result);
SELECT @result;

14.2.5 存储函数

创建存储函数:

# 创建函数:
delimiter $
create function 函数名(参数列表) returns 返回类型
begin    
    函数体
end $

/*注意:
1)参数列表包含两部分:参数 参数类型;
2)函数体中只有一句话,可以省略 begin end;
3)函数体中,必须有返回值,return 值;
4)使用delimiter $,可以用来设置结束标志;
*/

存储函数的参数列表:

1、空参列表

USE myemployees;
DELIMITER $
CREATE FUNCTION myfunc1() RETURNS INT
BEGIN
	DECLARE res INT DEFAULT 0;   # 自定义局部变量,并给定默认值0
	
	SELECT COUNT(*) INTO res
	FROM employees 
	GROUP BY department_id
	ORDER BY COUNT(*) DESC
	LIMIT 0,1;
	
	RETURN res;
END $
DELIMITER ;

SELECT myfunc1();

2、有参列表

DELIMITER $
CREATE FUNCTION myfunc2(employee_id INT) RETURNS VARCHAR(100)
BEGIN
	DECLARE res INT DEFAULT "";   # 自定义局部变量,并给定默认值0
	
	SELECT CONCAT(e.last_name,",",e.email) INTO res
	FROM employees AS e 
	WHERE e.employee_id=employee_id;
	
	RETURN res;
END $
DELIMITER ;    

DROP FUNCTION IF EXISTS myfunc2;

SELECT myfunc2(100);

查看函数的信息:

show create function 函数名; 

十五、变量、流程控制和游标

15.1 变量

        mysql数据库的存储过程和函数中,可以使用变量来存储查询或计算中间结果,或者输出最终的结果数据。mysql数据库中,将变量分为系统变量(分为全局系统变量和会话系统变量)和用户自定义的变量

        每个MySQL客户机成功连接后MySQL服务器后,都会产生与之对应的会话。会话期间,MySQL服务实例会在MySQL服务器内存中生成与该会话对应的会话系统变量(这些会话系统变量的初值是全局系统变量值的复制)。如下图:

15.1.1 系统变量:

说明:系统变量由系统提供,不是用户定义的,属于服务器层面上的;必须拥有super权限,才能为系统变量赋值;作用域为整个服务器,即针对所有的连接(会话)有效。

# 查看所有的系统变量
show global|[session] variables; 

# 查看满足某种条件的部分系统变量
show global|[session] variables like "...";  

# 查看指定的某个系统变量的值
select @@global|[session].系统变量名;         

# 为某个系统变量赋值
set global|[session] 系统变量名=值;     # 方式一
set @@global|[session].系统变量名=值;   # 方式二

总结:如果加global,则系统变量=全局变量;如果不加或者加session,则系统变量=会话变量

1、全局变量:

作用域:服务器每次启动,都会重新为所有的系统变量赋初值,且针对所有的连接(会话)有效。

# 查看所有的全局变量
SHOW GLOBAL VARIABLES;
# 查看部分的全部变量
SHOW GLOBAL VARIABLES LIKE "%autocommit%";
# 查看指定的全局变量的值
SELECT @@global.autocommit;
SELECT @@global.auto_increment_increment;
# 为某个指定的全局变量赋值
SET GLOBAL autocommit=0;

mysql 8.0中,可以利用新增的set persist命令,即set persist global 全局变量名 = 值;。mysql 8.0会将该命令的配置保存在数据目录下的mysqld_auto.cnf文件中,下次启动时会读取该文件,用其中的配置来覆盖默认的配置文件。

2、会话变量:

作用域:仅仅针对于当前会话(连接)有效。

# 查看所有的会话变量
SHOW SESSION VARIABLES; # 等价于SHOW VARIABLES;
# 查看部分的会话变量
SHOW SESSION VARIABLES LIKE "%autocommit%";   # 等价于SHOW VARIABLES LIKE "%autocommit%";
# 查看指定的会话变量的值
SELECT @@autocommit;                 # 等价于SELECT @@session.autocommit;    
SELECT @@auto_increment_increment;   # 等价于SELECT @@session.auto_increment_increment;   
# 为某个指定的会话变量赋值
SET SESSION autocommit=1;  # 等价于SET autocommit=1;  

15.1.2 自定义变量:

变量是用户自定义的,不是由系统提供的。

使用步骤:声明 -> 赋值 -> 使用(查看、比较、运算等)。

1、会话用户变量

作用域:针对当前会话(连接)有效,同于会话变量的作用域。

# 声明并初始化:
set @用户变量名=值;  或者 set @用户变量名:=值;  或者 select @用户变量名=值;

# 赋值:
方式一:select @用户变量名 := 表达式 from 表;
方式二:select 字段名 into @用户变量名 from 表;

# 查看:
select @用户变量名;

# 方式一:
select @m1 = 1;
select @m2 := 2;
select @sum := @m1 + @m2;

# 方式二:
select @count = COUNT(*) FROM employees;
select AVG(salary) INTO @avg_sal FROM employees;

2、局部变量

作用域:仅仅在定义它的begin end中有效,且必须是begin end中的第一句。

# 声明:
declare 局部变量名 类型;  或者 declare 局部变量名 类型 default 值;  

# 赋值:
方式一:set 局部变量名=值;  或者 set 局部变量名:=值;  或者 select @局部变量名:=值;
方式二:select 字段名 into 局部变量名 from 表;

# 查看:
select 用户变量名;

15.1.3 会话用户变量和局部变量的对比:

作用域定义和使用的位置语法
会话用户变量当前会话会话中的任何位置必须加@符号,但不用限定类型
局部变量begin end中begin end中的最开始的位置一般不加@符号,但需要限定类型

举例:

# 声明两个用户变量并求和
set @m=1;
set @n=2;            # 声明并给用户变量赋值;
set @sum = @m + @n;  # 使用用户变量求和;
select @sum;         # 显示用户变量

# 声明两个局部变量并求和
delimiter //
create procedure add_value()
begin 
    # 声明
    declare val1,val2 int;
    
    # 赋值
    set val1 = 10;
    set val2 := 10;
    
    set sum_res = val1 + val2;

    select sum_res;
end //
delimiter ;
call add_value();      # 调用存储过程

15.2 流程控制

执行的程序,流程就分为三大类:

  • 顺序结构:程序从上往下依次执行;
  • 分支结构:程序从两条或多条路径中选择一条去执行;
  • 循环结构:程序在满足一定条件的基础上,重复执行一段代码;

针对MySQL的流程控制语句主要有3类(只能用于存储程序):

  • 条件判断语句:if语句和case语句;
  • 循环语句:loop、while和repeat语句;
  • 跳转语句:iterate和leave语句;

15.2.1 分支结构:

1、if()函数(使用在begin end中):

# 情况一:
if(表达式1,表达式2,表达式3),执行顺序是:表达式1成立,则执行返回表达式2的值,否则返回表达式3的值

​# 情况二:
if 条件1 then 语句1;
elseif 条件2 then 语句2;
...
else 语句n;
end if;
delimiter $
create procedure leave_begin(in num int)
begin_label:begin 
	if num < 0 then leave begin_label;
	else if num = 1 then select avg(salary) from employees;
	else if num = 2 then select min(salary) from employees;
	else select max(salary) from employees;
	end if;
	
	# 退出if后,执行该sql语句
	select count(*) from employees;
end $
delimiter ;



delimiter //
create procedure update_salary_by_eid(in emp_id int)
begin
    declare emp_salary double;
    declare bonus decimal(3,2);
    
    select salary into emp_salary from employees where employee_id=emp_id;
    select commission_pct into bonus from employees where employee_id=emp_id;

    if emp_salary<9000 then update employees set salary=9000 where employee_id=emp_id;
    elseif emp_salary<10000 and bonus is null then update employees set commission_pct=0.1 where employee_id=emp_id;
    else update employees set salary=salary+100 where employee_id=emp_id;
    end if;
end //
delimiter ;

2、case的使用包含两种情况:

 1)类似于switch的功能
case 变量|表达式|字段
when 要判断的值 then 返回值1或语句1
when 要判断的值 then 返回值2或语句2
...
else 要返回的值n或语句n
end case;

​2)类似多重if语句,一般用于实现区间的判断
case 
when 要判断的条件1 then 要返回的值1或语句1
when 要判断的条件2 then 要返回的值2或语句2
...
else 要返回的值n或语句n
end case;

注意:如果then后面是语句,则可以放在 begin end 中;否则要放在 begin end 外。

# case实现多重if判断:
DELIMITER $        
CREATE PROCEDURE test_case(IN score INT)  # 创建存储体
BEGIN
	CASE 
	WHEN score>=90 THEN SELECT "A";
	WHEN score>=80 THEN SELECT "B";
	WHEN score>=60 THEN SELECT "C";
	ELSE SELECT "D";
	END CASE;
END $
 
CALL test_case(81);     # 调用存储体


# if多重判断:
DELIMITER $
CREATE FUNCTION test_if(score DOUBLE) RETURNS CHAR  # 创建函数
BEGIN 
	IF score>=90 THEN RETURN 'A';
	ELSEIF score>=80 THEN RETURN 'B';
	ELSEIF score>=60 THEN RETURN 'C';
	ELSE RETURN 'D';
	END IF;
END $

SELECT test_if(81);   # 调用函数

15.2.2 循环结构:

循环控制iterate/leave:

1)iterate类似于continue,结束本次循环继续执行下一次;只能在循环体内使用。

2)leave类似于break,结束当前所在的循环;既可以在循环语句内,又能在begin和end包裹的程序体内使用。

delimiter //
create procedure test_iterate_leave()
begin 
    declare num int default 0;

    loop_label:loop
        set num = num + 1;
        if num < 10 then iterate loop_label;
        elseif num > 15 then leave loop_label;
        end if;
        select 'loop execute once';
    end loop loop_label;
end //
delimiter ;

call test_iterate_leave();

while的使用(主要用来执行循环条件

# 语法:
[标签:]while 循环条件 do
	循环体;
end while [标签];
# while循环控制的使用:
USE test;
SELECT * FROM admin;  
DELETE FROM admin;    # 清空表admin

DELIMITER $
CREATE PROCEDURE test_while(IN insert_count INT)  # 创建存储体
BEGIN
	DECLARE i INT DEFAULT 1;  # 自定义的局部变量
	WHILE i <= insert_count DO
		INSERT INTO admin(id,userName,log_password) VALUES(i,CONCAT("kiki",i),CONCAT("555",i));
		SET i = i + 1;
	END WHILE;
END $
delimiter ;

DROP PROCEDURE IF EXISTS test_while;  # 销毁存储体

CALL test_while(9);    # 调用存储体
# 加入leave循环控制:
DELIMITER $
CREATE PROCEDURE test_while2(IN insert_count INT)  # 创建存储体
BEGIN
	DECLARE i INT DEFAULT 1;   # 自定义的局部变量
	
	# 设置标签
	func:WHILE i <= insert_count DO
		INSERT INTO admin(id,userName,log_password) VALUES(i+10,CONCAT("nini",i),CONCAT("666",i));
		
		IF i>=5 THEN LEAVE func;   # leave循环控制
		END IF;
		
		SET i = i + 1;
	END WHILE;
END $
delimiter ;

DROP PROCEDURE IF EXISTS test_while2;  # 销毁存储体

CALL test_while2(9);    # 调用存储体
SELECT * FROM admin;

repeat的使用(相当于do...while):

# 语法:
[标签:]repeat
	循环体;
until 结束循环的条件
end repeat [标签];
delimiter //
create procedure update_salary_repeat(out num int)
begin 
    declare avg_sal double;
    declare while_count int default 0;

    select avg(salary) into avg_sal from employees;
    repeat
        set repeat_count = repeat_count + 1;
        update employees set salary = salary * 1.15;
        select avg(salary) into avg_sal from employees;
        until avg_sal >= 13000
    end repeat;
    
    set num = repeat_count;
end //
delimiter ;

call update_salary_repeat(@num)
select @num;

loop的使用(主要用来模拟死循环)

# 语法:
[标签:]loop
	循环体;
end loop [标签];
# iterate相当于continue,使用如下:
delimiter $
create procedure test_loop_iterate()
begin
	declare num int default 0;
	loo_label:loop
		set num = num + 1;
		
		if num < 10 then iterate loop_label;
		else if num > 15 then leave loop_label;
		end if;
		
		select '共执行6次'
	end loop;
end $

# leave相当于break,使用如下:
delimiter $
create procedure test_loop_leave(out num)
begin
	declare avg_sal double default 0.0;
	declare loop_count int default 0;
	
	select avg(salary) into avg_sal from employees;
	
	loop_label:loop
		if avg_sal >= 12000 then leave loop_label;
		else update employees set salary = salary * 1.1;
		end if;
		
		# 更新avg_salary的值
		select avg(salary) into avg_sal from employees;
		
		# 记录循环次数
		set loop_count = loop_count + 1;
	end loop;
	
	set num = loop_count;
end $
delimiter ;

set @num := 0;
call test_loop_leave(@num);
select @num;

while、repeat和loop的对比:

名称语法特点位置
while[标签:]while 循环条件 do 循环体; end while [标签];先判断后执行begin end中
repeat[标签:]repeat 循环体; until 结束循环的条件 end repeat [标签];先执行后判断,至少执行一次begin end中
loop[标签:]loop 循环体; end loop [标签];没有条件的死循环begin end中

15.3 游标(光标)

        MySQL中,虽然可以通过筛选条件where和having,或者限定返回记录的关键字limit返回一条记录,但却不能像指针一样,随意的定位到某一条记录,并对记录进行处理。

        游标提供了一种灵活的操作方式,使我们能够对结果集中的每条记录进行定位,并对指向的记录中的数据进行操作。总的来说,游标让sql这种面向集合的语言有了面向过程开发的能力。

        sql中,游标的是一种临时的数据库对象,充当着指针的作用,可以指定存储在数据库中的数据行指针,通过操作游标来对数据行进行操作

        mysql中,游标可以在存储过程和函数中使用

15.3.1 使用游标的步骤:

# 声明游标:
declare cursor_name cursor for select_语句;

# 打开游标:打开游标的时候,select语句的查询结果集就会送到游标工作区,为后面游标的逐条读取结果集中的记录做准备
open cursor_name;

# 使用游标,并从游标中取得数据
fetch cursor_name into 变量名;  # 游标的查询结果集中的字段数,必须跟into后面的变量数一致;

# 关闭游标
close cursor_name;
# 因为游标会占用系统资源,如果不及时关闭,游标会一直保持到存储过程结束,影响系统运行的效率。
# 而关闭游标的操作,会释放游标占用的系统资源,即不能再检索查询结果中的数据行,如果需要检索则只能再次打开游标。

15.3.2 使用范例

delimiter $
create procedure getCountByLimitTotalSalary(in limit_total_salary double, out total_count int)
begin 
	declare sum_sal double default 0.0;
	declare emp_sal double;
	declare emp_count int default 0;

	# 声明游标
	declare emp_cursor cursor for select salary from employees;
	# 打开游标
	open emp_cursor;
	repeat
		# 使用游标
		fetch emp_cursor into emp_sal;
		
		set sum_sal = sum_sal + emp_sal;
		set emp_count = emp_count + 1;
		
		until sum_sal >= limit_total_salary
	end repeat;
	
	set total_count = emp_count;
	
	# 关闭游标
	close emp_cursor;
end $
delimiter //
create procedure update_salary(in dept_id int, in change_sal_count int)
begin
    # 声明变量
    declare emp_id int;
    declare emp_hire_date date;
    declare init_count int default 1;
    
    # 声明游标
    declare emp_cursor cursor for select employee_id, hire_date from employees where department_id=dept_id order by salary asc;

    # 打开游标
    open emp_cursor;
    while init_count <= change_sal_count do
        # 使用游标
        fetch emp_cursor into emp_id,emp_hire_date;

        if(year(emp_hire_date)<1995) then set add_sal_rate=1.2;
        elseif(year(emp_hire_date)<=1998) then set add_sal_rate=1.15;
        elseif(year(emp_hire_date)<=2001) then set add_sal_rate=1.10;
        else then set add_sal_rate=1.05;

        update employees set salary=salary*add_sal_rate where employee_id=emp_id;

        set init_count = init_count + 1;
        end if;
    end while;
    # 关闭游标
    close emp_cursor;
end //
delimiter //

call update_salary(50,2);

15.3.3 总结:

  1. 游标是mysql的一个重要的功能,为了逐条读取结果集中的数据,提供完美的解决方案;
  2. 游标可以在存储程序中使用,效率高,程序也更加简洁;
  3. 使用游标的过程中,会对数据行进行加锁,这在业务出现并发量大的时候,不仅会影响业务之间的效率,还会消耗系统的资源,造成内存不足,这是因为游标是在内存中进行的处理
  4. 总结:养成用完就关闭的习惯,才能提高系统的整体效率。

十六、触发器

16.1 触发器的引入:

        实际开发中,经常遇到有两个或多个关联表,为了保证数据的完整性,需要同时给多个相互关联的表添加数据。这就需要我们将两个关联的操作写在一个事务中,确保这两个操作成为一个原子操作,要么全部执行,要么全部不执行。如果遇到特殊情况,还需要对数据进行手动维护,这样很容易忘记其中一步,从而导致数据缺失。

        我们可以创建一个触发器,在多个关联表的一个表中添加数据时,自动触发并给另一个关联表中添加数据。这样就不用担心同时给多个关联表中添加数据时,忘记的问题。

16.2 触发器的介绍:

mysql从5.0.2的版本开始就有触发器,mysql的触发器和存储过程一样,都是嵌入到mysql服务器的一段程序。

16.3 触发器的创建:

# 语法结构:
delimiter $
create trigger 触发器的名称
[before|after] insert|delete|update on 表名
for each row 
begin
	触发器执行的语句块;
end $

16.3.1 说明:

1)表名:表示触发器监控的对象;

2)before|after:表示触发的时间。before表示在事件之前触发,after表示在事件之后触发;

3)insert|delete|update:表示触发的事件;

  • insert表示插入记录时触发;
  • update表示更新记录时触发;
  • delete表示删除记录时触发;

4)触发器执行的语句块:单条sql语句、begin...end结构组成的复合语句块。

16.3.2 同时给两张表添加相同的记录:

create database test_trigger;
use test_trigger;

create table employees 
as 
select employee_id,last_name,salary
from myemployees.employees;

create table employees_copy_new like employees;

# 创建触发器当给employees中添加记录时,同时也给employees_copy中添加该条记录
delimiter $
create trigger emp_insert_trigger
after insert on employees
for each row
begin 
	insert into employees_copy_new(employee_id,last_name,salary)
	values(New.employee_id,New.last_name,New.salary)
end $

insert into employees values(1,"timi",5000);
select * from employees;
select * from employees_copy_new;
# 此时,该条记录在employees和employees_copy_new中,都存在

16.3.3 将一张表中删除的记录存在另一张表中:

use test_trigger;
create table employees_copy_old like employees;

# 创建触发器当给employees中添加记录时,同时也给employees_copy中添加该条记录
delimiter $
create trigger emp_insert_trigger
before delete on employees
for each row
begin 
	insert into employees_copy_old(employee_id,last_name,salary)
	values(OLD.employee_id,OLD.last_name,OLD.salary)
end $

delete from employees where employee_id=1;
select * from employees;
select * from employees_copy_old;
# 此时,从employees中删除的该条记录在employees_copy_old中

16.3.4 在插入数据前,检查数据是否满足条件,不满足则添加失败

create table myemployees
as 
select * from employees;

create table mydepartments
as 
select * from departments;

# 在给employees中添加数据前,检查其工资是否高于其领导的工资,高于则报错,否则添加成功
delimiter //
create trigger salary_check_trigger
before insert on employees
for each row
begin
    declare mar_sal double;
    select salary into mar_sal from employees where employee_id=new.manager_id;
    
    if(mar_sal < new.salary) 
        then signal sqlstate 'HY000' set message_text='薪资高于领导的薪资错误';
    end if;
end //
delimiter ;

16.4 删除触发器:

drop trigger if exists 触发器名;

16.5 查看触发器的定义:

# 查看当前数据库中,所有的触发器的定义
show triggers;

# 查看当前数据库中,某个触发器的定义
show create trigger 触发器名;

16.6 优缺点:

优点:

  • 确保数据的完整性;
  • 触发器可以帮助我们记录操作日志;
  • 触发器可以用在操作数据前,对数据进行合法性检查;

缺点:

  • 触发器最大的一个问题就是可读性差;
  • 相关数据的变更,可能会导致触发器出错;

十七、MySQL8.0新增特性

        更简便的NoSQL支持;更好的索引;更完善的json支持;安全和账户管理;InnoDB的变化;公用表达式;窗口函数;

17.1 窗口函数:

17.1.1 窗口函数的介绍:

在这种需要用到分组统计的结果对每一条记录进行计算的场景下,使用窗口函数更好;

窗口函数会将分组的结果置于每一条数据记录中,即分组但不合并;

17.1.2 窗口函数的分类:

静态窗口函数和动态窗口函数;

1)静态窗口函数的窗口大小是固定的;

2)动态窗口函数的窗口大小会随着记录的不同而变化;

函数分类函数函数说明
序号函数row_number()顺序排序
rank()并列排序,会跳过重复复的序号,比如1、1、3
dense_rank()并列排序,不会跳过重复的序号,比如1、1、2
分布函数percent_rank()等级制百分比=(rank()-1)/(rows-1)
cume_dist()累积分布值
前后函数lag(expr,n)返回当前行的前n行的expr的值
lead(expr,n)返回当前行的后n行的expr的值
首尾函数first_value(expr)返回第一个expr的值
last_value(expr)返回最后一个expr的值
其他函数nth_value(expr,n)返回第n个expr的值
ntile(n)将分区中的有序数据分为n个桶,记录桶的编号

17.1.3 举例:

CREATE TABLE IF NOT EXISTS goods(
    id INT PRIMARY KEY AUTO_INCREMENT,
    category_id INT,
    category VARCHAR(15),
    name_ VARCHAR(30),
    price DECIMAL(10,2),
    stock INT,
    upper_time DATETIME
);

INSERT INTO goods(category_id,category,name_,price,stock,upper_time) 
VALUES(1, "女装", "T恤",39, 1000, "2021-11-10 00:00:00"),
(1, "女装", "卫衣", 49, 1000, "2020-11-10 00:00:00"),
(1, "女装","牛仔裤", 19, 1000,"2000-11-10 00:00:00"),
(1, "女装","外套",59,1000,"2021-11-10 00:00:00"),
(1, "女装","连衣裙",69,1000,"2021-11-10 00:00:00"),
(1, "女装","百褶裙",79,1000,"2018-11-10 00:00:00"),
(2, "男装","领带",89,1000,"2021-11-10 00:00:00"),
(3, "户外运动","骑行装备",119,1000,"2021-11-10 00:00:00"),
(2, "男装","滑板",99,1000,"2021-11-10 00:00:00");

序号函数:row_number()、rank()、dense_rank()

select row_number() over(partition by category_id order by price desc) as row_num, g.* from goods as g;
# 查询结果是:row_num在不同的category_id的分组中,都是从1-n的不重复不空缺的排列

# 出现price相同的情况,row_num会相同,比如1、1、3
select rank() over(partition by category_id order by price desc) as row_num, g.* from goods as g;
# 出现price相同的情况,row_num会相同,比如1、1、2
select dense_rank() over(partition by category_id order by price desc) as row_num, g.* from goods as g;

分布函数:percent_rank()=(rank-1)/(rows-1)、cume_dist()

# percent_rank()函数的使用:
select rank() 
over(partition by category_id order by price desc) as rank,
percent_rank() 
over(partition by category_id order by price desc) as percent_rank,
, g.* 
from goods as g 
where g.category_id=1;

# 等价于
select rank() over w as rank,
percent_rank() over w as percent_rank,
, g.* 
from goods as g 
where g.category_id=1 
window w as (partition by category_id order by price desc);
# cume_dist()函数的使用:
# 查询数据表中,<=当前价格的比例
select cume_dist() over(partition by category_id order by price asc) as cd, g.*
from goods as g;

前后函数:lag(expr, n)

# lag(expr,n)返回当前行的前n行的expr的值
# 查询goods数据表中,同一类category_id中前一个商品与当前商品的差值(每类category_id的第一个商品的前一个商品的价格是null,即差值也是null)
select t.*, t.pre_price, t.price-t.pre_price as diff_price
from (
	select g.*, lag(price,1) over w as pre_price
    from goods as g
    window w as (partition by category_id order by price)) as t;

17.1.4 总结:

        窗口函数的特点是可以分组,而且可以在分组内排序,另外,窗口函数不会因为分组而减少原表中的行数,这就便于我们在原表的基础上进行统计和排序。(分组并不合并)

17.2 公用表表达式:

17.2.1 介绍:

        公用表表达式,又称通用表表达式common table expression,简称CTE。CTE是一种命名的临时结果集,作用范围是当前语句。CTE可以理解为一个可以复用的子查询,但与子查询有区别。CTE可以引用其他的CTE,但子查询不可以,故可以考虑用CTE代替子查询。

17.2.2 分类:

根据语法结构和使用方式的不同,公用表表达式分为普通公用表表达式递归公用表表达式

普通公用表表达式:

# 语法结构:
with recursive CTE名称 
as 
(子查询)
select|update|delete 语句;
# 子查询的实现
select * from departments 
where department_id in (
	select distinct department_id from employees
);

# CTE实现
with cte_dp_id
as (select distinct department_id from employees)
select * from departments as dp
inner join cte_dp_id as dpid on dpid.department_id=dp.department_id;

递归公用表表达式:

递归公用表达式除了普通公用表表达式的特点外,还有自己的特点,就是可以自己调用自己

递归表达式由2部分组成,分别是种子查询和递归查询,中间通过关键字union all连接种子查询是获取递归的初始值。种子查询只执行一次,以创建初始数据集,之后递归查询会一直执行,直到没有任何新的查询数据产生,递归结束

# 找到初始100号员工为管理者的所有直接/间接下属,即‘树型结构’
with recursive cte
as (
    # 种子查询(获取递归的初始值)
	select employee_id,last_name,manager_id, 1 as n from employees where employee_id=100
    union all
	# 递归查询
    select employee_id,last_name,manager_id, n+1 from employees as e 
    join cte on e.manager_id=cte.employee_id
) 
select employee_id,last_name from cte where n>3;

17.2.3 总结:

  • 公用表表达式可以代替子查询,而且可以被多次引用。
  • 递归公用表表达式对查询,是有一个共同根节点的树形结构数据,非常高效。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小白要努力sgy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值