MySQL学习记录

文章导读

     本文使用的MySQL版本为5.7,学习过程中的实操既有通过第三方MySQL客户端工具Navicat远程连接MySQL服务端进行操作,也有使用MySQL自带的客户端工具进行实操,如果有概念无法理解的地方建议直接看该章节中对应的练习辅助理解知识点。

1、SQL语言介绍

SQL语言:结构化查询语言,简称 SQL,是一种数据库查询和程序设计语言,用于存取数据以及查询、更新和管理关系数据库系统。

SQL分类

①、数据查询语言(DQL):也称为“数据检索语句”,用于从表中获得数据,确定数据怎样在应用程序给出。关键字 SELECT 是 DQL(也是所有 SQL)用得最多的动词,常用语句包括SELECT,FROM,WHERE,ORDER BY,HAVING。

②、数据操作语言(DML):其语句包括动词 INSERT,UPDATE 和 DELETE。它们分别用于添加,修改和删除表中的行。

③、数据库定义语言(DDL):是定义数据库对象语言,其语句包括动词 CREATE,ORTER,DROP 。

④、数据库控制语言(DCL):它的语句通过 GRANT 或 REVOKE 获得许可,确定用户对数据库对象的访问权限。(GRANT用于授权,REVOKE用于回收权力)

⑤、事务控制语言(TCL):它的语句能确保被 DML 语句影响的表的所有行及时得以更新。(COMMIT用于提交事务,ROLLBACK用于回滚事务,SAVEPOINT用于设置回滚点)

注意:

数据操纵语言DML(insert、update、delete)是针对表中的数据 ;

而数据定义语言DDL(create、alter、drop)是针对数据库对象,比如数据库database、表table、索引index、视图view、存储过程procedure、触发器trigger;

SQL语法:不区分大小写且以分号结尾,可以单行书写也可以多行书写。

2、操作数据库

2.1、创建数据库

语法

CREATE DATABASE 数据库名 DEFAULT CHARACTER SET 字符编码;

语法解析

CREATE DATABASE:创建数据库的关键字。

DEFAULT CHARACTER SET:指定默认编码类型的关键字。

查看数据库

show databases;

查看数据库编码

-- 目前不要求掌握
select schema_name,default_character_set_name from information_schema.schemata 
where schema_name = '数据库名';

实操:创建一个名为firstDatabase的数据库,并为其指定默认编码为utf-8,并查看数据库是否创建成功。

-- 首先需要输入数据库密码

-- 创建名为firstDatabase的数据库并指定默认编码为utf-8
create database firstDatabase default character set utf8;

-- 查看所有的数据库
show databases;

运行结果如下:

2.2、删除数据库

语法

DROP DATABASE 数据库名称;

实操:删除上一小节中实操新建的数据库firstDatabase。

-- 删除名为firstDatabse的数据库
drop database firstDatabase;

-- 查看数据库
show databases;

运行结果如下:

2.3、选择数据库

概述:当我们需要对某个数据库进行新建表等操作时,需要选择指定的数据库后再进行操作。

语法

USE 数据库名;

实操:新建一个名为mydatabase的数据库并选择该数据库进行操作。

-- 创建一个名为mydatabase的数据库
create database mydatabase;

--查看数据库
show databases;

-- 选择新建的数据库进行操作
use mydatabase;

运行结果如下:

3、MySQL中的数据类型

3.1、整数类型

MySQL数据类型含义(有符号)
tinyint(m)1个字节 范围(-128~127)
smallint(m)2个字节 范围(-32768~32767)
mediumint(m)3个字节 范围(-8388608~8388607)
int(m)4个字节 范围(-2147483648~2147483647)
bigint(m)8个字节 范围(+-9.22*10的18次方)

注意:数值类型中的 m 是指显示长度,并不表示存储长度,只有字段指定 zerofill 时有用。

例如,int(3) ,存储的实际值为2时 (并且列指定了 zerofill) ,查询结果就是 002 ,左边用 0 来 填充。

3.2、浮点类型

MySQL数据类型含义
float(m,d)单精度浮点型 8位精度(4字节) 
double(m,d)双精度浮点型 16位精度(8字节) 

注意:float和double都需要指定两个参数,第一个参数m表示浮点数的总位数(不包含小数点),第二个参数d表示小数点后的位数。

3.3、字符类型

MySQL数据类型含义
char(n)固定长度,最多255个字符
tinytext可变长度,最多255个字符
varchar(n)可变长度,最多65535个字符
text可变长度,最多65535个字符
mediumtext可变长度,最多2的24次方-1个字符
longtext可变长度,最多2的32次方-1个字符

注意:①、char和varchar在指定时都需要传入一个参数n,char传入的n表示字符的总长度,当字符长度不够时会用空格去补齐;而varchar传入的n表示字符的最大长度,当字符长度不够时并不会用空格去补齐。

②、char的长度固定,因此适合指定身份证号,手机号等固定长度的数据。

③、varchar可以设置最大长度,长度可变,因此适合指定长度可变的属性。

④、text不设置长度,当不知道属性最大长度时推荐使用text。

⑤、按照查询速度: char最快, varchar次之,text最慢。

3.4、日期类型

MySQL数据类型含义
date日期 YYYY-MM-DD
time时间 HH:MM:SS
datetime日期时间 YYYY-MM-DD HH:MM:SS
timestamp时间戳YYYYMMDD HHMMSS

3.5、二进制数据(BLOB)

概述

①、BLOB和TEXT存储方式不同,TEXT以文本方式存储,英文存储区分大小写,而Blob是以二进制方式存储,不分大小写。

②、BLOB存储的数据只能整体读出。

③、TEXT可以指定字符集,BLOB不用指定字符集。

4、表操作

4.1、创建表

语法

CREATE TABLE 表名(列名 类型,列名 类型......);

查看已经创建的表

show tables;

实操:在数据库mydatabase中新建一个名为student的表,表包含学号(id),姓名(name)和成绩信息(score)字段。

-- 选择mydatabase数据库进行操作
use mydatabase;

-- 创建一个名为student的表并指定字段及其对应的类型
create table student(id int(4),name varchar(8),score float(5,2));

-- 查看student表是否创建成功
show tables;

运行结果如下:

4.2、删除表

语法

DROP TABLE 表名;

实操:删除mydatabase数据库下名为student的表。

-- 选择数据库
use mydatabase;

-- 删除指定的student表
drop table student;

-- 查看mydatabase数据库下所有的表
show tables;

运行结果如下:

4.3、修改表

4.3.1、修改表名

语法

ALTER TABLE 旧表名 RENAME 新表名;

实操:在数据库mydatabase中新建一个名为student的表,表包含学号,姓名和成绩信息,然后将表名改为student_info。

-- 选择名为mydatabase的数据库进行操作
use mydatabase;

-- 创建名为student的表并指定字段及其类型
create table student(id int(8),name varchar(8),score float(5,2));

-- 查看student表是否创建成功
show tables;

-- 将student表改名为student_info
alter tabel student rename student_info;

-- 查看表名是否修改成功
show tables;

运行结果如下:

4.3.2、修改列名

语法

ALTER TABLE 表名 CHANGE COLUMN 旧列名 新列名 类型;

注意:修改列名时,类型不能省略。

实操1:将mydatabae数据库下student表中name列的列名修改为name_info。

-- 选择mydatabase数据库进行操作
use mydatabase;

-- 将student_info表中的name列的列名修改为name_info,且将类型指定为varchar(10)
alter table student_info change column name name_info varchar(10);

-- 查看修改后的表结构
DESC student_info;

运行结果如下:

实操2:使用navicat对列名进行修改。

然后直接对表进行修改,修改完点击保存即可。

4.3.3、修改列类型

语法

ALTER TABLE 表名 MODIFY 列名 新类型;

实操:将mydatabase数据库下的student_info表中name_info字段的类型修改为char,且长度为6。

-- 选择mydatabase数据库
use mydatabase;

-- 修改student_info表下name_info列的类型为char且长度为6
alter table student_info modify name_info char(6);

-- 查看student_info的表结构
DESC student_info;

运行结果如下:

提示:navicat修改列类型的方法和修改列名的方法一样,可以参考上一小节navicat修改列名的方法。

4.3.4、添加新列

语法

ALTER TABLE 表名 ADD COLUMN 新列名 类型;

实操:为mydatabase数据库下的student_info表添加一个新字段sex,类型为tinyint,显示长度为3。

-- 选择mydatabase数据库
use mydatabase;

-- 为student_info表添加sex列并设置类型为tinyint且显示长度为3
alter table student_info add column sex tinyint(3);

-- 查看student_info的表结构
DESC student_info;

提示:navicat中添加新列的操作任然在表设计中,通过上下左右方向键移动光标到最后一行就可以进行新列的添加操作,操作完成后记得保存。

4.3.5、删除指定列

语法

ALTER TABLE 表名 DROP COLUMN 列名;

实操:删除mydatabase数据库中student_info表中sex列。

-- 选择mydatabase数据库
use mydatabase;

-- 删除student_info表中的sex列
alter table student_info drop column sex;

-- 查看student_info的表结构
desc student_info;

5、MySQL中的约束

概述:数据库约束是对表中的数据进行进一步的限制,保证数据的正确性、有效性和完整性。

主键约束(PK)

主键约束是使用最频繁的约束。在设计数据表时,一般情况下,都会要求表中设置一个主键。 主键是表的一个特殊字段,该字段能唯一标识该表中的每条信息。例如,学生信息表中的学号是唯一的。

外键约束(FK)

外键约束经常和主键约束一起使用,用来确保数据的一致性。

唯一性约束(Unique)

唯一约束与主键约束有一个相似的地方,就是它们都能够确保列的唯一性。与主键约束不同的是唯一约束在一个表中可以有多个,并且设置唯一约束的列允许有空值

非空约束(Not Null)

非空约束用来约束表中的字段不能为空。

检查约束(Check)

检查约束也叫用户自定义约束,是用来检查数据表中,字段值是否有效的一个手段,但目前 MySQL 数据库不支持检查约束。

5.1、添加主键约束(primary key)

单一主键:使用一个列作为主键列,当该列的值有重复时,则违反唯一约束。

联合主键:使用多个列作为主键列,当多个列的值都相同时,则违反唯一约束。

添加主键约束的语法

ALTER TABLE 表名 ADD PRIMARY KEY(列名[,列名,列名,...]);

实操:为mydatabase数据库中student_info表的id列添加主键约束。

-- 选择mydatabase数据库
use mydatabase;

-- 为student_info表中的id添加主键约束
alter table student_info add primary key(id);

-- 查看表结构
desc student_info;

MySQL的主键自增长

①、一个表中只能有一个列为自动增长。

②、自动增长的列的类型必须是整数类型。

③、自动增长只能添加到具备主键约束与唯一性约束的列上。

④、删除主键约束或唯一性约束,如果该列拥有自动增长能力,则需要先去掉自动增长然 后在删除约束。

添加主键自增长的语法

alter table 表名 modify 主键 类型 auto_increment;

实操:为mydatabase数据库中student_info表的id列添加主键自增长。

-- 选择mydatabase数据库
use mydatabase;

-- 为student_info表中的id添加主键自增长
alter table student_info modify id int(8) auto_increment;

-- 查看表结构
desc student_info;

5.2、删除主键约束

语法

ALTER TABLE 表名 DROP PRIMARY KEY;

注意:删除主键时,如果主键列具备自动增长能力,需要先去掉自动增长(通过改变列类型实现),然后再删除主键。

实操:通过命令删除mydatabase数据库下的student_info表中id列的自动增长及其主键约束。

-- 选择mydatabase数据库
use mydatabase;

-- 删除student_info表中id列的自动增长(通过前面学习的改变列类型实现)
alter table student_info modify id int(8);

-- 删除student_info表中id列的主键约束
alter table student_info drop primary key;

-- 查看表结构
desc student_info;

5.3、添加外键约束(foreign key)

语法

ALTER TABLE 表名 ADD CONSTRAINT 约束名 FOREIGN KEY(列名) REFERENCES 参照的表名(参照的列名);

注意:外键的约束名一般为 表名_fk 。

实操:在mydatabase数据库中新建一个名为class_info的表,并为其添加class_id,class_name和class_score字段,将class_id设置为主键并添加主键自增长,然后为student_info表添加一个新列取名为class,为class列添加外键约束指向class_info表中的class_id。

-- 选择mydatabase数据库进行操作
use mydatabase;

-- 创建class_info表并添加class_id,class_name和class_score字段并指定类型
create table class_info(class_id int(8),class_name varchar(10),class_score float(5,2));

-- 为class_info表的class_id添加主键
alter table class_info add primary key(class_id);

-- 为class_info表的class_id添加主键自增
alter table class_info modify class_id int(8) auto_increment;

-- 查看新建的class_info表结构
desc class_info;

-- 为student_info表添加class列
alter table student_info add column class;

-- 为student_info表的class列添加外键,指向class_info表中的class_id列
alter table student_info add constraint student_info_fk foreign key(class) references class_info(class_id);

在navicat中可以通过表设计 => 外键 查看表的外键:

此时就能看到指定表中所有的外键:

而通过navicat创建外链是一样的道理,先选择表 => 点击表设计 => 外键,参考上图的信息填入保存即可。

5.4、删除外键约束

语法

ALTER TABLE 表名 DROP FOREIGN KEY 约束名;

实操1:通过命令删除我们在上一章节中添加的外键。

-- 选择mydatabase数据库
use mydatabase;

-- 删除student_info表中class列的外键student_info_fk
alter table student_info drop foreign key student_info_fk;

实操2:通过navicat删除student_info表中class列的外键。

首先选择mydatabase数据库,再选择student_info表,然后点击表设计进入如下图所示界面,然后点击外键。

然后按如下图所示的步骤操作即可。

5.5、添加唯一性约束(Unique)

语法

ALTER TABLE 表名 ADD CONSTRAINT 约束名 UNIQUE(列名);

实操1:通过命令为mydatabase数据库中student_info表中的id列添加唯一性约束。

-- 选择mydatabase数据库
use mydatabase;

-- 为student_info表的id列添加唯一性约束
alter table student_info add constaint student_info_unique unique(id);

注意:唯一性约束允许有多个空值,但如果有内容就不能有相同值。

实操二:通过navicat为mydatabase数据库中student_info表中的id列添加唯一性约束。

5.6、删除唯一性约束

语法

ALTER TABLE 表名 DROP KEY 约束名;

实操1:删除mydatabase数据库中student_info表中id列的唯一性约束student_info_uk。

-- 选择mydatabase数据库
use mydatabase;

-- 删除student_info表中id列的唯一性约束student_info_uk
alter table student_info drop key student_info_uk;

实操2:通过navicat实现实操1同样的效果。

5.7、添加非空约束

语法

ALTER TABLE 表名 MODIFY 列名 类型 NOT NULL;

实操1:为mydatabase数据库下的student_info表中的id列设置非空约束。

-- 选择mydatabase数据库
use mydatabase;

-- 为student_info表的id列添加非空约束(通过更改列类型时在结尾添加not null实现)
alter table student_info modify id int(8) not null;

实操2:通过navicat为mydatabase数据库下的student_info表中的id列设置非空约束。

5.8、删除非空约束

语法

ALTER TABLE 表名 MODIFY 列名 类型 NULL;

实操:通过命令将mydatabase数据库下student_info表中id列的非空约束删除。

-- 选择mydatabase数据库
use mydatabase;

-- 移除student_info表中id列的非空约束
alter table student_info modify id int(8) null;

5.9、创建表时添加约束

查看表中约束信息的语法

SHOW KEYS FROM 表名;

实操:使用命令在创建表时实现添加约束,在mydatabase数据库下新建一个名为student_parent_info的表,包含id,name和tel列,其中,将id列设置为主键并添加主键自增,name列添加非空约束,tel列添加唯一性约束。

-- 选择mydatabase数据库
use mydatabase;

-- 按要求新建student_parent_info表
create table student_parent_info(id int(8) primary key auto_increment,name varchar(6) not null,tel varchar(11) unique);

-- 查看新建的student_parent_info表中的约束信息
show keys from student_parent_info;

6、MySQL中的DML操作

概述:在前面的章节中使用DDL语言创建、修改和删除了数据库及其包含的表,在第六章中我们将使用DML语言用于添加,修改和删除表中的数据。

6.1、添加数据

6.1.1、选择插入(向指定列中添加数据)

语法

INSERT INTO 表名(列名 1 ,列名 2 ,列名 3.....) VALUES(值 1 ,值 2 ,值 3......);

实操:使用选择插入向mydatabase数据库下的student_parent_info表中添加一行数据,name为muxi,tel为12345678。(由于id列设置了主键自增所以不用自己插入)

-- 选择mydatabase数据库进行操作
use mydatabase;

-- 为student_parent_info表中插入题目要求的数据
insert into student_parent_info(name,tel) values("muxi","12345678")

6.1.2、完全插入

语法

INSERT INTO 表名 VALUES(值 1 ,值 2 ,值 3......);

注意如果主键是自动增长,需要使用 default 或 null 或 0 占位

实操:使用完全插入向mydatabase数据库下student_parent_info表中插入一条输入,name为shiye,tel为120。

-- 选择mydatabase数据库
use mydatabase;

-- 向student_parent_info表中插入题目指定的数据
insert into student_parent_info values(default,"shiye","120");

此时通过navicat可以查看数据库中student_parent_info表的内容如下:

6.2、默认值处理

概述:在 MySQL 中可以使用 DEFAULT 为列设定一个默认值。如果在插入数据时并未指定该列的值,那么 MySQL 会将默认值添加到该列中。

6.2.1、创建表时添加默认值

语法

CREATE TABLE 表名(列名 类型 default 默认值,......);

实操:在mydatabase数据库下创建一个名为student_default_test表,表中包含id,name,tel字段,将id设置为主键并添加主键自增,为name字段添加非空约束,为tel字段添加唯一性约束,并为每个字段添加一个默认值。

-- 选择要操作的数据库
use mydatabase;

--新建student_default_info表并添加题目要求的字段和条件及其默认值
create table student_default_info(id int(8) primary key auto_increment,name varchar(20) not null default "muxishiye",tel varchar(11) unique default "110");

注意:不要为设置了自增长的列添加默认值,否则会报错。

6.2.2、修改表添加新列并指定默认值

语法

ALTER TABLE 表名 ADD COLUMN 列名 类型 DEFAULT 默认值;

实操:为student_default_test表添加名为age的新列,默认值为18。

-- 选择要操作的数据库
use mydatabase;

-- 为student_default_test表新建age列并给一个默认值为18
alter table student_default_test add column age int(3) default 18;

6.2.3、插入数据时默认值的处理

概述:如果在插入数据时并未指定该列的值,那么MySQL 会将默认值添加到该列中。如果是 完全项插入需要使用 default 来占位。

实操1:使用选择插入的方式为student_default_test表中插入一条数据,并且数据采用默认值(表中拥有id,name,age,tel字段,其中只有id是主键自增没有默认值,其他字段都有默认值)。

-- 选择数据库
use mydatabase;

-- 插入一条数据(由于每个字段都有默认值,且主键自增的字段不需要赋值,所以不需要指定列)
insert into student_default_test() values();

通过navicat查看此时的student_default_test表内容如下:

实操2:使用完全插入的方式为student_default_test表中插入一条数据,并且数据除tel列传入119外其他字段采用默认值(因为在建student_default_test表时为tel列添加了唯一性约束)。

-- 选择数据库
use mydatabase;

-- 按题目要求插入数据
insert into student_default_test values(0,default,"119",default);

通过navicat查看此时的student_default_test表内容如下:

6.3、更新数据

语法

UPDATE 表名 SET 列名=值,列名=值 WHERE 条件;

注意:如果省略了where 条件 ,那么将更改表中所有指定列的值,因此更新语句中一般都要给定更新条件。

实操:将student_default_test表中第一个tel的值修改为123321。

update student_default_test set tel=123321 where id=1;

通过navicat查看此时的student_default_test表内容如下:

6.4、删除数据

语法

DELETE FROM 表名 WHERE 条件;

注意:在DELETE语句中,如果没有给定删除条件则会删除表中的所有数据。

实操1:删除student_default_test表中第一行数据。

delete from student_default_test where id=1;

通过navicat查看此时的student_default_test表内容如下:

实操2:删除student_default_test表中所有的数据。

delete from student_default_test;

通过navicat查看此时的student_default_test表内容如下:

利用TRUNCATE清空表的语法

TRUNCATE TABLE 表名;

实操3:为student_default_test表添加任意数据,然后利用TRUNCATE清空表中的数据。

-- 为表中插入数据 
insert into student_default_test values(0,default,default,default);
insert into student_default_test values(null,default,123321,default);
insert into student_default_test values(default,default,456789,default);

通过navicat查看此时的student_default_test表内容如下:

-- 清空表
truncate table student_default_test;

通过navicat查看此时的student_default_test表内容如下:

清空表时DELETE与 TRUNCATE 区别

①、truncate 是整体删除(速度较快), delete 是逐条删除(速度较慢);

②、truncate 不写服务器日志,delete 写服务器日志,这就是 truncate 效率比 delete 高的原因;

③、truncate 是会重置自增值,相当于自增列会被置为初始值,又重新从 1 开始记录,而不是接着原来的值。而 delete 删除以后, 自增值仍然会继续累加。

7、MySQL查询数据

7.1、select基本查询介绍

概述:SELECT 语句从数据库中返回信息。使用一个 SELECT 语句,可以进行如下操作:

①、列选择:能够使用 SELECT 语句的列选择功能选择表中的列,这些列是想要用查询返回的。当查询时,能够返回列中的数据。

②、行选择:能够使用 SELECT 语句的行选择功能选择表中的行,这些行是想要用查询返回的。能够使用不同的标准限制看见的行。

③、连接:能够使用 SELECT 语句的连接功能来集合数据,这些数据被存储在不同的表中,在它们之间可以创建连接,查询出我们所关心的数据。

语法

-- SELECT用于指定列
-- FROM用于指定表

SELECT *|{[DISTINCT] column | expression [alias],...} FROM table;

注意:SELECT语句至少要包括一个SELECT子句用于指定被显示的列和一个FROM子句用于指定表(指定的表中一定要有SELECT子句指定的字段)。

语法解析

语句含义
SELECT是一个或多个字段的列表
*选择所有的列
DISTINCT禁止重复
column | expression选择指定的字段(列)或表达式
alias给所选择的列不同的标题
FROM table指定包含列的表

7.2、查询中的列选择

7.2.1、选择所有列

语法

SELECT * FROM 表名;

实操:首先通过命令在mydatabase数据库中创建名为query_test的表,表中包含id,name ,age,tel字段,并添加多行数据,然后通过navicat查询query_test表中所有列的数据。

---------------
-- 首先准备数据
---------------
-- 选择数据库
use mydatabase;

-- 创建名为query_test的表并添加题目要求的字段(仔细观察命令,命令中为name和age添加了默认值)
create table query_test(id int(8) primary key auto_increment,name varchar(20) not null, age int(3) not null,tel varchar(11) unique)

-- 为query_test表添加数据
insert into values(0,default,default,"123321");
insert into values(0,"zhangsan",21,"1387429");
insert into values(0,"李四",22,"1522863351");
insert into values(null,"王五",24,"110");
insert into values(default,"赵六",19,"122");

通过navicat查看query_tset表中的数据为:

接下来通过navicat进行查询,按下图步骤进入查询窗口:

在新窗口按如下步骤操作即可进行查询:

输入命令如下:

SELECT * FROM query_test;

7.2.2、选择指定列

语法

SELECT 列名1[,列名2,...] from 表名;

实操:通过SELECT命令查询query_test表中name和tel列的信息。

命令如下:

SELECT name,tel FROM query_test;

运行结果如下:

7.3、查询中的算术表达式

概述:需要修改数据显示方式,如执行计算,或者作假定推测,这些都可能用到算术表达式。一个算术表达式可以包含列名、固定的数字值和算术运算符。

实操:通过navicat的查询query_test表中的name和age列的信息,并且我想得到所有人五年后的年龄。

SELECT name,age,age+10 FROM query_test;

运行结果如下:

7.3.1、运算符的优先级

概述:如果算术表达式包含有一个以上的运算,先计算乘除,再运算加减。如果在一个表达式中的运算符优先级相同,计算从左到右进行。可以用圆括号强制其中的表达式先计算。

实操:为query_test表添加新列,列名为money,并为其填充一定数值作为每月零花钱,现已知表中的所有人每月能额外获得20元的零花钱,请通过navicat查询query_test表中name及其每个人每年的总零花钱(将表中的零花钱加上额外获得的20元乘以12即可)。

-- 为query_test表添加名为money的新列
alter table query_test add column money int(12);

-- 将money列的值都设置为100
update query_test set money=100;

-- 修改指定列的money值
update query_test set money=120 where id=3;
update query_test set money=80 where id=5;

此时的query_test表内容如下:

通过如下命令查询:

SELECT name,12*(money+20) FROM query_test;

运行结果如下:

7.4、MySQL中定义空值

概述:①、如果一行中的某个列缺少数据值,该值被置为 null, 或者说包含一个空。

②、空是一个难以获得的、未分配的、未知的,或不适用的值。空和 0 或者空格不相同。 0 是一个数字,而空格是一个字符。

③、包含空值的算术表达式结果也为null。

实操:设置query_test表的money列允许为空,且第一行money字段的值修改为空值,然后计算第一行每年的零花钱(money列代表每月零花钱)

首先修改列的类型为允许空:

alter table query_test modify money int(6) null;

然后修改query_test表中第一行money列的值:

-- 修改query_test表中第一行money列的值为空
update query_test set money=null where id=1;

通过navicat查看query_test表的内容如下,已经正确的将第一行money列的值修改为空:

接下来通过navicat的查询工具获得第一行的name列和年零花钱数:

SELECT name,money*12 FROM query_test WHERE id=1;

运行结果如下,报错说name,money列中存在未知列,因此也印证了包含空值的算术表达式结果也为空值:

7.5、MySQL中的别名

7.5.1、列别名

语法

-- 两种方式都可以实现
-------------------------------------------
SELECT 列名 AS 列别名 FROM 表名 WHERE 条件;
--------------------------------------------
SELECT 列名 列别名 FROM 表名 WHERE 条件;

实操:将query_test表中money列的别名设置为RMB。

-- 选择数据库
use mydatabase;

-- 为money列设置别名
select money as RMB from query_test;

7.5.2、表别名

语法

-- 和列别名一样,表别名中的as可省略
SELECT [表别名.]列名 [as 列别名] FROM 表名 [as] 表别名 WHERE 条件;

实操:将query_test表的别名设置为query_table,并且将表中name列的别名设置为student_name

select name as student_name from query_test as query_table;

运行结果如下:

7.6、MySQL去重

概述:SELECT语句默认显示所有行,包括相同的行,如果不需要显示相同行的内容,需要在SELECT语句中加入DISTINCT实现去重。

语法

SELECT DISTINCT 列名 FROM 表名;

下图是目前query_test表的数据:

实操1:通过查询语句返回money列去重后的结果。

SELECT DISTINCT money FROM query_test;

运行结果如下:

实操2:为了更好地理解DISTINCT的作用,我们在query_test表中插入一条数据,要求与第二行的name和money列的值相同,然后通过查询语句返回name和money列去重后的结果。

-- 插入数据
insert into query_test values(0,"张三",21,"120",100);

此时的query_test表内容如下:

-- 查询query_test表中name和money列组合的去重结果
SELECT DISTINCT name,money FROM query_test;

查询结果如下:

实操2总结:通过上面的例子可以发现,当查询多个列组合去重的结果时,只有两列的内容完全一样时才会被去除,单列相同并不会被去除。

7.7、查询中的行选择

语法

-- 投影列就是选择的列,允许有多个
SELECT * | 投影列 FROM 表名 WHERE 选择条件;

query_test表数据如下:

实操:通过查询语句获取每月零花钱大于或等于100学生的姓名和联系电话(money列代表每月零花钱)。

SELECT name,tel FROM query_test WHERE money>=100;

运行结果如下:

7.8、MySQL的比较条件

7.8.1、主要的比较运算符

运算符含义
=等于
>大于
<小于
>=大于等于
<=小于等于
<> 或 !=不等于

此时query_test表的数据如下:

实操:查询query_test表中年龄小于等于20岁的年龄和联系方法。

-- 根据题目进行查询
SELECT name,tel FROM query_test WHERE age<=20;

运行结果如下:

7.8.2、比较运算符补充

操作含义
BETWEEN...AND...在两个值之间进行比较(包括这两个值)
IN(LIST)匹配一个任意值列表(即括号里可以放多个值)
LIKE匹配一个字符模板
IS NULL是一个空值

当前query_test表数据如下:

实操1:查询query_test表中年龄大于等于20,小于等于22的学生姓名和联系电话。

SELECT name,tel FROM query_test WHERE age BETWEEN 20 AND 22;

运行结果如下:

实操2:查询query_test表中年龄为19,21,23岁的姓名,年龄和电话信息。

SELECT name,age,tel FROM query_test WHERE age IN(19,21,23);

运行结果如下:

知识点补充:LIKE只能对字符串做条件判断,LIKE常用于字符串的模糊查询,其中%代表0个或多个字符,_表示一个字符

实操3:查询query_test表name列中以字母m开头的学生姓名及其对应的年龄和电话。

SELECT name,age,tel FROM query_test WHERE name like "m%" or name like "m_";

运行结果如下:

实操4:查询query_test表中第一行的money列是否为空值,返回查询的name和money信息。

SELECT name,money FROM query_test WHERE money IS NULL AND id=1;

运行结果如下:

7.9、MySQL中的逻辑条件

运算含义
AND两部分都为真,返回true
OR两部分有一部分为真,返回true
NOT如果返回的条件为假,则返回True,否则反之

概述:可以在 WHERE 子句中用 AND 和 OR 运算符使用多个条件。

query_test表数据如下:

实操:查询query_test表中电话号以12开头并且每月零花钱在100以上(包括100)的name,age,tel和money列信息(如果无法理解可以先看7.10小节再来看这个实操)。

-- 首先要排除空值,然后再对照字符串是否以12开头(后面跟一个或多个字符都满足),最后看money是否大于等于100
SELECT name,age,tel,money 
FROM query_test 
WHERE NOT money IS NULL AND (tel LIKE "12%" OR tel LIKE "12_") AND money>=100;

运行结果如下:

7.10、MySQL中的优先规则

优先顺序
1算术运算
2连字操作
3比较操作
4IS [NOT] NULL,LIKE,[NOT] IN
5[NOT] BETWEEN
6NOT 逻辑条件
7AND 逻辑条件
8OR 逻辑条件

注意:可以使用圆括号改变优先规则(圆括号优先级最高)

7.11、使用ORDER BY子句排序

7.11.1、单列排序

语法

SELECT * | 投影列 FROM 表名 ORDER BY 需要排序的列名 参数;

----------------------- 参数如下------------------------
ASC:默认选项, 升序排序
DESC: 降序排序

注意:ORDER BY子句一定要放在SELECT语句的最后。

SELECT语句执行子句的顺序

①、FROM子句

②、WHERE子句

③、SELECT子句

④、ORDER BY子句

query_test的数据如下:

实操:将query_test表中的age按降序进行排序,返回表中的name,age,tel和money列的信息。

SELECT name,age,tel,money FROM query_test ORDER BY age DESC;

运行结果如下:

提示:如果需要排序的列有别名,ORDER BY 后面也可以跟别名进行排序,实现效果一样。

7.11.2、多列排序

概述:当数据量较大时,单个列中容易出现重复数据,如果我们还有其它列作为排序的参考就是多列排序。(当第一个列出现相同值时就对第二个列的数据进行排序,以此类推)

语法

SELECT * | 投影列 FROM 表名 ORDER BY 列名1 参数,列名2 参数[,列名3 参数,...];

query_test表数据如下(这里通过navicat将表数据做了修改):

实操:将query_test表中的age按降序进行排序,如果有相同值,相同值再按money列的值进行升序排序,返回表中的name,age,tel和money列的信息。

SELECT name,age,tel,money FROM query_test ORDER BY age DESC, money ASC;

运行结果如下:

8、SQL函数

概述:函数是 SQL 的一个非常强有力的特性,函数能够用于下面的目的:

①、执行数据计算

②、修改单个数据项

③、操作输出进行行分组

④、格式化显示日期时间和数字

⑤、转换列数据类型

注意:SQL 函数必须有输入参数,并且一定会有一个返回值。

函数分类

①、单行函数:对单个行进行运算,并且每行返回一个结果。(常见的函数类型:字符,数字,日期,转换)

②、多行函数:操纵成组的行,每个行组给出一个结果,这些函数也被称为组函数。

8.1、初识单行函数

概述:单行函数用于操作数据项,接收多个参数并返回一个结果,作用于每一个返回行并且每行返回一个结果,支持数据类型的修改和嵌套(参数可以是一个列或一个表达式)。

语法

函数名 [(参数1,参数2,参数3,...)]

单行函数分类:字符函数、数字函数、日期函数、转换函数,通用函数。

8.2、字符函数

 概述:字符函数分为大小写函数和字符处理函数。

8.2.1、大小写处理函数

函数描述实例
LOWER(s) 或 LCASE(s)将字符串 s 转换为小写将字符串 OLDLU转换为小写:SELECT LOWER("OLDLU"); -- oldlu
UPPER(s) 或UCASE(s)将字符串s转换为大写将字符串 oldlu转换为大写:SELECT UPPER("oldlu"); -- OLDLU

query_test表数据如下:

实操:对query_test表的name列数据进行升序排序,要求返回的name列数据中包含字母时需将字母转换为大写,并且为返回列设置一个别名。

SELECT UPPER(name) as rename_upper_name FROM query_test ORDER BY name;

运行结果如下:

8.2.2、字符处理函数

函数描述实例
LENGTH(s)返回字符串 s 的长度返回字符串oldlu的字符数SELECT LENGTH("study"); --5;
CONCAT(s1,s2...sn)字符串 s1,s2 等多个字符串合并为一个字符串合并多个字符串SELECT CONCAT("keep", "study", "habits"); --keepstudyhabits;
LPAD(s1,len,s2)在字符串 s1 的开始处填充字符串 s2,使字符串长度达到 len将字符串 x 填充到study字符串的开始处:SELECT LPAD('study',8,'x'); --xxxstudy
LTRIM(s)去掉字符串 s 开始处的空格去掉字符串study开始处的空格:SELECT LTRIM(" study") ;--study
REPLACE(s,s1,s2)将字符串 s2 替代字符串 s 中的字符串 s1将字符串study中的字符s替换为字符 S:SELECT REPLACE('study','s','S'); --Study
REVERSE(s)将字符串s的顺序反过来将字符串 abc 的顺序反过来:SELECT REVERSE('abc'); --cba
RPAD(s1,len,s2)在字符串 s1 的结尾处添加字符串 s2,使字符串的长度达到 len将字符串感叹号填充到study字符串的结尾处:SELECT RPAD('study',8,'!'); --study!!!
RTRIM(s)去掉字符串 s 结尾处的空格去掉字符串study的末尾空格:SELECT RTRIM("study "); --study
SUBSTR(s, start, length)从字符串 s 的 start 位置截取长度为 length 的子字符串从字符串study中的第 2 个位置截取2个字符:SELECT SUBSTR("study",2,2); --tu
SUBSTRING(s, start, length)从字符串 s 的 start 位置截取长度为 length 的子字符串从字符串study中的第 2个位置截取 2个字符:SELECT SUBSTRING("study",2,2); --tu
TRIM(s)去掉字符串 s 开始和结尾处的空格去掉字符串study的首尾空格:SELECT TRIM(' study ');--study

8.3、数字函数

函数名描述实例
ABS(x)返回 x 的绝对值返回 -1 的绝对值:SELECT ABS(-1) --返回1
ACOS(x)求 x 的反余弦值(参数是弧度)SELECT ACOS(0.25);
ASIN(x)求反正弦值(参数是弧度)SELECT ASIN(0.25);
ATAN(x)求反正切值(参数是弧度)SELECT ATAN(2.5);
ATAN2(n, m)求反正切值(参数是弧度)SELECT ATAN2(-0.8, 2);
AVG(expression)返回一个表达式的平均值,expression 是一个字段返回 Products 表中Price 字段的平均值:SELECT AVG(Price) AS AveragePrice FROM Products;
CEIL(x)返回大于或等于 x 的最小整数SELECT CEIL(1.5) -- 返回2
CEILING(x)返回大于或等于 x 的最小整数SELECT CEILING(1.5); -- 返回2
COS(x)求余弦值(参数是弧度)SELECT COS(2);
COT(x)求余切值(参数是弧度)SELECT COT(6);
COUNT(expression)返回查询的记录总数,expression 参数是一个字段或者 * 号返回 Products 表中 products 字段总共有多少条记录:SELECT COUNT(ProductID) AS NumberOfProducts FROM Products;
DEGREES(x)将弧度转换为角度SELECT DEGREES(3.1415926535898) -- 180
n DIV m整除,n 为被除数,m 为除数计算 10 除于 5:SELECT 10 DIV 5; -- 2
EXP(x)返回 e 的 x 次方计算 e 的三次方:SELECT EXP(3) -- 20.085536923188
FLOOR(x)返回小于或等于 x 的最大整数小于或等于 1.5 的整数:SELECT FLOOR(1.5) -- 返回1
GREATEST(expr1, expr2, expr3, ...)返回列表中的最大值返回以下数字列表中的最大值:SELECT GREATEST(3, 12, 34, 8, 25); -- 34返回以下字符串列表中的最大值:SELECT GREATEST("Google", "Runoob", "Apple"); -- Runoob
LEAST(expr1, expr2, expr3, ...)返回列表中的最小值返回以下数字列表中的最小值:SELECT LEAST(3, 12, 34, 8, 25); -- 3返回以下字符串列表中的最小值:SELECT LEAST("Google", "Runoob", "Apple"); -- Apple
LN返回数字的自然对数,以 e 为底。返回 2 的自然对数:SELECT LN(2); -- 0.6931471805599453
LOG(x) 或 LOG(base, x)返回自然对数(以 e 为底的对数),如果带有 base 参数,则 base 为指定带底数。SELECT LOG(20.085536923188) -- 3 SELECT LOG(2, 4); -- 2
LOG10(x)返回以 10 为底的对数SELECT LOG10(100) -- 2
LOG2(x)返回以 2 为底的对数返回以 2 为底 6 的对数:SELECT LOG2(6); -- 2.584962500721156
MAX(expression)返回字段 expression 中的最大值返回数据表 Products 中字段 Price 的最大值:SELECT MAX(Price) AS LargestPrice FROM Products;
MIN(expression)返回字段 expression 中的最小值返回数据表 Products 中字段 Price 的最小值:SELECT MIN(Price) AS MinPrice FROM Products;
MOD(x,y)返回 x 除以 y 以后的余数5 除于 2 的余数:SELECT MOD(5,2) -- 1
PI()返回圆周率(3.141593)SELECT PI() --3.141593
POW(x,y)返回 x 的 y 次方2 的 3 次方:SELECT POW(2,3) -- 8
POWER(x,y)返回 x 的 y 次方2 的 3 次方:SELECT POWER(2,3) -- 8
RADIANS(x)将角度转换为弧度180 度转换为弧度:SELECT RADIANS(180) -- 3.1415926535898
RAND()返回 0 到 1 的随机数SELECT RAND() --0.93099315644334
ROUND(x)返回离 x 最近的整数SELECT ROUND(1.23456) --1
SIGN(x)返回 x 的符号,x 是负数、0、正数分别返回 -1、0 和 1SELECT SIGN(-10) -- (-1)
SIN(x)求正弦值(参数是弧度)SELECT SIN(RADIANS(30)) -- 0.5
SQRT(x)返回x的平方根25 的平方根:SELECT SQRT(25) -- 5
SUM(expression)返回指定字段的总和计算 OrderDetails 表中字段 Quantity 的总和:SELECT SUM(Quantity) AS TotalItemsOrdered FROM OrderDetails;
TAN(x)求正切值(参数是弧度)SELECT TAN(1.75); -- -5.52037992250933
TRUNCATE(x,y)返回数值 x 保留到小数点后 y 位的值(与 ROUND 最大的区别是不会进行四舍五入)SELECT TRUNCATE(1.23456,3) -- 1.234

8.3.1、ROUND(column|expressionn函数

概述:ROUND函数用于四舍五入列、表达式或者 n 位小数的值。能够接收两个参数,第一个参数为列,表达式或n位的数值,第二个参数用于确定对哪一位数进行四舍五入,如果是正数n就对小数点后第n位做四舍五入,如果是0或空缺就是对整数做四舍五入,如果是负数-n,就是对小数点从右往左数第n位做四舍五入。

实操:通过案例了解ROUND函数的三种情况(即第二个参数是正数,0/空缺,负数的情况)

SELECT ROUND(3.1415),ROUND(3.1415,3),ROUND(315.2425,-1)

运行结果如下:

8.3.2、TRUNCATE(column|expression,n) 函数

概述:TRUNCATE函数与ROUND函数很相像,区别一在于TRUNCATE函数用于清空处理(将指定的位数后面的值都置为0),而ROUND函数对指定位数的值做四舍五入的处理,区别二在于参数方面,TRUNCATE函数的第二个参数不能省略(省略会报错),而ROUND函数的第二个参数可以省略。

实操:通过案例了解TRUNCATE函数的三种情况(即第二个参数是正数,0,负数的情况)

SELECT TRUNCATE(3.1415,0),TRUNCATE(3.1415,3),TRUNCATE(315.2425,-2);

运行结果如下:

8.3.3、MOD(m,n) 函数

概述:MOD(m,n)用于计算m除以n的余数。

实操:通过案例秒懂MOD函数的作用。

SELECT MOD(15,7),MOD(-15,7),MOD(15,-7);

运行结果如下:

8.4、日期函数

概述:在MySQL中允许直接使用字符串表示日期,但是要求字符串的日期格式必须为:"YYYY-MM-DD HH:MI:SS" 或者" YYYY/MM/DD HH:MI:SS ";

函数名描述实例
CURDATE()返回当前日期SELECT CURDATE(); -> 2024-05-07
CURTIME()返回当前时间SELECT CURTIME(); -> 09:33:50
CURRENT_DATE()返回当前日期SELECT CURRENT_DATE(); -> 2024-05-07
CURRENT_TIME()返回当前时间SELECT CURRENT_TIME();-> 09:34:38
DATE()从日期或日期时间表达式中提取日期值SELECT DATE("2024-05-07"); -> 2024-05-07
DATEDIFF(d1,d2)计算日期 d1->d2 之间相隔的天数SELECT DATEDIFF("2024-05-07","2024-10-01") -> -147
DAY(d)返回日期值 d 的日期部分SELECT DAY("2024-05-07"); -> 7
DAYNAME(d)返回日期 d 是星期几,如 Monday,TuesdaySELECT DAYNAME("2024-05-07 09:33:50") ->Tuesday
DAYOFMONTH(d)计算日期 d 是本月的第几天SELECT DAYOFMONTH("2024-05-07 09:33:50") ->7
DAYOFWEEK(d)日期 d 今天是星期几,1 星期日,2 星期一,以此类推SELECT DAYOFWEEK("2024-05-07 09:33:50") ->3
DAYOFYEAR(d)计算日期 d 是本年的第几天SELECT DAYOFYEAR("2024-05-07 09:33:50") ->128
HOUR(t)返回 t 中的小时值SELECT HOUR("2024-05-07 09:33:50") -> 9
LAST_DAY(d)返回给给定日期的那一月份的最后一天SELECT LAST_DAY("2024-05-07 09:33:50"); -> 2024-05-31
MONTHNAME(d)返回日期当中的月份名称,如 NovemberSELECT MONTHNAME("2024-05-07 09:33:50") -> May
MONTH(d)返回日期d中的月份值,1 到 12SELECT MONTH("2024-05-07 09:33:50") ->5
NOW()返回当前日期和时间SELECT NOW() -> 2024-05-07 09:45:07
SECOND(t)返回 t 中的秒钟值SELECT SECOND("2024-05-07 09:45:07") -> 7
SYSDATE()返回当前日期和时间SELECT SYSDATE() -> 2024-05-07 09:46:25
TIMEDIFF(time1, time2)计算时间差值SELECT TIMEDIFF("2024-05-07 09:33:50","2024-10-01 0:0:0"); -> -838:59:59
TO_DAYS(d)计算日期 d 距离 0000 年 1 月 1 日的天数SELECT TO_DAYS('0001-01-01 00:00:00') -> 366
WEEK(d)计算日期 d 是本年的第几个星期,范围是 0 到 53SELECT WEEK("2024-05-07 09:45:07") -> 18
WEEKDAY(d)日期 d 是星期几,0 表示星期一,1 表示星期二SELECT WEEKDAY("2024-05-07 09:45:07"); -> 1
WEEKOFYEAR(d)计算日期 d 是本年的第几个星期,范围是 0 到 53SELECT WEEKOFYEAR("2024-05-07 09:45:07") -> 19
YEAR(d)返回年份SELECT YEAR("2024-05-07 09:45:07"); -> 2024

8.5、转换函数

8.5.1、隐式数据转换

概述:隐式数据类型转换是指MySQL服务器能够自动地进行类型转换。如:可以将标准格式的字串日期自动转换为日期类型。(MySQL字符串日期格式为:‘YYYY-MM-DD HH:MI:SS’ 或 ‘YYYY/MM/DD HH:MI:SS’)

8.5.2、显式类型转换

概述:显式类型转换是依赖转换函数来完成相关类型的转换。

转换函数

DATE_FORMAT(date, 对照前面的date填入参数)  -- 将日期转换成字符串;

STR_TO_DATE(str, 对照前面的str填入参数) -- 将字符串转换成日期;

参数

格式描述
%a缩写星期名
%b缩写月名
%c月,数值
%D带有英文前缀的月中的天
%d月的天,数值(00-31)
%e月的天,数值(0-31)
%f微秒
%H小时(00-23)
%h小时(01-12)
%I小时(01-12)
%i分钟,数值(00-59)
%j年的天(001-366)
%k小时(0-23)
%l小时(1-12)
%M月名
%m月,数值(00-12)
%pAM或PM
%r时间,12小时(hh:mm:ss AM或PM)
%S秒(00-59)
%s秒(00-59)
%T时间,24小时(hh:mm:ss)
%U周(00-53) 星期日是一周的第一天
%u周(00-53) 星期一是一周的第一天
%V周(01-53) 星期日是一周的第一天,与%X一起使用
%v周(01-53) 星期一是一周的第一天,与%x一起使用
%W星期名
%w周的天(0代表星期日,6代表星期六)
%X年,星期日是一周的第一天,表示4位,与%V一起使用
%x年,星期一是一周的第一天,表示4位,与%v一起使用
%Y年,表示4位
%y年,表示2位

实操1:在mydatabase数据库下的class_info表中插入一个名为join_time的新列,然后插入多行数据。

student_info表字段如下:

--在navicat的查询界面执行如下操作
-- 首先选择数据库
use mydatabase;

-- 添加datetime类型的新列join_time
alter table class_info add column join_time datetime;

-- 查看student_info表的结构
desc student_info;

运行结果如下:

-- 插入一条class_info表中匹配的字段
INSERT INTO class_info VALUES(null,"muxi",98.3,STR_TO_DATE("2024年5月1日","%Y年%m月%d日"));

-- 打印class_info表的内容查看是否插入成功
SELECT * FROM class_info;

运行结果如下:

实操2:在实操1的基础上为class_info表插入一行数据,要求时间要精确到秒。

-- 插入带时分秒的时间
INSERT INTO class_info VALUES(null,"keqi",88.9,STR_TO_DATE("2024年5月2日12时15分20秒","%Y年%m月%d日%H时%i分%s秒"));

-- 查询数据是否插入成功
SELECT * FROM class_info;

运行结果如下:

实操3:查询class_info 表中class_name为keqi的日期,要求显示格式为 xxxx 年 xx 月 xx 日 xx时xx分xx秒。

SELECT DATE_FORMAT(start_time,"%Y年%m月%d日%H时%i分%s秒") AS "创建时间"  FROM class_info WHERE class_name="muxi";

运行结果如下:

8.6、通用函数

函数名描述实例
IF(expr,v1,v2)如果表达式 expr 成立,返回结果 v1;否则,返回结果 v2。SELECT IF(1 > 0,'正确','错误') ->正确
IFNULL(v1,v2)如果 v1 的值不为 NULL,则返回 v1,否则返回 v2。

SELECT IFNULL(null,'Hello Word')

->Hello Word

ISNULL(expression)判断表达式是否为 NULLSELECT ISNULL(NULL); ->1
NULLIF(expr1, expr2)比较两个参数是否相同,如果参数 expr1 与 expr2 相等 返回 NULL,否则返回 expr1

SELECT NULLIF(25, 25);

->

COALESCE(expr1, expr2, ...., expr_n)返回参数中的第一个非空表达式(从左向右)

SELECT COALESCE(NULL, NULL, NULL, 'muxikeqi', NULL, 'laoshe');

-> muxikeqi

CASE expression WHEN condition1 THEN result1 WHEN condition2 THEN result2 ... WHEN conditionN THEN resultN ELSE result ENDCASE 表示函数开始,END 表示函数结束。如果 condition1 成立,则返回 result1, 如果 condition2 成立,则返回 result2,当全部不成立则返回 result,而当有一个成立之后,后面的就不执行了。

SELECT CASE 'muxi' WHEN 'shiye' THEN 'SHIYE' WHEN 'admin' THEN 'ADMIN' ELSE 'Muxi' END;

-> Muxi

实操1:查询student_info表中年龄在20~23之间所有学生的姓名,年龄,电话和加入时间,为了保护隐私,在显示电话(tel)时,如果tel不为空就显示True,否则返回False。

student_info表字段及其数据如下:

-- 由于IF的第一个参数是表达式,用于返回true或false判断返回的值,所以第一个参数不能直接写tel
SELECT name,age,IF(ISNULL(tel),"False","True"),join_date 
FROM student_info 
WHERE age BETWEEN 20 AND 23;

运行结果如下:

实操2:查询student_info表中tel列的值,当长度大于1小于3时显示短号码,当长度大于4小于11时显示长号码,否则显示空号。

SELECT name,tel,
    CASE 
        WHEN LENGTH(tel) BETWEEN 1 AND 3 THEN '短号码'
        WHEN LENGTH(tel) BETWEEN 4 AND 11 THEN '长号码'
        ELSE '空号'
    END AS tel_category
FROM student_info;

运行结果如下:

8.7、单行函数练习

实操1:查询student_info表中加入时间(join_date)在2024年5月1日和5月2日之间学生的姓名,联系电话和加入时间,要求按加入时间进行降序排序。

student_info表字段及其数据如下:

SELECT name,tel,join_date 
FROM student_info 
WHERE join_date BETWEEN "2024-5-1-0-0-0" AND "2024/5/2/23/59/59" 
ORDER BY join_date DESC;

运行结果如下:

实操2:显示student_info表中所有2024年加入的学生姓名,电话和日期。

SELECT name,tel,join_date FROM student_info WHERE join_date like "2024%";

运行结果如下:

实操3:显示student_info表中所有以"a","m","l"开头的姓名及其电话和加入时间,并且按名字的首字母进行降序排序。

SELECT name,tel,join_date
FROM student_info
WHERE name LIKE "a%" OR name LIKE "m%" OR name LIKE "l%" 
ORDER BY name DESC;

运行结果如下:

9、多表查询

笛卡尔乘积:当一个连接条件无效或被遗漏时,其结果是一个笛卡尔乘积 ,其中所有行的组合都被显示。第一个表中的所有行连接到第二个表中的所有行。一个笛卡尔乘积会产生大量的行,其结果没有什么用。你应该在 WHERE 子句中始终包含一个有效的连接条件,除非你有特殊的需求,需要从所有表中组合所有的行。(口角:列相加,行相乘)

为了能够充分地理解SQL92中不同连接方式的含义,这里举例解释下:

下面是A表的内容:

姓名性别
张三
李四

下面是B表的内容:

姓名课程成绩
张三语文99
李四数学88

A表与B表的笛卡尔乘积为(A表中的每一行与B表中的每一行合并成一行):

姓名性别姓名课程成绩
张三张三语文99
张三李四数学88
李四张三语文99
李四李四数学88

A表与B表的等值连接为(当A.姓名 = B.姓名时):

姓名性别姓名课程成绩
张三张三语文99
李四李四数学88

A表与B表的自然连接(去掉重复属性的等值连接就是自然连接)为:

姓名性别课程成绩
张三语文99
李四数学88

多表查询分类:①、sql92标准:内连接(等值连接 、非等值连接 、 自连接)。

②、sql99标准:内连接、外连接(左外、右外、全外(MySQL不支持全外连接))、交叉连接。

9.1、SQL92标准中的查询

9.1.1、等值连接

等值连接的特定

①、多表等值连接的结果为多表的交集部分

②、n表连接,至少需要n-1个连接条件;

③、多表不分主次,没有顺序要求;

④、一般为表起别名,提高阅读性和性能;

⑤、可以搭配排序、分组、筛选….等子句使用;

提示:等值连接也被称为简单连接或内连接。

等值连接中限制不明确的列名

①、如果两个表中有相同的字段就容易含糊不清,此时就需要在 WHERE 子句中用表的名字限制列的名字以避免含糊不清。

②、如果列名在两个表之间不相同,就不需要限定列。但是,使用表前缀可以改善性能,因为MySQL服务器可以根据表前缀找到对应的列。

③、必须限定不明确的列名也适用于在其它子句中可能引起混淆的那些列,例如 SELECT子句或 ORDER BY 子句。

等值连接中的表别名

①、表别名不易过长,短一些更好。

②、表别名应该是有意义的。

③、别名只对当前的 SELECT 语句有效。

实操前说明

student_info表的字段及其数据如下(student_info表中不存在任何外键):

class_info表的字段及其数据如下:

实操1:由于student_info表的class字段与class_info表的class_id字段的类型一样,使用等值连接查询student_info表中name信息及其每个学生在class_info表中对应的班级名称。(利用等值连接连接两个表)

SELECT student_info.name,class_info.class_name 
FROM student_info,class_info 
WHERE student_info.class = class_info.class_id;

运行结果如下:

等值连接中的多表连接

概述:为了连接n个表,你最少需要n-1个连接条件,例如:现在有需求将三个表连接起来,此时至少需要写2个连接条件。

实操前准备:新建一个名为student_score_info的表,包含id字段和score字段,并为id列添加外键,连接到student_info表的id列,并插入几条数据。

首先创建id字段和score字段,并指定类型和长度:

然后插入数据保存即可:

实操2:使用等值连接查询student_info表中name信息及每个学生在class_info表中对应的班级名称和student_score_info表中的成绩信息,最后按班级信息进行降序排序。(利用等值连接连接三个表)

SELECT stu.name,ss.score ,c.class_name 
FROM student_info as stu,student_score_info as ss,class_info as c 
WHERE stu.id=ss.id AND stu.class=c.class_id  
ORDER BY c.class_name DESC; 

运行结果如下:

9.1.2、非等值连接

概述:一个非等值连接是一种不同于等值操作的连接条件,可以是大于,等于,不等于或在两个值之间。

实操前准备:首先通过命令创建一个名为salary_grades的表,包括lowest,highest和level字段,然后插入几条数据用于划定薪资等级。

-- 创建salary_grades表
create table salary_grades(lowest FLOAT(7,2),highest FLOAT(7,2),level CHAR(1));

-- 插入数据
INSERT INTO salary_grades VALUES(0,2999,"A");
INSERT INTO salary_grades VALUES(3000,5999,"B");
INSERT INTO salary_grades VALUES(6000,7999,"C");
INSERT INTO salary_grades VALUES(8000,11999,"D");
INSERT INTO salary_grades VALUES(12000,15000,"E");

创建后的表如下:

然后创建员工工资表取名为salary_info,包括姓名name,工资salary和部门section字段,然后插入

-- 创建salary_info表
CREATE TABLE salary_info(name varchar(20),salary FLOAT(7,2),section VARCHAR(10));

-- 插入数据
INSERT INTO salary_info VALUES("muxi","5699","1");
INSERT INTO salary_info VALUES("shiye","2699","1");
INSERT INTO salary_info VALUES("zywoo","12299","2");
INSERT INTO salary_info VALUES("simple","8699","2");
INSERT INTO salary_info VALUES("device","3699","3");
INSERT INTO salary_info VALUES("monisy","9499","3");
INSERT INTO salary_info VALUES("niko","14653","1");
INSERT INTO salary_info VALUES("jimy","7383","2");
INSERT INTO salary_info VALUES("flame","8467","3");
INSERT INTO salary_info VALUES("apex","6295","1");

创建后的表如下:

实操:查询上面创建的salary_info表中所有员工的薪资等级及其姓名和薪资信息,最后对薪资降序显示。(等值连接和非等值连接的区别就在于where后跟的条件是等于判断还是非等于判断)

SELECT salary_info.name ,salary_info.salary ,salary_grades.level 
FROM salary_info,salary_grades 
WHERE salary_info.salary BETWEEN salary_grades.lowest AND salary_grades.highest 
ORDER BY salary_info.salary DESC;

运行后的结果如下:

9.1.3、自连接

概述:自连接就是同一个表中不同列相互连接。

实操前准备:创建一个名为employes的员工表,包含所属经理工号,员工工号和员工工号对应姓名。

create TABLE employes (manager_id int(8),employes_id int(8),name varchar(20));

接下来插入十条数据:

INSERT INTO employes VALUES(10001,20001,"muxikeqi");
INSERT INTO employes VALUES(NULL,10001,"niko");
INSERT INTO employes VALUES(10002,20002,"zywoo");
INSERT INTO employes VALUES(10001,20003,"nertz");
INSERT INTO employes VALUES(10002,20004,"boot");
INSERT INTO employes VALUES(10002,20005,"dexter");
INSERT INTO employes VALUES(NULL,10002,"simple");
INSERT INTO employes VALUES(10001,20006,"hobbit");
INSERT INTO employes VALUES(10002,20007,"nexa");
INSERT INTO employes VALUES(10001,20008,"donk");

创建的表如下:

实操1:通过employes表查询每个经理管理的所有员工(利用表中的所属经理号匹配员工号)

SELECT manager.`name` "经理", worker.`name` "员工"
-- 这里的manager和worker的顺序要根据表中的左右顺序来定义,在上表中,经理号在前所以第一个定义经理号,第二个再定义员工号。
FROM employes manager ,employes worker
WHERE manager.employes_id = worker.manager_id ORDER BY manager.`name`;

运行结果如下:

实操2:查询muxikeqi的经理名。(在实操1的基础上再加上限定条件就行)

SELECT manager.`name` "经理", worker.`name` "员工"
-- 这里的manager和worker的顺序要根据表中的左右顺序来定义,在上表中,经理号在前所以第一个定义经理号,第二个再定义员工号。
FROM employes manager ,employes worker
WHERE manager.employes_id = worker.manager_id AND worker.`name`="muxikeqi" ORDER BY manager.`name`;

运行结果如下:

9.2、SQL99标准中的查询

概述:MySQL5.7 支持部分的SQL99 标准。

举例:这里用A表与B表举例说明什么是内连接,左外连接,什么是右外连接。

A表数据:

id姓名
1张三
2李四

B表数据:

id 学科成绩
2语文88
3数学99

A表为左表时,A表与B表的左外连接为(以两个表的id为连接条件):

id姓名id学科成绩
1张三nullnullnull
2李四2语文88

B表为右表时,A表与B表的右外连接为(以两个表的id为连接条件):

id学科成绩id姓名
2语文882李四
3数学nullnullnull

A表与B表的内连接为(以两个表的id为连接条件):

id姓名id学科成绩
2李四2语文88

9.2.1、交叉查询

概述:在sql92标准中,想要实现笛卡尔连接可以通过where后不给条件实现(a表有3条数据,b表有5条数据,笛卡尔连接后的结果为15条数据),而在sql99标准中通过CROSS JOIN子句导致两个表交叉相乘,交叉连接和两个表的笛卡尔乘积是一样的。

student表的字段及数据如下:

class_info表字段和数据如下:

实操:使用交叉连接查询student_info表和class_info表。

SELECT * FROM student_info CROSS JOIN class_info;

运行结果如下:

9.2.2、自然连接(NATURAL JOIN)

概述:连接只能发生在两个表中有相同名字和数据类型的列上。如果列有相同的名字,但

数据类型不同,NATURAL JOIN 语法会引起错误。

实操前准备:这是class_info表的字段,不包含任何外键:

以下是student_info表的字段,也不包含任何外键:

实操1:从上图可知,student_info表和class_info表都有相同字段class,要求使用自然连接查询student_info表中的name字段以及class_info表中对应的class_name字段的数据。

SELECT s.`name`,c.class_name
FROM class_info c NATURAL JOIN student_info s;

运行结果如下:

实操2:利用sql92中的等值连接实现实操1相同的效果。

SELECT s.`name`, c.class_name
FROM class_info c,student_info s
WHERE c.class=s.class 

运行结果如下:

9.2.3、内连接(INNER JOIN)

概述:内连接用于查询两个或两个以上表的交集部分数据。

语法

SELECT 查询列表
FROM 表1 别名
[INNER] JOIN 连接表
ON 连接条件(包括等值连接还是非等值连接)
-- 如果有多个表就继续用inner join连接表,用on连接条件
[INNER JOIN 连接表(INNER关键字可省略)
ON 连接条件(包括等值连接还是非等值连接)];

实操前准备:以下是student_info表的字段和数据:

以下是student_score_info表的字段和数据:

以下是class_info表的字段和数据:

实操:利用SQL99的内连接查询student_info表中所有班级号为1的学生(name字段)对应在class_info表中的班级名和student_score_info表中的成绩(score字段)

SELECT stu.`name`,c.class_name,ss.score
FROM student_info stu
INNER JOIN class_info c
ON stu.class=c.class
INNER JOIN student_score_info ss
ON stu.id=ss.id  
WHERE stu.class=1 ORDER BY stu.name DESC

运行结果如下:

提醒:INNER JOIN用于连接表,ON用于标记连接条件,如果还有其他条件还是需要用到WHERE条件子句。

9.2.4、左外连接和右外连接

概述:左外连接用于查询左表所有数据以及两张表交集部分的数据;右外连接用于查询右表所有数据以及两张表交集部分数据。

要点

①、外连接查询(OUTER JOIN)包括左外连接和右外连接。

②、在SQL99标准中,连接两个表,仅返回匹配的行连接,称为内连接。

③、在两个表之间的连接,返回内连接的结果,同时还返回不匹配行的左(或右)表的连接称为左(或右)外连接。

④、在两个表之间的连接,返回内连接的结果,还同时返回左和右连接称为全外连接。

⑤、孤儿数据是指被连接的列的值为空的数据。

左外连接语法

SELECT 查询列表
FROM 表1 [别名]
LEFT [OUTER] JOIN 表2 [别名]
ON 连接条件(等值连接还是非等值连接)
[WHERE子句];

实操前准备:以下是student_info表的字段信息及其数据(注意:这里删除了class字段中的一个数据):

以下是class_info表的字段信息及其数据:

实操1:要求使用左外连接,显示student_info表中的name信息和tel信息及其对应在class_info表中的class_name信息,要求显示student_info表中没有分配班级的学生姓名(name)和联系电话(tel)。

SELECT s.`name`,s.tel,c.class_name
FROM student_info s
LEFT OUTER JOIN class_info c
ON s.class=c.class 

运行结果如下:

实操2:用SQL92标准的等值连接实现实操1的要求。

SELECT s.`name`,s.tel,c.class_name
FROM student_info s , class_info c
WHERE s.class=c.class

运行结果如下:

总结:对比实操1和实操2不难发现,等值连接是无法显示孤儿数据的,而左外连接能显示左外连接的孤儿数据(对放在LEFT OUTER JOIN左侧的表做左外连接)。

右外连接语法

SELECT 查询列表
FROM 表1 [别名]
RIGHT [OUTER] JOIN 表2 [别名]
ON 连接条件(等值连接还是非等值连接)
[WHERE子句];

实操3:利用右外连接实现实操1要求的基础上查询姓名以"s","m"和"l"开头的字段信息(其实实现方法和实操1几乎一样,只是将LEFT改为RIGHT,然后将表面调换位置,最后加一个WHERE子句用于筛选固定字母开头的姓名即可,因此只要搞懂左外连接,右外连接也就能理解了)

SELECT s.name,s.tel,c.class_name
FROM class_info c
RIGHT JOIN student_info s
ON c.class=s.class
WHERE s.`name` LIKE "s%" OR s.`name` LIKE "m%" OR s.`name` LIKE "l%" OR s.`name` LIKE "s_" OR s.`name` LIKE "m_" OR s.`name` LIKE "l_";

运行结果如下:

9.2.5、全外连接

概述:①、在两个表之间的连接,返回内连接的结果,还同时返回左和右连接称为全外连接,但MySQL是不支持全外连接的,但可以使用union实现全外连接的效果。

②、UNION可以将两个查询结果集合并,并且会自动剔除掉结果集中重复的数据,如同对整个结果集合使用了 DISTINCT。

③、UNION ALL是简单的将两个结果合并后就返回。这样,如果返回的两个结果集中有重复的数据, 那么返回的结果集就会包含重复的数据了。

语法

(SELECT  投影列 FROM 表名 LEFT OUTER JOIN  表名 ON  连接条件) 
UNION 
(SELECT  投影列 FROM 表名 RIGHT OUTER JOIN  表名 ON  连接条件);

实操前准备:下面是student_info表的字段信息及其数据:

下面是student_score_info表的字段信息及其数据:

实操:查询student_info表中学生姓名(name),学号(id)和手机号(tel)以及学生姓名对应student_score_info表中的score字段数据,并且要显示没有成绩的学生信息和有成绩没有找到对应学生的数据。

(SELECT stu.id,stu.`name`,ss.score
FROM student_score_info ss
LEFT JOIN student_info stu
ON ss.id=stu.id 
)
UNION
(SELECT stu.id,stu.`name`,ss.score
FROM student_score_info ss
RIGHT JOIN student_info stu
ON ss.id=stu.id
);

运行结果如下:

10、聚合函数

概述:聚合函数也称之为多行函数,组函数或分组函数。聚合函数不象单行函数,聚合函数对行的分组进行操作,对每组给出一个结果。如果在查询中没有指定分组,那么聚合函数则将查询到的结果集视为一组。

聚合函数说明

函数名描述实例
AVG(expression)返回一个表达式的平均值,expression 是一个字段返回 Products 表中Price 字段的平均值:SELECT AVG(Price) AS AveragePrice FROM Products;
COUNT(expression)返回查询的记录总数,expression 参数是一个字段或者 * 号返回 Products 表中 products 字段总共有多少条记录:SELECT COUNT(ProductID) AS NumberOfProducts FROM Products;
MAX(expression)返回字段 expression 中的最大值返回数据表 Products 中字段 Price 的最大值:SELECT MAX(Price) AS LargestPrice FROM Products;
MIN(expression)返回字段 expression 中的最小值返回数据表 Products 中字段 Price 的最小值:SELECT MIN(Price) AS MinPrice FROM Products;
SUM(expression)返回指定字段的总和计算 OrderDetails 表中字段 Quantity 的总和:SELECT SUM(Quantity) AS TotalItemsOrdered FROM OrderDetails;

使用聚合函数的原则:①、DISTINCT 使得函数只考虑不重复的值;

②、所有聚合函数忽略空值。为了用一个值代替空值,用 IFNULL 或 COALESCE 函数。

10.1、AVG函数与SUM函数

10.1.1、AVG(arg)函数

概述:对分组数据做平均值运算,其中arg参数的类型只能为数字类型。

实操1:对student_score_info表中score列的值求平均值。

下面是student_score_info表的字段及其数据:

SELECT AVG(score) "平均值"
FROM student_score_info;

运行结果如下:

10.1.2、SUM(arg)函数

概述:SUM(arg)函数用于对分组数据求和,arg参数类型只能是数字类型。

实操2:计算student_score_info表中id列为1,2,3的总成绩(score)。

SELECT SUM(score)
FROM student_score_info
WHERE id BETWEEN 1 AND 3;

运行结果如下:

10.2、MIN函数与MAX函数

10.2.1、MIN(arg)函数

概述:MIN(arg)函数用于求分组中的最小值,arg的参数类型可以是字符,数字和日期,对字符求MIN值是求首字母最靠前的字符,对日期求MIN值就是求最靠前的时间。

10.2.1、MAX(arg)函数

概述:MAX(arg)函数用于求分组中的最大值,arg的参数类型可以是字符,数字和日期,对字符求MAX值就是先对所有字符的首字母排序(无论做升序还是降序都不影响结果),然后获取首字母在字母表中最靠后的字符作为结果返回;而对日期求MAX值就是求时间最靠后的时间(可以把时间看成一个时间轴,最左侧的时间就是MIN值,最右侧的时间就是MAX值)

实操:求出class_info表中创建时间最早和创建时间最晚的时间(start_time列)。

下图为class_info表的字段和数据:

SELECT MAX(start_time) "创建时间最晚", MIN(start_time) "创建时间最早"
FROM class_info;

运行结果如下:

10.3、COUNT()函数

概述:COUNT函数用于返回分组中的总行数。

COUNT函数的三种格式

COUNT(*):返回表中满足 SELECT 语句的所有列的行数,包括重复行,包括有空值列

的行(包括有空列的行是因为一行中一般是某一列为空,而不是整个一行都为空)。

COUNT(expr):返回在列中的由 expr 指定的非空值的数(因为聚合函数会忽略空值)。

COUNT(DISTINCT expr):返回在列中的由 expr 指定的唯一的非空值的数。

注意:COUNT(DISTINCT expr) 返回对于表达式 expr 非空并且值不相同的行数。

实操前说明:以下是student_info表的字段及其数据:

实操1:获取student_info表中一共有多少个学生。

SELECT COUNT(*) "学生总数"
FROM student_info;

运行结果如下:

实操2:获取student_info表中有多少个不同年龄的学生。

SELECT COUNT(DISTINCT age) "不同年龄的数量"
FROM student_info;

运行结果如下:

10.4、创建数据分组(GROUP BY)

概述:在没有进行数据分组之前,所有聚合函数是将结果集作为一个大的信息组进行处理。但是,有时,则需要将表的信息划分为较小的组,可以用 GROUP BY 子句实现。

语法

SELECT 列名,[聚合函数(列)]
FROM 表名
[WHERE 子句]
[GROUP BY 列名]
[ORDER BY 列名];

原则:①、使用 WHERE 子句,可以在划分行成组以前过滤行。

②、如果有WHERE子句,那么GROUP BY 子句必须在WHERE的子句后面。

③、在GROUP BY 子句中必须包含列。

实操前说明:以下是employes表的字段和数据,其中name表示员工姓名,manager_id表示员工所属经理的工号,employes_id表示每个员工自己的工号,salary表示薪资。

实操1:根据员工所属经理的工号(mnager_id)进行分组,求不同经理下属员工的平均薪资。

SELECT manager_id "经理工号",AVG(salary) "下属员工的平均薪资"
FROM employes
WHERE manager_id=10001 OR manager_id=10002
GROUP BY manager_id; 

运行结果如下:

实操说明:上面的案例中,首先利用WHERE子句选择employes表中的manager_id列为10001和10002的行( 这里使用了WHERE子句在划分行成组以前过滤行),然后对manager_id列进行分组(分组原则就是相同值为一组),最后查询返回符合要求的manager_id信息和对应的薪资平均值。

实操2:根据员工所属经理的工号(mnager_id)进行分组,求不同经理下属员工的人数。

SELECT manager_id "经理工号",COUNT(*) "员工数"
FROM employes
WHERE manager_id=10001 OR manager_id=10002
GROUP BY manager_id;

运行结果如下:

10.5、在多列进行分组

语法

SELECT 投影列,聚合函数(投影列)
FROM 表名
[WHERE 子句]
[GROUP BY 列名1,列名2[,列名3,...]]
[ORDER BY 列名];

概述:对于上面的语法,在多列进行分组就是首先对列名1进行相同值的分组,然后在已经分组的基础上对列名2进行分组,如果后面还有列名3,就在已经分组的基础上再对列名3进行分组,以此类推。

实操前说明:以下是employes表的字段和数据,其中name为员工姓名,manager_id为所属经理的工号,employes_id为员工工号,并且将其设置为主键,salary为月薪资,sex为性别,0表示男,1表示女:

实操:查询employes表中不同经理下属的不同性别的员工数量。

SELECT manager_id "经理工号",sex "性别", COUNT(*) "员工数"
FROM employes
WHERE manager_id=10001 OR manager_id=10002
GROUP BY manager_id, sex;

运行结果如下:

10.6、约束分组结果(HAVING)

概述:①、HAVING 子句是对查询出结果集分组后的结果再进行过滤。

②、用 WHERE 子句约束选择的行,用 HAVING 子句约束组。

语法

SELECT 投影行, 聚合函数(投影列)
FROM 表名
WHERE 子句
GROUP BY 列名1[,列名2,...]
HAVING 组子句;
ORDER BY 列名;

实操前说明:以下是employes表的字段和数据,其中name为员工姓名,manager_id为所属经理的工号,employes_id为员工工号,并且将其设置为主键,salary为月薪资,sex为性别,0表示男,1表示女:

实操:对employes表的manager_id进行分组(要排除空值),然后查询每个组内总薪资(salary表示薪资)大于18000的所属经理号和总薪资。

SELECT manager_id,SUM(salary)
FROM employes e
WHERE manager_id IS NOT NULL
GROUP BY manager_id HAVING SUM(salary)>18000;

运行结果如下:

总结:当涉及多个列进行分组时,例如获取不同部门不同性别员工的个数,可以使用多列分组处理,但如果是涉及对已经分组的数值进行判断,需要使用约束分组结果HAVING进行处理。

11、子查询

11.1、初识子查询

了解:用一个例子来了解什么是子查询,假如要写一个查询来找出挣钱比 muxikeqi 工资少的人。为了解决这个问题,需要两个查询:首先需要找出 muxikeqi 的工资,然后再查询出工资低于 muxikeqi 的人。可以用组合两个查询的方法解决这个问题。内查询或子查询返回一个值给外查询或主查询。使用一个子查询相当于执行两个连续查询并且用第一个查询的结果作为第二个查询的搜索值,而区分子查询和主查询的方法就是看谁有小括号,有小括号的先执行,所以有小括号的是子查询。

语法

SELECT 投影列
FROM 表名
WHERE 子句
(SELECT 投影列
FROM 表名)

概述:子查询是一个 SELECT 语句,它是嵌在另一个 SELECT 语句中的子句。使用子查询可以用简单的语句构建功能强大的语句。可以将子查询放在许多的 SQL 子句中,包括:WHERE子句,HAVING子句和FROM子句。

子查询的分类:①、子查询分为单行子查询和多行子查询,它们在写法上没有任何区别,区别在于返回的数据条数和判断符号的使用;

②、在返回的数据方面,单行子查询只返回一条数据,但多行子查询会返回超过1条的数据;

③、在判断符号的使用上,单行子查询中用单行运算符(> ,<,<> ,>= ,<=),在多行子查询中用多行运算符(IN,ANY,ALL)。

实操前说明:以下是employes表的字段和数据,其中name为员工姓名,manager_id为所属经理的工号,employes_id为员工工号,并且将其设置为主键,salary为月薪资,sex为性别,0表示男,1表示女:

实操:查询与muxikeyi一样,属于同一经理管理的员工信息,包括姓名和性别。

SELECT name,sex
FROM employes
WHERE manager_id = (SELECT manager_id FROM employes WHERE name="muxikeqi")

运行结果如下:

11.2、单行子查询

概述:单行子查询是从内查询返回一行的查询。在该子查询类型中用一个单行操作符。

实操:(employes表与上一小节的字段和数据完全一样) 查询与muxikeyi一样,属于同一经理管理的其他员工信息(不包括muxikeyi自己的信息),包括姓名和性别。

SELECT name,sex
FROM employes
WHERE manager_id = (SELECT manager_id FROM employes WHERE name="muxikeqi")
 AND name<>"muxikeqi";

运行结果如下:

11.3、多行子查询

概述:子查询返回多行被称为多行子查询。对多行子查询要使用多行运算符而不是单行运

算符。

多行运算符

运算符含义
IN等于列表中的任何成员
ANY比较子查询返回的任意一个值
ALL比较子查询返回的全部值

ANY运算符概述

①、ANY 运算符比较一个值与一个子查询返回的任意一个值。

②、< ANY 意思是小于最大值。

③、> ANY 意思是大于最小值。

④、=ANY 等同于 IN。

ALL运算符概述

①、ALL 运算符比较一个值与子查询返回的全部值。

②、<ALL 意思是小于最小值。

③、>ALL 意思是大于最大值。

子查询中的空值概述

①、内查询返回的值含有空值,并因此整个查询无返回行,原因是用大于、小于或不等于比较Null值,都返回null。所以,只要空值可能是子查询结果集的一部分,就不能用 NOT IN 运算符。NOT IN 运算符相当于 <> ALL。

②、空值作为一个子查询结果集的一部分,如果使用 IN 操作符的话就不会产生问题,因为IN 操作符相当于 =ANY。

实操前说明:以下是employes表的字段和数据,其中name为员工姓名,manager_id为所属经理的工号,employes_id为员工工号,并且将其设置为主键,salary为月薪资,sex为性别,0表示男,1表示女:

实操:查询employes表中不同经理管理的员工工资最低的员工信息,包括员工所属经理ID,员工姓名和薪资。

SELECT manager_id,name,salary
FROM employes
WHERE salary IN 
(SELECT MIN(salary)
FROM employes
WHERE manager_id IS NOT NULL
GROUP BY manager_id
) AND manager_id IS NOT NULL 
GROUP BY manager_id

运行结果如下:

12、索引

12.1、初识索引

概述:索引是对数据库表中的一列或多列值进行排序的一种结构,使用索引可以快速访问数据库表中的特定信息。索引是一种特殊的文件,它们包含着对数据表里所有记录的位置信息。更通俗的说,数据库索引好比是一本书前面的目录,能加快数据库的查询速度。MySQL 索引的建立对于MySQL 的高效运行是很重要的,索引可以大大提高 MySQL 的检索速度。

作用:索引相当于图书上的目录,可以根据目录上的页码快速找到所需的内容,提高性能(查询速度)。

优点

①、通过创建唯一性索引,可以保证数据库表中的每一行数据的唯一性;

②、可以加快数据的检索速度;

③、可以加速表与表之间的连接;

④、在使用分组和排序进行检索的时候,可以减少查询中分组和排序的时间;

缺点

①、创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加;

②、索引需要占用物理空间,数据量越大,占用空间越大;

③、会降低表的增删改的效率,因为每次增删改索引都需要进行动态维护;

适合创建索引的情况

①、频繁作为查询条件的字段应该创建索引;

②、查询中排序的字段创建索引将大大提高排序的速度(索引就是排序加快速查找);

③、查询中统计或者分组的字段;

不适合创建索引的情况

①、频繁更新的字段不适合创建索引,因为每次更新不单单是更新记录,还会更新索引,保存索引文件;

②、where条件里用不到的字段,不创建索引;

③、表记录太少,不需要创建索引;

④、经常增删改的表;

⑤、数据重复且分布平均的字段,因此为经常查询的和经常排序的字段建立索引。注意某些数据包含大量重复数据,因此他建立索引就没有太大的效果,例如性别字段,只有男女,不适合建立索引;

MySQL中的索引类型

①、普通索引:最基本的索引,它没有任何限制。

②、唯一索引:索引列的值必须唯一,但允许有空值,如果是组合索引,则列值的组合必须唯一,当为某一列添加了唯一性约束时,那么这一列默认就存在唯一索引。

③、主键索引:特殊的索引,唯一的标识一条记录,不能为空,一般用primary key来约束,当为某一列添加了主键约束,那么该列就默认存在主键索引。

④、联合索引:在多个字段上建立索引,能够加速查询到速度。

12.2、普通索引

概述:是最基本的索引,它没有任何限制。在创建索引时,可以指定索引长度。length 为可选参数,表示索引的长度,只有字符串类型的字段才能指定索引长度,如果是 BLOB 和 TEXT 类型,必须指定 length。

注意:创建索引时,如果指定单列索引长度,length 必须小于这个字段所允许的最大字符个数。

查询索引

SHOW INDEX FROM 表名;

直接创建索引

-- 索引名一般为 表名_列名_index
CREATE INDEX 索引名 ON 表名(列名[(length)]);

修改表时添加索引

ALTER TABLE 表名 ADD INDEX 索引名(列名[(length)]);

创建表时指定索引列

CREATE TABLE 表名(
列名 列类型 [约束] ,
INDEX 索引名(列名[(length)])
);

删除索引

DROP INDEX 索引名 ON 表名;

实操前说明:以下是employes表的字段和数据:

实操1:为employes表的manager_id列直接创建索引,并查看索引是否创建成功。

CREATE INDEX employes_manager_id_index ON employes(manager_id);
SHOW INDEX FROM employes;

运行结果如下:

注意:由于manager_id列的类型是int,所以不能指定索引的长度,因为只有字符串类型的字段才能指定索引长度。

实操2:为employes表的employes_id列使用修改表的方式创建索引。

ALTER TABLE employes ADD INDEX employes_employes_id(employes_id);
SHOW INDEX FROM employes;

运行结果如下:

实操3:创建一个名为test_info的表,包含id,name,age,address字段,其中id字段为主键自增,并且为name字段添加索引,指定索引长度为20。

CREATE TABLE test_info(id int(8) PRIMARY KEY auto_increment, name varchar(20), age int(3),address VARCHAR(40),INDEX test_info_name(name(20)));
SHOW INDEX FROM test_info;

运行结果如下:

实操4:删除实操3中创建的索引。

DROP INDEX test_info_name ON test_info;
SHOW INDEX FROM test_info;

运行结果如下:

12.3、唯一索引

概述:唯一索引与普通索引类似,不同的就是: 索引列的值必须唯一,但允许有空值(普通索引也允许有空)。

直接创建唯一索引

CREATE UNIQUE INDEX 索引名 ON 表名(列名[(length)]);

实操1:为employes表的employes_id列添加唯一性索引。

CREATE UNIQUE INDEX employes_employes_id_unique ON employes(employes_id);
SHOW INDEX FROM employes;

运行结果如下:

修改表添加唯一索引

ALTER TABLE 表名 ADD UNIQUE 索引名(列名[(length)]);

实操前说明:以下是student_info表的字段及其数据:

实操2:为student_info表的tel列添加唯一性索引,并且指定索引长度为11。

ALTER TABLE student_info ADD UNIQUE student_info_tel_unique(tel(11));
SHOW INDEX FROM student_info;

运行结果如下:

创建表时指定唯一索引

CREATE TABLE 表名(
列名 类型 [约束],
[...,]
UNIQUE 约束名(列名[(length)])
);

实操3:创建一个名为test_info2的表,包含id,name,tel,age和address字段,为id添加主键自增,为name添加普通索引,为tel添加唯一索引,并且为所有的索引都限制长度。

CREATE TABLE test_info2(
id int PRIMARY KEY auto_increment,
name VARCHAR(20),
tel VARCHAR(11),
address VARCHAR(45),
UNIQUE test_info2_tel_unique(tel(11)),
INDEX test_info2_name_index(name(20))
);

SHOW INDEX FROM test_info2;

运行结果如下:

12.4、主键索引

概述:主键索引是一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。一般是在建表的时候同时创建主键索引。

修改表添加主键索引

ALTER TABLE 表名 ADD PRIMARY KEY(列名);

实操1:创建一个名为test_info3的表,包含name,id,age字段,创建完表后为id列添加主键索引。

CREATE TABLE test_info3(
id int,
name VARCHAR(20),
age VARCHAR(5)
);
ALTER TABLE test_info3 ADD PRIMARY KEY(id);
SHOW INDEX FROM test_info3;

运行结果如下:

创建表时添加主键索引

CREATE TABLE 表名(
列名 类型,
PRIMARY KEY(列名)
);

实操2:创建一个名为test_info4的表,包含name,id,age字段,在创建表的同时为id列添加主键索引。

CREATE TABLE test_info4(
id int,
name VARCHAR(20),
age varchar(3),
PRIMARY KEY(id)
);
show INDEX FROM test_info4;

运行结果如下:

12.5、组合索引

概述:组合索引是指使用多个字段创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用(最左前缀原则)。

最左前缀原则就是最左优先。

举例说明:例如我们使用任意一个表,表中包括name,address和salary字段,现使用表中的 name ,address ,salary 创建组合索引,那么想要组合索引生效, 我们只能使用如下组合:name/address/salary或name/address或name/,但是如果使用 addrees/salary 或者是 salary/ 则索引不会生效。

添加组合索引

ALTER TABLE 表名 ADD INDEX 索引名 (列名1(length),列名2(length));

实操1:为test_info4表中的name和age字段创建组合索引。

ALTER TABLE test_info4 ADD INDEX test_info_index(name,age);
SHOW INDEX FROM test_info4;

运行结果如下:

创建表时创建组合索引

CREATE TABLE 表名(
    列名 类型,
    INDEX 索引名(列名1(length),列名2(length))
);

实操2:创建 名为test_info5 的表,包含 id,name,address 列,同时为 name,address 列创建组合索引,并为id列创建主键索引。

CREATE TABLE test_info5(
	id int,
	name VARCHAR(20),
	age int,
	PRIMARY KEY(id),
	INDEX test_info5_index(name,age)
);
SHOW INDEX FROM test_info5;

13、MySQL事务

13.1、初识事务

概述:事务指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。

定义

①、事务是一个最小的不可再分的工作单元;通常一个事务对应一个完整的业务。

②、一个完整的业务需要批量的DML(insert、update、delete)语句共同联合完成。

③、事务只和DML语句有关,或者说DML语句才有事务。这个和业务逻辑有关,业务逻辑不同,DML语句的个数不同

事务的四大特性

①、原子性:事务中的操作要么都不做,要么就全做。

②、一致性:一个事务应该保护所有定义在数据上的不变的属性(例如完整性约束)。在完成了一个成功的事务时,数据应处于一致的状态。

③、隔离性:一个事务的执行不能被其他事务干扰。

④、持久性:一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。

事务类型

①、显式事务:需要手动的提交或回滚。DML 语言中的所有操作都是显示事务操作。

②、隐式事务:数据库自动提交不需要我们做任何处理,同时也不具备回滚性。DDL、DCL 语言都是隐式事务操作。

13.2、使用事务

知识点补充:在MySQL中,对DML操作的默认的事务是自动提交的,如果不想让某些DML操作自动提交,将这些DML操作放在一个事务中即可。

TCL语句描述
start transaction事务开启
commit事物提交
rollback事物回滚(撤回)

实操:创建一个account表,包含id,card_id,name,和balance字段,为id设置主键自增长,然后插入两条数据,插入完数据后完成一个转账事务。

-- 创建指定字段的account表
CREATE TABLE account(
	id int PRIMARY KEY auto_increment,
	card_id VARCHAR(19),
	name VARCHAR(20),
	balance FLOAT(11,2)
);
-- 插入两条数据
INSERT INTO account VALUES(null,"123123123","muxikeqi",4567.89);
INSERT INTO account VALUES(0,"456456456","Niko",7890.12);

account表数据如下:

接下来我们开启一个转账事务,但我们不提交事务:

-- 注意:这里要逐行选择执行,否则会报错
START TRANSACTION
UPDATE account SET balance=balance-1000 WHERE card_id="123123123";
UPDATE account SET balance=balance+1000 WHERE card_id="456456456";
SELECT * FROM account;

运行结果如下:

但是眼见不一定为真,当我们关闭数据库后再打开数据库查看account表的数据如下:

分析:当我们关闭数据库重新打开后,muxikeqi和Niko的账户余额并没发生任何变化。这是因为当我们使用“STARTTRANSACTION”开启一个事务后,该事务的提交方式不再是自动的,而是需要手动提交,而在这里,我们并没有使用事务提交语句COMMIT,所以对account表中数据的修改并没有永久的保存到数据库中,也就是说我们的转账事务并没有执行成功。

为使转账成功,接下来我们通过提交语句COMMIT手动提交,使数据修改永久保存到数据库中:

-- 一定要逐行运行,否则会报错
START TRANSACTION
UPDATE account SET balance=balance-1000 WHERE card_id="123123123";
UPDATE account SET balance=balance+1000 WHERE card_id="456456456";
SELECT * FROM account;
COMMIT;

重启数据库查看account表数据如下:

以下是通过navicat逐行执行代码的图像提示:

知识点补充:事务的回滚用于让数据库恢复到了执行事务操作前的状态,需要注意的是事务的回滚必须在事务提交之前,因为事务一旦提交就不能再进行回滚操作。

13.3、事务并发与隔离级别

13.3.1、事务并发问题

概述:MySQL属于关系型数据库,关系型数据库采用多线程的方式提高运行的效率,但多线程的使用就容易导致事务的并发问题(因为多个线程是不同线程交替执行,交替执行就容易产生漏洞)。

①、脏读

概述:指一个事务读取了另外一个事务未提交的数据。

举例说明:A事务读取B事务尚未提交的数据,此时如果B事务发生错误并执行回滚操作,那么A事务读取到的数据就是脏数据。

②、不可重复读

概述:在一个事务内读取表中的某一行数据,多次读取结果不同。

举例说明:事务A在执行读取操作,由整个事务A比较大,前后读取同一条数据需要经历很长的时间 。而在事务A第一次读取数据,比如此时读取了小明的年龄为20岁,事务B执行更改操作,将小明的年龄更改为30岁,此时事务A第二次读取到小明的年龄时,发现其年龄是30岁,和之前的数据不一样了,系统不能读取到重复的数据,称为不可重复读。

③、幻读

概述:是指在一个事务内读取到了别的事务插入的数据,导致前后读取数量总量不一致。

举例说明:事务A在执行读取操作,需要两次统计数据的总量,前一次查询数据总量后,此时事务B执行了新增数据的操作并提交后,这个时候事务A读取的数据总量和之前统计的不一样,就像产生了幻觉一样,平白无故的多了几条数据,称为幻读。

13.3.2、事务的隔离级别

概述:事务的隔离级别用于决定如何控制并发用户读写数据的操作。数据库是允许多用户并发访问的,如果多个用户同时开启事务并对同一数据进行读写操作的话,有可能会出现脏读、不可重复读和幻读问题,所以MySQL中提供了四种隔离级别来解决上述问题。

事务的隔离级别从低到高依次为

READ UNCOMMITTED (不提交)

READ COMMITTED (读已提交)

REPEATABLE READ (重复读)

SERIALIZABLE (串行化)

提示:隔离级别越低,越能支持高并发的数据库操作(下表中T表示能解决,F表示不能解决)。

隔离级别脏读不可重复读幻读
SERIALIZABLETTT
REPEATABLE READTTF
READ COMMITTEDTFF
READ UNCOMMITTEDFFF

知识点补充:MySQL中默认的隔离级别是REPEATABLE READ,而大多数其他关系型数据库的默认隔离级别是READ COMMITTED,例如Oracle。

查看MySQL默认事务隔离级别

SELECT @@transaction_isolation;

设置事务隔离级别:(仅对当前session有效,例如用navicat连接了数据库,会产生一个session,然后用MySQL自带的客户端(MySQL 5.7 Command Line Client)连接数据库会产生另一个session,当我在navicat设置了一个新的事务隔离级别,这个事务隔离级别只会在navicat生效,但不会在MySQL自带的客户端生效)

set session transaction isolation level read uncommitted;
set session transaction isolation level read committed;
set session transaction isolation level repeatable read;
set session transaction isolation level serializable;

提示:虽然MySQL的事务隔离级别只针对会话生效,不能很好的解决并发问题,但无需过多担心,因为spring中有更加细致的事务控制。

14、MySQL的用户管理

14.1、创建用户

概述:MySQL 是一个多用户的数据库系统,按权限,用户可以分为两种: 第一种是root 用户,又称超级管理员,第二种是由 root 用户创建的普通用户。

用root用户创建普通用户

CREATE USER "用户名" IDENTIFIED BY "密码";

查看用户

-- user表示用户名,host表示用户权限
SELECT USER,HOST FROM mysql.user;

实操1:创建一个名为muxikeqi的用户,并查看是否创建成功。

CREATE USER "muxikeqi" IDENTIFIED BY "muxikeqi";
SELECT user,host FROM mysql.user;

运行结果如下:

14.2、权限管理

概述:新用户创建完后是无法登陆的,需要分配权限(从如下命令不难发现,该命令不仅分配了权限还设置了生效表和生效设备,因此也可以通过如下命令直接创建用户)。

 GRANT 权限 ON 数据库.表 TO 用户名@登录主机 IDENTIFIED BY "密码";

登录主机的参数选择

字段含义
%匹配所有主机,如果没有指定登录主机默认为%
localhostlocalhost 不会被解析成 IP 地址,直接通过 UNIXsocket 连接
127.0.0.1会通过 TCP/IP 协议连接,并且只能在本机访问
:: 1::1 就是兼容支持 ipv6 的,表示同 ipv4 的 127.0.0. 1

权限的参数选择

权 限作用范围作 用
all [privileges]服务器所有权限
select表、列选择行
insert表、列插入行
update表、列更新行
delete删除行
create数据库、表、索引创建
drop数据库、表、视图删除
reload服务器允许使用flush语句
shutdown服务器关闭服务
process服务器查看线程信息
file服务器文件操作
grant option数据库、表、存储过程授权
references数据库、表外键约束的父表
index创建/删除索引
alter修改表结构
show databases服务器查看数据库名称
super服务器超级权限
create temporary tables创建临时表
lock tables数据库锁表
execute存储过程执行
replication client服务器允许查看主/从/二进制日志状态
replication slave服务器主从复制
create view视图创建视图
show view视图查看视图
create routine存储过程创建存储过程
alter routine存储过程修改/删除存储过程
create user服务器创建用户
event数据库创建/更改/删除/查看事件
trigger触发器
create tablespace服务器创建/更改/删除表空间/日志文件
proxy服务器代理成为其它用户
usage服务器没有权限

实操1:为muxikeqi用户分配插入和更新mydatabase数据库下所有表,并且只能在本机登录的权限,最后验证该用户的权限。

-- 分配权限
GRANT INSERT,UPDATE 
ON mydatabase.* 
TO "muxikeqi"@"localhost" 
IDENTIFIED BY "muxikeqi";

-- 查看用户及其权限
SELECT HOST,USER FROM mysql.user;

运行结果如下:

接下来我们尝试登录创建的muxikeqi用户,首先打开文件所在位置:

接下来通过cmd打开该文件:

打开cmd窗口后输入如下命令用于登录muxikeqi用户:

--回车后输入密码muxikeqi
mysql -umuxikeqi -p

实战图如下(显示如下所示的图就表示登录成功了):

接下来验证muxikeqi的插入和修改权限,对test_info5表插入一条数据并对插入的数据进行修改,test_info5的字段和数据如下:

对test_info5表插入数据:

-- 选择数据库
use mydatabase;

-- 插入数据
insert into test_info5 values(1,"niko","28");

-- 尝试用where精准的对数据进行修改(报错是因为没有使用where的权限)
update test_info5 set name="donk" where id=1;

-- 再次对插入的数据进行修改
update test_info5 set name="donk";

实战图如下:

最后,通过navicat连接的root用户查看test_info5的数据如下:

知识点补充:如果需要退出用户通过如下命令实现:

exit;

每当调整权限后,通常需要执行以下语句刷新权限

FLUSH PRIVILEGES;

删除用户的命令如下:

DROP USER 用户名@登录主机;

实操2:删除我们在实操1中创建的用户muxikeqi。

DROP USER "muxikeqi"@"localhost";
SELECT USER,host FROM mysql.user;

运行结果如下:只剩下一个没有分配权限的muxikeqi用户了

如果我们想将这个没有分配权限的muxikeqi用户也删除掉,只需将登录主机的参数设置为上图中对应的%即可,命令如下:

DROP USER "muxikeqi"@"%";
SELECT user,host FROM mysql.`user`;

运行结果如下:

14.3、使用Navicat管理用户

注意:以下操作的前提是登录的是root用户进行操作。

14.3.1、创建用户

接下来为用户添加权限,不要退出当前页面:

点击确认后再按Ctrl+S将整个用户对应的权限保存即可。

14.3.2、连接用户

此时,我们就连接了两个用户:

14.3.3、删除用户

14.3.4、导出数据

方式1:如果不是导出为SQL脚本文件就选择方式1。

完成后点击下一步,然后选择需要导出的字段(默认是全部勾选的,如果需要去掉部分字段要将下面的全部字段前的勾去掉即可选择字段),选择完成后点击下一步:

后面的步骤基本无脑下一步,最后点击开始即可:

方式2:如果是导出为sql文件建议选择方式2。

14.3.5、导入数据

方式1

方式2:首先将要导入的数据用任意文本工具打开复制一下(Ctrl+C),然后打开navicat新建查询:

15、MySQL分页查询

概述:分页查询就是在查询的结果集中取固定条数的数据用于分页展示。

原则

①、在MySQL 数据库中使用 LIMIT 子句进行分页查询。

②、MySQL 分页中开始位置为 0。

③、分页子句在查询语句的最后侧。

提示:LIMIT子句和LIMIT OFFSET子句达到的效果相同,只是参数的位置不同。

15.1、LIMIT子句

语法

SELECT 投影列 FROM 表名 WHERE 条件 [ORDER BY 排序的列 参数] LIMIT 开始位置,查询数量;

实操前说明:下面是student_info表的字段和数据:

实操1:查询student_info表的name,age,tel字段信息,并对age做降序排序显示,最后使用LIMIT做分页,每页展示3行数据。

首先查询第一页的数据:

SELECT name,age,tel
FROM student_info
ORDER BY age DESC
LIMIT 0,3;

运行结果如下:

接下来查询第二页数据:

SELECT name,age,tel
FROM student_info
ORDER BY age DESC
LIMIT 3,3;

运行结果如下:

最后查询第三页的数据:

SELECT name,age,tel
FROM student_info
ORDER BY age DESC
LIMIT 6,3;

运行结果如下:

15.2、LIMIT OFFSET子句

语法

SELECT  投影列 FROM  表名 WHERE  条件 [ORDER BY 排序的列名 参数] LIMIT  查询数量 OFFSET 开始位置;

实操:查询student_info表的name,age,tel字段信息,并对age做降序排序显示,最后使用LIMIT OFFSET子句做分页,每页展示4行数据。

首先查询第一页:

SELECT name,age,tel
FROM student_info
ORDER BY age DESC
LIMIT 4 OFFSET 0;

运行结果如下:

接下来查询第二页:

SELECT name,age,tel
FROM student_info
ORDER BY age DESC
LIMIT 4 OFFSET 4;

运行结果如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

沐曦可期

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

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

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

打赏作者

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

抵扣说明:

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

余额充值