数据库的概述

1. 为什么要使用数据库

  • 持久化(persistence):==把数据保存到可掉掉电式存储设备中以供之后使用==,大多数情况下,特别是企业级应用,==数据库持久化意味着将内存中的数据保存到硬盘上加以“固化”==,而持久化的实现过程大多数通过各种关系数据库来完成
  • 持久化的主要作用是==将内存中的数据存储在关系型数据库中==,当然页可以存储在磁盘文件、XML数据文件中。

2. 数据库的相关概念

==DB:数据库(Database)==
即存储数据的“仓库”,其本质是一个我呢见系统。它保存了一系列有组织的数据。
==DBMS:数据库管理系统(Database Management System)==
是一种操纵和管理数据库的大型软件,用于建立、使用和维护数据库,对数据库进行统一管理和控制。用户通过数据库管理系统访问数据库中表内的数据
SQL:结构化查询语言(Structured Query Language)====
专门用来与数据库通信的语言

3. RDBMS 和 非RDBMS

关系数据库绝对是DBMS的主流,其中使用最多的分别是Oracle、Mysql和SQL Server。这些都是关系型数据库。

3.1 关系型数据库(RDBMS)

3.1.1 实质

  • 这种类型的数据库是最古老的数据库类型,关系型数据库模式是把复杂的数据结构归算为简单的二元关系(即二维表格形式)。

  • 关系型数据库是以行(row)列(column)的形式存储数据,以便于用户理解。这一系列的行和列被称为表(table),一组表组成一个库(database).

  • 表与表之间的数据记录有关系(relationship)。现实世界中的各个实体以及实体之间的各种联系均用关系模型

3.1.2 优势

  • 复杂查询

    可以使用SQL语句方便的在一个表以及多个表之间做非常复杂的数据查询。

  • 事务支持

    使得对于安全性能很高的数据访问要求得以实现。

3.2 非关系型数据库(非RDBMS)

3.2.1 介绍

非关系型数据库。可以看成传统关系型数据库的功能阉割版本,基于键值存储数据,不需要经过SQL层的解析,性能非常高。同时通过减少不常用的功能,进一步提供性能。

目前基本上大部分主流非关系型数据库都是免费的。

3.2.2 有哪些非关系型数据库

相比于SQL,NoSQL泛指非关系型数据库,包括榜单上的键值型数据库、文档型数据库,搜索引擎和列存储等,除此之外还包括图形数据库。也只有用NoSQL一词才能将这些技术囊括进来。

==键值型数据库==

键值型数据库通过Key-Value键值方式来存储数据,其中Key和Value是简单的对象,也可以是复杂的对象。Key作为唯一的标识符,优点是查找速度快,这方面明显优于关系型数据库,缺点是无法像关系型数据库一样使用条件过滤(比如WHERE),如果你不知道去哪里找数据,就要遍历所有的键,这样就会消耗大量的计算。

键值型数据库典型的使用场景是作为内存缓存。Redis是最流行的键值型数据库。

==文档型数据库==
此类数据库可存放并获取文档,可以是XML、JSON等格式。在数据库中文档作为处理信息的基本单位,一个文档就相当于一条记录。文档数据库所存放的文档,就相当于键值数据库所存放的“值”。MongoDB是最流行的文档型数据库。此外,还有CouchDB等。

==搜索引擎数据库==
虽然关系型数据库采用了索引提升检索效率,但是针对全文索引效率却较低。搜索引擎数据库是应用在搜索引擎领域的数据存储形式,由于搜索引擎会爬取大量的数据,并以特定的格式进行存储,这样在检索的时候才能保证性能最优。核心原理是“倒排索引”。
典型产品: SolrElasticsearchSplunk等。

==列式数据库==
列式数据库是相对于行式存储的数据库,Oracle、MySQL、sQL Server等数据库都是采用的行式存储(Row-based),而列式数据库是将数据按照列存储到数据库中,这样做的好处是可以大量降低系统的V/O,适合于分布式文件系统,不足在于功能相对有限。典型产品:HBase等。

==图形数据库==
图形数据库,利用了图这种数据结构存储了实体(对象)之间的关系。图形数据库最典型的例子就是社交网络中人与人的关系,数据模型主要是以节点和边(关系)来实现,特点在于能高效地解决复杂的关系问题。
图形数据库顾名思义,就是一种存储图形关系的数据库。它利用了图这种数据结构存储了实体(对象)之间的关系。关系型数据用于存储明确关系的数据,但对于复杂关系的数据存储却有些力不从心。如社交网络中人物之间的关系,如果用关系型数据库则非常复杂,用图形数据库将非常简单。典型产品: Neo4J、InfoGrid等。

4. 关系型数据库设计的规则

4.1.1 表的关联关系

  • 表与表之间的数据记录有关系(relationship)。现实世界中的各种实体以及实体之间的各种联系均用关系模型来表示。
  • 四种:一对一关联、一对多关联、多对多关联、自我引用

4.1.2 一对一关联(one-to-one)

在实际的开发中应用不多,因为一对一可以创建成一张表。

  • 举例:设计学生表:学号、姓名、手机号码、班级、系别、身份证号码、家庭住址、籍贯、紧急联系人、…
    • 拆为两个表:两个表的记录是——对应关系。
    • 基础信息表(常用信息):学号、姓名、手机号码、班级、系别、…
    • 档案信息表(不常用信息):学号、身份证号码、家庭住址、籍贯、紧急联系人、…
  • 两种建表原则:
    • 外键唯一:主表的主键和从表的外键(唯一),形成主外键关系,外键唯一。
    • 外键是主键:主表的主键和从表的主键,形成主外键关系。

4.1.3 一对多关系(one-to-many)

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

  • 举例:
    • 员工表:编号、姓名、…、所属部门。
    • 部门表:编号、名称、简介
  • 一对多建表原则:在从表(多方)创建一个字段,字段作为外键指向主表(一方)的主键

4.1.4 多对多(many-to-many)

要表示多对多关系,必须创建第三个表,该表通常称为联接表,它将多对多关系划分为两个一对多关系。将这两个表的主键都插入到第三个表中。

image-20220330220823547

image-20220330221211202

Mysql环境搭建

1. Mysql的下载安装和配置(社区版)

==1.下载地址==

官网:https://www.mysql.com

==2.打开官网,点击DOWNLOADS==

image-20220330222643507

==3.点击MySQL Community (GPL) Downloads==

image-20220330222615486

==4.打开官网,点击MySQL Community Server==

image-20220330222753428

==5.下载最新版本==

image-20220330222942566

image-20220330223008181

image-20220330223031655

==6.下载指定版本==

image-20220330223159612

2. Mysql的安装

image-20220330223542807

image-20220330223641504

image-20220330223817997

安装路径不能包含中文或中文的空格。

image-20220330223918397

image-20220330224248511

image-20220330224400192

以上就完成了Mysql的安装,但是还需要进行Mysql的配置。

image-20220330224501535

image-20220330224520542

image-20220330224715984

image-20220330224731014

设置密码,默认用户名是root,也可以添加其他用户

image-20220330224821134

image-20220330225156352

image-20220330225222261

完成以上步骤Mysql配置完成

3. 配置MySQL8.0环境变量

如果不配置MysQL环境变量,就不能在命令行直接输入MysQL登录命令。下面说如何配置MysQL的环境变量:

步骤1:在桌面上右击【此电脑】图标,在弹出的快捷菜单中选择【属性】菜单命令。

步骤2:打开【系统】窗口,单击【高级系统设置】链接。

步骤3:打开【系统属性】对话框,选择【高级】选项卡,然后单击【环境变量】按钮。

步骤4∶打开【环境变量】对话框,在系统变量列表中选择path变量。

步骤5∶单击【编辑】按钮,在【编辑环境变量】对话框中,将MySQL应用程序的bin目录(C:\ProgramFiles\MySQL|MySQL Server 8.0\bin)添加到变量值中,用分号将其与其他路径分隔开。

步骤6:添加完成之后,单击【确定】按钮,这样就完成了配置path变量的操作,然后就可以直接输入MySQL命令来登录数据库了。

==测试是否安装成功==

快捷键【win+R】输入cmd回车,输入命令mysql --version或mysql -V提示版本信息即安装成功

image-20220330230925130

4. 使用图形化工具可能会出现的问题

安装默认选择8.0新增的加密方式,选5.x版本无此问题。

image-20220330224731014

image-20220330235705366

出现这个原因是MySQL8之前的版本中加密规则是mysql_native_password,而在MySQL8之后,加密规则是
caching_sha2_password。解决问题方法有两种。

第一种是升级图形界面工具版本,二是把MySQL8用户登录密码加密规则还原成mysql_native_password。

第二种解决方案如下,用命令行登录MysQL数据库之后,执行如下命令修改用户密码加密规则并更新用户密码,这里修改用户名为“root@localhost”的用户密码规则为“mysql_native_password”,密码值为“123456
‘,如图所示。

1
2
3
4
5
6
# 使用mysql数据库
use mysql;
# 修改用户名为“root@localhost”的用户密码规则
alter user 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '密码'
# 刷新权限
FLUSH PRIVILEGES;

基本的SELECT语句

1. SQL语言的分类

SQL语言在功能上主要分为如下三大类:

  • ==DDL(Data Definition Languages、数据库定义语言)==,这些语句定义了不同的数据库、表、视图、索引等数据库对象,还可以用来创建、删除、修改数据库表的结构
    • 主要的关键字包括CREATEDELETEALTER等。
  • ==DML(Data Mainpulation Language、数据库操作语言)==,用于添加、删除、更新和查询数据库记录,并检查数据完整性。
    • 主要的语句关键字包括INSERTDELETEUPDATESELECT等。
    • ==SELECT是SQL的基础,最为重要。==
  • ==DCL(Data Control Language、数据库控制语言)==,用于定义数据库、表、字段、用户的访问权限和安全级别。
    • 主要的语句关键字包括GRANTREVOKE,COMMIT,ROLLBACK,SAVEPOINT

因为查询语句使用的非常的频繁,所以很多人把查询语句单拎出来一类:DQL (数据查询语言)。还有单独将COMMIT、ROLLBACK 取出来称为TCL (Transaction Control Language,事务控制语言)

2. SQL语言的规则与规范

2.1 基本规则

  • SQL可以写在一行或者多行。为了提高可读性,各字句分行写,必要时使用缩进
  • 每条命令以;\g\G结束
  • 关键字不能被缩写也不能分行
  • 关于标点符号
    • 必须保证所有的()、单引号、双引号是成双成对的
    • 必须使用英文状态下的半角输入方式
    • 字符串型和日期类型数据可以使用可以使用单引号(’’)表示
    • 列的别名,尽量使用双引号(’’),而且不建议省略as

2.2 SQL大小写规范

  • MySQL在Window环境下是大小写不敏感的
  • MySQL在Linux环境下是大小写敏感的
    • 数据库名、表名、表的别名、变量名是严格区分大小写的
    • 关键字、函数名、列名(或字段名)、列的别名(字段的别名)是忽略大小写的。
  • 推荐采用统一的书写规范
    • 数据库名、表名、表别名、字段名、字段别名等都小写
    • SQL关键字、函数名、绑定变量等都大写

2.3 注释

1
2
3
4
5
6
# 单行注释
-- 单行注释
/*
多行注释
多行注释
*/

2.4 命名规则

  • 数据库、表名不得超过30个字符,变量名限制为29个。必须只能包含A-Z,a-z,O-9,_共63个字符
  • 数据库名、表名、字段名等对象名中间不要包含空格
  • 同一个MysQL软件中,数据库不能同;同一个库中,表不能重名;同一个表中,字段不能重名
  • 必须保证你的字段没有和保留字、数据库系统或常用方法冲突。如果坚持使用,请在sQL语句中使用`(着重号)引起来
  • 保持字段名和类型的一致性,在命名字段并为其指定数据类型的时候一定要保证一致性。假如数据类型在一个表里是整数,那在另一个表里可就别变成字符型了

2.5 数据的导入

方式一:在命令行客户端登陆mysql,使用source命令

1
2
# source 文件名全路径
mysql> source D:\atguigudb.sql

方式二:图形化工具导入

3. 基本的SELECT语句

3.1 SELECT …

1
2
SELECT 1+1,1*3;
SELECT DUAL; # DUAL伪表

3.2 SELECT … FROM

语法:

1
2
SELECT 	# 标识选择那些列
FROM # 标识从那个表中选择
  • 选择全部列
1
2
# *:表示查询表中所有的字段(列)
SELECT * FROM employee;
  • 查询指定列
1
SELECT employee_id,email,phone_number FROM employees;

3.3 列的别名

  • as:全称是alias(别名),可以省略

  • 列的别名可以使用""引起来,不可以使用单引号

  • 举例:

1
2
3
SELECT employee_id emp_id,phone_number phone FROM employees;
SELECT employee_id AS emp_id,phone_number AS phone FROM employees;
SELECT employee_id "emp_id",phone_number "phone" FROM employees;

3.4 去除重复行

使用DISTINCT关键字去除重复行

  • 举例
1
SELECT DISTINCT department_id FROM employees;

针对于:

1
SELECT DISTINCT department_id,salay FROM employees;

DISTINCT 其实是对后面所有列名的组合进行去重。如果你想要看都有哪些不同的部门(department_id),只需要写 DISTINCT department_id 即可,后面不需要再加其他的列名了。

3.5 空值参与运算

  • 所有运算符或列值遇到null值,运算的结果都为null

  • 空值:null

  • null 不等于 0,'','null'

  • 在 MySQL 里面,空值是占用空间的。

3.6 着重号(``)

我们需要保证表中的字段、表名等没有和保留字、数据库系统或常用方法冲突。如果真的相同,请在SQL语句中使用一对``(着重号)引起来。

  • 举例
1
2
3
4
# 错误写法,ORDER是一个关键字
SELECT * FROM ORDER;
# 正确写法
SELECT * FROM `ORDER`;

4. 显示表结构

使用DESCRIBEDESC关键字

1
2
DESCRIBE employees;
DESC employees;

image-20220401232217300

其中,各个字段的含义分别解释如下:

  • Field:表示字段名称。

  • Type:表示字段类型,这里 barcode、goodsname 是文本型的,price 是整数类型的。

  • Null:表示该列是否可以存储NULL值。

  • Key:表示该列是否已编制索引。PRI表示该列是表主键的一部分;UNI表示该列是UNIQUE索引的一

    部分;MUL表示在列中某个给定值允许出现多次。

  • Default:表示该列是否有默认值,如果有,那么值是多少。

  • Extra:表示可以获取的与给定列有关的附加信息,例如AUTO_INCREMENT等。

5. 过滤数据

使用WHERE关键字

  • 语法
1
2
3
SELECT 	字段1,字段2 
FROM 表名
WHERE 过滤条件
  • 举例
1
2
# 查询员工月薪大于 > 10000的用户
SELECT * FROM employees WHERE salary > 10000;

6. 课后练习

1
2
3
4
5
6
7
8
9
10
11
12
SELECT * FROM employees;
# 1.查询员工12个月的工资总和,并起别名为ANNUAL SALARY
SELECT last_name,salary * 12 * (1 + IFNULL(commission_pct,0)) AS "ANNUAL SALARY" FROM employees;
# 2.查询employees表中去除重复的job_id以后的数据
SELECT DISTINCT job_id FROM employees;
# 3.查询工资大于12000的员工姓名和工资
SELECT last_name AS "empName",salary FROM employees WHERE salary > 12000;
# 4.查询员工号为176的员工的姓名和部门号
SELECT last_name,department_id FROM employees WHERE employee_id = 176;
# 5.显示表 departments 的结构,并查询其中的全部数据
DESC departments;
SELECT * FROM departments;

运算符

1.算术运算符

image-20220403230757726

  • 举例
1
2
3
# 在SQL中+号没有链接作用,仅表示加法运算
SELECT 100,100+10,100-10,100*2,100/2 FROM DUAL; #字符串‘1’会隐式转换为数值
SELECT 100+'1',100+'a' FROM DUAL; # 101,100

2.比较运算符

比较运算符用来对表达式左边的操作数和右边的操作数进行比较,比较的结果为真则返回1,比较结果为假则返回0,其他情况返回NULL

比较运算符经常被用来作为SELECT查询语句的条件来使用,返回符合条件的结果记录。

image-20220403230856878

  • 举例
1
2
3
SELECT 1=2,1='1',1='a' FROM DUAL; 	 # 字符串存在隐式转换,如果数组转换不成功,则看做0
SELECT 'a'='a','a' = 'b' FROM DUAL; # 如果两边都是字符串,则按照ANST的比较规则进行比较
SELECT 1 = NULL,NULL=NULL FROM DUAL; # 只要有NULL参与,结果都是NULL

2.1 安全等于运算符

安全等于运算符<=>与等于运算符=作用是相似的,唯一区别是<=>运算符可以对==NULL==进行判断。在两个操作数为NULL时,其返回值为1,而不为NULL;当一个操作数为NULL时,其返回值为0,而不为NULL。

  • 举例
1
SELECT 1 <=> NULL,NULL <=> NULL FROM DUAL;

此外还有符号类型的运算符

image-20220406224352770

  • 举例
1
2
3
4
5
6
7
8
9
10
11
12
# IS NULL / ISNULL / IS NOT NULL
SELECT * FROM employees WHERE commission_pct IS NULL;
SELECT * FROM employees WHERE ISNULL(commission_pct);
SELECT * FROM employees WHERE commission_pct IS NOT NULL;
# LEAST / GREATEST
SELECT LEAST('a','b','c'),GREATEST('a','b','c') FROM DUAL;
# BETWEEN 条件下限一 AND 条件上限二(查询条件一和条件二之间的数据,保护边界)
SELECT employee_id,last_name,salary FROM employees WHERE salary BETWEEN 6000 AND 8000;
SELECT employee_id,last_name,salary FROM employees WHERE salary >=6000 AND salary <= 8000;
# IN / NOT IN
SELECT employee_id,last_name,salary FROM employees WHERE salary IN(6000,8000);
SELECT employee_id,last_name,salary FROM employees WHERE salary NOT IN(6000,8000);

2.2 LIKE运输符

  • %代表0个或多个不确定的字符
1
2
3
4
5
6
7
# 获取包含‘a’的字符
SELECT last_name FROM employees WHERE last_name LIKE '%a%';
# 获取以‘a’开头的字符
SELECT last_name FROM employees WHERE last_name LIKE 'a%';
# 获取保护‘a’并且包含'e'的字符
SELECT last_name FROM employees WHERE last_name LIKE '%a%' AND last_name LIKE '%e%';
SELECT last_name FROM employees WHERE last_name LIKE '%a%e%' OR last_name LIKE '%e%a%';
  • -代表一个不确定的字符,如果需要获取指定字符就是’_’的需要使用转义字符\,或者使用ESCAPE。如果使用的是\,则可以省略ESCAPE,如果不是则需要加上ESCAPE
1
2
3
# 获取第二个字符是_并且第三个字符是‘a’的名称
SELECT last_name FROM employees WHERE last_name LIKE '_\_a%';
SELECT last_name FROM employees WHERE last_name LIKE '_$_a%' ESCAPE '$';

3. 逻辑运算符

逻辑运算符主要用来判断表达式的真假,在MySQL中,逻辑运算符的返回结构为1,0或者NULL。

MySQL支持4种逻辑运算符如下:

image-20220409153242195

1
2
3
4
5
SELECT employee_id,last_name,salary,department_id FROM employees WHERE NOT department_id = 50
SELECT employee_id,last_name,salary,department_id FROM employees WHERE salary > 6000 AND department_id = 50
SELECT employee_id,last_name,salary,department_id FROM employees WHERE salary > 6000 OR department_id = 50
# XOR,满足A并且不满足B,满足B并且不满足A的集合
SELECT employee_id,last_name,salary,department_id FROM employees WHERE salary > 6000 XOR department_id = 50

4. 位运算符

位运算符是在二进制数上进行计算的运算符。位运算符会优先将操作数变成二进制数,然后进行位运算,最后将计算结构从二进制变回十进制数。

image-20220409154907546

1
2
3
SELECT 12 & 5, 12 | 5, 12 ^ 5 FROM DUAL;# 4,13,9
# 在一定范围内满足:每向左移一位,相当于乘以2,每向右移一位,相当于除以2;
SELECT 4 << 1,8 >> 1 FROM DUAL;# 8,4

5. 课后练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 1.选择工资不在5000到12000的员工的姓名和工资
SELECT last_name,salary FROM employees WHERE NOT salary BETWEEN 5000 AND 12000;
# 2.选择在20或50号部门工作的员工姓名和部门号
SELECT last_name,department_id FROM employees WHERE department_id IN(20,50);
# 3.选择公司中没有管理者的员工姓名及job_id
SELECT last_name,job_id,manager_id FROM employees WHERE manager_id IS NULL;
# 4.选择公司中有奖金的员工姓名,工资和奖金级别
SELECT last_name,salary,commission_pct FROM employees WHERE commission_pct IS NOT NULL;
# 5.选择员工姓名的第三个字母是a的员工姓名
SELECT last_name FROM employees WHERE last_name LIKE '__a%';
# 6.选择姓名中有字母a和k的员工姓名
SELECT last_name FROM employees WHERE last_name LIKE '%a%k%' || last_name LIKE '%k%a%';
# 7.显示出表 employees 表中 first_name 以 'e'结尾的员工信息
SELECT last_name,first_name FROM employees WHERE first_name LIKE '%e';
SELECT last_name,first_name FROM employees WHERE first_name REGEXP 'e$';
# 8.显示出表 employees 部门编号在 80-100 之间的姓名、工种
SELECT last_name,department_id FROM employees WHERE department_id BETWEEN 80 AND 100;
# 9.显示出表 employees 的 manager_id 是 100,101,110 的员工姓名、工资、管理者id
SELECT last_name,salary,manager_id FROM employees WHERE manager_id IN(100,101,110);

排序与分页

1. 排序数据

1.1 排序规则

  • 使用ORDER BY子句排序
    • ASC(ascend):升序,默认排序规则
    • DESC(descend):降序
  • ORDER BY字句在WHERE的结尾

1.2 单列排序

1
2
# 对所有员工的工资进行升序排序
SELECT last_name,salary FROM employees ORDER BY salary;

1.3 多列排序

1
2
# 对所有员工的工资进行升序排序,如果工资相同,则对员工的部门进行降序排序
SELECT last_name,salary,department_id FROM employees ORDER BY salary,department_id DESC;
  • 可以使用不在SELECT列表中的列排序
  • 在对多列排序的时候,首先必须是第一列有相同的值,才会对第二列进行排序。如果第一列都撒唯一的,则不会对第二列进行排序。

2. 分页

  • 语法格式

    LIMIT 位置偏移量,行数

    Mysql8.0语法规则

    LIMIT 行数 OFFSET 位置偏移量

    位置偏移量为0可以省略

  • 分页规则

1
2
3
4
5
6
# 显示第1页数据,每页20条
SELECT last_name,salary FROM employees LIMIT 0,20;
# 显示第2页数据,每页20条
SELECT last_name,salary FROM employees LIMIT 1,20;
# 显示第pageNo页数据,每页pageSize条
# LIMIT (pageNo -1) * pageSize,pageSize;
  • 注意:LIMIT字句必须放在整个SELECT语句的最后。

约束返回结构的数量可以==减少数据库表的网络传播量==,==也可以提升查询效率==。好处就是我们知道结果只有1条,就可以使用limit 1,告诉SELECT语句只需要查询一条记录即可,这样做的好处就是不需要扫描完整的表,只需要检索到一条符合条件的记录即可返回。

3. 课后练习

1
2
3
4
5
6
#1. 查询员工的姓名和部门号和年薪,按年薪降序,按姓名升序显示
SELECT last_name,department_id,salary * 12 AS annyual_salary FROM employees ORDER BY annyual_salary DESC,last_name;
#2. 选择工资不在 8000 到 17000 的员工的姓名和工资,按工资降序,显示第21到40位置的数据
SELECT last_name,salary FROM employees WHERE NOT salary BETWEEN 8000 AND 17000 ORDER BY salary DESC LIMIT 20,20;
#3. 查询邮箱中包含 e 的员工信息,并先按邮箱的字节数降序,再按部门号升序
SELECT last_name,email,department_id FROM employees WHERE email LIKE '%e%' ORDER BY LENGTH(email) DESC,department_id ASC;

多表查询


多表查询,也称关联查询,指两个或更多个表之间完成查询操作。

前提条件:这些一起查询的表之间是有关系的(一对一、一对多),它们之间一定是有关联字段,这个关联字段可能建立了外键,也可能没有建立外键。比如:员工表和部门表,两个表之间依靠部门编号关联

1. 笛卡尔积(或交叉连接)的理解

笛卡尔积是一个数学运算。假设我有两个集合X和Y,那么X和Y的笛卡尔积就是X和Y的所有可能组合。也就是第一个对象来自于X,第二个对象来自于Y的所有可能。组合的个数即为两个集合中元素个数的乘积数。

1
2
3
4
5
6
7
8
9
10
11
12
# 笛卡尔积错误,没加链接条件
SELECT employee_id,department_name
FROM employees,departments;
# 解决错误,多表查询
SELECT employee_id,department_name
FROM employees,departments
WHERE employees.department_id = departments.department_id;
# 如果在多个表中都存在该字段,则该字段必须标明是哪个表的
# 建议:从sql优化的角度,建议多表查询时,每个字段前都指明其所在的表
SELECT employee_id,department_name,departments.department_id
FROM employees,departments
WHERE employees.department_id = departments.department_id;
1
2
3
4
5
# 练习:查询员工的employee_id,last_name,department_name,city
SELECT e.employee_id,e.last_name,d.department_id,l.city
FROM employees e,departments d,locations l
WHERE e.department_id = d.department_id
AND d.location_id = l.location_id;

2. 多表查询的分类讲解

  • 等值连接和非等值链接(SQL92语法)
1
2
3
4
5
6
7
8
9
10
# 等值链接
SELECT employee_id,department_name
FROM employees,departments
WHERE employees.department_id = departments.department_id;
# 非等值链接
# 查询员工的工资级别
SELECT e.employee_id,e.last_name,e.salary,j.grade_level
FROM employees e,job_grades j
WHERE e.salary BETWEEN j.lowest_sal AND j.highest_sal
ORDER BY grade_level;
  • 自链接和非自链接(SQL92语法)
1
2
3
4
5
6
7
8
# 自链接查询,查询员工id,员工姓名及管理者id和姓名
SELECT emp1.employee_id,emp1.last_name,emp2.manager_id,emp2.last_name
FROM employees emp1,employees emp2
WHERE emp1.manager_id = emp2.employee_id
# 非自链接
SELECT employee_id,department_name
FROM employees,departments
WHERE employees.department_id = departments.department_id;
  • 内链接和外链接

    内链接:合并具有同一列的两个以上的表的行,结果集中不包含一个表于另一个表不匹配的行

    外链接:两个表在链接过程中除了返回满足链接条件的行以外,还返回左(或右)表中不满足条件的行,这种称为左(或右)外链接。没有匹配的行时,结果集中相应的列为空(NULL)。

    • 如果是左外链接,则链接条件左边的表也称为主表,右边的表称为从表
    • 如果是右外链接,则链接条件右边的表也称为主表,左边的表称为从表

3. SQL99语法实现夺表查询

SQL99语法多表查询规则

1
2
3
4
SELECT table1.column, table2.column,table3.column
FROM table1
JOIN table2 ON table1 和 table2 的连接条件
JOIN table3 ON table2 和 table3 的连接条件

语法说明:

  • 可以使用 ON 子句指定额外的连接条件。
  • 这个连接条件是与其它条件分开的。
  • ON 子句使语句具有更高的易读性。
  • 关键字 JOININNER JOINCROSS JOIN的含义是一样的,都表示内连接

3.1 内链接使用(INNER JOIN)实现

  • 语法
1
2
3
4
SELECT 字段列表
FROM A表 INNER JOIN B表
ON 关联条件
WHERE 等其他子句;
  • 举例
1
2
3
4
# SQL99语法实现内链接
SELECT employee_id,department_name
FROM employees INNER JOIN departments
ON employees.department_id = departments.department_id;

3.2 外连接(OUTER JOIN)的实现

3.2.1 左外链接

1
2
3
4
# SQL99语法实现左外链接,OUTER可以省略
SELECT employee_id,department_name
FROM employees LEFT OUTER JOIN departments
ON employees.department_id = departments.department_id;

3.2.2 右外链接

1
2
3
4
# SQL99语法实现左外链接,OUTER可以省略
SELECT employee_id,department_name
FROM employees RIGHT OUTER JOIN departments
ON employees.department_id = departments.department_id;

3.2.3 全外链接(满外链接)

1
2
3
4
# SQL99语法实现全外链接,OUTER可以省略,MYSQL不支持FULL OUTER JOIN
SELECT employee_id,department_name
FROM employees FULL OUTER JOIN departments
ON employees.department_id = departments.department_id;

4. UNION关键字的使用

==合并查询结果==

利用UNION关键字,可以给出多条SELECT语句,并将他们的结果组合成单个结果集。合并时,两个表对应的列数和数据类型必须相同,并且对应。各个SELECT语句之间使用UNIONUNION ALL关键字分隔

语法格式

1
2
3
SELECT column,... FROM table1
UNION [ALL]
SELECT column,... FROM table2

==UNION操作符==

UNION操作符返回两个查询的结果集的并集,去除重复记录。

==UNION ALL操作符==

UNION ALL操作符返回两个查询的结果集的并集,对于两个结果集的重复部分,不去重。

注意:执行UNION ALL语句时所需要的资源比UNION语句少。如果明确知道合并数据后的结果数据 不存在重复数据,或者不需要去除重复的数据,则尽量使用UNION ALL语句,以提高数据查询的效 率。

5. 7中JOIN的实现

image-20220410162255641

  • 中图:内链接
1
2
3
SELECT e.employee_id,d.department_name 
FROM employees e INNER JOIN departments d
ON e.department_id = d.department_id;
  • 左上图:左外链接
1
2
3
SELECT e.employee_id,d.department_name 
FROM employees e LEFT JOIN departments d
ON e.department_id = d.department_id;
  • 右上图:右外链接
1
2
3
SELECT e.employee_id,d.department_name 
FROM employees e RIGHT JOIN departments d
ON e.department_id = d.department_id;
  • 左中图:左外链接取右表为空的部分
1
2
3
4
SELECT e.employee_id,d.department_name 
FROM employees e LEFT JOIN departments d
ON e.department_id = d.department_id
WHERE d.department_id IS NULL;
  • 右中图:右外链接取左表为空的部分
1
2
3
4
SELECT e.employee_id,d.department_name 
FROM employees e RIGHT JOIN departments d
ON e.department_id = d.department_id
WHERE e.employee_id IS NULL;
  • 左下图: 左上图 UNION ALL 右中图(或左中图 UNION ALL 右上图 )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
SELECT e.employee_id,d.department_name 
FROM employees e LEFT JOIN departments d
ON e.department_id = d.department_id
UNION ALL
SELECT e.employee_id,d.department_name
FROM employees e RIGHT JOIN departments d
ON e.department_id = d.department_id
WHERE e.employee_id IS NULL;
# 不推荐(需要多一步去重操作,效率下降)
SELECT e.employee_id,d.department_name
FROM employees e LEFT JOIN departments d
ON e.department_id = d.department_id
UNION
SELECT e.employee_id,d.department_name
FROM employees e RIGHT JOIN departments d
ON e.department_id = d.department_id;
  • 右下图
1
2
3
4
5
6
7
8
9
SELECT e.employee_id,d.department_name 
FROM employees e LEFT JOIN departments d
ON e.department_id = d.department_id
WHERE d.department_id IS NULL
UNION ALL
SELECT e.employee_id,d.department_name
FROM employees e RIGHT JOIN departments d
ON e.department_id = d.department_id
WHERE e.employee_id IS NULL;

6. SQL99语法新特性

6.1 自然链接

SQL99在SQL92的基础上提供了一些特殊语法,比如NATURAL JOIN用来表示自然连接。我们可以把自然连接理解为SQL92中的等值连接。它会帮你自动查询两张连接表中所有相同的字段,然后进行等值链接

1
2
3
4
5
6
7
SELECT e.employee_id,e.last_name,d.department_name
FROM employees e INNER JOIN departments d
ON e.department_id = d.department_id
AND e.manager_id = d.manager_id;
# 自然链接
SELECT e.employee_id,e.last_name,d.department_name
FROM employees e NATURAL JOIN departments d;

6.2 USING链接

SQL99还支持中使用USING对同名字段进行值链接。但只能配合JOIN关键字一起使用。

1
2
3
4
5
6
7
SELECT e.employee_id,d.department_name 
FROM employees e INNER JOIN departments d
ON e.department_id = d.department_id;
# USING链接
SELECT e.employee_id,d.department_name
FROM employees e INNER JOIN departments d
USING(department_id);

7. 课后练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# 1.显示所有员工的姓名,部门号和部门名称。
SELECT e.last_name,d.department_id,d.department_name
FROM employees e LEFT JOIN departments d
ON e.department_id = d.department_id;
# 2.查询90号部门员工的job_id和90号部门的location_id
SELECT e.job_id,d.location_id
FROM employees e INNER JOIN departments d
ON e.department_id = d.department_id
WHERE d.department_id = 90;
# 3.选择所有有奖金的员工的 last_name , department_name , location_id , city
SELECT e.last_name,e.commission_pct,d.department_name,d.location_id,l.city
FROM employees e LEFT JOIN departments d
ON e.department_id = d.department_id
LEFT JOIN locations l
ON d.location_id = l.location_id
WHERE e.commission_pct IS NOT NULL;
# 4.选择city在Toronto工作的员工的 last_name , job_id , department_id , department_name
SELECT e.last_name,e.job_id,d.department_id,d.department_name,l.city
FROM employees e INNER JOIN departments d
ON e.department_id = d.department_id
INNER JOIN locations l
ON d.location_id = l.location_id
WHERE city = 'Toronto';
# 5.查询员工所在的部门名称、部门地址、姓名、工作、工资,其中员工所在部门的部门名称为’Executive’
SELECT e.last_name,e.salary,d.department_name,l.street_address
FROM departments d LEFT JOIN employees e
ON e.department_id = d.department_id
LEFT JOIN locations l
ON d.location_id = l.location_id
WHERE department_name = 'Executive';
# 6.选择指定员工的姓名,员工号,以及他的管理者的姓名和员工号,结果类似于下面的格式
# employees Emp# manager Mgr#
# kochhar 101 king 100
SELECT e.last_name 'employees',e.employee_id 'Emp#',e1.last_name 'manager',e1.manager_id 'Mgr#'
FROM employees e LEFT JOIN employees e1
ON e.manager_id = e1.employee_id;
# 7.查询哪些部门没有员工
SELECT e.last_name,d.department_name
FROM employees e RIGHT JOIN departments d
ON e.department_id = d.department_id
WHERE e.last_name IS NULL;
# 8. 查询哪个城市没有部门
SELECT d.department_name,l.city
FROM departments d
RIGHT JOIN locations l
ON d.location_id = l.location_id
WHERE d.department_name IS NULL;
# 9. 查询部门名为 Sales 或 IT 的员工信息
SELECT e.last_name,e.salary,d.department_name
FROM employees e INNER JOIN departments d
ON e.department_id = d.department_id
WHERE d.department_name IN('Sales','IT');

单行函数

单行函数

  • 操作数据对象
  • 接受参数返回一个结果
  • 只对一行进行变换
  • 每行返回一个结果
  • 可以嵌套
  • 参数可以是一列或一个值

1. 数值函数

1.1 基本函数

函数 用法
ABS(x) 返回x的绝对值
SIGN(X) 返回X的符号。正数返回1,负数返回-1,0返回0
PI() 返回圆周率的值
CEIL(x),CEILING(x) 返回大于或等于某个值的最小整数
FLOOR(x) 返回小于或等于某个值的最大整数
LEAST(e1,e2,e3…) 返回列表中的最小值
GREATEST(e1,e2,e3…) 返回列表中的最大值
MOD(x,y) 返回X除以Y后的余数
RAND() 返回0~1的随机值
RAND(x) 返回0~1的随机值,其中x的值用作种子值,相同的X值会产生相同的随机 数
ROUND(x) 返回一个对x的值进行四舍五入后,最接近于X的整数
ROUND(x,y) 返回一个对x的值进行四舍五入后最接近X的值,并保留到小数点后面Y位
TRUNCATE(x,y) 返回数字x截断为y位小数的结果
SQRT(x) 返回x的平方根。当X的值为负数时,返回NULL

1.2 角度与弧度互换函数

函数 用法
RADIANS(x) 将角度转化为弧度,其中,参数x为角度值
DEGREES(x) 将弧度转化为角度,其中,参数x为弧度值

1.3 三角函数

函数 用法
SIN(x) 返回x的正弦值,其中,参数x为弧度值
ASIN(x) 返回x的反正弦值,即获取正弦为x的值。如果x的值不在-1到1之间,则返回NULL
COS(x) 返回x的余弦值,其中,参数x为弧度值
ACOS(x) 返回x的反余弦值,即获取余弦为x的值。如果x的值不在-1到1之间,则返回NULL
TAN(x) 返回x的正切值,其中,参数x为弧度值
ATAN(x) 返回x的反正切值,即返回正切值为x的值
ATAN2(m,n) 返回两个参数的反正切值
COT(x) 返回x的余切值,其中,X为弧度值

1.4 指数和对数

函数 用法
POW(x,y),POWER(X,Y) 返回x的y次方
EXP(X) 返回e的X次方,其中e是一个常数,2.718281828459045
LN(X),LOG(X) 返回以e为底的X的对数,当X <= 0 时,返回的结果为NULL
LOG10(X) 返回以10为底的X的对数,当X <= 0 时,返回的结果为NULL
LOG2(X) 返回以2为底的X的对数,当X <= 0 时,返回NULL

1.5 进制之间的转换

函数 用法
BIN(x) 返回x的二进制编码
HEX(x) 返回x的十六进制编码
OCT(x) 返回x的八进制编码
CONV(x,f1,f2) 返回f1进制数变成f2进制数

2. 字符串函数

函数 用法
ASCII(S) 返回字符串S中的第一个字符的ASCII码值
CHAR_LENGTH(s) 返回字符串s的字符数。作用与CHARACTER_LENGTH(s)相同
LENGTH(s) 返回字符串s的字节数,和字符集有关
CONCAT(s1,s2,……,sn) 连接s1,s2,……,sn为一个字符串
CONCAT_WS(x,s1,s2,……,sn) 同CONCAT(s1,s2,…)函数,但是每个字符串之间要加上x
INSERT(str,idx,len,replacestr) 将字符串str从第idx位置开始,len个字符长的子串替换为字符串replacestr
REPLACE(str, a, b) 用字符串b替换字符串str中所有出现的字符串a
UPPER(s) 或 UCASE(s) 将字符串s的所有字母转成大写字母
LOWER(s) 或LCASE(s) 将字符串s的所有字母转成小写字母
LEFT(str,n) 返回字符串str最左边的n个字符
RIGHT(str,n) 返回字符串str最右边的n个字符
LPAD(str, len, pad) 用字符串pad对str最左边进行填充,直到str的长度为len个字符
RPAD(str ,len, pad) 用字符串pad对str最右边进行填充,直到str的长度为len个字符
LTRIM(s) 去掉字符串s左侧的空格
RTRIM(s) 去掉字符串s右侧的空格
TRIM(s) 去掉字符串s开始与结尾的空格
TRIM(s1 FROM s) 去掉字符串s开始与结尾的s1
TRIM(LEADING s1 FROM s) 去掉字符串s开始处的s1
TRIM(TRAILING s1 FROM s) 去掉字符串s结尾处的s1
REPEAT(str, n) 返回str重复n次的结果
SPACE(n) 返回n个空格
STRCMP(s1,s2) 比较字符串s1,s2的ASCII码值的大小
SUBSTR(s,index,len) 返回从字符串s的index位置其len个字符,作用与SUBSTRING(s,n,len)、 MID(s,n,len)相同
LOCATE(substr,str) 返回字符串substr在字符串str中首次出现的位置,作用于POSITION(substr IN str)、INSTR(str,substr)相同。未找到,返回0
ELT(m,s1,s2,…,sn) 返回指定位置的字符串,如果m=1,则返回s1,如果m=2,则返回s2,如 果m=n,则返回sn
FIELD(s,s1,s2,…,sn) 返回字符串s在字符串列表中第一次出现的位置
FIND_IN_SET(s1,s2) 返回字符串s1在字符串s2中出现的位置。其中,字符串s2是一个以逗号分 隔的字符串
REVERSE(s) 返回s反转后的字符串
NULLIF(value1,value2) 比较两个字符串,如果value1与value2相等,则返回NULL,否则返回 value1

3. 日期和时间函数

3.1 获取日期、时间

函数 用法
CURDATE() ,CURRENT_DATE() 返回当前日期,只包含年、 月、日
CURTIME() , CURRENT_TIME() 返回当前时间,只包含时、 分、秒
NOW() / SYSDATE() / CURRENT_TIMESTAMP() / LOCALTIME() / LOCALTIMESTAMP() 返回当前系统日期和时间
UTC_DATE() 返回UTC(世界标准时间) 日期
UTC_TIME() 返回UTC(世界标准时间) 时间
1
2
# 获取日期、时间、当前时间
SELECT CURDATE(),CURTIME(),NOW() FROM DUAL;

3.2 日期和时间戳的转换

函数 用法
UNIX_TIMESTAMP() 以UNIX时间戳的形式返回当前时间。SELECT UNIX_TIMESTAMP() - >1634348884
UNIX_TIMESTAMP(date) 将时间date以UNIX时间戳的形式返回。
FROM_UNIXTIME(timestamp) 将UNIX时间戳的时间转换为普通格式的时间
1
2
# 日期和时间戳的转换
SELECT UNIX_TIMESTAMP(),UNIX_TIMESTAMP("2021-10-01"),FROM_UNIXTIME(1649684431) FROM DUAL;

3.3 获取月份、星期、星期数、天数等函数

函数 用法
YEAR(date) / MONTH(date) / DAY(date) 返回具体的日期值
HOUR(time) / MINUTE(time) / SECOND(time) 返回具体的时间值
MONTHNAME(date) 返回月份:January,…
DAYNAME(date) 返回星期几:MONDAY,TUESDAY…..SUNDAY
WEEKDAY(date) 返回周几,注意,周1是0,周2是1,。。。周日是6
QUARTER(date) 返回日期对应的季度,范围为1~4
WEEK(date) , WEEKOFYEAR(date) 返回一年中的第几周
DAYOFYEAR(date) 返回日期是一年中的第几天
DAYOFMONTH(date) 返回日期位于所在月份的第几天
DAYOFWEEK(date) 返回周几,注意:周日是1,周一是2,。。。周六是 7
1
2
3
4
5
6
7
8
9
# 获取年、月、日、时、分、秒
SELECT YEAR(CURDATE()),MONTH(CURDATE()),DAY(CURDATE()),
HOUR(CURTIME()),MINUTE(NOW()),SECOND(NOW())
FROM DUAL;
# 获取月份、星期几(英文)、周几、季度、一年中的第几周、一年中的第几天、月份中的第几天、周几
SELECT MONTHNAME(NOW()),DAYNAME(NOW()),WEEKDAY(NOW()),
QUARTER(NOW()),WEEK(NOW()),DAYOFYEAR(NOW()),
DAYOFMONTH(NOW()),DAYOFWEEK(NOW())
FROM DUAL;

3.4 日期操作函数

函数 用法
EXTRACT(type FROM date) 返回指定日期中特定的部分,type返回指定的值

type的取值

type的取值 描述
MICROSECOND 返回毫秒数
SECOND 返回秒数
MINUTE 返回分钟数
HOUR 返回小时数
DAY 返回天数
WEEK 返回日期在一年中的第几个星期
MONTH 返回日期在一年中的第几个月
QUARTER 返回日期在一年中的第几个季度
YEAR 返回日期的年份
SECOND_MICROSECOND 返回秒和毫秒值
MINUTE_MICROSECOND 返回分钟和毫秒值
MINUTE_SECOND 返回分钟和秒值
HOUR_MICROSECOND 返回小时和毫秒值
HOUR_SECOND 返回小时和秒值
HOUR MINUTE 返回小时和分钟值
DAY_MICROSECOND 返回天和毫秒值
DAY_SECOND 返回天和秒值
DAY_MINUTE 返回天和分钟值
DAY_HOUR 返回天和小时
YEAR_MONTH 返回年和月
1
2
3
4
# 获取当前时间的分钟数、一年中的第几个星期、一年中的第几个季度、分钟和秒值
SELECT EXTRACT(MINUTE FROM NOW()),EXTRACT( WEEK FROM NOW()),
EXTRACT( QUARTER FROM NOW()),EXTRACT( MINUTE_SECOND FROM NOW())
FROM DUAL;

3.5 时间和秒钟转换的函数

函数 用法
TIME_TO_SEC(time) 将 time 转化为秒并返回结果值。转化的公式为: 小时*3600+分钟*60+秒
SEC_TO_TIME(seconds) 将 seconds 描述转化为包含小时、分钟和秒的时间
1
2
# 获取当前时间的秒值(今天0点到当前时间的秒数)和秒数转换后的时间值
SELECT TIME_TO_SEC(CURTIME()),SEC_TO_TIME(81314) FROM DUAL;

3.6 计算日期和时间的函数

函数 用法
DATE_ADD(datetime, INTERVAL expr type) ,ADDDATE(date,INTERVAL expr type) 返回与date相差INTERVAL时间段的日期时间
DATE_SUB(date,INTERVAL expr type), SUBDATE(date,INTERVAL expr type) 返回与date相差INTERVAL时间间隔的日期

type取值

image-20220411224045629

1
2
3
4
5
6
7
8
9
10
11
12
13
# 计算当前时间后一年和前一年的时间
SELECT
NOW(),ADDDATE(NOW(),INTERVAL 1 YEAR),
NOW(),ADDDATE(NOW(),INTERVAL -1 YEAR)
FROM DUAL;
# 计算当前时间的1天后、1分1秒后、前一年、后1年1个月的时间
SELECT
NOW(),
ADDDATE(NOW(),INTERVAL 1 DAY) AS col1,
DATE_ADD(NOW(),INTERVAL '1_1' MINUTE_SECOND) AS col2,
DATE_ADD(NOW(), INTERVAL -1 YEAR) AS col3, #可以是负数
DATE_ADD(NOW(), INTERVAL '1_1' YEAR_MONTH) AS col4 #需要单引号
FROM DUAL;

==第二组:==

函数 用法
ADDTIME(time1,time2) 返回time1加上time2的时间。当time2为一个数字时,代表的是 ,可以为负数
SUBTIME(time1,time2) 返回time1减去time2后的时间。当time2为一个数字时,代表的是 ,可以为负数
DATEDIFF(date1,date2) 返回date1 - date2的日期间隔天数
TIMEDIFF(time1, time2) 返回time1 - time2的时间间隔
FROM_DAYS(N) 返回从0000年1月1日起,N天以后的日期
TO_DAYS(date) 返回日期date距离0000年1月1日的天数
LAST_DAY(date) 返回date所在月份的最后一天的日期
MAKEDATE(year,n) 针对给定年份与所在年份中的天数返回一个日期
MAKETIME(hour,minute,second) 将给定的小时、分钟和秒组合成时间并返回
PERIOD_ADD(time,n) 返回time加上n后的时间
1
2
3
4
5
6
7
SELECT
ADDTIME(NOW(),20),SUBTIME(NOW(),30),SUBTIME(NOW(),'1:1:3'),
DATEDIFF(NOW(),'2021-10-01'),TIMEDIFF(NOW(),'2021-10-25 22:10:10'),
FROM_DAYS(366),TO_DAYS('0000-12-25'),
LAST_DAY(NOW()),MAKEDATE(YEAR(NOW()),12),MAKETIME(10,21,23),
PERIOD_ADD(20200101010101,10)
FROM DUAL;

3.7 日期格式化与解析

函数 用法
DATE_FORMAT(date,fmt) 按照字符串fmt格式化日期date值
TIME_FORMAT(time,fmt) 按照字符串fmt格式化时间time值
GET_FORMAT(date_type,format_type) 返回日期字符串的显示格式
STR_TO_DATE(str, fmt) 按照字符串fmt对str进行解析,解析为一个日期

上述 非GET_FORMAT函数中fmt参数常用的格式符:

格 式 符 说明 格式 符 说明
%Y 4位数字表示年份 %y 表示两位数字表示年份
%M 月名表示月份(January,….) %m 两位数字表示月份 (01,02,03。。。)
%b 缩写的月名(Jan.,Feb.,….) %c 数字表示月份(1,2,3,…)
%D 英文后缀表示月中的天数 (1st,2nd,3rd,…) %d 两位数字表示月中的天数(01,02…)
%e 数字形式表示月中的天数 (1,2,3,4,5…..)
%H 两位数字表示小数,24小时制 (01,02..) %h 和%I 两位数字表示小时,12小时制 (01,02..)
%k 数字形式的小时,24小时制(1,2,3) %l 数字形式表示小时,12小时制 (1,2,3,4….)
%i 两位数字表示分钟(00,01,02) %S 和%s 两位数字表示秒(00,01,02…)
%W 一周中的星期名称(Sunday…) %a 一周中的星期缩写(Sun., Mon.,Tues.,..)
%w 以数字表示周中的天数 (0=Sunday,1=Monday….)
%j 以3位数字表示年中的天数(001,002…) %U 以数字表示年中的第几周, (1,2,3。。)其中Sunday为周中第一 天
%u 以数字表示年中的第几周, (1,2,3。。)其中Monday为周中第一 天
%T 24小时制 %r 12小时制
%p AM或PM %% 表示%
1
SELECT DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s'),TIME_FORMAT(NOW(), '%H:%i:%s') FROM DUAL;

GET_FORMAT函数中date_typeformat_type参数取值如下:

image-20220411233225862

4. 流程控制函数

函数 用法
IF(value,value1,value2) 如果value的值为TRUE,返回value1,否则返回value2
IFNULL(value1, value2) 如果value1不为NULL,返回value1,否则返回value2
CASE WHEN 条件1 THEN 结果1 WHEN 条件2 THEN 结果2 END 相当于Java的if…else if…else…
CASE expr WHEN 常量值1 THEN 值1 WHEN 常量值1 THEN END 相当于Java的switch…case…
1
2
SELECT IF(1 > 0,'正确','错误') FROM DUAL;
->正确
1
2
SELECT IFNULL(null,'Hello Word') FROM DUAL;
->Hello Word
1
2
3
4
5
6
7
SELECT CASE 
WHEN 1 > 0 THEN '1 > 0'
WHEN 2 > 0 THEN '2 > 0'
ELSE '3 > 0'
END
FROM DUAL;
->1 > 0
1
2
3
4
5
6
7
SELECT CASE 1 
WHEN 1 THEN '我是1'
WHEN 2 THEN '我是2'
ELSE '你是谁'
END
FROM DUAL;
->我是1

5. 加密和解密函数

函数 用法
PASSWORD(str) 返回字符串str的加密版本,41位长的字符串。加密结果不可逆 ,常用于用户的密码加密
MD5(str) 返回字符串str的md5加密后的值,也是一种加密方式。若参数为NULL,则会返回NULL
SHA(str) 从原明文密码str计算并返回加密后的密码字符串,当参数为NULL时,返回NULL。 SHA加密算法比MD5更加安全
ENCODE(value,password_seed) 返回使用password_seed作为加密密码加密value
DECODE(value,password_seed) 返回使用password_seed作为加密密码解密value

聚合函数

1. 常见的聚合函数

  • AVG()
  • SUM()
  • MAX()
  • MIN()
  • COUNT()

1.1 AVG 和 SUM函数

可以对数值型数据使用AVG 和 SUM 函数。

1
SELECT AVG(salary), MAX(salary),MIN(salary), SUM(salary) FROM employees;

1.2 MAX 和 MIN函数

可以对任意数据类型的数据使用 MIN 和 MAX 函数。

1
SELECT MIN(hire_date), MAX(hire_date) FROM employees;

1.3 COUNT 函数

COUNT()返回表中记录总数,适用于*任意数据类型

1
SELECT COUNT(*) FROM employees WHERE department_id = 50;

问题:用count(*),count(1),count(列名)谁好呢?

其实,对于MyISAM引擎的表是没有区别的。这种引擎内部有一计数器在维护着行数。

Innodb引擎的表用count(*),count(1)直接读行数,复杂度是O(n),因为innodb真的要去数一遍。但好

于具体的count(列名)。

能不能使用count(列名)替换count(*)?

不要使用 count(列名)来替代 count(*) , count(*) 是 SQL92 定义的标准统计行数的语法,跟数

据库无关,跟 NULL 和非 NULL 无关。

说明:count(*)会统计值为 NULL 的行,而 count(列名)不会统计此列为 NULL 值的行。

2. GROUP BY

可以使用关键字GROUP BY进行分组。搭配聚合函数使用。

1
2
# 查询各个部门的平均工资
SELECT AVG(salary),department_id FROM employees GROUP BY department_id;

==使用多个列分组==

1
2
3
4
# 查询各个department_id、job_id的平均工资
SELECT department_id,job_id,AVG(salary) FROM employees GROUP BY department_id,job_id;
# 等同于
SELECT department_id,job_id,AVG(salary) FROM employees GROUP BY job_id,department_id;

使用 WITH ROLLUP 关键字之后,在所有查询出的分组记录之后增加一条记录,该记录计算查询出的所有记录的总和,即统计记录数量。

1
SELECT department_id,AVG(salary) FROM employees WHERE department_id > 80 GROUP BY department_id WITH ROLLUP;

注意:

SELECT中出现的非组函数的字段必须声明在GROUP BY中,反之,GROUP BY中声明的字段可以不出现在SELECT中。

当使用ROLLUP时,不能同时使用ORDER BY子句进行结果排序,即ROLLUP和ORDER BY是互相排斥的。

3. HAVING

3.1 基本使用

过滤分组:HAVING子句(用来过滤数据)

  1. 行已经被分组。
  2. 使用了聚合函数。
  3. 满足HAVING 子句中条件的分组将被显示。
  4. HAVING 不能单独使用,必须要跟 GROUP BY 一起使用。
  5. 如果过滤条件中使用了聚合函数,则必须使用HAVING替换WHERE。
  6. HAVGIN声明必须在GROUP BY后面。
1
2
3
4
5
# 查询部门最高工资大于1万的
SELECT department_id,MAX(salary)
FROM employees
GROUP BY department_id
HAVING MAX(salary) > 10000;

3.2 WHERE 和 HAVING 的对比

优点 缺点
WHERE 先筛选数据再关联,执行效率高 不能使用分组中的计算函数进行筛选
HAVING 可以使用分组中的计算函数 在最后的结果集中进行筛选,执行效率较低

==开发中的选择:==

WHERE和HAVING也不是互相排斥的,我们可以在一个查询里面同时使用WHERE和HAVING。包含分组统计函数的条件用HAVING,普通条件用WHERE。这样,我们就既利用了WHERE条件的高效快速,又发挥了HAVING可以使用包含分组统计函数的查询条件的优点。当数据量特别大的时候,运行效率会有很大的差别。

4. SELECT的执行过程

4.1 查询数据的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#方式1: 
SELECT ...,....,...
FROM ...,...,....
WHERE 多表的连接条件 AND 不包含组函数的过滤条件
GROUP BY ...,...
HAVING 包含组函数的过滤条件
ORDER BY ... ASC/DESC
LIMIT ...,...

#方式2:
SELECT ...,....,...
FROM ...
JOIN ... ON 多表的连接条件
JOIN ... ON ...
WHERE 不包含组函数的过滤条件 AND/OR 不包含组函数的过滤条件
GROUP BY ...,...
HAVING 包含组函数的过滤条件
ORDER BY ... ASC/DESC
LIMIT ...,...

#其中:
#(1)from:从哪些表中筛选
#(2)on:关联多表查询时,去除笛卡尔积
#(3)where:从表中筛选的条件
#(4)group by:分组依据
#(5)having:在统计结果中再次筛选
#(6)order by:排序
#(7)limit:分页

关键字的顺序

1
SELECT...FROM...WHERE...GROUP BY...HAVING...ORDER BY...LIMIT...

SELECT语句的执行顺序

1
FROM -> ON -> (LEFT/RIGHT JOIN) -> WHERE -> GROUP BY -> HAVING -> SELECT column -> DISTINCT -> ORDER BY -> LIMIT

4.2 SELECT的执行过程

SELECT 是先执行 FROM 这一步的。在这个阶段,如果是多张表联查,还会经历下面的几个步骤:

  1. 首先先通过 CROSS JOIN 求笛卡尔积,相当于得到虚拟表 vt(virtual table)1-1;

  2. 通过 ON 进行筛选,在虚拟表 vt1-1 的基础上进行筛选,得到虚拟表 vt1-2;

  3. 添加外部行。如果我们使用的是左连接、右链接或者全连接,就会涉及到外部行,也就是在虚拟表 vt1-2 的基础上增加外部行,得到虚拟表 vt1-3。
    当然如果我们操作的是两张以上的表,还会重复上面的步骤,直到所有表都被处理完为止。这个过程得到是我们的原始数据(FROM ... LEFT/RIGHT JOIN 后的数据)

  4. 当我们拿到了查询数据表的原始数据,也就是最终的虚拟表 vt1,就可以在此基础上再进行WHERE阶段 。在这个阶段中,会根据 vt1表的结果进行筛选过滤,得到虚拟表 vt2

  5. 然后进入第三步和第四步,也就是GROUP 和 HAVING 阶段。在这个阶段中,实际上是在虚拟表 vt2 的基础上进行分组和分组过滤,得到中间的虚拟表vt3vt4

  6. 当我们完成了条件筛选部分之后,就可以筛选表中提取的字段,也就是进入到SELECT 和 DISTINCT阶段

首先在 SELECT 阶段会提取想要的字段,然后在 DISTINCT 阶段过滤掉重复的行,分别得到中间的虚拟表vt5-1vt5-2

  1. 当我们提取了想要的字段数据之后,就可以按照指定的字段进行排序,也就是ORDER BY 阶段,得到虚拟表 vt6

  2. 最后在 vt6 的基础上,取出指定行的记录,也就是LIMIT 阶段,得到最终的结果,对应的是虚拟表vt7 。

当然我们在写 SELECT 语句的时候,不一定存在所有的关键字,相应的阶段就会省略。

同时因为 SQL 是一门类似英语的结构化查询语言,所以我们在写 SELECT 语句的时候,还要注意相应的

关键字顺序,所谓底层运行的原理,就是我们刚才讲到的执行顺序。

5. 课后练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#1.where子句可否使用组函数进行过滤?
No
#2.查询公司员工工资的最大值,最小值,平均值,总和
SELECT MAX(salary),MIN(salary),AVG(salary),SUM(salary)
FROM employees;
#3.查询各job_id的员工工资的最大值,最小值,平均值,总和
SELECT job_id,MAX(salary),MIN(salary),AVG(salary),SUM(salary)
FROM employees
GROUP BY job_id;
#4.选择具有各个job_id的员工人数
SELECT job_id,COUNT(*)
FROM employees
GROUP BY job_id;
# 5.查询员工最高工资和最低工资的差距(DIFFERENCE)
SELECT MAX(salary)-MIN(salary) "DIFFERENCE"
FROM employees;
# 6.查询各个管理者手下员工的最低工资,其中最低工资不能低于6000,没有管理者的员工不计算在内
SELECT MIN(salary)
FROM employees
WHERE manager_id IS NOT NULL
GROUP BY manager_id
HAVING MIN(salary) >= 6000;
# 7.查询所有部门的名字,location_id,员工数量和平均工资,并按平均工资降序
SELECT d.department_name,d.location_id,COUNT(*),AVG(salary)
FROM departments d LEFT JOIN employees e
ON d.department_id = e.department_id
GROUP BY d.department_name,location_id
ORDER BY AVG(salary) DESC;
# 8.查询每个工种、每个部门的部门名、工种名和最低工资
SELECT department_name,job_id,MIN(salary)
FROM departments d LEFT JOIN employees e
ON d.department_id = e.department_id
GROUP BY department_name,job_id;

子查询


子查询是指一个查询语句嵌套在另一个查询语句内部的查询,这个特性从MySQL4.1开始引入。

SQL中子查询的使用大大增强了SELECT查询的能力,因为很多时候查询需要从结果集中获取数据,或者需要从同一个表中先计算得出一个数据结果,然后与这个结果(可能是某个标量,也可能是某个集合)进行比较。

1. 子查询的基本使用

1
2
3
SELECT last_name,salary 
FROM employees
WHERE salary > (SELECT salary FROM employees WHERE last_name='Abel');
  • 子查询(内查询)在主查询之间一次执行完成。
  • 子查询记的结果被主查询(外查询)使用。
  • 注意事项
    • 子查询要包含在括号内
    • 将子查询放到比较条件的右侧
    • 单行操作符对应单行子查询,多行操作符对应多行子查询。

1.1 子查询的分类

分类方式一:

按内查询的结果返回一条还是多条记录,将子查询分为单行子查询多行子查询

分类方式二:

按照是否被执行多次,将子查询划分为相关(关联)子查询不相关(非关联)子查询

子查询从数据表中查询了数据结果,如果这个数据结果只能执行一次,然后这个数据结果作为主查询的条件进行执行,那么这样的子查询叫做不相关子查询

同样,如果子查询需要执行多次,即采用循环的方式,从外部查询开始,每次都传入子查询进行查询,然后在将结果反馈给外部,这种嵌套的执行方式就称为相关子查询

2. 单行子查询

2.1 单行比较操作符

操作符:=>>=<<=<>

2.2 代码案例

1
2
3
4
# 查询工资大于149号员工的工资的员工信息
SELECT last_name,salary
FROM employees
WHERE salary > (SELECT salary FROM employees salary = 149)
1
2
3
4
5
# 返回job_id与141号员工相同,salary比143号员工多的员工姓名,job_id和工资
SELECT last_name,job_id,salary
FROM employees
WHERE salary > (SELECT salary FROM employees WHERE employee_id = 143)
AND job_id = (SELECT job_id FROM employees WHERE employee_id = 141)
1
2
3
4
# 返回公司工资最少的员工的lastname,job_id,salary
SELECT last_name,job_id,salary
FROM employees
WHERE salary = (SELECT MIN(salary) FROM employees)
1
2
3
4
5
6
# 查询与141号员工的manager_ id和department_id相同的其他员工的employee_id,manager_id, department_id
SELECT employee_id,manager_id,department_id
FROM employees
WHERE manager_id = (SELECT manager_id FROM employees WHERE employee_id =141)
AND department_id = (SELECT department_id FROM employees WHERE employee_id =141)
AND employee_id <> 141;
1
2
3
4
5
6
# 查询最低工资大于50号部门最低工资的部门id和其最低工资
SELECT department_id,MIN(salary)
FROM employees
WHERE department_id IS NOT NULL
GROUP BY department_id
HAVING MIN(salary) > (SELECT MIN(salary) FROM employees WHERE department_id = 50)
1
2
3
4
5
6
# 显示员工的employee id,last name和locationo
# 其中,若员工department id与location_id为1800的department_id相同,#则location为'Canada',其余则为'USA'。
SELECT employee_id,last_name,CASE department_id
WHEN (SELECT department_id FROM departments WHERE location_id = 1800) THEN 'Canada'
ELSE 'USA' END "locationo"
FROM employees;

2.3 空值问题

1
2
3
SELECT last_name,job_id
FROM employees
WHERE job_id = (SELECT job_id FROM employees WHERE last_name = "Haas");

3. 多行子查询

  • 多行子查询
  • 内查询返回多行
  • 使用多行子查询比较操作符

3.1 多行比较操作符

操作符 描述
IN 等于列表的任意一个
ANY 需要和单行比较操作符一起使用,和子查询返回某一个值的比较
ALL 需要和单行比较操作符一起使用,和子查询返回的所有值比较
SOME 实际上是ANY的别名,作用相同,一般常使用ANY

3.2 代码案例

1
2
3
4
5
6
# 查询与141号或174号员工的manager_ id和department_id相同的其他员工的employee_id,manager_id, department_id
SELECT employee_id,manager_id,department_id
FROM employees
WHERE manager_id IN((SELECT manager_id FROM employees WHERE employee_id IN(141,174)))
AND department_id IN((SELECT department_id FROM employees WHERE employee_id IN(141,174)))
AND employee_id NOT IN(141,174);
1
2
3
4
5
6
7
8
9
10
# 返回其他job_id中比job_id为“IT_PROG”部门任一工资低的员工号、姓名、job_id、salary
SELECT department_id,last_name,job_id,salary
FROM employees
WHERE job_id <> 'IT_PROG'
AND salary < ANY (SELECT salary FROM employees WHERE job_id = 'IT_PROG');
# 返回其他job_id中比job_id为“IT_PROG”部门所有工资低的员工号、姓名、job_id、salary
SELECT department_id,last_name,job_id,salary
FROM employees
WHERE job_id <> 'IT_PROG'
AND salary < ANY (SELECT salary FROM employees WHERE job_id = 'IT_PROG');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 查询平均工资最低的部门id
SELECT department_id
FROM employees
GROUP BY department_id
HAVING AVG(salary) <= ALL (SELECT AVG(salary) FROM employees GROUP BY department_id);
# 方式二:
SELECT department_id
FROM employees
GROUP BY department_id
HAVING AVG(salary) = (SELECT MIN(avg_sal) FROM (SELECT AVG(salary) avg_sal FROM employees GROUP BY department_id) t_dept_avg_sal);
# 方式三:
SELECT department_id
FROM employees
GROUP BY department_id
ORDER BY AVG(salary) ASC
LIMIT 1;

3.3 空值问题

1
2
3
4
5
6
7
8
9
10
11
12
13
# 查询出所有管理者的名称
SELECT last_name
FROM employees
WHERE employee_id IN (SELECT manager_id FROM employees);
# 查询出除了管理者外的员工名称
# 错误写法:发现无数据,因为内查询查出管理者的manager_id是空的,操作符无法判断是否为空
SELECT last_name
FROM employees
WHERE employee_id NOT IN (SELECT manager_id FROM employees);
# 正确写法
SELECT last_name
FROM employees
WHERE employee_id NOT IN (SELECT manager_id FROM employees WHERE manager_id IS NOT NULL);

4. 相关子查询

4.1 相关子查询的执行流程

如果子查询的执行依赖于外部查询,通常情况下都是因为子查询中的表用到了外部的表,并进行了条件关联,因此每执行一次外部查询,子查询都要重新计算一次,这样的查询就称为关联子查询。

相关子查询按照一行接一行的顺序执行,主查询的每一行都执行一次子查询。

说明:子查询中使用主查询中的列

4.2 代码案例

查询员工工资大于本部门平均工资的员工的last_name,salary,department_id

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
SELECT last_name,salary,department_id
FROM employees e1
WHERE salary > (
SELECT AVG(salary)
FROM employees e2
WHERE e2.department_id = e1.department_id
);
# 方式2
SELECT e.last_name,e.salary,e.department_id
FROM employees e INNER JOIN (
SELECT department_id,AVG(salary) avg_sal
FROM employees
GROUP BY department_id) t_dept_avg_sal
ON e.department_id = t_dept_avg_sal.department_id
WHERE e.salary > t_dept_avg_sal.avg_sal;
1
2
3
4
5
6
7
# 查询员工的id,salary,按照department_name排序
SELECT employee_id,salary
FROM employees e
ORDER BY (
SELECT department_name
FROM departments
WHERE department_id = e.department_id);

4.3 EXISTS 和 NOT EXISTS关键字

  • 关联子查询通常也会和EXISTS操作符来一起使用,用来检查在子查询中是否存在满足条件的行。
  • 如果子查询中不存在满足条件的行:
    • 条件返回false
    • 继续在子查询中查找
  • 如果在子查询中找到满足条件的行:
    • 不在子查询中继续查找
    • 条件返回true
  • NOT EXISTS关键字表示如果不存在某种条件,则返回ture,否则返回false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 查询公司管理者的employee_id,last_name,job_id,department_id
# 方式一:自查询
# 方式二:子查询
SELECT employee_id,last_name,job_id,department_id
FROM employees
WHERE employee_id IN(
SELECT DISTINCT manager_id
FROM employees
);
# 方式三:EXISTS
SELECT employee_id,last_name,job_id,department_id
FROM employees e
WHERE EXISTS (
SELECT *
FROM employees e2
WHERE e.employee_id = e2.manager_id
);

**结论:**除了GROUP BYLIMIT外,其他地方都可以写子查询

单行操作符 ANY ALL
>>= 大于任意一个,大于最小的 大于所有,大于最大的
<<= 小于任意一个,小于最大的 小于所有,小于最小的

4.4 相关更新

语法:

1
2
3
4
5
6
UPDATE table1 alias1
SET column = (
SELECT expression
FROM table2 alias1
WHERE alias1.column1 = table2.column2
);

使用相关子查询依据一个表中的数据更新另一个表的数据。

4.5 相关删除

语法:

1
2
3
4
5
6
7
DELETE FROM table1 alias1
WHERE column operator (
SELECT expression
FROM table2 alias1
WHERE alias1.column1 = table2.column2
);
# operator 操作符

使用相关子查询依据一个表中的数据删除另一个表的数据。

5. 课后练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
#1.查询和Zlotkey相同部门的员工姓名和工资
SELECT last_name,salary
FROM employees
WHERE department_id IN (
SELECT department_id
FROM employees
WHERE last_name = 'Zlotkey'
);
#2.查询工资比公司平均工资高的员工的员工号,姓名和工资。
SELECT employee_id,last_name,salary
FROM employees
WHERE salary > (
SELECT AVG(salary)
FROM employees
);
#3.选择工资大于所有JOB_ID = 'SA_MAN'的员工的工资的员工的last_name, job_id, salary
SELECT last_name,job_id,salary
FROM employees
WHERE salary > ALL (
SELECT salary
FROM employees
WHERE job_id = 'SA_MAN'
);
#4.查询和姓名中包含字母u的员工在相同部门的员工的员工号和姓名
SELECT employee_id,last_name
FROM employees
WHERE department_id IN (
SELECT DISTINCT department_id
FROM employees
WHERE last_name LIKE '%u%'
);
#5.查询在部门的location_id为1700的部门工作的员工的员工号
SELECT employee_id
FROM employees
WHERE department_id IN (
SELECT department_id
FROM departments
WHERE location_id = 1700
);
#6.查询管理者是King的员工姓名和工资
SELECT last_name,salary
FROM employees
WHERE manager_id IN (
SELECT employee_id
FROM employees
WHERE last_name = 'King'
);
#7.查询工资最低的员工信息: last_name, salary
SELECT last_name,salary
FROM employees
WHERE salary = (
SELECT MIN(salary)
FROM employees
);
#8.查询平均工资最低的部门信息
SELECT *
FROM departments
WHERE department_id = (
SELECT department_id
FROM employees
GROUP BY department_id
ORDER BY AVG(salary) ASC
LIMIT 0,1
);
# 方法2:
SELECT *
FROM departments
WHERE department_id = (
SELECT department_id
FROM employees
GROUP BY department_id
HAVING AVG(salary) <= ALL(
SELECT AVG(salary) avg_sal
FROM employees
GROUP BY department_id
)
);
#9.查询平均工资最低的部门信息和该部门的平均工资(相关子查询)
SELECT d.*,(SELECT AVG(salary) FROM employees WHERE department_id = d.department_id) avg_sal
FROM departments d
WHERE department_id = (
SELECT department_id
FROM employees
GROUP BY department_id
ORDER BY AVG(salary) ASC
LIMIT 0,1
)
# 方法2:
SELECT d.*,(SELECT AVG(salary) FROM employees WHERE department_id = d.department_id) avg_sal
FROM departments d
WHERE department_id = (
SELECT department_id
FROM employees
GROUP BY department_id
HAVING AVG(salary) <= ALL(
SELECT AVG(salary) avg_sal
FROM employees
GROUP BY department_id
)
);
#10.查询平均工资最高的 job 信息
SELECT *
FROM jobs
WHERE job_id = (
SELECT job_id
FROM employees
GROUP BY job_id
ORDER BY AVG(salary) DESC
LIMIT 1
);
#11.查询平均工资高于公司平均工资的部门有哪些?
SELECT department_id
FROM employees
WHERE department_id IS NOT NULL
GROUP BY department_id
HAVING AVG(salary) > (
SELECT AVG(salary)
FROM employees
);
#12.查询出公司中所有 manager 的详细信息
SELECT *
FROM employees
WHERE employee_id IN (
SELECT DISTINCT manager_id
FROM employees
);
#13.各个部门最高工资中,最低的那个部门的最低工资是多少?
# 方法一:
SELECT MIN(salary)
FROM employees
GROUP BY department_id
HAVING MAX(salary) = (
SELECT MIN(max_sal)
FROM (
SELECT MAX(salary) max_sal
FROM employees
GROUP BY department_id
) tb_emp_max
);
#14.查询平均工资最高的部门的 manager 的详细信息: last_name, department_id, email, salary
SELECT last_name,department_id,email,salary
FROM employees
WHERE employee_id IN(
SELECT DISTINCT manager_id
FROM employees
WHERE department_id = (
SELECT department_id
FROM employees
GROUP BY department_id
HAVING AVG(salary)
ORDER BY AVG(salary) DESC
LIMIT 1
)
);
#15. 查询部门的部门号,其中不包括job_id是"ST_CLERK"的部门号
#方法一:
SELECT department_id
FROM departments d
WHERE department_id NOT IN (
SELECT DISTINCT department_id
FROM employees
WHERE job_id = 'ST_CLERK'
);
#方法二:
SELECT department_id
FROM departments d
WHERE NOT EXISTS (
SELECT *
FROM employees e
WHERE d.`department_id` = e.`department_id`
AND job_id = 'ST_CLERK'
);
#16. 选择所有没有管理者的员工的last_name
SELECT last_name
FROM employees e1
WHERE NOT EXISTS(
SELECT *
FROM employees e2
WHERE e1.manager_id = e2.employee_id
);
#17.查询员工号、姓名、雇用时间、工资,其中员工的管理者为 'De Haan'
SELECT employee_id,last_name,hire_date,salary
FROM employees e1
WHERE manager_id IN (
SELECT manager_id
FROM employees e2
WHERE e2.last_name = 'De Haan'
);
#18.查询各部门中工资比本部门平均工资高的员工的员工号, 姓名和工资(相关子查询)
# 方式一
SELECT employee_id,last_name,salary
FROM employees e1
WHERE salary > (
SELECT AVG(salary)
FROM employees e2
WHERE e1.department_id = e2.department_id
GROUP BY department_id
);
# 方式二
SELECT employee_id,last_name,salary
FROM employees emp
WHERE EXISTS(
SELECT department_id,AVG(salary)
FROM employees
WHERE department_id = emp.department_id
GROUP BY department_id
HAVING AVG(salary) < emp.salary
);
#19.查询每个部门下的部门人数大于 5 的部门名称(相关子查询)
SELECT department_name
FROM departments d
WHERE EXISTS(
SELECT department_id
FROM employees
WHERE department_id = d.department_id
GROUP BY department_id
HAVING COUNT(*) > 5
)
#20.查询每个国家下的部门个数大于 2 的国家编号(相关子查询)

创建和管理库表

1. 基础知识

1.1 标识符的命名规则

  • 数据库名、表名不得超过30个字符,变量名限制为29个
  • 必须只能包含A-Z、a-z、0-9、_共63个字符
  • 数据库名、表名字段名等对象名中间不要包含空格
  • 同一个MySQL软件中,数据库不能同名;同一个库中,表不能重名;同一个表中,字段不能重名
  • 必须保证字段没有和保留字、数据库系统、或常用方法冲突,如果坚持使用,请在SQL语句中使用`(着重号)引起来
  • 必须保证字段名和类型的一致性:在命名字段并为其指定数据类型的时候一定要保证一致性,假如数据类型在一个表里是整数,那在另一个表里别变成字符型了

1.2 MySQL中的数据类型

类型 类型举例
整数类型 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数组
空间数据类型 单值:GEOMETRY、POINT、LINESTRING、POLYGON;
集合:MULTIPOINT、MULTILINESTRING、MULTIPOLYGON、GEOMETRYCOLLECTION

其中,常用的几类类型介绍如下

数据类型 描述
INT 从-2^31到2^31-1的整型数据。存储大小为4个字节
CHAR(size) 定长字符数据。若未指定,默认为一个字符,最大长度为255
VARCHAR(size) 可变长字符数据,根据字符串实际长度保存,必须指定长度
FLOAT(M,D) 单精度,占用4个字节,M=整数位+小数位,D=小数位。D<=M<=244,0<=D<=30,默认M+D<=6
DOUBLE(M,D) 双精度,占用8个字节,D<=M<=255,0<=D<=30,默认M+D<=15
DECIMAL(M,D) 高精度小数,占用M+2个字节,D<=M<=65,0<=D<=30,最大取值范围于DOUBLE相同
DATE 日期类型数据,格式YYYY-MM-DD
BLOB 二进制形式的长文本数据,最大可达4G
TEXT 长文本数据,最大可达4G

2. 创建和管理数据库

2.1 创建数据库

  • 方式一:创建数据库
1
CREATE DATABASE 数据库名;
  • 方式二:创建数据库并指定字符集
1
CREATE DATABASE 数据库名 CHARACTER SET 字符集;
  • 方式三:判断数据库中是否存在,不存在则创建数据库(推荐
1
CREATE DATABASE 数据库名 IF NOT EXISTS 数据库名;

如果MySQL中已经存在相关的数据库,则忽略创建语句,不在创建数据库。

注意:DATABASE不能改名。一些可视化工具可以改名,它是建新库,把所有表复制到新库,在删除旧库完成的

2.2 使用数据库

  • 查看当前的所有数据库
1
SHOW DATABASES; # s代表有多个数据库,复数
  • 查询指定库下的所有表
1
2
SHOW TABLES; # 查询当前库下的所有表
SHOW TABLES FROM 数据库名;
  • 查询数据库创建信息
1
2
3
SHOW CREATE DATABASE 数据库名;
或者
SHOW CREATE DATABASE 数据库名\G;
  • 查看当前正在使用的数据库
1
SELECT DATABASE() FROM DUAL; # 使用MySQL的一个全局函数
  • 切换数据库
1
USE 数据库名; 

注意:要操作的表格和数据之前必须先说明是对哪个数据库进行操作,否则就要对所有对象加上”数据库名”。

2.3 修改数据库

  • 更改数据库字符集
1
ALTER DATABASE 数据库名 CHARACTER SET 字符集; # 比如jbk,utf8等。

2.4 删除数据库

  • 方式一
1
DROP DATABASE 数据库名;
  • 方式二(推荐)
1
DROP DATABASE IF EXISTS 数据库名;

3. 创建和管理表

3.1 创建表

3.1.1 创建方式一

  • 必须具备
    • CREATE TABLE的权限
    • 存储空间
  • 语法格式
1
2
3
4
5
6
7
CREATE TABLE [IF NOT EXISTS] 表名(
字段1 数据类型 [约束条件] 默认值,
字段2 数据类型 [约束条件] 默认值,
......
字段n 数据类型 [约束条件] 默认值
[表约束条件]
);

加上了IF NOT EXISTS关键字,则表示:如果当前数据库中不存在要创建的数据表,则创建数据表; 如果当前数据库中已经存在要创建的数据表,则忽略建表语句,不再创建数据表。

  • 案例
1
2
3
4
5
CREATE TABLE IF NOT EXISTS myemp1(
id INT,
emp_name VARCHAR(15),
hire_date DATE
);

3.1.2 创建方式二

  1. 使用 AS subquery 选项,将创建表和插入数据结合起来
  2. 指定的列和子查询中的列要一一对应
  3. 通过列名和默认值定义列
  • 语法
1
2
3
CREATE TABLE table
[(column1,column2 ...)]
AS subquery;
  • 举例
1
2
CREATE TABLE emp1 AS SELECT * FROM employees; # 相当于复制employees表,名称叫emp1
CREATE TABLE emp2 AS SELECT * FROM employees WHERE 1=2; # 创建的emp2是空表,条件为flase
1
2
3
4
5
6
7
8
# 此时的查询语句可以结构比较丰富,使用前面学到的各种SELECT
# 查询语句中的字段别名,将作为新创建表的字段名名称出现
CREATE TABLE dept80
AS
SELECT employee_id, last_name, salary*12 ANNSAL, hire_date
FROM employees
WHERE department_id = 80;
DESCRIBE dept80;

image-20220426233007584

3.1.3 查看数据表结构

在MySQL中创建好数据表之后,可以查看数据表的结构。MySQL支持使用 DESCRIBE/DESC 语句查看数据表结构,也支持使用SHOW CREATE TABLE语句查看数据表结构。

语法格式如下:

1
2
SHOW CREATE TABLE 表名;
SHOW CREATE TABLE 表名\G;

使用SHOW CREATE TABLE语句不仅可以查看表创建时的详细语句,还可以查看存储引擎和字符编码。

3.2 修改表

修改表指的是修改数据库中已经存在的数据表的结构。

使用 ALTER TABLE 语句可以实现:

  • 向已有的表中添加字段
  • 修改现有表中的字段
  • 删除现有表中的字段
  • 重命名现有表中的字段名

3.2.1 添加一个字段

  • 语法
1
ALTER TABLE 表名 ADD [COLUMN] 字段名 字段类型 [FIRST|AFTER 字段名];
  • 举例
1
2
3
ALTER TABLE dept80 ADD job_id varchar(15); # 默认将字段追加到表的最后位置
ALTER TABLE dept80 ADD department_name varchar(15) FIRST; # 在表的最前面插入一个字段
ALTER TABLE dept80 ADD department_id varchar(15) AFTER employee_id; # 在指定字段后面插入一个字段

3.2.2 修改字段

  • 可以修改列的数据类型,长度、默认值和位置

  • 修改字段数据类型、长度、默认值、位置的语法格式如下:

1
ALTER TABLE 表名 MODIFY [COLUMN] 字段名1 字段类型 [DEFAULT 默认值] [FIRST|AFTER 字段名2];
  • 举例
1
2
ALTER TABLE dept80 MODIFY last_name VARCHAR(30);
ALTER TABLE dept80 MODIFY salary double(9,2) default 1000;
  • 对默认值的修改只影响今后对表的修改
  • 此外,还可以通过此种方式修改列的约束。这里暂先不讲。

3.2.3 重命名一个字段

  • 语法
1
ALTER TABLE 表名 CHANGE [column] 列名 新列名 新数据类型;
  • 举例
1
ALTER TABLE dept80 CHANGE department_name dept_name varchar(15);

3.2.4 删除一个字段

  • 语法
1
ALTER TABLE 表名 DROP [COLUMN] 字段名;
  • 举例
1
ALTER TABLE dept80 DROP COLUMN job_id;

3.3 重命名表

  • 方式一:使用RENAME
1
2
RENAME TABLE emp
TO myemp;
  • 方式二:
1
2
ALTER table dept
RENAME [TO] detail_dept; -- [TO]可以省略
  • 必须是对象的拥有者

3.4 删除表

  • 在MySQL中,当一张数据表 没有与其他任何数据表形成关联关系 时,可以将当前数据表直接删除。
  • 数据和结构都被删除
  • 所有正在运行的相关事务被提交
  • 所有相关索引被删除
  • 语法格式:
1
DROP TABLE [IF EXISTS] 数据表1 [, 数据表2, …, 数据表n];

IF EXISTS 的含义为:如果当前数据库中存在相应的数据表,则删除数据表;如果当前数据库中不存 在相应的数据表,则忽略删除语句,不再执行删除数据表的操作。

  • 举例:
1
DROP TABLE dept80;
  • DROP TABLE 语句不能回滚

3.5 清空表

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

  • TRUNCATE TABLE语句:
    • 删除表中所有的数据
    • 释放表的存储空间
  • 举例:
1
TRUNCATE TABLE detail_dept;
  • TRUNCATE语句不能回滚,而使用 DELETE 语句删除数据,可以回滚

阿里开发规范:

【参考】TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少,但 TRUNCATE 无 事务且不触发 TRIGGER,有可能造成事故,故不建议在开发代码中使用此语句。

说明:TRUNCATE TABLE 在功能上与不带 WHERE 子句的 DELETE 语句相同。

4. (事务)COMMIT 和 ROLLBACK

COMMIT:提交数据。一旦执行COMMIT,则数据就被永久保存在数据库中,意味着数据不可以回滚

ROLLBACK:回滚数据。一旦执行ROLLBACK,则可以实现数据的回滚。回滚到最近一次COMMIT之后

  • 对比TRUNCATE TABLEDELETE FROM

    • 相同点

      都可以实现对表中所有数据的删除,同时保留表结构

    • 不同点

      TRUNCATE TABLE一旦执行此操作,表中数据全部清除。同时数据是不可以回滚的。

      DELETE FROM一旦执行此操作,表数据可以全部清除(不带WHERE)。同时数据是可以回滚的。同时,数据是可以回滚的。

  • DML 和 DDL的说明

    • DDL操作一旦执行就不可以回滚,指令SET AUTOCOMMIT = FALSE对DDL失效
    • DML默认情况下,一旦执行,也是不可以回滚的。但是如果在执行DML之前执行了SET AUTOCOMMIT = FALSE(设置不自动提交),则执行的DML就可以实现回滚。
1
2
3
4
5
6
SET AUTOCOMMIT = FALSE;
DELETE FROM emp2; # 删除数据
#TRUNCATE TABLE emp2; # 此操作不可以回滚
SELECT * FROM emp2; # 查询发现无数据
ROLLBACK; # 数据回滚
SELECT * FROM emp2; # 查询发现数据还原

5. 课后练习

练习一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#1. 创建数据库test01_office,指明字符集为utf8。并在此数据库下执行下述操作
SHOW CREATE DATABASE IF NOT EXISTS test01_office CHARACTER SET 'utf8';
USE test01_office;
#2. 创建表dept01
/*
字段 类型
id INT(7)
NAME VARCHAR(25)
*/
CREATE TABLE dept01(
id INT(7),
`name` VARCHAR(25)
);
#3. 将表departments中的数据插入新表dept02中
CREATE TABLE dept02 AS
SELECT department_id id,department_name `name`
FROM atguigudb.departments;
#4. 创建表emp01
/*
字段 类型
id INT(7)
first_name VARCHAR (25)
last_name VARCHAR(25)
dept_id INT(7)
*/
CREATE TABLE emp01(
id INT(7),
first_name VARCHAR(25),
last_name VARCHAR(25),
dept_id INT(7)
);
#5. 将列last_name的长度增加到50
ALTER TABLE emp01 MODIFY last_name VARCHAR(50);
#6. 根据表employees创建emp02
CREATE TABLE emp02 AS
SELECT * FROM atguigudb.employees;
#7. 删除表emp01
DROP TABLE IF EXISTS emp01;
#8. 将表emp02重命名为emp01
RENAME TABLE emp02 TO emp01;
#9.在表dept02和emp01中添加新列test_column,并检查所作的操作
ALTER TABLE dept02 ADD test_column VARCHAR(15);
ALTER TABLE emp01 ADD test_column VARCHAR(15);
#10.直接删除表emp01中的列 department_id
ALTER TABLE emp01 DROP department_id;

练习二

1
2
3
4
5
6
7
8
9
10
11
# 1、创建数据库 test02_market
CREATE DATABASE test02_market;
USE test02_market;
# 2、创建数据表 customers
CREATE TABLE IF NOT EXISTS customers(
c_num INT,
c_name VARCHAR(50),
c_contact VARCHAR(50),
c_city VARCHAR(50),
c_birth DATE
);
字段名 数据类型
c_num int
c_name varchar(50)
c_contact varchar(50)
c_city varchar(50)
c_birth date
1
2
3
4
5
6
7
8
9
10
11
12
# 3、将 c_contact 字段移动到 c_birth 字段后面
ALTER TABLE customers MODIFY c_contact VARCHAR(50) AFTER c_birth;
# 4、将 c_name 字段数据类型改为 varchar(70)
ALTER TABLE customers MODIFY c_name VARCHAR(70);
# 5、将c_contact字段改名为c_phone
ALTER TABLE customers CHANGE c_contact c_phone VARCHAR(50);
# 6、增加c_gender字段到c_name后面,数据类型为char(1)
ALTER TABLE customers ADD c_gender CHAR(1) AFTER c_name;
# 7、将表名改为customers_info
RENAME TABLE customers TO customers_info;
# 8、删除字段c_city
ALTER TABLE customers_info DROP c_city;

练习三

1
2
3
4
5
6
7
8
9
10
11
# 1、创建数据库test03_company
CREATE DATABASE ;
USE test03_company;
# 2、创建表offices
CREATE TABLE offices(
officeCode INT,
city VARCHAR(30),
address VARCHAR(50),
country VARCHAR(50),
postalCode VARCHAR(25)
);
字段名 数据类型
officeCode int
city varchar(30)
address varchar(50)
country varchar(50)
postalCode varchar(25)
1
2
3
4
5
6
7
8
9
10
11
12
# 3、创建表employees
CREATE TABLE employees(
empNum INT,
lastName VARCHAR(50),
firstName VARCHAR(50),
mobile VARCHAR(25),
`code` INT,
jobTitle VARCHAR(50),
birth DATE,
note VARCHAR(255),
sex VARCHAR(5)
);
字段名 数据类型
empNum int
lastName varchar(50)
firstName varchar(50)
mobile varchar(25)
code int
jobTitle varchar(50)
birth date
note varchar(255)
sex varchar(5)
1
2
3
4
5
6
7
8
9
10
11
12
# 4、将表employees的mobile字段修改到code字段后面
ALTER TABLE employees MODIFY mobile VARCHAR(25) AFTER `code`;
# 5、将表employees的birth字段改名为birthday
ALTER TABLE employees CHANGE birth birthday DATE;
# 6、修改sex字段,数据类型为char(1)
ALTER TABLE employees MODIFY sex char(1);
# 7、删除字段note
ALTER TABLE employees DROP note;
# 8、增加字段名favoriate_activity,数据类型为varchar(100)
ALTER TABLE employees ADD favoriate_activity VARCHAR(100) FIRST;
# 9、将表employees的名称修改为 employees_info
RENAME TABLE employees TO employees_info;

数据处理之增删改

1. 插入数据

1.1 方式一:Value方式添加

情况1:为表的所有字段按默认的顺序插入

  • 语法
1
INSERT INTO 表名 VALUE(value1,value2,value3,...);

值列表中需要为表的每一个字段指定值,并且值的顺序必须和数据表中字段定义时的顺序相同。

  • 举例
1
2
3
4
5
6
# 准备表
CREATE TABLE IF NOT EXISTS emp01(
id INT,
`name` VARCHAR(20),
hire_date date
);
1
INSERT INTO emp01 VALUES(1,'Tom',now());

情况2:为表指定字段插入数据

  • 语法
1
2
INSERT INTO 表名(column1 [, column2, …, columnn]) 
VALUES (value1 [,value2, …, valuen]);

为表的指定字段插入数据,就是在INSERT语句中只向部分字段中插入值,而其他字段的值为表定义时的默认值。

在 INSERT 子句中随意列出列名,但是一旦列出,VALUES中要插入的value1,….valuen需要与column1,…columnn列一一对应。如果类型不同,将无法插入,并且MySQL会产生错误。

  • 举例
1
2
INSERT INTO emp01(id,`name`,hire_date) VALUES(2,'zhuli',now());
INSERT INTO emp01(id,hire_date,`name`) VALUES(3,now(),'Jare');

情况3:同时插入多条记录

  • 语法
1
2
3
4
5
6
7
8
9
10
11
12
13
INSERT INTO table_name 
VALUES
(value1 [,value2, …, valuen]),
(value1 [,value2, …, valuen]),
……
(value1 [,value2, …, valuen]);
# 或
INSERT INTO table_name(column1 [, column2, …, columnn])
VALUES
(value1 [,value2, …, valuen]),
(value1 [,value2, …, valuen]),
……
(value1 [,value2, …, valuen]);

INSERT语句可以同时向数据表中插入多条记录,插入时指定多个值列表,每个值列表之间用逗号分隔开。

  • 举例
1
2
3
INSERT INTO emp01(id,`name`,hire_date) 
VALUES(4,'Jira',now()),
(5,'lisa',now());

小结:

  • VALUES 也可以写成 VALUE ,但是VALUES是标准写法。

1.2 方式二:将查询结果插入到表中

INSERT还可以将SELECT语句查询的结果插入到表中,此时不需要把每一条记录的值一个一个输入,只需要使用一条INSERT语句和一条SELECT语句组成的组合语句即可快速地从一个或多个表中向一个表中插入多行。

  • 语法
1
2
3
4
5
INSERT INTO 目标表名 
(tar_column1 [, tar_column2, …, tar_columnn])
SELECT (src_column1 [, src_column2, …, src_columnn])
FROM 源表名
[WHERE condition]
  • 在 INSERT 语句中加入子查询

  • 不必书写 VALUES 子句。

  • 子查询中的值列表应与 INSERT 子句中的列名对应。


  • 举例
1
2
3
INSERT INTO emp01
SELECT employee_id,last_name,hire_date
FROM employees;

2. 更新数据

  • 语法
1
2
3
UPDATE table_name 
SET column1=value1, column2=value2, … , column=valuen
[WHERE condition]
  • 可以一次更新多条数据。

  • 如果需要回滚数据,需要保证在DML前,进行设置:SET AUTOCOMMIT = FALSE;


  • 使用 WHERE 子句指定需要更新的数据。
1
UPDATE emp01 set `name`='bobo' WHERE id = 1;
  • **注意:**如果省略 WHERE 子句,则表中的所有数据都将被更新。

3. 删除数据

  • 语法
1
DELETE FROM table_name [WHERE <condition>];

able_name指定要执行删除操作的表;“[WHERE ]”为可选参数,指定删除条件,如果没有WHERE子句,

DELETE语句将删除表中的所有记录

  • 使用 WHERE 子句删除指定的记录。
1
DELETE FROM emp01 WHERE id = 1;
  • 如果省略 WHERE 子句,则表中的全部数据将被删除

4. 计算列

什么叫计算列呢?简单来说就是某一列的值是通过别的列计算得来的。例如,a列值为1、b列值为2,c列不需要手动插入,定义a+b的结果为c的值,那么c就是计算列,是通过别的列计算得来的。

在MySQL 8.0中,CREATE TABLE 和 ALTER TABLE 中都支持增加计算列。下面以CREATE TABLE为例进行讲解。

举例:定义数据表tb1,然后定义字段id、字段a、字段b和字段c,其中字段c为计算列,用于计算a+b的值。 首先创建测试表tb1,语句如下:

1
2
3
4
5
6
CREATE TABLE tb1( 
id INT,
a INT,
b INT,
c INT GENERATED ALWAYS AS (a + b) VIRTUAL
);
1
2
INSERT INTO VALUES(1,20,30); # a=20,b=30,c=50
INSERT INTO VALUES(2,70,30); # a=70,b=30,c=100

4. 课后练习

练习一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# 1. 创建数据库dbtest11
CREATE DATABASE IF NOT EXISTS dbtest11 CHARACTER SET 'utf8';
# 2. 运行以下脚本创建表my_employees
USE dbtest11;
CREATE TABLE my_employees(
id INT(10),
first_name VARCHAR(10),
last_name VARCHAR(10),
userid VARCHAR(10),
salary DOUBLE(10,2)
);
CREATE TABLE users(
id INT,
userid VARCHAR(10),
department_id INT
);
# 3. 显示表my_employees的结构
DESC my_employees;
# 4. 向my_employees表中插入下列数据
# ID FIRST_NAME LAST_NAME USERID SALARY
# 1 patel Ralph Rpatel 895
# 2 Dancs Betty Bdancs 860
# 3 Biri Ben Bbiri 1100
# 4 Newman Chad Cnewman 750
# 5 Ropeburn Audrey Aropebur 1550
INSERT INTO my_employees
VALUES(1,'patel','Ralph','Rpatel',895),
(2,'Dancs','Betty','Bdancs',860),
(3,'Biri','Ben','Bbiri',1100),
(4,'Newman','Chad','Cnewman',750),
(5,'Ropeburn','Audrey','Aropebur',1550);

# 5. 向users表中插入数据
# 1 Rpatel 10
# 2 Bdancs 10
# 3 Bbiri 20
# 4 Cnewman 30
# 5 Aropebur 40
INSERT INTO users
VALUES(1,'Rpatel',10),
(2,'Bdancs',10),
(3,'Bbiri',20),
(4,'Cnewman',30),
(5,'Aropebur',40);

# 6. 将3号员工的last_name修改为“drelxer”
UPDATE my_employees SET last_name = 'drelxer' WHERE id = 3;
# 7. 将所有工资少于900的员工的工资修改为1000
UPDATE my_employees SET salary = 1000 WHERE salary < 900;
# 8. 将userid为Bbiri的user表和my_employees表的记录全部删除
DELETE FROM my_employees WHERE userid = 'Bbiri';
DELETE FROM users WHERE userid = 'Bbiri';
# 9. 删除my_employees、users表所有数据
DELETE FROM users;
DELETE FROM my_employees;
# 10. 检查所作的修正
SELECT * FROM my_employees;
SELECT * FROM users;
# 11. 清空表my_employees
TRUNCATE TABLE my_employees;

练习二

1
2
3
4
5
6
7
8
9
10
11
# 1. 使用现有数据库dbtest11
USE dbtest11;
# 2. 创建表格pet
CREATE TABLE pet(
`name` VARCHAR(20),
`owner` VARCHAR(20),
species VARCHAR(20),
sex CHAR(1),
birth YEAR,
death YEAR
);
字段名 字段说明 数据类型
name 宠物名称 VARCHAR(20)
owner 宠物主人 VARCHAR(20)
species 种类 VARCHAR(20)
sex 性别 CHAR(1)
birth 出生日期 YEAR
death 死亡日期 YEAR
1
2
3
4
5
6
7
8
9
# 3. 添加记录
INSERT INTO pet VALUES('Fluffy','harold','Cat','f','2013','2010');
INSERT INTO pet(`name`,`owner`,species,sex,Birth)
VALUES('Claws','gwen','Cat','m','2014');
INSERT INTO pet(`name`,species,sex,Birth) VALUES('Buffy','Dog','f','2009');
INSERT INTO pet(`name`,`owner`,species,sex,Birth)
VALUES('Fang','benny','Dog','m','2000');
INSERT INTO pet VALUES('bowser','diane','Dog','m','2003','2009');
INSERT INTO pet(`name`,species,sex,birth) VALUES('Chirpy','Bird','f','2008');
name owner species sex birth death
Fluffy harold Cat f 2003 2010
Claws gwen Cat m 2004
Buffy Dog f 2009
Fang benny Dog m 2000
bowser diane Dog m 2003 2009
Chirpy Bird f 2008
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 4. 添加字段:主人的生日owner_birth DATE类型
ALTER TABLE pet ADD COLUMN owner_birth DATE;
# 5. 将名称为Claws的猫的主人改为kevin
UPDATE pet SET `owner`='kevin' WHERE `name`='Claws' AND species='Cat';
# 6. 将没有死的狗的主人改为duck
UPDATE pet SET `owner`='duck' WHERE species='Dog' AND death IS NULL;
# 7. 查询没有主人的宠物的名字
SELECT `name` FROM pet WHERE `owner` IS NULL;
# 8. 查询已经死了的cat的姓名,主人,以及去世时间
SELECT `name`,`owner`,death FROM pet WHERE death IS NOT NULL AND species = 'Cat';
# 9. 删除已经死亡的狗
DELETE FROM pet WHERE death IS NOT NULL and species = 'Dog';
# 10. 查询所有宠物信息
SELECT * FROM pet;

练习三

1
2
3
4
5
6
7
8
9
10
11
# 1. 使用dbtest11数据库
USE dbtest11;
# 2. 创建employee表
CREATE TABLE employee(
id INT,
`name` VARCHAR(20),
sex VARCHAR(2),
tel VARCHAR(20),
addr VARCHAR(50),
salary DOUBLE
);
id name sex tel addr salary
10001 张一一 13456789000 山东青岛 1001.58
10002 刘小红 13454319000 河北保定 1201.21
10003 李四 0751-1234567 广东佛山 1004.11
10004 刘小强 0755-5555555 广东深圳 1501.23
10005 王艳 020-1232133 广东广州 1405.16
1
2
3
4
5
6
7
8
# 3. 查询出薪资在1200~1300之间的员工信息
SELECT * FROM employee WHERE salary BETWEEN 1200 AND 1300;
# 4. 查询出姓“刘”的员工的工号,姓名,家庭住址
SELECT id,name,addr FROM employee WHERE `name` LIKE '刘%';
# 5. 将“李四”的家庭住址改为“广东韶关”
UPDATE employee SET addr='广东韶关' WHERE `name`='李四';
# 6. 查询出名字中带“小”的员工
SELECT * FROM employee WHERE `name` LIKE '%小%';

5. 综合案例

1
2
3
4
5
6
7
8
9
10
11
12
13
# 1、创建数据库test01_library
CREATE DATABASE IF NOT EXISTS test01_library;
# 2、创建表 books,表结构如下:
USE test01_library;
CREATE TABLE IF NOT EXISTS books(
id INT,
`name` VARCHAR(50),
`authors` VARCHAR(100),
price FLOAT,
pubdate YEAR,
note VARCHAR(100),
num INT
);
字段名 字段说明 数据类型
id 书编号 INT
name 书名 VARCHAR(50)
authors 作者 VARCHAR(100)
price 价格 FLOAT
pubdate 出版日期 YEAR
note 说明 VARCHAR(100)
num 库存 INT
1
2
3
4
5
6
7
8
9
10
11
12
# 3、向books表中插入记录
# 1)不指定字段名称,插入第一条记录
INSERT INTO books VALUES(1,'Tal of AAA','Dickes',23,'1995','novel',11);
# 2)指定所有字段名称,插入第二记录
INSERT INTO books(id,`name`,`authors`,price,pubdate,note,num) VALUES(2,'EmmaT','Jane lura',35,'1993','joke',22);
# 3)同时插入多条记录(剩下的所有记录)
INSERT INTO books (id,name,`authors`,price,pubdate,note,num) VALUES
(3,'Story of Jane','Jane Tim',40,2001,'novel',0),
(4,'Lovey Day','George Byron',20,2005,'novel',30),
(5,'Old land','Honore Blade',30,2010,'Law',0),
(6,'The Battle','Upton Sara',30,1999,'medicine',40),
(7,'Rose Hood','Richard haggard',28,2008,'cartoon',28);
id name authors price pubdate note num
1 Tal of AAA Dickes 23 1995 novel 11
2 EmmaT Jane lura 35 1993 joke 22
3 Story of Jane Jane Tim 40 2001 novel 0
4 Lovey Day George Byron 20 2005 novel 30
5 Old land Honore Blade 30 2010 law 0
6 The Battle Upton Sara 30 1999 medicine 40
7 Rose Hood Richard haggard 28 2008 cartoon 28
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# 4、将小说类型(novel)的书的价格都增加5。
UPDATE books SET price = price + 5 WHERE note = 'novel';
# 5、将名称为EmmaT的书的价格改为40,并将说明改为drama。
UPDATE books SET price = 40 WHERE `name` = 'EmmaT';
# 6、删除库存为0的记录。(删除后还原)
DELETE FROM books WHERE num = 0;
# 7、统计书名中包含a字母的书
SELECT * FROM books WHERE `name` LIKE '%a%';
# 8、统计书名中包含a字母的书的数量和库存总量
SELECT COUNT(*),SUM(num) FROM books WHERE `name` LIKE '%a%';
# 9、找出“novel”类型的书,按照价格降序排列
SELECT note,price FROM books WHERE note = 'novel' ORDER BY price DESC;
# 10、查询图书信息,按照库存量降序排列,如果库存量相同的按照note升序排列
SELECT * FROM books ORDER BY num DESC,note ASC;
# 11、按照note分类统计书的数量
SELECT note,COUNT(*) FROM books GROUP BY note;
# 12、按照note分类统计书的库存量,显示库存量超过30本的
SELECT note,SUM(num) FROM books GROUP BY note HAVING SUM(num) > 30;
# 13、查询所有图书,每页显示5本,显示第二页
SELECT * FROM books LIMIT 5,5;
# 14、按照note分类统计书的库存量,显示库存量最多的
SELECT note,SUM(num) sum_num FROM books
GROUP BY note
ORDER BY sum_num DESC
LIMIT 0,1;
# 15、查询书名达到10个字符的书,不包括里面的空格
SELECT `name` FROM books WHERE LENGTH(REPLACE(`name`,' ','')) >= 10;
# 16、查询书名和类型,其中note值为novel显示小说,law显示法律,medicine显示医药,cartoon显示卡通,joke显示笑话
SELECT `name`, note,CASE note
WHEN 'novel' THEN '小说'
WHEN 'law' THEN '法律'
WHEN 'medicine' THEN '医药'
WHEN 'law' THEN '卡通'
WHEN 'joke' THEN '笑话'
ELSE '其他'
END '类型'
FROM books;
# 17、查询书名、库存,其中num值超过30本的,显示滞销,大于0并低于10的,显示畅销,为0的显示需要无货
SELECT `name`,num,CASE
WHEN num > 30 THEN '滞销'
WHEN num > 0 AND num < 10 THEN '畅销'
WHEN num = 0 THEN '无货'
ELSE '正常'
END
FROM books;
# 18、统计每一种note的库存量,并合计总量
# 方式一:
SELECT note,SUM(num),(SELECT SUM(num) FROM books) count_num FROM books GROUP BY note;
# 方式二:
SELECT note,SUM(num) count_num FROM books GROUP BY note WITH ROLLUP;
# 19、统计每一种note的数量,并合计总量
SELECT note,COUNT(*),(SELECT COUNT(*) FROM books) count_num FROM books GROUP BY note;
# 20、统计库存量前三名的图书
SELECT * FROM books ORDER BY num DESC LIMIT 0,3;
# 21、找出最早出版的一本书
SELECT * FROM books ORDER BY pubdate ASC LIMIT 0,1;
# 22、找出novel中价格最高的一本书
SELECT * FROM books ORDER BY price DESC LIMIT 0,1;
# 23、找出书名中字数最多的一本书,不含空格
SELECT * FROM books ORDER BY LENGTH(REPLACE(`name`,' ','')) DESC LIMIT 0,1;

MySQL数据类型

1. MySQL中的数据类型

类型 类型举例
整数类型 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数组
空间数据类型 单值:GEOMETRY、POINT、LINESTRING、POLYGON;
集合:MULTIPOINT、MULTILINESTRING、MULTIPOLYGON、GEOMETRYCOLLECTION

常用数据类型的属性,如下:

MySQL关键字 含义
NULL 数据列可包含NULL值
NOT NULL 数据列不允许包含NULL值
DEFAULT 默认值
PRIMARY KEY 主键
AUTO_INCREMENT 自动递增,适用于整数类型
UNSIGNED 无符号
CHARACTER SET name 指定一个字符集

2. 整数类型

2.1 类型介绍

整数类型一共包括五种TINYINT、SMALLINT、MEDIUMNT、INT(INTEGER)和BIGNT。

他们的区别如下表所示

整数类型 字节 有符号数取值范围 无符号数取值范围
TINYINT 1 -128~127 0~255
SMALLINT 2 -32768~32767 0~65535
MEDIUMINT 3 -8388608~8388607 0~16777215
INT、INTEGER 4 -2147483648~2147483647 0~4294967295
BIGINT 8 9223372036854775808~9223372036854775807 0~18446744073709551615

2.2 可选属性

整数类型的可选属性有三个:

  • M

    M : 表示显示宽度,M的取值范围是(0, 255)。例如,int(5):当数据宽度小于5位的时候在数字前面需要用 字符填满宽度。该项功能需要配合ZEROFILL使用,表示用“0”填满宽度,否则指定显示宽度无效。

    如果设置了显示宽度,那么插入的数据宽度超过显示宽度限制,会不会截断或插入失败?

    答案:不会对插入的数据有任何影响,还是按照类型的实际宽度进行保存,即显示宽度与类型可以存储的值范围无关 。从MySQL 8.0.17开始,整数数据类型不推荐使用显示宽度属性。

    整型数据类型可以在定义表结构时指定所需要的显示宽度,如果不指定,则系统为每一种类型指定默认 的宽度值。

  • UNSIGNED

    UNSIGNED : 无符号类型(非负),所有的整数类型都有一个可选的属性UNSIGNED(无符号属性),无 符号整数类型的最小取值为0。所以,如果需要在MySQL数据库中保存非负整数值时,可以将整数类型设 置为无符号类型。

    int类型默认显示宽度为int(11),无符号int类型默认显示宽度为int(10)。

  • ZEROFILL

    ZEROFILL : 0填充,(如果某列是ZEROFILL,那么MySQL会自动为当前列添加UNSIGNED属性),如果指 定了ZEROFILL只是表示不够M位时,用0在左边填充,如果超过M位,只要不超过数据存储范围即可。

    原来,在 int(M) 中,M 的值跟 int(M) 所占多少存储空间并无任何关系。 int(3)、int(4)、int(8) 在磁盘上都 是占用 4 bytes 的存储空间。也就是说,int(M),必须和UNSIGNED ZEROFILL一起使用才有意义。如果整 数值超过M位,就按照实际位数存储。只是无须再用字符 0 进行填充。

2.3 适用场景

TINYINT:一般用于枚举数据,比如系统设定取值范围很小且固定的场景。

SMALLINT:可以用于较小范围的统计数据,比如统计工厂的固定资产库存数量等

MEDIUMINT:用于较大整数的计算,比如车站每日的客流量等。

INT、INTEGER :取值范围足够大,一般情况下不用考虑超限问题,用得最多。比如商品编号。

BIGINT:只有当你处理特别巨大的整数时才会用到。比如双十一的交易量、大型门户网站点击量、证 券公司衍生产品持仓等。

3. 浮点类型

浮点数和定点数类型的特点是可以 处理小数 ,你可以把整数看成小数的一个特例。因此,浮点数和定点数的使用场景,比整数大多了。 MySQL支持的浮点数类型,分别是 FLOAT、DOUBLE、REAL。

  • FLOAT 表示单精度浮点数;
  • DOUBLE 表示双精度浮点数;

image-20220506220418936

  • REAL默认就是 DOUBLE。如果你把 SQL 模式设定为启用“ REAL_AS_FLOAT ”,那 么,MySQL 就认为REAL 是 FLOAT。如果要启用“REAL_AS_FLOAT”,可以通过以下 SQL 语句实现:\

    1
    SET sql_mode = “REAL_AS_FLOAT”;

**问题1:**FLOAT 和 DOUBLE 这两种数据类型的区别是啥呢?

FLOAT 占用字节数少,取值范围小;DOUBLE 占用字节数多,取值范围也大。

**问题2:**为什么浮点数类型的无符号数取值范围,只相当于有符号数取值范围的一半,也就是只相当于有符号数取值范围大于等于零的部分呢?

MySQL 存储浮点数的格式为: 符号(S) 、 尾数(M) 和 阶码(E) 。因此,无论有没有符号,MySQL 的浮点数都会存储表示符号的部分。因此, 所谓的无符号数取值范围,其实就是有符号数取值范围大于等于零的部分。

  • 注意:

    MySQL 8.0.17开始,FLOAT(M,D) DOUBLE(M,D)用法在官方文档中已经明确不推荐使用,将来可能被移除。另外,关于浮点型FLOAT和DOUBLE的UNSIGNED也不推荐使用了,将来也可能被移除。推荐使用DECIMAL

4. 定点数类型

  • MySQL中的定点数类型只有 DECIMAL 一种类型。

    数据类型 字节数 含义
    DECIMAL(M,D),DEC,NUMERIC M+2字节 有效范围由M和D决定

    使用 DECIMAL(M,D) 的方式表示高精度小数。其中,M被称为精度,D被称为标度。0<=M<=65, 0<=D<=30,D<M。例如,定义DECIMAL(5,2)的类型,表示该列取值范围是-999.99~999.99。

  • DECIMAL(M,D)的最大取值范围与DOUBLE类型一样,但是有效的数据范围是由M和D决定的。DECIMAL 的存储空间并不是固定的,由精度值M决定,总共占用的存储空间为M+2个字节。也就是说,在一些对精度要求不高的场景下,比起占用同样字节长度的定点数,浮点数表达的数值范围可以更大一些。

  • 定点数在MySQL内部是以字符串的形式进行存储,这就决定了它一定是精准的。

  • 当DECIMAL类型不指定精度和标度时,其默认为DECIMAL(10,0)。当数据的精度超出了定点数类型的精度范围时,则MySQL同样会进行四舍五入处理

5. 位类型:BIT

BIT类型中存储的是二进制值,类似010110。

二进制字符串类型 长度 长度范围 占用空间
BIT(M) M 1 <= M <= 64 约为(M + 7)/8个字节

BIT类型,如果没有指定(M),默认是1位。这个1位,表示只能存1位的二进制值。这里(M)是表示二进制的位数,位数最小值为1,最大值为64。

6. 日期和时间类型

日期与时间是重要的信息,在我们的系统中,几乎所有的数据表都用得到。原因是客户需要知道数据的时间标签,从而进行数据查询、统计和处理。

MySQL有多种表示日期和时间的数据类型,不同的版本可能有所差异,MySQL8.0版本支持的日期和时间类型主要有:YEAR类型、TIME类型、DATE类型、DATETIME类型和TIMESTAMP类型。

  • YEAR类型通常用来表示年
  • DATE类型通常用来表示年、月、日
  • TIME类型通常用来表示时、分、秒
  • DATETIME类型通常用来表示年、月、日、时、分、秒
  • TIMESTAMP类型通常用来表示带时区的年、月、日、时、分、秒
类型 名称 字节 日期格式 最小值 最大值
YEAR 1 YYYY或YY 1901 2155
DATE 日期 3 YYYY-MM-DD 1000-01-01 9999-12-03
TIME 时间 3 HH:MM:SS -838:59:59 838:59:59
DATETIME 日期时间 8 YYYY-MM-DD
HH:MM:SS
1000-01-01
00:00:00
9999-12-31
23:59:59
TIMESTAMP 日期时间 4 YYYY-MM-DD
HH:MM:SS
1970-01-01
00:00:00 UTC
2038-01-19
03:14:07UTC

可以看到,不同数据类型表示的时间内容不同、取值范围不同,而且占用的字节数也不一样,你要根据实际需要灵活选取。

为什么时间类型 TIME 的取值范围不是 -23:59:59~23:59:59 呢?原因是 MySQL 设计的 TIME 类型,不光表示一天之内的时间,而且可以用来表示一个时间间隔,这个时间间隔可以超过 24 小时。


6.1 YEAR类型

YEAR类型用来表示年份,在所有的日期时间类型中所占用的存储空间最小,只需要1个字节的存储空间。

在MySQL中,YEAR有以下几种存储格式:

  • 以4位字符串或数字格式表示YEAR类型,其格式为YYYY,最小值为1901,最大值为2155。

  • 以2位字符串格式表示YEAR类型,最小值为00,最大值为99。

    • 当取值为01到69时,表示2001到2069;
    • 当取值为70到99时,表示1970到1999;
    • 当取值整数的0或00添加的话,那么是0000年;
    • 当取值是日期/字符串的’0’添加的话,是2000年。

从MySQL5.5.27开始,2位格式的YEAR已经不推荐使用。YEAR默认格式就是“YYYY”,没必要写成YEAR(4),

从MySQL 8.0.19开始,不推荐使用指定显示宽度的**YEAR(4)**数据类型。


6.2 DATE类型

DATE类型表示日期,没有时间部分,格式为YYYY-MM-DD,其中,YYYY表示年份,MM表示月份,DD表示日期。需要3个字节的存储空间。在向DATE类型的字段插入数据时,同样需要满足一定的格式条件。

  • YYYY-MM-DD格式或者 YYYYMMDD格式表示的字符串日期,其最小取值为1000-01-01,最大取值为9999-12-03。YYYYMMDD格式会被转化为YYYY-MM-DD格式。

  • YY-MM-DD格式或者YYMMDD格式表示的字符串日期,此格式中,年份为两位数值或字符串满足YEAR类型的格式条件为:当年份取值为00到69时,会被转化为2000到2069;当年份取值为70到99时,会被转化为1970到1999。

  • 使用CURRENT_DATE()或者NOW()函数,会插入当前系统的日期。


6.3 TIME类型

TIME类型用来表示时间,不包含日期部分。在MySQL中,需要3个字节的存储空间来存储TIME类型的数据,可以使用“HH:MM:SS”格式来表示TIME类型,其中,HH表示小时,MM表示分钟,SS表示秒。

在MySQL中,向TIME类型的字段插入数据时,也可以使用几种不同的格式。

  1. 可以使用带有冒号的字符串,比如’D HH:MM:SS‘ 、’HH:MM:SS ‘、’HH:MM‘、’D HH:MM‘、’D HH‘或’SS‘格式,都能被正确地插入TIME类型的字段中。其中D表示天,其最小值为0,最大值为34。如果使用带有D格式的字符串插入TIME类型的字段时,D会被转化为小时,计算格式为D*24+HH。当使用带有冒号并且不带D的字符串表示时间时,表示当天的时间,比如12:10表示12:10:00,而不是00:12:10。
  2. 可以使用不带有冒号的字符串或者数字,格式为’HHMMSS‘或者HHMMSS。如果插入一个不合法的字符串或者数字,MySQL在存储数据时,会将其自动转化为00:00:00进行存储。比如1210,MySQL会将最右边的两位解析成秒,表示00:12:10,而不是12:10:00。
  3. 使用CURRENT_TIME()或者NOW(),会插入当前系统的时间。

6.4 DATETIME类型

DATETIME类型在所有的日期时间类型中占用的存储空间最大,总共需要8个字节的存储空间。在格式上为DATE类型和TIME类型的组合,可以表示为 YYYY-MM-DD HH:MM:SS ,其中YYYY表示年份,MM表示月份,DD表示日期,HH表示小时,MM表示分钟,SS表示秒。

在向DATETIME类型的字段插入数据时,同样需要满足一定的格式条件。

  • YYYY-MM-DD HH:MM:SS格式或者YYYYMMDDHHMMSS格式的字符串插入DATETIME类型的字段时,最小值为1000-01-01 00:00:00,最大值为9999-12-03 23:59:59。

    • 以YYYYMMDDHHMMSS格式的数字插入DATETIME类型的字段时,会被转化为YYYY-MM-DD HH:MM:SS格式。
  • YY-MM-DD HH:MM:SS格式或者YYMMDDHHMMSS格式的字符串插入DATETIME类型的字段时,两位数的年份规则符合YEAR类型的规则,00到69表示2000到2069;70到99表示1970到1999。

  • 使用函数CURRENT_TIMESTAMP()NOW(),可以向DATETIME类型的字段插入系统的当前日期和时间。


6.5 TIMESTAMP类型

TIMESTAMP类型也可以表示日期时间,其显示格式与DATETIME类型相同,都是YYYY-MM-DD HH:MM:SS,需要4个字节的存储空间。但是TIMESTAMP存储的时间范围比DATETIME要小很多,只能存储“1970-01-01 00:00:01 UTC”到“2038-01-19 03:14:07 UTC”之间的时间。其中,UTC表示世界统一时间,也叫作世界标准时间。

  • 存储数据的时候需要对当前时间所在的时区进行转换,查询数据的时候再将时间转换回当前的时区。因此,使用TIMESTAMP存储的同一个时间值,在不同的时区查询时会显示不同的时间。

向TIMESTAMP类型的字段插入数据时,当插入的数据格式满足YY-MM-DD HH:MM:SS和YYMMDDHHMMSS时,两位数值的年份同样符合YEAR类型的规则条件,只不过表示的时间范围要小很多。

TIMESTAMP和DATETIME的区别:

  • TIMESTAMP存储空间比较小,表示的日期时间范围也比较小
  • 底层存储方式不同,TIMESTAMP底层存储的是毫秒值,距离1970-1-1 0:0:0 0毫秒的毫秒值。
  • 两个日期比较大小或日期计算时,TIMESTAMP更方便、更快。
  • TIMESTAMP和时区有关。TIMESTAMP会根据用户的时区不同,显示不同的结果。而DATETIME则只能反映出插入时当地的时区,其他时区的人查看数据必然会有误差的。

6.6 开发中的经验

用得最多的日期时间类型,就是 DATETIME 。虽然 MySQL 也支持 YEAR(年)、 TIME(时间)、DATE(日期),以及 TIMESTAMP 类型,但是在实际项目中,尽量用 DATETIME 类型。因为这个数据类型包括了完整的日期和时间信息,取值范围也最大,使用起来比较方便。毕竟,如果日期时间信息分散在好几个字段,很不容易记,而且查询的时候,SQL 语句也会更加复杂。

此外,一般存注册时间、商品发布时间等,不建议使用DATETIME存储,而是使用 ·时间戳 ,因为DATETIME虽然直观,但不便于计算。

1
2
3
4
5
6
7
8
# 获取当前时间戳
mysql> SELECT UNIX_TIMESTAMP();
+------------------+
| UNIX_TIMESTAMP() |
+------------------+
| 1635932762 |
+------------------+
1 row in set (0.00 sec)

7. 文本字符串

在实际的项目中,我们还经常遇到一种数据,就是字符串数据。

MySQL中,文本字符串总体上分为 CHAR VARCHARTINYTEXT TEXT MEDIUMTEXT LONGTEXT ENUM SET 等类型。

image-20220506232211373

7.1 CHAR与VARCHAR类型

CHAR和VARCHAR类型都可以存储比较短的字符串

字符串(文本类型) 特点 长度 长度范围 占用的存储空间
CHAR(M) 固定长度 M 0 <= M <= 255 M个字节
VARCHAR(M) 可变长度 M 0 <= M <= 65535 (实际长度 + 1) 个字节

CHAR类型:

  • CHAR(M) 类型一般需要预先定义字符串长度。如果不指定(M),则表示长度默认是1个字符。
  • 如果保存时,数据的实际长度比CHAR类型声明的长度小,则会在右侧填充空格以达到指定的长度。当MySQL检索CHAR类型的数据时,CHAR类型的字段会去除尾部的空格。
  • 定义CHAR类型字段时,声明的字段长度即为CHAR类型字段所占的存储空间的字节数。

VARCHAR类型:

  • VARCHAR(M) 定义时, 必须指定长度M,否则报错。

  • MySQL4.0版本以下,varchar(20):指的是20字节,如果存放UTF8汉字时,只能存6个(每个汉字3字节) ;MySQL5.0版本以上,varchar(20):指的是20字符。

  • 检索VARCHAR类型的字段数据时,会保留数据尾部的空格。VARCHAR类型的字段所占用的存储空间为字符串实际长度加1个字节。

哪些情况使用 CHAR 或 VARCHAR更好

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

情况1:存储很短的信息。比如门牌号码101,201……这样很短的信息应该用char,因为varchar还要占个byte用于存储信息长度,本来打算节约存储的,结果得不偿失。

情况2:固定长度的。比如使用uuid作为主键,那用char应该更合适。因为他固定长度,varchar动态根据长度的特性就消失了,而且还要占个长度信息。

情况3:十分频繁改变的column。因为varchar每次存储都要有额外的计算,得到长度等工作,如果一个非常频繁改变的,那就要有很多的精力用于计算,而这些对于char来说是不需要的。

情况4:具体存储引擎中的情况:

  • MyISAM数据存储引擎和数据列:MyISAM数据表,最好使用固定长度(CHAR)的数据列代替可变长度(VARCHAR)的数据列。这样使得整个表静态化,从而使 数据检索更快 ,用空间换时间。

  • MEMORY存储引擎和数据列:MEMORY数据表目前都使用固定长度的数据行存储,因此无论使用CHAR或VARCHAR列都没有关系,两者都是作为CHAR类型处理的。

  • InnoDB 存储引擎,建议使用VARCHAR类型。因为对于InnoDB数据表,内部的行存储格式并没有区分固定长度和可变长度列(所有数据行都使用指向数据列值的头指针),而且主要影响性能的因素是数据行使用的存储总量,由于char平均占用的空间多于varchar,所以除了简短并且固定长度的,其他考虑varchar。这样节省空间,对磁盘I/O和数据存储总量比较好。

7.2 TEXT类型

在MySQL中,TEXT用来保存文本类型的字符串,总共包含4种类型,分别为TINYTEXT、TEXT、 MEDIUMTEXT 和 LONGTEXT 类型。

在向TEXT类型的字段保存和查询数据时,系统自动按照实际长度存储,不需要预先定义长度。这一点和VARCHAR类型相同。

每种TEXT类型保存的数据长度和所占用的存储空间不同,如下:

文本字符串类型 特点 长度 长度范围 占用存储空间
TINYTEXT 小文本、可变长度 L 0 <= L <= 255 L + 2 个字节
TEXT 文本、可变长度 L 0 <= L <= 65535 L + 2 个字节
MEDIUMTEXT 中等文本、可变长度 L 0 <= L <= 16777215 L + 3 个字节
LONGTEXT 大文本、可变长度 L 0 <= L<= 4294967295(相当于4GB) L + 4 个字节

由于实际存储的长度不确定,MySQL 不允许TEXT类型的字段做主键。遇到这种情况,你只能采用CHAR(M),或者 VARCHAR(M)。

**说明:**在保存和查询数据时,并没有删除TEXT类型的数据尾部的空格。

开发中经验:

TEXT文本类型,可以存比较大的文本段,搜索速度稍慢,因此如果不是特别大的内容,建议使用CHAR, VARCHAR来代替。还有TEXT类型不用加默认值,加了也没用。而且text和blob类型的数据删除后容易导致“空洞”,使得文件碎片比较多,所以频繁使用的表不建议包含TEXT类型字段,建议单独分出去,单独用一个表。

7.3 ENUM类型

ENUM类型也叫作枚举类型,ENUM类型的取值范围需要在定义字段时进行指定。设置字段值时,ENUM类型只允许从成员中选取单个值,不能一次选取多个值。

其所需要的存储空间由定义ENUM类型时指定的成员个数决定。

文本字符串类型 长度 长度范围 占用的存储空间
ENUM L 1 <= L <= 65535 1或2个字节
  • 当ENUM类型包含1~255个成员时,需要1个字节的存储空间
  • 当ENUM类型包含256~65535个成员时,需要2个字节的存储空间。
  • ENUM类型的成员个数的上限为65535个。

举例

1
2
3
4
5
6
7
8
CREATE TABLE test_enum( 
season ENUM('春','夏','秋','冬','unknow')
);

INSERT INTO test_enum VALUES('春'),('秋');

# 当ENUM类型的字段没有声明为NOT NULL时,插入NULL也是有效的
INSERT INTO test_enum VALUES(NULL);

7.4 SET类型

SET表示一个字符串对象,可以包含0个或多个成员,但成员个数的上限为64 。设置字段值时,可以取取值范围内的 0 个或多个值。

当SET类型包含的成员个数不同时,其所占用的存储空间也是不同的,具体如下:

成员个数范围(L表示实际成员个数) 占用的存储空间
1 <= L <= 8 1个字节
9 <= L <= 16 2个字节
17 <= L <= 24 3个字节
25 <= L <= 32 4个字节
25 <= L <= 32 8个字节

SET类型在存储数据时成员个数越多,其占用的存储空间越大。注意:SET类型在选取成员时,可以一次选择多个成员,这一点与ENUM类型不同。

举例

1
2
3
4
5
6
7
8
CREATE TABLE test_set( s SET ('A', 'B', 'C') );

INSERT INTO test_set (s) VALUES ('A'), ('A,B');
#插入重复的SET类型成员时,MySQL会自动删除重复的成员
INSERT INTO test_set (s) VALUES ('A,B,C,A');

#向SET类型的字段插入SET成员中不存在的值时,MySQL会抛出错误。
INSERT INTO test_set (s) VALUES ('A,B,C,D'); SELECT * FROM test_set;

8. JSON 类型

JSON(JavaScript Object Notation)是一种轻量级的 数据交换格式 。简洁和清晰的层次结构使得 JSON 成 为理想的数据交换语言。它易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效 率。JSON 可以将 JavaScript 对象中表示的一组数据转换为字符串,然后就可以在网络或者程序之间轻 松地传递这个字符串,并在需要的时候将它还原为各编程语言所支持的数据格式。

在MySQL 5.7中,就已经支持JSON数据类型。在MySQL 8.x版本中,JSON类型提供了可以进行自动验证的 JSON文档和优化的存储结构,使得在MySQL中存储和读取JSON类型的数据更加方便和高效。 创建数据 表,表中包含一个JSON类型的字段 js 。

1
2
3
4
5
6
7
8
9
10
11
CREATE TABLE test_json(
js json
);

INSERT INTO test_json (js)
VALUES ('{"name":"songhk", "age":18, "address":{"province":"beijing",
"city":"beijing"}}');

# 查询指定的数据
SELECT js -> '$.name' AS NAME,js -> '$.age' AS age ,js -> '$.address.province'
AS province, js -> '$.address.city' AS city

约束

1. 约束的概述

1.1 为什么需要约束

数据完整性是指数据的精确性和可靠性。他是防止数据库中存在不符合语义规定的数据和防止因错误信息的输入输出造成无效操作或错误信息而提出的。

为了保证数据的完整性,SQL规范以约束的方式对表数据进行额外的条件限制。从以下四个方面考虑:

  • 实体完整性(Entity Integrity):例如,同一个表中,不能存在两条完全相同无法区分的记录
  • 域完整性(Domain Integrity):例如,年龄范围0-120,性别范围“男/女”
  • 引用完整性(Referential Integrity):例如,员工所在部门,在部门表中要找到这个部门
  • 用户自定义完整性(User-defined Integrity):例如,用户名唯一,密码不能为空等,本部门经理的工资不能高于本部门职工的平均工资的5倍。

1.2 什么是约束

约束是表级的强制规定。

可以在创建表的时规定约束(通过CREATE TABLE语句),或者在创建之后通过ALTER TABLE 语句规定约束。

1.3 约束的分类

  • 根据约束数据列的限制,约束可分为:

    • ==单列约束==:每个约束只约束一列
    • ==多列约束==:每个约束可约束多列
  • 根据约束的作用范围,约束可分为

    • ==列级约束==:只能作用在一个列上,跟在列的定义后面
    • ==表级约束==:可以作用在多个列上,不与列一起,而是单独定义。
    位置 支持的约束类型 是否可以起约束名
    列级约束 列的后面 语法都支持,但外键没有效果 不可以
    表级约束 所有列的下面 默认和非空不支持,其他支持 可以
  • 根据约束的作用,约束可分为:

    • NOT NULL:非空约束,规定某个字段不能为空
    • UNIQUE:唯一约束,规定某个字段在整个表中是唯一的
    • PRIMARY KEY:主键(且非空唯一)约束
    • FOREIGN KEY:外键约束
    • CHECK:检查约束
    • DEFAULT:默认值约束

注意: MySQL不支持check约束,但可以使用check约束,而没有任何效果

  • 查看某个表已有的约束
1
2
3
4
#information_schema数据库名(系统库)
#table_constraints表名称(专门存储各个表的约束)
SELECT * FROM information_schema.table_constraints
WHERE table_name = '表名称';

2. 非空约束

  • 作用

    限定某个字段的值不能为空

  • 关键字

    NOT NULL

  • 特点

    • 默认,所有的类型的值都可以是NULL,包括FLOAT,DOUBLE等数据类型。
    • 非空约束只能出现在表对象的列上,只能某个列单独限定非空,不能组合非空。
    • 一个表可以有多个列都分别限定了非空
    • 空字符串不等于NULL,0也不等于NULL

2.1 添加非空约束

  • 建表时
1
2
3
4
5
CREATE TABLE 表名称(
字段名 数据类型,
字段名 数据类型 NOT NULL,
字段名 数据类型 NOT NULL
);
  • 举例
1
2
3
4
5
6
CREATE TABLE student(
sid int,
sname varchar(20) not null,
tel char(11) ,
cardid char(18) not null
);
1
2
3
4
5
6
7
8
9
insert into student values(1,'张三','13710011002','110222198912032545'); #成功

# ERROR 1048 (23000): Column 'cardid' cannot be null
insert into student values(2,'李四','13710011002',null);#失败,身份证号为空

insert into student values(2,'李四',null,'110222198912032546');#成功,tel允许为空

# ERROR 1048 (23000): Column 'sname' cannot be null
insert into student values(3,null,null,'110222198912032547');#失败

  • 修改表时
1
alter table 表名称 modify 字段名 数据类型 not null;
  • 举例
1
2
# 如果添加失败,需要确保已经存在的数据中该字段的所有值必须不为空
ALTER TABLE student MODIFY tel char(11) NOT NULL;
1
INSERT INTO student VALUES(4,'王五',null,'485930459684768495');# 失败,tel列不能为空

2.2 删除主键约束

1
2
3
alter table 表名称 modify 字段名 数据类型 NULL;#去掉not null,相当于修改某个非注解字段,该字段允许为空
# 或
alter table 表名称 modify 字段名 数据类型;#去掉not null,相当于修改某个非注解字段,该字段允许为空

3. 唯一性约束

  • 作用

    用来限制某个字段的列/某列的值不能重复

  • 关键字

    UNIQUE

  • 特点

    • 同一个表可以有多个唯一性约束
    • 唯一约束可以是某一列的值唯一,也可以是多个列组合的值唯一。
    • 唯一性约束允许列值为空
    • 在创建唯一约束的时候,如果不给唯一约束命名,就默认和列名相同。
    • MySQL会给唯一约束的列上默认创建一个唯一索引

3.1 添加唯一约束

  • 建表时
1
2
3
4
5
6
7
8
9
10
11
12
13
create table 表名称(
字段名 数据类型,
字段名 数据类型 unique,
字段名 数据类型 unique key,
字段名 数据类型
);
# 或
create table 表名称(
字段名 数据类型,
字段名 数据类型,
字段名 数据类型,
[constraint 约束名] unique key(字段名)
);
  • 举例
1
2
3
4
5
6
7
8
CREATE TABLE test2(
id INT UNIQUE, #列级约束
last_name VARCHAR(15),
emial VARCHAR(25),
salary DECIMAL(10,2),
# 表级约束语法
CONSTRAINT uk_test2_emial UNIQUE(emial)
);
1
2
3
4
INSERT INTO test2 VALUES(1,'张三','11@qq.com',1200);# 成功
INSERT INTO test2 VALUES(2,'李四','11@qq.com',1200);# 失败,邮箱重复
INSERT INTO test2 VALUES(3,NULL,NULL,NULL);# 成功
INSERT INTO test2 VALUES(4,NULL,NULL,NULL);# 成功,唯一约束允许列值为空

  • 建表后指定唯一键约束
1
2
3
4
5
#字段列表中如果是一个字段,表示该列的值唯一。如果是两个或更多个字段,那么复合唯一,即多个字段的组合是唯一的
#方式1:
alter table 表名称 add unique key(字段列表);
#方式2:
alter table 表名称 modify 字段名 字段类型 unique;

3.2 关于复合唯一约束

1
2
3
4
5
6
7
create table 表名称(
字段名 数据类型,
字段名 数据类型,
字段名 数据类型,
unique key(字段列表)
#字段列表中写的是多个字段名,多个字段名用逗号分隔,表示那么是复合唯一,即多个字段的组合是唯一的
);
  • 举例
1
2
3
4
5
6
CREATE TABLE test_user(
id INT,
`username` VARCHAR(20),
`password` VARCHAR(20),
CONSTRAINT uk_ts_name_pwd UNIQUE KEY(`username`,`password`)
);
1
2
3
INSERT INTO test_user VALUES(1,'李四','123456');# 成功
INSERT INTO test_user VALUES(2,'李四','654321');# 成功
INSERT INTO test_user VALUES(3,'李四','123456');# 失败,一个公司内同名的员工不能密码也=相同

3.3 删除唯一约束

  • 添加唯一性约束的列上也会自动创建唯一索引
  • 删除唯一约束只能通过删除唯一索引的方式删除
  • 删除时需要指定唯一约束名,唯一索引名和唯一约束名一样
  • 如果创建唯一约束时未指定名称,如果是但列,默认和列名相同;如果是组合列,那么默认值和()中排第一个的列名相同。也可以自定义唯一约束名。
1
2
#查看都有哪些约束
SELECT * FROM information_schema.table_constraints WHERE table_name = '表名';

删除约束

1
ALTER TABLE 表名 DROP INDEX uk_name_pwd;

注意:可以通过 show index from 表名称; 查看表的索引

4. 主键约束

  • 作用

    用来唯一标识表中的一行记录

  • 关键字

    PRIMARY KEY

  • 特点

    • 主键约束相当于唯一约束+非空约束的组合,主键约束列不允许重复,也不允许出现空值。
    • 一个表最多只能有一个主键约束,建立主键约束可以在列级别创建,也可以在表级别上创建。
    • 主键约束对应着表中的一列或多列(复合主键)
    • 如果是多列组合的复合主键约束,那么这些列都不允许为空值,并且组合的值不允许重复。
    • MySQL的主键名总是PRIMARY,就算自己命名了主键约束名也没用。
    • 当创建主键时,系统默认会在所在的列或组合上建立对应的主键索引(能够根据主键查询的,就根据主键查询,效率更高)。如果删除了主键约束,主键约束对应的索引就自动删除了
    • 需要注意的一点是,不要修改主键字段的值。因为主键是数据记录的唯一标识,如果修改了主键的值,就有可能会破坏数据的完整性。

4.1 添加主键约束

  • 建表时指定主键约束
1
2
3
4
5
6
7
8
9
10
11
create table 表名称(
字段名 数据类型 primary key, #列级模式
字段名 数据类型,
字段名 数据类型
);
create table 表名称(
字段名 数据类型,
字段名 数据类型,
字段名 数据类型,
[constraint 约束名] primary key(字段名) #表级模式
);
  • 举例
1
2
3
4
create table temp(
id int primary key,
name varchar(20)
);
1
2
3
insert into temp values(1,'张三');#成功
insert into temp values(2,'李四');#成功
insert into temp values(1,'王五');#失败

  • 建表后指定主键约束
1
2
3
# 字段列表可以是一个字段,也可以是多个字段,如果是多个字段的话,是复合主键
ALTER TABLE 表名称 ADD PRIMARY KEY(字段列表);
ALTER TABLE 表名称 MODIFY 字段名称 字段类型 PRIMARY KEY;

4.2 复合主键约束

1
2
3
4
5
6
create table 表名称(
字段名 数据类型,
字段名 数据类型,
字段名 数据类型,
primary key(字段名1,字段名2) #表示字段1和字段2的组合是唯一的,也可以有更多个字段
);
1
2
3
4
5
6
CREATE TABLE emp6(
id INT NOT NULL,
NAME VARCHAR(20),
pwd VARCHAR(15),
CONSTRAINT emp7_pk PRIMARY KEY(NAME,pwd)
);

4.3 删除主键约束

1
alter table 表名称 drop primary key;

说明:删除主键约束,不需要指定主键名,因为一个表只有一个主键,删除主键约束后,非空约束还存在

注意:在开发中,不要去删除主键约束!!!!

5. 自增列

  • 作用

    某个字段值自增

  • 关键字

    AUTO_INCREMENT

  • 特点和要求

    • 一个表最多只能有一个自增长列
    • 当需要产生唯一标识符或顺序值时,可设置自增长
    • 自增长列约束的列必须是键列(主键列,唯一键列)
    • 自增约束的列的数据类型必须是整数类型
    • 如果自增列指定了0和null,会在当前最大值的基础上自增;如果自增列手动设置了具体值,则直接赋值具体值。

5.1 指定自增约束

  • 建表时
1
2
3
4
5
6
7
8
9
10
11
12
create table 表名称(
字段名 数据类型 primary key auto_increment,
字段名 数据类型 unique key not null,
字段名 数据类型 unique key,
字段名 数据类型 not null default 默认值,
);
create table 表名称(
字段名 数据类型 default 默认值 ,
字段名 数据类型 unique key auto_increment,
字段名 数据类型 not null default 默认值,,
primary key(字段名)
);
  • 举例
1
2
3
4
create table employee(
eid int primary key auto_increment,
ename varchar(20)
);

  • 建表后
1
alter table 表名称 modify 字段名 数据类型 auto_increment;

5.2 删除自增约束

1
2
#alter table 表名称 modify 字段名 数据类型 auto_increment;#给这个字段增加自增约束
alter table 表名称 modify 字段名 数据类型; #去掉auto_increment相当于删除

6. 外键约束

  • 作用

    限定某个表的某个字段的引用完整性

    比如:员工表的员工所在部门的选择,必须在部门表中能找到对应的部分。

  • 关键字

    FOREIGN KEY

  • 主表和从表/父表和子表

    主表(父表):被引用的表,被参考的表

    从表(子表):引用别人的表,参考别人的表

    例如:员工表的员工所在部门这个字段的值要参考部门表:部门表是主表,员工表是从表。

  • 特点

    1. 从表的外键列,必须引用/参考主表的键(主键或唯一约束的列

      为啥?因为被依赖/ 被参考的值必须是唯一的

    2. 在创建外键约束时,如果不给外键约束命名,默认名不是列名,而是自动产生一个外键名(例如student_ibfk_1),也可以指定外键约束名。

    3. 创建(CREATE)表时就指定外键约束的话,先创建主表,在创建从表

    4. 删表时,先删从表(或先删除外键约束),在删除主表

    5. 当主表的记录被从表参照时,主表的记录将不允许删除,如果要删除数据,需要先删除从表中依赖该记录的数据,然后才可以删除主表的数据。

    6. 在从表中指定外键约束,并且一个表可以建立多个外键约束

    7. 从表的外键列与主表被参照的列名字可以不相同,但是数据类型必须一样,逻辑意义一致。如果类型不一样,创建子表时就会出现错误“ERROR 1005 (HY000): Can’t create table’database.tablename’(errno: 150)”

    8. 当创建外键约束时,系统默认会在所在列上建立对应的普通索引。但是索引名是列名,不是外键约束的约束名(根据外键查询效率很高)

    9. 删除外键约束后,必须手动删除对应的索引

6.1 添加外键索引

  • 建表时
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 先创建主表,在创建从表
create table 主表名称(
字段1 数据类型 primary key,
字段2 数据类型
);
create table 从表名称(
字段1 数据类型 primary key,
字段2 数据类型,
[CONSTRAINT <外键约束名称>] FOREIGN KEY(从表的某个字段) references 主表名(被参考字段)
);
# (从表的某个字段)的数据类型必须与主表名(被参考字段)的数据类型一致,逻辑意义也一样
# (从表的某个字段)的字段名可以与主表名(被参考字段)的字段名一样,也可以不一样
-- FOREIGN KEY: 在表级指定子表中的列
-- REFERENCES: 标示在父表中的列
  • 举例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
create table dept( #主表
did int primary key, #部门编号
dname varchar(50) #部门名称
);
create table emp(#从表
eid int primary key, #员工编号
ename varchar(5), #员工姓名
deptid int, #员工所在的部门
foreign key (deptid) references dept(did) #在从表中指定外键约束
#emp表的deptid和和dept表的did的数据类型一致,意义都是表示部门的编号
);
说明:
(1)主表dept必须先创建成功,然后才能创建emp表,指定外键成功。
(2)删除表时,先删除从表emp,再删除主表dept

  • 建表后

    一般情况下,表与表的关联都是提前设计好了的,因此,会在创建表的时候就把外键约束定义好。不过,如果需要修改表的设计(比如添加新的字段,增加新的关联关系),但没有预先定义外键约束,那么,就要用修改表的方式来补充定义。

1
2
ALTER TABLE 从表名 
ADD [CONSTRAINT 约束名] FOREIGN KEY (从表的字段) REFERENCES 主表名(被引用字段) [on update xx][on delete xx];
  • 举例
1
2
ALTER TABLE emp1
ADD [CONSTRAINT emp_dept_id_fk] FOREIGN KEY(dept_id) REFERENCES dept(dept_id);

总结:约束关系是针对双方的

  • 添加了外键约束后,主表的修改和删除数据受约束
  • 添加了外键约束后,从表的添加和修改数据受约束
  • 在从表上建立外键,要求主表必须存在
  • 删除主表时,要求从表从表先删除,或将从表中外键引用该主表的关系先删除

6.2 约束等级

  • Cascade方式 :在父表上update/delete记录时,同步update/delete掉子表的匹配记录

  • Set null方式 :在父表上update/delete记录时,将子表上匹配记录的列设为null,但是要注意子 表的外键列不能为not null

  • No action方式 :如果子表中有匹配的记录,则不允许对父表对应候选键进行update/delete操作

  • Restrict方式 :同no action, 都是立即检查外键约束

  • Set default方式 (在可视化工具SQLyog中可能显示空白):父表有变更时,子表将外键列设置 成一个默认的值,但Innodb不能识别

6.3 删除外键约束

流程如下:

1
2
3
4
5
6
7
8
# (1)第一步先查看约束名和删除外键约束
SELECT * FROM information_schema.table_constraints WHERE table_name = '表名称';
# 查看某个表的约束名
ALTER TABLE 从表名 DROP FOREIGN KEY 外键约束名;
#(2)第二步查看索引名和删除索引。(注意,只能手动删除)
SHOW INDEX FROM 表名称;
# 查看某个表的索引名
ALTER TABLE 从表名 DROP INDEX 索引名;

6.4 阿里开发规范

强制 】不得使用外键与级联,一切外键概念必须在应用层解决。

说明:(概念解释)学生表中的 student_id 是主键,那么成绩表中的 student_id 则为外键。如果更新学 生表中的 student_id,同时触发成绩表中的 student_id 更新,即为级联更新。外键与级联更新适用于单机低并发,不适合分布式高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响 数据库的插入速度

7 CHECK约束

  • 作用

    检查某个字段的值是否符合xx要求,一般指的是指的范围

  • 关键字

    CHECK

  • 说明:MYSQL 5.7 不支持

  • 演示

1
2
3
4
5
create table employee(
eid int primary key,
ename varchar(5),
gender char check ('男' or '女')
);
1
insert into employee values(1,'张三','妖');

8. DEFAULT约束

  • 作用

    给某个字段/某列指定默认值,一旦设置默认值,在插入数据时,如果此字段没有显式赋值,则赋值为默认值。

  • 关键字

    DEFAULT

8.1 给字段加默认值

  • 建表时
1
2
3
4
5
6
7
8
9
10
11
12
13
14
create table 表名称(
字段名 数据类型 primary key,
字段名 数据类型 unique key not null,
字段名 数据类型 unique key,
字段名 数据类型 not null default 默认值,
);
create table 表名称(
字段名 数据类型 default 默认值 ,
字段名 数据类型 not null default 默认值,
字段名 数据类型 not null default 默认值,
primary key(字段名),
unique key(字段名)
);
说明:默认值约束一般不在唯一键和主键列上加
  • 建表后
1
2
3
4
alter table 表名称 modify 字段名 数据类型 default 默认值;
# 如果这个字段原来有非空约束,你还保留非空约束,那么在加默认值约束时,还得保留非空约束,否则非空约束就被删除了
# 同理,在给某个字段加非空约束也一样,如果这个字段原来有默认值约束,你想保留,也要在modify语句中保留默认值约束,否则就删除了
alter table 表名称 modify 字段名 数据类型 default 默认值 not null;

8.2 删除默认值约束

1
2
alter table 表名称 modify 字段名 数据类型 ;# 删除默认值约束,也不保留非空约束
alter table 表名称 modify 字段名 数据类型 not null; # 删除默认值约束,保留非空约束

9. 加强记忆

  • 唯一约束、主键约束(都涉及到索引)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 唯一约束
# 创建时
CREATE TABLE 表名(
# 约束名称默认是字段名
字段名 字段类型 UNIQUE,
字段名 字段类型 UNIQUE KEY,
# CONSTRAINT 约束名称 UNIQUE(字段名)
CONSTRAINT 约束名称 UNIQUE KEY(字段名)
);
# 修改时
ALTER TABLE 表名 ADD UNIQUE KEY(字段名);
ALTER TABLE 表名 MODIFY 字段名 字段类型 UNIQUE KEY;
# 删除时
ALTER TABLE 表名 DROP INDEX 约束名;

# 主键索引
# 创建时
CREATE TABLE 表名(
# 约束名称默认是字段名
字段名 字段类型 PRIMARY KEY,
[CONSTRAINT 约束名称] PRIMARY KEY
);
# 修改时
ALTER TABLE 表名 ADD PRIMARY KEY(字段名);
ALTER TABLE 表名 MODIFY 字段名 字段类型 PRIMARY KEY;
# 删除时
ALTER TABLE 表名 DROP PRIMARY KEY;
  • 非空约束,自增列,DEFAULT约束
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 非空约束
# 创建时
CREATE TABLE 表名(
字段名 字段类型 NOT NULL
);
# 修改时,删除时
ALTER TABLE 表名 MODIFY 字段名 字段类型 NOT NULL;
ALTER TABLE 表名 MODIFY 字段名 字段类型;

# 自动增长
# 创建时
CREATE TABLE 表名(
# 必须是主键或唯一约束
字段名 数据类型 PRIMARY KEY AUTO_INCREMENT
# 字段名 数据类型 UNIQUE KEY AUTO_INCREMENT
)
# 修改时,删除时时
ALTER TABLE 表名 MODIFY 字段名 数据类型 AUTO_INCREMENT;
ALTER TABLE 表名 MODIFY 字段名 数据类型 AUTO_INCREMENT;

# DEFAULT约束
# 创建时
CREATE TABLE 表名(
字段名 字段类型 DEFAULT 默认值
);
# 修改时,删除时
ALTER TABLE 表名 MODIFY 字段名 字段类型 DEFAULT 默认值;
ALTER TABLE 表名 MODIFY 字段名 字段类型;
# 如果原来存在非空约束和默认值约束,删除后需要保留非空则需要加上,反之一样
ALTER TABLE 表名 MODIFY 字段名 字段类型 NOT NULL;

视图

1. 常见的数据库对象

对象 描述
表(TABLE) 表是存储数据的逻辑单元,以行和列的形式存在,列就是字段,行就是记录
数据字典 就是系统表,存放数据库相关信息的表。系统表的数据通常由数据库系统维护,程序员通常不应该修改,只可查看。
约束(CONSTRAINT) 执行数据校验的规则,用于保证数据完整性的规则
视图(VIEW) 一个或者多个数据表里的数据的逻辑显示,视图并不存储数据
索引(INDEX) 用于提供查询性能,相当于书的目录
存储过程(PROCEDURE) 用于完成一次完整的业务处理,没有返回值,但可通过传出参数将多个值传给调用环境
存储函数(FUNCTION) 用于完成一次特定的计算,具有一个返回值
触发器(TRIGGER) 相当于一个事件监听器,当数据库发生特定事件后,触发器被处罚,完成相应的处理。

2. 视图的概述

2.1 为什么使用视图

视图一方面可以帮我们使用表的一部分而不是所有的表,另一方面也可以针对不同的用户指定不同的查询视图。

2.2 视图的理解

  • 视图是一种虚拟表,本身是不具有数据的,占用很少的内存空间,它是SQL中的一个重要概念。

  • 视图建立在已有表的基础上,视图赖以建立的这些表称为基表

  • 视图的创建和删除只影响视图本身,不影响对应的基表。但是当对视图中的数据进行增加、删除和修改操作时,数据表中的数据会相应地发生编号,反之亦然。

  • 向视图提供数据内容的语句为SELECT语句,可以将视图理解为存储起来的SELECT语句。

  • 视图,向用户提供基表数据的另一种表现形式。通常情况下,小型项目的数据库可以不使用视图,但是在大型项目中,以及数据表比较复杂的情况下,视图的价值就凸显出来了,它可以帮助我们把经常查询的结果集放到虚拟表中,提升使用效率。理解和使用起来都非常方便。

3. 创建视图

  • CREATE VIEW语句中嵌入子查询
1
2
3
4
5
CREATE [OR REPLACE]
[ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}]
VIEW 视图名称 [(字段列表)]
AS 查询语句
[WITH [CASCADED|LOCAL] CHECK OPTION]
  • 精简
1
2
CREATE VIEW 视图名称
AS 查询语句

3.1 创建单表视图

举例:

1
2
3
4
5
CREATE VIEW empvu80
AS
SELECT employee_id, last_name, salary
FROM employees
WHERE department_id = 80;

查询视图:

1
SELECT * FROM empvu80;

举例:

1
2
3
4
CREATE VIEW emp_year_salary (ename,year_salary)
AS
SELECT ename,salary*12*(1+IFNULL(commission_pct,0))
FROM t_employee;

举例:

1
2
3
4
5
CREATE VIEW salvu50
AS
SELECT employee_id ID_NUMBER, last_name NAME,salary*12 ANN_SALARY
FROM employees
WHERE department_id = 50;

说明1:实际上就是我们在 SQL 查询语句的基础上封装了视图 VIEW,这样就会基于 SQL 语句的结果集形 成一张虚拟表。

说明2:在创建视图时,没有在视图名后面指定字段列表,则视图中字段列表默认和SELECT语句中的字段列表一致。如果SELECT语句中给字段取了别名,那么视图中的字段名和别名相同。

3.2 创建多表联合视图

1
2
3
4
5
6
7
CREATE VIEW dept_sum_vu
(name, minsal, maxsal, avgsal)
AS
SELECT d.department_name, MIN(e.salary), MAX(e.salary),AVG(e.salary)
FROM employees e, departments d
WHERE e.department_id = d.department_id
GROUP BY d.department_name;

3.3 查看视图

  • 语法1:查看数据库的表对象、视图对象
1
SHOW TABLES;
  • 语法2:查看视图的结构
1
DESC / DESCRIBE 视图名称;
  • 语法3:查看视图的属性信息
1
2
# 查看视图信息(显示数据表的存储引擎、版本、数据行数和数据大小等)
SHOW TABLE STATUS LIKE '视图名称'\G
  • 语法4:查看视图的详细定义信息
1
SHOW CREATE VIEW 视图名称;

4. 更新视图的数据

MySQL支持使用INSERT、UPDATE和DELETE语句对视图中的数据进行插入、更新和删除操作。当视图中的 数据发生变化时,数据表中的数据也会发生变化,反之亦然。

4.1 不可更新的视图

要使视图可更新,视图中的行和底层基本表中的行之间必须存在 一对一 的关系。另外当视图定义出现如下情况时,视图不支持更新操作:

  • 在定义视图的时候指定了“ALGORITHM = TEMPTABLE”,视图将不支持INSERT和DELETE操作;

  • 视图中不包含基表中所有被定义为非空又未指定默认值的列,视图将不支持INSERT操作;

  • 在定义视图的SELECT语句中使用了JOIN联合查询 ,视图将不支持INSERT和DELETE操作;

  • 在定义视图的SELECT语句后的字段列表中使用了数学表达式子查询,视图将不支持INSERT,也 不支持UPDATE使用了数学表达式、子查询的字段值;

  • 在定义视图的SELECT语句后的字段列表中使用DISTINCT聚合函数GROUP BYHAVINGUNION等,视图将不支持INSERT、UPDATE、DELETE;

  • 在定义视图的SELECT语句中包含了子查询,而子查询中引用了FROM后面的表,视图将不支持 INSERT、UPDATE、DELETE;

  • 视图定义基于一个 不可更新视图 ;

  • 常量视图。

虽然可以更新视图数据,但总的来说,视图作为虚拟表,主要用于方便查询,不建议更新视图的 数据。对视图数据的更改,都是通过对实际数据表里数据的操作来完成的。

5. 修改、删除视图

5.1 修改视图

方式1:使用CREATE OR REPLACE VIEW子句修改视图

1
2
3
4
5
6
CREATE OR REPLACE VIEW empvu80
(id_number, name, sal, department_id)
AS
SELECT employee_id, first_name || ' ' || last_name, salary, department_id
FROM employees
WHERE department_id = 80;

说明:CREATE VIEW 子句中各列的别名应和子查询中各列相对应。

方式二:ALTER VIEW

1
ALTER VIEW 视图名称 AS 查询语句

5.2 删除视图

除视图只是删除视图的定义,并不会删除基表的数据。

1
2
DROP VIEW IF EXISTS 视图名称;
DROP VIEW IF EXISTS 视图名称1,视图名称2,视图名称3,...;

6. 总结

6.1 视图的优点

  1. 操作简单

    将经常使用的查询操作定义为视图,可以使开发人员不需要关心视图对应的数据表的结构、表与表之间的关联关系,也不需要关心数据表之间的业务逻辑和查询条件,而只需要简单地操作视图即可,极大简 化了开发人员对数据库的操作。

  2. 减少数据冗余

    视图跟实际数据表不一样,它存储的是查询语句。所以,在使用的时候,我们要通过定义视图的查询语句来获取结果集。而视图本身不存储数据,不占用数据存储的资源,减少了数据冗余。

  3. 数据安全

    MySQL将用户对数据的访问限制在某些数据的结果集上,而这些数据的结果集可以使用视图来实现。用 户不必直接查询或操作数据表。这也可以理解为视图具有隔离性 。视图相当于在用户和实际的数据表之 间加了一层虚拟表。

    同时,MySQL可以根据权限将用户对数据的访问限制在某些视图上,用户不需要查询数据表,可以直接通过视图获取数据表中的信息。这在一定程度上保障了数据表中数据的安全性。

  4. 适应灵活多变的需求

    当业务系统的需求发生变化后,如果需要改动数据表的结构,则工作量相对较 大,可以使用视图来减少改动的工作量。这种方式在实际工作中使用得比较多。

  5. 能够分解复杂的查询逻辑

    数据库中如果存在复杂的查询逻辑,则可以将问题进行分解,创建多个视图获取数据,再将创建的多个视图结合起来,完成复杂的查询逻辑。

6.2 视图的缺点

如果我们在实际数据表的基础上创建了视图,那么,如果实际数据表的结构变更了,我们就需要及时对 相关的视图进行相应的维护。特别是嵌套的视图(就是在视图的基础上创建视图),维护会变得比较复 杂, 可读性不好 ,容易变成系统的潜在隐患。因为创建视图的 SQL 查询可能会对字段重命名,也可能包 含复杂的逻辑,这些都会增加维护的成本。

实际项目中,如果视图过多,会导致数据库维护成本的问题。

存储过程与函数

1.存储过程概述

1.1 理解

含义:就是一组经过预先编译的SQL语句的封装。

执行过程:存储过程预先存储在Mysql服务器上,需要执行的时候,客户端只需要向服务器发出调用存储过程的命令,服务器端就可以把预先存储好的这一系列SQL语句全部执行。

好处

  1. 简化操作,提高sql语句的重用性,减少开发程序员的压力
  2. 减少操作过程中的失误,提高效率
  3. 减少网络传输量(客户端不需要把所有的SQL语句通过网络发给服务器)
  4. 减少SQL语句暴露在网上的风险,也提高了数据查询的安全性

和视图、函数的对比:

它和视图有着同样的优点,清晰、安全、还可以减少网络传输量。不过它和视图不同,视图是虚拟表,通常不对底层数据表直接操作,而存储过程是程序化的SQL,可以直接操作底层数据表,相比于面向集合的操作方式,能够实现一些更复杂的数据处理。

一旦存储过程被创建出来,使用它就像使用函数一样简单,我们直接通过调用存储过程名即可。相较于函数,存储过程是没有返回值的。

1.2 分类

存储过程的参数类型可以是IN、OUT和INOUT。根据这点分类如下:

  1. 没有参数(没有返回值)
  2. 仅仅带IN类型(有参数无返回)
  3. 仅仅带OUT类型(无参数有返回)
  4. 即带IN,又带OUT(有参数有返回)
  5. 带INOUT(有参数有返回)

注意:IN、OUT、INOUT都可以在一个存储过程中带多个。

2. 创建存储过程

2.1 语法分析

1
2
3
4
5
CREATE PROCEDURE 存储过程名(IN|OUT|INOUT 参数名 参数类型,...)
[characteristics ...]
BEGIN
存储过程体
END

类似于JAVA中的方法

1
2
3
修饰符 返回类型 方法名(参数类型 参数名){
方法体;
}

说明:
1、参数前面的符号的意思

  • IN:当前参数为输入参数,也就是表示入参;存储过程只是读取这个参数的值。如果没有定义参数种类,默认就是IN,表示输入参数。“

  • OUT:当前参数为输出参数,也就是表示出参;执行完成之后,调用这个存储过程的客户端或者应用程序就可以读取这个参数返回的值了。

  • INOUT:当前参数既可以为输入参数,也可以为输出参数。

2、形参类型可以是MySQL数据库中的任意类型。

3、characteristics表示创建存储过程时指定的对存储过程的约束条件,其取值信息如下:

1
2
3
4
5
LANGUAGE SQL
| [NOT] DETERMINISTIC
| { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
| SQL SECURITY { DEFINER | INVOKER }
| COMMENT 'string'

4、需要设置新的结束标记

1
DELIMITER 新的结束标记

因为MySQL默认的语句结束符号为分号‘;’。为了避免与存储过程中SQL语句结束符相冲突,需要使用 DELIMITER改变存储过程的结束符。

示例:

1
2
3
4
5
6
7
DELIMITER $
CREATE PROCEDURE 存储过程名(IN|OUT|INOUT 参数名 参数类型,...)
[characteristics ...]
BEGIN
sql语句1;
sql语句2;
END $

2.2 代码举例

举例1:创建存储过程select_all_data(),查看 emps 表的所有数据

1
2
3
4
5
6
7
8
DELIMITER $
CREATE PROCEDURE select_all_data()
BEGIN
SELECT * FROM emps;
END $
DELIMITER ;
# 调用
CALL select_all_data();

举例2:创建存储过程avg_employee_salary(),返回所有员工的平均工资

1
2
3
4
5
6
7
CREATE PROCEDURE avg_employee_salary()
BEGIN
SELECT AVG(salary) AS avg_salary FROM emps;
END //
DELIMITER ;
# 调用
CALL avg_employee_salary();

举例3:创建存储过程show_min_salary(),查看“emps”表的最低薪资值。并将最低薪资通过OUT参数“ms” 输出

1
2
3
4
5
6
7
8
9
DELIMITER //
CREATE PROCEDURE show_min_salary(OUT ms DOUBLE)
BEGIN
SELECT MIN(salary) INTO ms FROM emps;
END //
DELIMITER ;
# 调用
CALL avg_employee_salary(@ms);
SELECT @ms;

举例4:创建存储过程show_someone_salary(),查看“emps”表的某个员工的薪资,并用IN参数empname 输入员工姓名。

1
2
3
4
5
6
7
8
9
10
11
12
DELIMITER //
CREATE PROCEDURE show_someone_salary(IN empname VARCHAR(20))
BEGIN
SELECT salary FROM emps WHERE last_name = empname;
END //
DELIMITER ;
# 调用
# 方法一
CALL show_empname_salary('Ernst');
# 方式二
set @empname := 'Ernst';
CALL show_empname_salary(@empname);

举例5:创建存储过程show_someone_salary2(),查看“emps”表的某个员工的薪资,并用IN参数empname 输入员工姓名,用OUT参数empsalary输出员工薪资。

1
2
3
4
5
6
7
8
9
DELIMITER //
CREATE PROCEDURE show_someone_salary2(IN empname VARCHAR(20),OUT empsalary DOUBLE)
BEGIN
SELECT salary INTO empsalary FROM emps WHERE last_name = empname;
END //
DELIMITER ;
# 调用
CALL show_someone_salary2('Ernst',@empsalary);
SELECT @empsalary;

举例6:创建存储过程show_mgr_name(),查询某个员工领导的姓名,并用INOUT参数“empname”输入员 工姓名,输出领导的姓名。

1
2
3
4
5
6
7
DELIMITER //
CREATE PROCEDURE show_mgr_name(INOUT empname VARCHAR(20))
BEGIN
SELECT last_name INTO empname FROM emps
WHERE employee_id = (SELECT manager_id FROM emps WHERE last_name = empname);
END //
DELIMITER ;

3. 调用存储过程

3.1 调用格式

存储过程有多种调用方法。存储过程必须使用CALL语句调用,并且存储过程和数据库相关,如果要执行 其他数据库中的存储过程,需要指定数据库名称,例如CALL dbname.procname

1
CALL 存储过程名(实参列表)

1、调用in模式的参数:

1
CALL sp1('值');

2、调用out模式的参数:

1
2
3
SET @name;
CALL sp1(@name);
SELECT @name;

3、调用inout模式的参数:

1
2
3
SET @name=值;
CALL sp1(@name);
SELECT @name;

如果你用的是 Navicat 工具,那么在编写存储过程的时候,Navicat 会自动设置 DELIMITER 为其他符号, 我们不需要再进行 DELIMITER 的操作。 直接使用 CALL add_num(50); 即可。这里我传入的参数为 50,也就是统计 1+2+…+50 的积累之和。

4. 存储函数的使用

4.1 语法分析

语法格式:

1
2
3
4
5
6
CREATE FUNCTION 函数名(参数名 参数类型,...)
RETURNS 返回值类型
[characteristics ...]
BEGIN
函数体 #函数体中肯定有 RETURN 语句
END

说明:

1、参数列表:指定参数为IN、OUT或INOUT只对PROCEDURE是合法的,FUNCTION中总是默认为IN参 数。

2、RETURNS type 语句表示函数返回数据的类型; RETURNS子句只能对FUNCTION做指定,对函数而言这是 强制 的。它用来指定函数的返回类型,而且函 数体必须包含一个 RETURN value 语句。

3、characteristic 创建函数时指定的对函数的约束。取值与创建存储过程时相同,这里不再赘述。

4、函数体也可以用BEGIN…END来表示SQL代码的开始和结束。如果函数体只有一条语句,也可以省略 BEGIN…END。

4.2 调用存储函数

在MySQL中,存储函数的使用方法与MySQL内部函数的使用方法是一样的。换言之,用户自己定义的存 储函数与MySQL内部函数是一个性质的。区别在于,存储函数是 用户自己定义 的,而内部函数是MySQL 的 开发者定义的。

1
SELECT 函数名(实参列表)

4.3 代码举例:

举例一:创建存储函数,名称为email_by_name(),参数定义为空,该函数查询Abel的email,并返回,数据类型为 字符串型。

1
2
3
4
5
6
7
8
CREATE FUNCTION email_by_name()
RETURNS VARCHAR(25)
DETERMINISTIC
CONTAINS SQL
BEGIN
RETURN (SELECT email FROM employees WHERE last_name = 'Abel');
END //
DELIMITER ;
1
2
# 调用
SELECT email_by_name();

举例二:创建存储函数,名称为email_by_id(),参数传入emp_id,该函数查询emp_id的email,并返回,数据类型 为字符串型。

1
2
3
4
5
6
7
8
9
DELIMITER //
CREATE FUNCTION email_by_id(emp_id INT)
RETURNS VARCHAR(25)
DETERMINISTIC
CONTAINS SQL
BEGIN
RETURN (SELECT email FROM employees WHERE employee_id = emp_id);
END //
DELIMITER ;
1
2
3
# 调用
SET @emp_id = 102;
SELECT email_by_id(102);

注意:

若在创建存储函数中报错“ you might want to use the less safe log_bin_trust_function_creators variable ”,有两种处理方法:

方式1:加上必要的函数特性“[NOT] DETERMINISTIC”和“{CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA}”

方式2:

1
mysql> SET GLOBAL log_bin_trust_function_creators = 1;

4.4 对比存储函数和存储过程

关键字 调用语法 返回值 应用场景
存储过程 PROCEDURE CALL 存储过程() 理解为有0个或 多个 一般用于更新
存储函数 FUNCTION SELECT 函数() 只能是一个 一般用于查询结果为一个值并返回时

此外,存储函数可以放在查询语句中使用,存储过程不行。反之,存储过程的功能更加强大,包括能够 执行对表的操作(比如创建表,删除表等)和事务操作,这些功能是存储函数不具备的。

5. 存储过程和函数的查看、修改、删除

5.1 查看

MySQL存储了存储过程和函数的状态信息,用户可以使用SHOW STATUS语句或SHOW CREATE语句来查 看,也可直接从系统的information_schema数据库中查询。这里介绍3种方法。

1. 使用SHOW CREATE语句查看存储过程和函数的创建信息

基本语法结构如下:

1
SHOW CREATE {PROCEDURE | FUNCTION} 存储过程名或函数名

举例:

1
SHOW CREATE FUNCTION test_db.CountProc \G

2. 使用SHOW STATUS语句查看存储过程和函数的状态信息

基本语法结构如下:

1
SHOW {PROCEDURE | FUNCTION} STATUS [LIKE 'pattern']

这个语句返回子程序的特征,如数据库、名字、类型、创建者及创建和修改日期。

举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
mysql> SHOW PROCEDURE STATUS LIKE 'SELECT%' \G
*************************** 1. row ***************************
Db: test_db
Name: SelectAllData
Type: PROCEDURE
Definer: root@localhost
Modified: 2021-10-16 15:55:07
Created: 2021-10-16 15:55:07
Security_type: DEFINER
Comment:
character_set_client: utf8mb4
collation_connection: utf8mb4_general_ci
Database Collation: utf8mb4_general_ci
1 row in set (0.00 sec)

3. 从information_schema.Routines表中查看存储过程和函数的信息

MySQL中存储过程和函数的信息存储在information_schema数据库下的Routines表中。可以通过查询该表 的记录来查询存储过程和函数的信息。其基本语法形式如下:

1
2
SELECT * FROM information_schema.Routines
WHERE ROUTINE_NAME='存储过程或函数的名' [AND ROUTINE_TYPE = {'PROCEDURE|FUNCTION'}];

说明:如果在MySQL数据库中存在存储过程和函数名称相同的情况,最好指定ROUTINE_TYPE查询条件来 指明查询的是存储过程还是函数。

举例:从Routines表中查询名称为CountProc的存储函数的信息,代码如下:

1
2
SELECT * FROM information_schema.Routines
WHERE ROUTINE_NAME='count_by_id' AND ROUTINE_TYPE = 'FUNCTION' \G`

5.2 修改

修改存储过程或函数,不影响存储过程或函数功能,只是修改相关特性。使用ALTER语句实现。

1
ALTER {PROCEDURE | FUNCTION} 存储过程或函数的名 [characteristic ...]

其中,characteristic指定存储过程或函数的特性,其取值信息与创建存储过程、函数时的取值信息略有 不同。

1
2
3
{ CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
| SQL SECURITY { DEFINER | INVOKER }
| COMMENT 'string'
  • CONTAINS SQL ,表示子程序包含SQL语句,但不包含读或写数据的语句。

  • NO SQL ,表示子程序中不包含SQL语句。

  • READS SQL DATA ,表示子程序中包含读数据的语句。

  • MODIFIES SQL DATA ,表示子程序中包含写数据的语句。

  • SQL SECURITY { DEFINER | INVOKER } ,指明谁有权限来执行。

    • DEFINER ,表示只有定义者自己才能够执行。
    • INVOKER ,表示调用者可以执行。
  • COMMENT ‘string’ ,表示注释信息

5.3 删除

删除存储过程和函数,可以使用DROP语句,其语法结构如下:

1
DROP {PROCEDURE | FUNCTION} [IF EXISTS] 存储过程或函数的名

IF EXISTS:如果程序或函数不存储,它可以防止发生错误,产生一个用SHOW WARNINGS查看的警告。

举例:

1
2
DROP PROCEDURE CountProc;
DROP FUNCTION CountProc;

6. 优点和缺点

优点

**1、存储过程可以一次编译多次使用。**存储过程只在创建时进行编译,之后的使用都不需要重新编译, 这就提升了 SQL 的执行效率。

**2、可以减少开发工作量。**将代码 封装 成模块,实际上是编程的核心思想之一,这样可以把复杂的问题 拆解成不同的模块,然后模块之间可以 重复使用 ,在减少开发工作量的同时,还能保证代码的结构清 晰。

**3、存储过程的安全性强。**我们在设定存储过程的时候可以 设置对用户的使用权限 ,这样就和视图一样具 有较强的安全性。

**4、可以减少网络传输量。**因为代码封装到存储过程中,每次使用只需要调用存储过程即可,这样就减 少了网络传输量。

**5、良好的封装性。**在进行相对复杂的数据库操作时,原本需要使用一条一条的 SQL 语句,可能要连接 多次数据库才能完成的操作,现在变成了一次存储过程,只需要 连接一次即可 。

缺点

**1、可移植性差。**存储过程不能跨数据库移植,比如在 MySQL、Oracle 和 SQL Server 里编写的存储过 程,在换成其他数据库时都需要重新编写。

**2、调试困难。只有少数 DBMS 支持存储过程的调试。**对于复杂的存储过程来说,开发和维护都不容 易。虽然也有一些第三方工具可以对存储过程进行调试,但要收费。

**3、存储过程的版本管理很困难。**比如数据表索引发生变化了,可能会导致存储过程失效。我们在开发 软件的时候往往需要进行版本管理,但是存储过程本身没有版本控制,版本迭代更新的时候很麻烦。

**4、它不适合高并发的场景。**高并发的场景需要减少数据库的压力,有时数据库会采用分库分表的方 式,而且对可扩展性要求很高,在这种情况下,存储过程会变得难以维护, 增加数据库的压力 ,显然就 不适用了。

7. 课后练习

准备工作

1
2
CREATE DATABASE test15_pro_func;
USE test15_pro_func;
  1. 创建存储过程insert_user(),实现传入用户名和密码,插入到admin表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
CREATE TABLE admin(
id INT PRIMARY KEY AUTO_INCREMENT,
user_name VARCHAR(15) NOT NULL,
pwd VARCHAR(25) NOT NULL
);

DELIMITER //
CREATE PROCEDURE insert_user(IN username VARCHAR(15),IN `password` VARCHAR(25))
BEGIN
INSERT INTO admin(user_name,pwd) VALUES(username,`password`);
END //
DELIMITER ;

CALL insert_user('lisa','123456');
SELECT * FROM admin;
  1. 创建存储过程get_phone(),实现传入女神编号,返回女神姓名和女神电话
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
CREATE TABLE beauty(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(15) NOT NULL,
phone VARCHAR(15) UNIQUE,
birth DATE
);
INSERT INTO beauty(NAME,phone,birth)
VALUES
('朱茵','13201233453','1982-02-12'),
('孙燕姿','13501233653','1980-12-09'),
('田馥甄','13651238755','1983-08-21'),
('邓紫棋','17843283452','1991-11-12'),
('刘若英','18635575464','1989-05-18'),
('杨超越','13761238755','1994-05-11');
SELECT * FROM beauty;

DELIMITER $
CREATE PROCEDURE get_phone(IN bea_id VARCHAR(15),OUT `bname` VARCHAR(20),OUT bphone VARCHAR(20))
BEGIN
SELECT `name`,phone INTO bname,bphone FROM beauty WHERE id = bea_id;
END $

CALL get_phone('2',@bname,@bphone);
SELECT @bname,@bphone;
  1. 创建存储过程date_diff(),实现传入两个女神生日,返回日期间隔大小
1
2
3
4
5
6
7
8
9
10
DELIMITER $
CREATE PROCEDURE date_diff(IN birth1 date,IN birth2 DATE,OUT sum_date INT)
BEGIN
SELECT DATEDIFF(birth1,birth2) INTO sum_date FROM DUAL;
END $
DELIMITER ;
SET @birth1 = '1999-10-5';
SET @birth2 = '1999-12-1';
CALL date_diff(@birth1,@birth2,@sum_date);
SELECT @sumdate;

变量,流程控制和游标

在MySQL数据库的存储过程和函数中,可以使用变量来存储查询或计算的中间结果数据,或者输出最终 的结果数据。

在 MySQL 数据库中,变量分为 系统变量 以及 用户自定义变量 。

1. 变量

1.1 系统变量

  • 查看所有或部分系统变量
1
2
3
4
5
6
#查看所有全局变量
SHOW GLOBAL VARIABLES;
#查看所有会话变量
SHOW SESSION VARIABLES;

SHOW VARIABLES;
1
2
3
4
#查看满足条件的部分系统变量。
SHOW GLOBAL VARIABLES LIKE '%标识符%';
#查看满足条件的部分会话变量
SHOW SESSION VARIABLES LIKE '%标识符%';

举例:

1
SHOW GLOBAL VARIABLES LIKE 'admin_%';
  • 查看指定系统变量

作为 MySQL 编码规范,MySQL 中的系统变量以 两个“@” 开头,其中“@@global”仅用于标记全局系统变 量,“@@session”仅用于标记会话系统变量。“@@”首先标记会话系统变量,如果会话系统变量不存在, 则标记全局系统变量。

1
2
3
4
5
6
#查看指定的系统变量的值
SELECT @@global.变量名;
#查看指定的会话变量的值
SELECT @@session.变量名;
#或者
SELECT @@变量名;
  • 修改系统变量的值

有些时候,数据库管理员需要修改系统变量的默认值,以便修改当前会话或者MySQL服务实例的属性、 特征。具体方法:

方式1:修改MySQL 配置文件 ,继而修改MySQL系统变量的值(该方法需要重启MySQL服务)

方式2:在MySQL服务运行期间,使用“set”命令重新设置系统变量的值

1
2
3
4
5
6
7
8
9
10
11
#为某个系统变量赋值
#方式1:
SET @@global.变量名=变量值;
#方式2:
SET GLOBAL 变量名=变量值;

#为某个会话变量赋值
#方式1:
SET @@session.变量名=变量值;
#方式2:
SET SESSION 变量名=变量值;

举例:

1
2
SELECT @@global.autocommit;
SET GLOBAL autocommit=0;
1
2
SELECT @@session.tx_isolation;
SET @@session.tx_isolation='read-uncommitted';

1.2 用户变量

1.2.1 用户变量分类

用户变量是用户自己定义的,作为 MySQL 编码规范,MySQL 中的用户变量以一个“@”开头。根据作用 范围不同,又分为会话用户变量局部变量

  • 会话用户变量:作用域和会话变量一样,只对当前连接会话有效。

  • 局部变量:只在 BEGIN 和 END 语句块中有效。局部变量只能在存储过程和函数中使用。

1.2.2 会话用户变量

  • 变量的定义
1
2
3
4
5
6
7
#方式1:“=”或“:=”
SET @用户变量 = 值;
SET @用户变量 := 值;

#方式2:“:=” 或 INTO关键字
SELECT @用户变量 := 表达式 [FROM 等子句];
SELECT 表达式 INTO @用户变量 [FROM 等子句];
  • 查看用户变量的值 (查看、比较、运算等)
1
SELECT @用户变量
  • 举例
1
2
3
4
5
SELECT @num := COUNT(*) FROM employees;
SELECT @num;

SELECT AVG(salary) INTO @avgsalary FROM employees;
SELECT @avgsalary;

1.2.3 局部变量

定义:可以使用 DECLARE 语句定义一个局部变量

作用域:仅仅在定义它的 BEGIN … END 中有效

位置:只能放在 BEGIN … END 中,而且只能放在第一句

1
2
3
4
5
6
7
8
9
10
BEGIN
#声明局部变量
DECLARE 变量名1 变量数据类型 [DEFAULT 变量默认值];
DECLARE 变量名2,变量名3,... 变量数据类型 [DEFAULT 变量默认值];
#为局部变量赋值
SET 变量名1 = 值;
SELECT 值 INTO 变量名2 [FROM 子句];
#查看局部变量的值
SELECT 变量1,变量2,变量3;
END
  • 定义变量
1
DECLARE 变量名 类型 [default 值]; # 如果没有DEFAULT子句,初始值为NULL

举例:

1
DECLARE myparam INT DEFAULT 100;
  • 变量赋值
1
2
3
4
5
# 方式1:一般用于赋简单的值
SET 变量名=值;
SET 变量名:=值;
# 方式2:一般用于赋表中的字段值
SELECT 字段名或表达式 INTO 变量名 FROM 表;
  • 使用变量(查看、比较、运算等)
1
SELECT 局部变量名;

举例1:声明局部变量,并分别赋值为employees表中employee_id为102的last_name和salary

1
2
3
4
5
6
7
8
9
CREATE PROCEDURE set_value()
BEGIN
DECLARE emp_name VARCHAR(25);
DECLARE sal DOUBLE(10,2);
SELECT last_name,salary INTO emp_name,sal
FROM employees
WHERE employee_id = 102;
SELECT emp_name,sal;
END

1.2.4 对比会话用户变量与局部变量

作用域 定义位置 语法
会话用户变量 当前会话 会话的任何地方 加@符号,不用指定类型
局部变量 定义它的BEGIN END中 BEGIN END的第一句话 一般不用加@,需要指定类型

2. 流程控制

解决复杂问题不可能通过一个 SQL 语句完成,我们需要执行多个 SQL 操作。流程控制语句的作用就是控 制存储过程中 SQL 语句的执行顺序,是我们完成复杂操作必不可少的一部分。只要是执行的程序,流程 就分为三大类:

  • 顺序结构 :程序从上往下依次执行、
  • 分支结构 :程序按条件进行选择执行,从两条或多条路径中选择一条执行
  • 循环结构 :程序满足一定条件下,重复执行一组语句

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

  • 条件判断语句 :IF 语句和 CASE 语句
  • 循环语句 :LOOP、WHILE 和 REPEAT 语句
  • 跳转语句 :ITERATE 和 LEAVE 语句

2.1 分支结构之 IF

IF 语句的语法结构是:

1
2
3
4
IF 表达式1 THEN 操作1
[ELSEIF 表达式2 THEN 操作2]……
[ELSE 操作N]
END IF

根据表达式的结果为TRUE或FALSE执行相应的语句。这里“[]”中的内容是可选的。

  • 特点:① 不同的表达式对应不同的操作 ② 使用在begin end中

举例:声明存储过程“update_salary_by_eid2”,定义IN参数emp_id,输入员工编号。判断该员工 薪资如果低于9000元并且入职时间超过5年,就涨薪500元;否则就涨薪100元。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
DELIMITER //
CREATE PROCEDURE update_salary_by_eid2(IN emp_id INT)
BEGIN
DECLARE emp_salary DOUBLE;
DECLARE hire_year DOUBLE;
SELECT salary INTO emp_salary FROM employees WHERE employee_id = emp_id;
SELECT DATEDIFF(CURDATE(),hire_date)/365 INTO hire_year
FROM employees WHERE employee_id = emp_id;
IF emp_salary < 8000 AND hire_year > 5
THEN UPDATE employees SET salary = salary + 500 WHERE employee_id =
emp_id;
ELSE
UPDATE employees SET salary = salary + 100 WHERE employee_id = emp_id;
END IF;
END //
DELIMITER ;

2.2 分支结构之 CASE

  • CASE 语句的语法结构1:
1
2
3
4
5
6
7
#情况一:类似于switch
CASE 表达式
WHEN 值1 THEN 结果1或语句1(如果是语句,需要加分号)
WHEN 值2 THEN 结果2或语句2(如果是语句,需要加分号)
...
ELSE 结果n或语句n(如果是语句,需要加分号)
END [case](如果是放在begin end中需要加上case,如果放在select后面不需要)

举例:使用CASE流程控制语句的第1种格式,判断val值等于1、等于2,或者两者都不等。

1
2
3
4
5
CASE val
WHEN 1 THEN SELECT 'val is 1';
WHEN 2 THEN SELECT 'val is 2';
ELSE SELECT 'val is not 1 or 2';
END CASE;
  • CASE 语句的语法结构2:
1
2
3
4
5
6
7
#情况二:类似于多重if
CASE
WHEN 条件1 THEN 结果1或语句1(如果是语句,需要加分号)
WHEN 条件2 THEN 结果2或语句2(如果是语句,需要加分号)
...
ELSE 结果n或语句n(如果是语句,需要加分号)
END [case](如果是放在begin end中需要加上case,如果放在select后面不需要)

举例:使用CASE流程控制语句的第2种格式,判断val是否为空、小于0、大于0或者等于0。

1
2
3
4
5
6
CASE
WHEN val IS NULL THEN SELECT 'val is null';
WHEN val < 0 THEN SELECT 'val is less than 0';
WHEN val > 0 THEN SELECT 'val is greater than 0';
ELSE SELECT 'val is 0';
END CASE;

2.3 循环结构之LOOP

LOOP循环语句用来重复执行某些语句。LOOP内的语句一直重复执行直到循环被退出(使用LEAVE子 句),跳出循环过程。

LOOP语句的基本格式如下:

1
2
3
[loop_label:] LOOP
循环执行的语句
END LOOP [loop_label]

其中,loop_label表示LOOP语句的标注名称,该参数可以省略。

举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 举例2:当市场环境变好时,公司为了奖励大家,决定给大家涨工资。声明存储过程
# “update_salary_loop()”,声明OUT参数num,输出循环次数。存储过程中实现循环给大家涨薪,薪资涨为
# 原来的1.1倍。直到全公司的平均薪资达到12000结束。并统计循环次数。
CREATE PROCEDURE update_salary_loop(OUT num INT)
BEGIN
# 循环次数
DECLARE loop_count INT DEFAULT 0;
# 全公司平均工资
DECLARE avg_salary DECIMAL(8,2);
loopcount:LOOP
SELECT AVG(salary) INTO avg_salary FROM emps;
IF avg_salary >= 12000
THEN LEAVE loopcount;
ELSE
UPDATE emps set salary = salary * 1.1;
SET loop_count = loop_count + 1;
END IF;
END LOOP loopcount;
SELECT loop_count INTO num;
END

CALL update_salary_loop(@num);
SELECT @num;

2.4 循环结构之WHILE

WHILE语句创建一个带条件判断的循环过程。WHILE在执行语句执行时,先对指定的表达式进行判断,如 果为真,就执行循环内的语句,否则退出循环。WHILE语句的基本格式如下:

1
2
3
[while_label:] WHILE 循环条件 DO
循环体
END WHILE [while_label];

while_label为WHILE语句的标注名称;如果循环条件结果为真,WHILE语句内的语句或语句群被执行,直 至循环条件为假,退出循环。

举例:WHILE语句示例,i值小于10时,将重复执行循环过程,代码如下:

1
2
3
4
5
6
7
8
9
10
CREATE PROCEDURE test_while()
BEGIN
DECLARE i INT DEFAULT 0;
WHILE i < 10 DO
SET i = i + 1;
END WHILE;
SELECT i;
END
#调用
CALL test_while();

2.5 循环结构之REPEAT

REPEAT语句创建一个带条件判断的循环过程。与WHILE循环不同的是,REPEAT 循环首先会执行一次循 环,然后在 UNTIL 中进行表达式的判断,如果满足条件就退出,即 END REPEAT;如果条件不满足,则会 就继续执行循环,直到满足退出条件为止。

REPEAT语句的基本格式如下:

1
2
3
4
[repeat_label:] REPEAT
循环体的语句
UNTIL 结束循环的条件表达式
END REPEAT [repeat_label]

repeat_label为REPEAT语句的标注名称,该参数可以省略;REPEAT语句内的语句或语句群被重复,直至expr_condition为真。

举例一:

1
2
3
4
5
6
7
8
9
CREATE PROCEDURE test_repeat()
BEGIN
DECLARE i INT DEFAULT 0;
REPEAT
SET i = i + 1;
UNTIL i >= 10
END REPEAT;
SELECT i;
END

对比三种循环

1、这三种循环都可以省略名称,但如果循环中添加了循环控制语句(LEAVE或ITERATE)则必须添加名称。

2、 LOOP:一般用于实现简单的”死”循环 WHILE:先判断后执行 REPEAT:先执行后判断,无条件 至少执行一次

2.6 跳转语句之LEAVE语句

LEAVE语句:可以用在循环语句内,或者以 BEGIN 和 END 包裹起来的程序体内,表示跳出循环或者跳出 程序体的操作。如果你有面向过程的编程语言的使用经验,你可以把 LEAVE 理解为 break。

基本格式如下:

1
LEAVE 标记名

其中,label参数表示循环的标志。LEAVE和BEGIN … END或循环一起被使用。

创建存储过程 “leave_begin()”,声明INT类型的IN参数num。给BEGIN…END加标记名,并在 BEGIN…END中使用IF语句判断num参数的值。

  • 如果num<=0,则使用LEAVE语句退出BEGIN…END;

  • 如果num=1,则查询“employees”表的平均薪资;

  • 如果num=2,则查询“employees”表的最低薪资;

  • 如果num>2,则查询“employees”表的最高薪资。

1
2
3
4
5
6
7
8
9
10
11
12
13
CREATE PROCEDURE leave_begin(IN num INT)
begin_label: BEGIN
IF num<=0
THEN LEAVE begin_label;
ELSEIF num=1
THEN SELECT AVG(salary) FROM employees;
ELSEIF num=2
THEN SELECT MIN(salary) FROM employees;
ELSE
SELECT MAX(salary) FROM employees;
END IF;
SELECT COUNT(*) FROM employees;
END

2.7 跳转语句之ITERATE语句

ITERATE语句:只能用在循环语句(LOOP、REPEAT和WHILE语句)内,表示重新开始循环,将执行顺序 转到语句段开头处。如果你有面向过程的编程语言的使用经验,你可以把 ITERATE 理解为 continue,意 思为“再次循环”。

语句基本格式如下:

1
ITERATE label

label参数表示循环的标志。ITERATE语句必须跟在循环标志前面。

举例: 定义局部变量num,初始值为0。循环结构中执行num + 1操作。

  • 如果num < 10,则继续执行循环;
  • 如果num > 15,则退出循环结构;
1
2
3
4
5
6
7
8
9
10
11
12
13
CREATE PROCEDURE test_iterate()
BEGIN
DECLARE num INT DEFAULT 0;
my_loop:LOOP
SET num = num + 1;
IF num < 10
THEN ITERATE my_loop;
ELSE IF num > 15
THEN LEAVE my_loop;
END IF;
SELECT '尚硅谷:让天下没有难学的技术';
END LOOP my_loop;
END

3. 游标

3.1 什么是游标?

虽然我们也可以通过筛选条件 WHERE 和 HAVING,或者是限定返回记录的关键字 LIMIT 返回一条记录, 但是,却无法在结果集中像指针一样,向前定位一条记录、向后定位一条记录,或者是随意定位到某一 条记录,并对记录的数据进行处理。

这个时候,就可以用到游标。游标,提供了一种灵活的操作方式,让我们能够对结果集中的每一条记录 进行定位,并对指向的记录中的数据进行操作的数据结构。游标让 SQL 这种面向集合的语言有了面向过 程开发的能力。

在 SQL 中,游标是一种临时的数据库对象,可以指向存储在数据库表中的数据行指针。这里游标充当了 指针的作用,我们可以通过操作游标来对数据行进行操作。

3.2 使用游标步骤

游标必须在声明处理程序之前被声明,并且变量和条件还必须在声明游标或处理程序之前被声明。

如果我们想要使用游标,一般需要经历四个步骤。不同的 DBMS 中,使用游标的语法可能略有不同。

第一步,声明游标

在MySQL中,使用DECLARE关键字来声明游标,其语法的基本形式如下:

1
DECLARE cursor_name CURSOR FOR select_statement;

这个语法适用于 MySQL,SQL Server,DB2 和 MariaDB。如果是用 Oracle 或者 PostgreSQL,需要写成:

1
DECLARE cursor_name CURSOR IS select_statement;

要使用 SELECT 语句来获取数据结果集,而此时还没有开始遍历数据,这里select_statement代表的是 SELECT 语句,返回一个用于创建游标的结果集。

例如:

1
2
DECLARE cur_emp CURSOR FOR
SELECT employee_id,salary FROM employees;

第二步,打开游标

1
OPEN cursor_name

当我们定义好游标之后,如果想要使用游标,必须先打开游标。打开游标的时候 SELECT 语句的查询结 果集就会送到游标工作区,为后面游标的逐条读取结果集中的记录做准备。

第三步,使用游标(从游标中取得数据)

1
FETCH cursor_name INTO var_name [, var_name] ...

这句的作用是使用 cursor_name 这个游标来读取当前行,并且将数据保存到 var_name 这个变量中,游 标指针指到下一行。如果游标读取的数据行有多个列名,则在 INTO 关键字后面赋值给多个变量名即可。

注意:var_name必须在声明游标之前就定义好。

注意:游标的查询结果集中的字段数,必须跟 INTO 后面的变量数一致,否则,在存储过程执行的时候,MySQL 会提示错误。

第四步,关闭游标

1
CLOSE cursor_name

有 OPEN 就会有 CLOSE,也就是打开和关闭游标。当我们使用完游标后需要关闭掉该游标。因为游标会占用系统资源,如果不及时关闭,游标会一直保持到存储过程结束,影响系统运行的效率。而关闭游标 的操作,会释放游标占用的系统资源。

关闭游标之后,我们就不能再检索查询结果中的数据行,如果需要检索只能再次打开游标。


  • 举例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 创建存储过程“get_count_by_limit_total_salary()”,声明IN参数 limit_total_salary,DOUBLE类型;声明
# OUT参数total_count,INT类型。函数的功能可以实现累加薪资最高的几个员工的薪资值,直到薪资总和
# 达到limit_total_salary参数的值,返回累加的人数给total_count。
CREATE PROCEDURE get_count_by_limit_total_salary(IN limit_total_salary DOUBLE(8,2),OUT total_count INT)
BEGIN
DECLARE sum_salary DOUBLE DEFAULT 0; #记录累加的总工资
DECLARE cursor_salary DOUBLE DEFAULT 0; #记录某一个工资值
DECLARE emp_count INT DEFAULT 0; #记录循环个数

# 1.声明游标
DECLARE emp_sal CURSOR FOR SELECT salary FROM emps ORDER BY salary DESC;
# 2.打开游标
OPEN emp_sal;

REPEAT
# 3.使用游标
FETCH emp_sal INTO cursor_salary;
SET sum_salary = sum_salary + cursor_salary;
SET emp_count = emp_count + 1;
UNTIL sum_salary >= limit_total_salary
END REPEAT;

# 4. 关闭游标
CLOSE emp_sal;
SELECT emp_count INTO total_count;
END

CALL get_count_by_limit_total_salary(200000,@totalcount);
SELECT @totalcount;

3.5 小结

游标是 MySQL 的一个重要的功能,为逐条读取结果集中的数据,提供了完美的解决方案。跟在应用层 面实现相同的功能相比,游标可以在存储程序中使用,效率高,程序也更加简洁。

但同时也会带来一些性能问题,比如在使用游标的过程中,会对数据行进行加锁,这样在业务并发量大 的时候,不仅会影响业务之间的效率,还会 消耗系统资源 ,造成内存不足,这是因为游标是在内存中进 行的处理。

建议:养成用完之后就关闭的习惯,这样才能提高系统的整体效率。

触发器

1. 触发器的概述

触发器是由事件来触发某个操作,这些事件包括NSERTUPDATEDELETE事件。所谓事件就是指 用户的动作或者触发某项行为。如果定义了触发程序,当数据库执行这些语句时候,就相当于事件发生 了,就会自动激发触发器执行相应的操作。

当对数据表中的数据执行插入、更新和删除操作,需要自动执行一些数据库逻辑时,可以使用触发器来 实现。

2. 触发器的创建

2.1 创建触发器语法

创建触发器的语法结构是:

1
2
3
4
CREATE TRIGGER 触发器名称
{BEFORE|AFTER} {INSERT|UPDATE|DELETE} ON 表名
FOR EACH ROW
触发器执行的语句块;

说明

  • 表名 :表示触发器监控的对象

  • BEFORE|AFTER :表示触发的时间。BEFORE 表示在事件之前触发;AFTER 表示在事件之后触发。

  • INSERT|UPDATE|DELETE :表示触发的事件。

    • INSERT 表示插入记录时触发;
    • UPDATE 表示更新记录时触发;
    • DELETE 表示删除记录时触发。
  • 触发器执行的语句块 :可以是单条SQL语句,也可以是由BEGIN…END结构组成的复合语句块。

2.2 代码举例

1、创建表

1
2
3
4
5
6
7
8
CREATE TABLE test_trigger (
id INT PRIMARY KEY AUTO_INCREMENT,
t_note VARCHAR(30)
);
CREATE TABLE test_trigger_log (
id INT PRIMARY KEY AUTO_INCREMENT,
t_log VARCHAR(30)
);

2、创建触发器:创建名称为before_insert的触发器,向test_trigger数据表插入数据之前,向 test_trigger_log数据表中插入before_insert的日志信息。

1
2
3
4
5
6
7
8
9
DELIMITER //
CREATE TRIGGER before_insert
BEFORE INSERT ON test_trigger
FOR EACH ROW
BEGIN
INSERT INTO test_trigger_log (t_log)
VALUES('before_insert');
END //
DELIMITER ;

3、向test_trigger表中插入记录

1
INSERT INTO test_trigger (t_note) VALUES ('测试 BEFORE INSERT 触发器');

4、查询数据

1
SELECT * FROM test_trigger_log;

3. 查看、删除触发器

3.1 查看触发器

查看触发器是查看数据库中已经存在的触发器的定义、状态和语法信息等。

1
2
3
4
5
6
# 方式1:查看当前数据库的所有触发器的定义
SHOW TRIGGERS\G
# 方式2:查看当前数据库中某个触发器的定义
SHOW CREATE TRIGGER 触发器名
# 方式3:从系统库information_schema的TRIGGERS表中查询“salary_check_trigger”触发器的信息。
SELECT * FROM information_schema.TRIGGERS;

3.2 删除触发器

1
DROP TRIGGER IF EXISTS 触发器名称;

4. 触发器的优缺点

优点

1、触发器可以确保数据的完整性。

2、触发器可以帮助我们记录操作日志。

3、触发器还可以用在操作数据前,对数据进行合法性检查。

缺点

1、触发器最大的一个问题就是可读性差。

2、相关数据的变更,可能会导致触发器出错。