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)持久化的保存数据到本地硬盘上加以“固化”,这通常需要通过各种关系数据库来完成,当然也可以存储在磁盘文件中;
2)可以实现结构化查询,方便管理;
1)DB(数据库database):存储数据的“仓库”,保存了一系列有组织的数据;
2)DBMS(数据库管理系统`Database Management System`):数据库是通过DBMS创建和操作的容器;
常见的数据库管理软件:MySQL、Oracle(收费)、DB2
(适用于处理海量的数据,常用于银行系统中)、sqlServer
(只能安装在windows环境下);
DBMS的分类:
总结:MySQL数据库服务器中安装了`MySQL DBMS`,使用`MySQL DBMS`并通过SQL来管理和操作DB。
1)不是某个特定数据库供应商专有的语言,几乎所有的数据库管理系统DBMS都支持SQL;
2)一种强有力的语言,灵活使用其语言元素,可以进行非常复杂和高级的数据库操作;
键值型数据库:Redis
,使用场景是作为内存缓存;
文档型数据库MongoDB
、搜索引擎数据库Elasticsearch/Solr
、列式数据库HBase
、图形数据库InfoGrid
;
列式数据库:相对于行式数据库而存在,Oracle、MYSQL、SQL Server等数据库都是采用行式存储(Row-based)。相对于行式存储,列式存储的优势在于能大大降低系统的I/O,适合于分布式文件系统,不足是功能相对有限。
1)关系型数据库的典型数据结构就是数据表,且这些数据表的组成是结构化的;
2)将数据放在表中,表再存放在库中;
3)一个数据库可以有多张表,每个表都有自己的名字,用来标识自己;且表名具有唯一性;
4)表具有一些特性,这些特性定义了数据在表中如何存储,类似于“类”的设计;行式存储的,每行类似于 “对象”,一行数据称为一个实体/一条记录;表由一个或多个列组成,每列类似于 “属性”,一列数据称为一个字段;
表与表之间的数据是有关系的。现实世界中,各种实体以及实体间的各种联系均用关系模型来表示。
四种关联关系:一对一关联、一对多关联、多对多关联、自我关联;
实际的开发中应用的并不多,因此一对一可以创建成一张表;
两种建表原则:
实例场景:客户表和订单表、分类表和商品表、部门表和员工表;
一对多的建表原则:在从表创建一个字段,并将该字段作为外键指向主表的主键;
将两个表的主键都插入到中间表中,该中间表常称为联接表;
同一张表中,两列的数据是有关系的;
复杂查询:支持多表查询;
事务支持:使得安全性很高的数据访问得以实现。
存储数据时处理数据的第一步,只有正确地把数据存储起来,我们才能进行有效地处理和分析。
MySQL中,一个完整地数据存储过程总共4步,如下图:
从系统架构的层次看,MySQL数据库系统从小到大依次是,数据库服务器、数据库、数据表、数据表的行与列。
(57条消息) mysql数据库安装(详细)体会!的博客-CSDN博客mysql安装
任务管理器/服务/MySQL/启动即可 或者通过用命令行启动的方式实现:
(mysql最终将数据也是以文件形似保存在磁盘中,路径(可以通过my.ini配置文件查看):# Path to the database root datadir="C:/ProgramData/MySQL/MySQL Server 5.5/Data/"
)
在命令行客户端无法正常显示中文,原因:服务器端认为你的命令行客户端的字符集是utf8,而实际上你的命令行客户端的字符集是GBK。
解决方案:设置当前命令行客户端的字符集是gbk。
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;
1)不区分大小写,建议关键字大写,表名、列名小写;
2)标识符命名的规则:
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中可以使用的运算符号包括:
运算符的优先级:数字越大,优先级越高;
9)sql中字符+数值,会发生隐式类型转换,如果转换成功会将字符中的数字+数值,否则会置字符为0;
使用规范:任何字段如果是非负数,必须是unsigned无符号型;
默认所有类型的值都可以为null,且空字符串和0都不是null;
类型 | 类型举例 |
---|---|
整型 | 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 |
约束的关键字 | 含义 |
---|---|
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来存储;
一种限制,用于限制表中字段的数据,为了保证表数据得准确性和可靠性。SQL通过约束的形式对表数据进行额外的条件限制,主要从下列四个方面考虑:
create table 表名(
字段名 字段类型 列级约束,
表级约束
)
约束的关键字 | 作用 |
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值而影响计算 或 降低效率。
添加约束的时机:创建表时、修改表时;
约束的添加分类:列级约束、表级约束;
列级约束:六大约束都支持,只是外键/check约束在mysql 5.7中没有效果;
表级约束:除了非空约束not null、默认值约束default,其他的都支持;
列级约束和表级约束的区别:
列级约束的组合:列级约束,可以在一个字段上追加多个,中间用逗号隔开,且没有顺序要求;
复合唯一性约束(表级约束):
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'); # 添加失败
注意:如果是多列组合的复合主键约束,那么这些列都不允许为空值,并且组合的值不允许重复。
主键 | 唯一 | |
---|---|---|
唯一性 | √ | √ |
是否允许为空 | × | √ |
一个表中能否有多个 | × | √ |
是否允许组合 | √(不推荐) | √(不推荐) |
作用:限制某个表的某个字段的引用完整性。
使用规范:
总结:外键约束的约束关系是针对主表和从表双方的;添加外键约束后,主表的修改和删除、从表的添加和删除受约束;
使用外键时的约束等级:
注意:如果没有指定等级,就相当于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
);
总结:
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;
# 添加列级约束
alter table 表名 modify column 字段 字段类型 列级约束;
# 添加表级约束
alter table 表名 add [constraint 外键名] 表级约束(字段);
#添加外键
alter table 表名 add [constraint 外键名] foreign key(从表的字段) references(主表的字段);
# 删除非空约束
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 字段名;
不可以手动的插入值,系统会提供默认的序列值;
set auto_increment_increment = 3
设置,默认是1;# 语法:
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;
# 修改表并添加标识列
alter table tab_identity modify column id int primary key auto_increment;
# 修改表时删除标识列
alter table tab_identity modify column id int;
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
DDL :Data Define Language
主要用来做库和表的管理:创建create、删除alter、修改drop;这些语句定义了不同的数据库、表、视图、索引等数据库对象,还可以用来创建、删除、修改数据库和数据表的结构;
DDL中对库和表的操作,在事务中,无法实现回滚,要慎用;
# 库的创建语法:
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创建后不能修改名字。但一些可视化工具可以通过建新库,并把原库中所有表复制到新库中,再删除原库来完成的。
# 表的创建语法:
#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; # 显示表的详细信息:
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;
数据库的字段名的修改代价很大,会发生数据的拷贝,所以字段名的修改要慎重。
# 表的修改:修改列名、修改列的类型或约束、添加新的列、删除列、修改表名
# 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;
DROP TABLE IF EXISTS book;
清空表中的所有数据,但保留表的结构。
truncate table employees_copy;
DQL :Data Query Language,主要用来查询数据。
语法:select 查询列表 from 表名
特点:
1、查询列表可以是:表中的字段、常量值、表达式、函数
2、查询的结果是一个虚拟的表格
# 显示当前所有的数据库
SHOW DATABASES;
# 跳到myemployees数据库中
USE myemployees;
select database(); # 查看当前在哪个数据库中
# 查询表中的单个字段
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;
# 查询常量值
select 100;
select 'John';
# 查询常量表达式
select 100*98;
select 100%98;
# 查询函数
select version();
# 显示表中的结构
DESC departments;
#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; # 别名中有特殊字符要加双引号
SELECT DISTINCT department_id FROM employees;
SELECT CONCAT(last_name,' ',first_name) AS 姓名 FROM employees;
SELECT CONCAT(`first_name`,',',`last_name`,',',`email`,',',`phone_number`,',',`job_id`,',',`salary`,',',IFNULL(commission_pct,0))
FROM employees;
select 查询列表 from 表名 where 筛选条件
# 条件运算符:>、<、=/<=>安全等于、!=/<>、>=、<=
SELECT * FROM employees WHERE salary>2000;
# 逻辑运算符:&&/and、||/or、!/not
SELECT * FROM employees WHERE salary>2000 AND salary<10000;
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;
order by子句,一般放在查询语句的最后,limit语句除外;
order by子句中,可以支持单个字段(一级排序)、多个字段(多级排序)、表达式、函数、别名等;
select 查询列表 from 表 【where 筛选条件】order by 排序列表 asc/desc
#其中,asc表升序,desc表降序,默认是升序
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; # 二级排序
类似于c++/go中的方法,将一组逻辑语句封装在方法体中,对外暴露方法名;
优点:1、隐藏了实现细节;2、提高了代码的重用性;
从定义的角度,函数可以分类为:内置函数(系统内置的通用函数)、自定义函数;
单行函数:ifnull()、isnull()、concat();字符函数、数学函数、日期函数、其他函数;流程控制函数等;
分组函数(多行函数/聚合函数):做统计使用,又称统计函数、聚合函数、组函数。
# 语法:
select 函数名(实参列表);
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;
用作统计使用,又称统计函数、聚合函数、组函数。它是对一组数据进行汇总的函数,输入的是一组数据的集合,输出的是单个值。
常见的分组函数:sum()、avg()、max()、min()、count()计算非空值的个数;
1)sum()、avg():一般用于处理数值型;
2)max()、min()、count():可以处理任何类型,如数值类型、字符串类型、日期时间类型等;
注意:
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(字段名),谁更好?
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编码;
可以使用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);
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 | 可以使用分组中的计算函数 | 在最后的结果集中进行筛选,执行效率低 |
select count(*), length(last_name)
from employees
group by length(last_name)
having count(*)>5;
# 按多个字段进行分组:查询每个部门、每个工种的员工的平均工资;并在分组前筛选掉部门为空数据,最后按照平均工资进行降序
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;
查询到字段来自于多个表时,需要添加有效的连接条件(即如果是n个表的查询,则需要n-1个连接条件)(有效的连接条件,能避免出现笛卡尔乘积现象)。从sql优化的角度讲,建议在多表查询时,尽量在select后的字段前指明该字段来自哪个表,以便节省sql查询时间。
1)按年代分类:sql92
标准(只支持内连接),sql99
标准(支持内连接、外连接(左连接和右连接)和交叉连接);
2)按功能分类:
单/多表等值连接:多表等值连接的结果是多表的交集部分;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`;
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%";
外连接(左外、右外、全外连接):
应用场景:用于查询一个表中有,而另一个表中没有的;一般在需求中,出现了“所有的”字眼,则需要使用外连接进行查询;
外连接的查询结果为主表中的所有记录 +
主表 和 从表的界定:左外连接: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;
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`;
出现在其他语句中的select语句,称为子查询或内查询;外部的查询语句,称为主查询或外查询。总之,从子查询中获取一个结果集,然后再对该结果集进行主查询的过程。
注意:相关子查询的需求:查询工资大于本部门平均工资的员工信息;不相关子查询的需求:查询工资大于本公司平均工资的员工信息。
# 查询员工中,工资大于本部门平均工资的员工的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
);
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
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中都对自连接进行了优化,故都可以使用时,推荐使用自连接。
总结:
当要查询的信息一页无法完全显示,需要分页提交sql请求。另外,在没有索引的情况下,通过使用limit,可以极大的提高查询效率。
# 语法:
limit offset,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
);
union联合查询,会将多条查询语句的结果合并成一个结果,即并集;
应用场景:要查询的结果来自于多表,且多个表之间没有直接的连接关系,但要查询的信息一致;
特点:
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 :Data Manipulation Language,用来添加、删除、更新表中的数据,并检查数据完整性。
# 语法:
# 插入方式一:
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";
# 修改单表的记录
# 语法: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;
# 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不可以;
某一列的值是通过表中其余两列的值得到的,不需要手动植入和计算。
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 | 相当于一个事件监听器,当数据库发生特定事件后,触发器被触发,完成相应的处理。 |
视图,是从5.0.1版本后提供的新功能,是一种虚拟存在的表,行和列的数据来自定义视图的查询中使用的表,并且视图是在执行时动态生成的,只保存了sql逻辑,不保存查询结果,故占用内存很少。本质上:视图就是存储起来的select的语句。
优点:
不足:
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;
# 方式一:
create or replace view 视图名
as
查询语句;
# 方式二:
alter view 视图名
as
查询语句;
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`;
1. 更新时,使用insert、delete、update与普通表用法相同;更新视图,也会对原始表(基表)产生影响;
2. 视图的可更新性和视图中查询的定义有关系,以下类型是不能更新的:
创建语法的关键字 | 是否实际占用了物理空间 | 使用 | |
---|---|---|---|
视图 | create view | 只保存了sql逻辑 | 一般不能进行增删改,主要用来查 |
表 | create table | 保存了数据 | 增删改查 |
存储过程和存储函数:类似于c++中的方法,都能够将复杂的sql逻辑封装在一起,并预先存储在数据库服务器中,需要使用时客户端只需要向服务器端发送请求,服务器就会执行预先存储好的存储过程和函数全部执行,并返回结果给客户端;
一组预先编译好的sql语句的集合,可以理解成批处理语句;
关键字 | 调用语法 | 返回值 | 应用场景 | |
---|---|---|---|---|
存储过程 | procedure | call 存储过程名(参数); | 可以设置0或多个out|inout模式的参数,作为输出 | 一般用于更新 |
存储函数 | function | select 函数(参数); | 只能有一个 | 一般用于查询并返回一个结果,且结果只能是一个值 |
此外,存储函数可以放在查询语句中,但存储过程不行;存储过程,能够执行对表的更新和事务的操作,这些功能存储函数并不具备。
存储过程的创建和调用、销毁:
# 创建语法:
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;
创建存储函数:
# 创建函数:
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 函数名;
mysql数据库的存储过程和函数中,可以使用变量来存储查询或计算中间结果,或者输出最终的结果数据。mysql数据库中,将变量分为系统变量(分为全局系统变量和会话系统变量)和用户自定义的变量。
每个MySQL客户机成功连接后MySQL服务器后,都会产生与之对应的会话。会话期间,MySQL服务实例会在MySQL服务器内存中生成与该会话对应的会话系统变量(这些会话系统变量的初值是全局系统变量值的复制)。如下图:
说明:系统变量由系统提供,不是用户定义的,属于服务器层面上的;必须拥有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;
变量是用户自定义的,不是由系统提供的。
使用步骤:声明 -> 赋值 -> 使用(查看、比较、运算等)。
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 用户变量名;
作用域 | 定义和使用的位置 | 语法 | |
---|---|---|---|
会话用户变量 | 当前会话 | 会话中的任何位置 | 必须加@符号,但不用限定类型 |
局部变量 | 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(); # 调用存储过程
执行的程序,流程就分为三大类:
针对MySQL的流程控制语句主要有3类(只能用于存储程序):
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); # 调用函数
循环控制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中 |
MySQL中,虽然可以通过筛选条件where和having,或者限定返回记录的关键字limit返回一条记录,但却不能像指针一样,随意的定位到某一条记录,并对记录进行处理。
游标提供了一种灵活的操作方式,使我们能够对结果集中的每条记录进行定位,并对指向的记录中的数据进行操作。总的来说,游标让sql这种面向集合的语言有了面向过程开发的能力。
sql中,游标的是一种临时的数据库对象,充当着指针的作用,可以指定存储在数据库中的数据行指针,通过操作游标来对数据行进行操作。
mysql中,游标可以在存储过程和函数中使用。
# 声明游标:
declare cursor_name cursor for select_语句;
# 打开游标:打开游标的时候,select语句的查询结果集就会送到游标工作区,为后面游标的逐条读取结果集中的记录做准备
open cursor_name;
# 使用游标,并从游标中取得数据
fetch cursor_name into 变量名; # 游标的查询结果集中的字段数,必须跟into后面的变量数一致;
# 关闭游标
close cursor_name;
# 因为游标会占用系统资源,如果不及时关闭,游标会一直保持到存储过程结束,影响系统运行的效率。
# 而关闭游标的操作,会释放游标占用的系统资源,即不能再检索查询结果中的数据行,如果需要检索则只能再次打开游标。
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);
实际开发中,经常遇到有两个或多个关联表,为了保证数据的完整性,需要同时给多个相互关联的表添加数据。这就需要我们将两个关联的操作写在一个事务中,确保这两个操作成为一个原子操作,要么全部执行,要么全部不执行。如果遇到特殊情况,还需要对数据进行手动维护,这样很容易忘记其中一步,从而导致数据缺失。
我们可以创建一个触发器,在多个关联表的一个表中添加数据时,自动触发并给另一个关联表中添加数据。这样就不用担心同时给多个关联表中添加数据时,忘记的问题。
mysql从5.0.2的版本开始就有触发器,mysql的触发器和存储过程一样,都是嵌入到mysql服务器的一段程序。
# 语法结构:
delimiter $
create trigger 触发器的名称
[before|after] insert|delete|update on 表名
for each row
begin
触发器执行的语句块;
end $
1)表名:表示触发器监控的对象;
2)before|after:表示触发的时间。before表示在事件之前触发,after表示在事件之后触发;
3)insert|delete|update:表示触发的事件;
4)触发器执行的语句块:单条sql语句、begin...end结构组成的复合语句块。
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中,都存在
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中
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 ;
drop trigger if exists 触发器名;
# 查看当前数据库中,所有的触发器的定义
show triggers;
# 查看当前数据库中,某个触发器的定义
show create trigger 触发器名;
优点:
缺点:
MySQL8.0
新增特性更简便的NoSQL支持;更好的索引;更完善的json支持;安全和账户管理;InnoDB的变化;公用表达式;窗口函数;
在这种需要用到分组统计的结果对每一条记录进行计算的场景下,使用窗口函数更好;
窗口函数会将分组的结果置于每一条数据记录中,即分组但不合并;
静态窗口函数和动态窗口函数;
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个桶,记录桶的编号 |
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;
窗口函数的特点是可以分组,而且可以在分组内排序,另外,窗口函数不会因为分组而减少原表中的行数,这就便于我们在原表的基础上进行统计和排序。(分组并不合并)
公用表表达式,又称通用表表达式common table expression,简称CTE。CTE是一种命名的临时结果集,作用范围是当前语句。CTE可以理解为一个可以复用的子查询,但与子查询有区别。CTE可以引用其他的CTE,但子查询不可以,故可以考虑用CTE代替子查询。
根据语法结构和使用方式的不同,公用表表达式分为普通公用表表达式和递归公用表表达式。
普通公用表表达式:
# 语法结构:
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;