目前互联网上常见的数据库管理软件有 Oracle、MySQL、MS SQL Server、DB2、PostgreSQL、Access、SyBase、Informix这几种(查看数据库最新排名:https://db-engines.com/en/ranking)
MySQL是一个开放源码的关系型数据库管理系统,由瑞典 MySQL AB(创始人Michael Widenius)公司1995年开发,迅速成为开源数据库的No.1。2008年被Sun收购(10亿美金),2009年Sun被Oracle收购。MariaDB应运而生。(MySQL的创造者担心MySQL有闭源的风险,因此创建了MySQL的分支项目MariaDB)。MySQL6.x版本之后分为社区版和商业版。MySQL是一种关联数据库管理系统,将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提高了灵活性。
MySQL是开源的,所以你不需要支付额外的费用。MySQL是可以定制的,采用了GPL(GUN General Public License)协议,你可以需改源码来开发自己的MySQL系统。MySQL支持大型的数据库。可以处理拥有上千万条记录的大型数据库。MySQL支持大型数据库,支持5000万条记录的数据仓库 ,32位系统表文件最大可支持4GB,64位系统支持最大的表文件为8TB。MySQL使用标准的SQL数据语言形式。MySQL可以允许运行于多个系统上,并且支持多种语言。这些编程语言包括C、C++、Python、Java、Perl、PHP、和Ruby等
关系型数据库的优点:复杂查询可以用SQL语句方便的在一个表以及多个表之间做非常复杂的数据查询;事务支持使得对于安全性能很高的数据访问要求得以实现。
非关系型数据库,可看成传统关系型数据库的功能阉割版本,基于键值对存储数据,不需要经过SQL层的解析,性能非常高。同时通过减少不常用的功能,进一步提高性能
官网下载:https://dev.mysql.com/downloads/
下载之前先进行卸载(关闭mysql服务,卸载MySQL,清理文件,清理注册表(可选),删除环境变量配置),MySQL有四个版本
此外,官方还提供了MySQL Workbench(GUITOOL)一款专为MySQL设计的图形界面管理工具。MySQLWorkbench又分为两个版本,分别是社区版(MySQL Workbench OSS)、商用版(MySQLWorkbenchSE)。Windows平台下提供两种安装文件:MySQL二进制分发版(.msi安装文件)和免安装版(.zip压缩文件)。一般来讲,应当使用二进制分发版,因为该版本提供了图形化的安装向导过程,比其他的分发版使用起来要简单,不再需要其他工具启动就可以运行MySQL。最后需要将mysql的bin目录放在环境变量中
# 启动 MySQL 服务命令
net start MySQL服务名# 停止 MySQL 服务命令
net stop MySQL服务名
# 也可以进入服务自己去打开# 登录
mysql -h 主机名 -P 端口号 -u 用户名 -p密码
mysql -h localhost -P 3306 -u root -pabc123
mysql -V
# 进入mysql后
select version();
exit/quit
# 查看所有数据库
show databases;
# 数据库的默认编码改为utf8mb4,见下面的ini修改,记得重启服务
# 创建自己的数据库
create database 数据库名;
# 使用自己的数据库
use 数据库名;
# 查看某个库的所有表格
show tables; #要求前面有use语句
show tables from 数据库名;# 创建新的表格
create table 表名称(
字段名 数据类型,
字段名 数据类型
);
#创建学生表
create table student(
id int,
name varchar(20) #说名字最长不超过 20 个字符
);# 查看一个表的数据
select * from 数据库表名称;
#查看学生表的数据
select * from student;# 添加一条记录
insert into 表名称 values(值列表);
#添加两条记录到student表中
insert into student values( 1 ,'张三');
insert into student values( 2 ,'李四');# 查看表的创建信息
show create table 表名称\G
#查看student表的详细创建信息
show create table student\G# 查看数据库的创建信息
show create database 数据库名\G
#查看atguigudb数据库的详细创建信息
show create database atguigudb\G# 删除表格
drop table 表名称;
#删除学生表
drop table student;# 删除数据库
drop database 数据库名;
#删除atguigudb数据库
drop database atguigudb;
MySQL图形化管理工具极大地方便了数据库的操作与管理,常用的图形化管理工具有:MySQL Workbench、phpMyAdmin、Navicat Preminum、MySQLDumper、SQLyog、dbeaver、MySQL ODBCConnector(推荐navicate)
# 如果图形化连接出现错误,原因是MySQL8之前的版本中加密规则是mysql_native_password,而在MySQL8之后,加密规则是caching_sha2_password
# 解决问题方法有两种,第一种是升级图形界面工具版本,第二种是把MySQL8用户登录密码加密规则还原成mysql_native_password
#使用mysql数据库
USE mysql;#修改'root'@'localhost'用户的密码规则和密码
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'abc123';#刷新权限
FLUSH PRIVILEGES;
如果root密码忘记
# 通过任务管理器或者服务管理,关掉mysqld(服务进程)
# 通过命令行+特殊参数开启
mysqld mysqld – defaults-file="D:\ProgramFiles\mysql\MySQLServer5.7Data\my.ini" –skip-grant-tables
# 此时,mysqld服务进程已经打开。并且不需要权限检查
# 无密码登陆服务器
mysql -uroot
# 修改权限表
use mysql;
update user set authentication_string=password('新密码') where user='root' and Host='localhost;
flush privileges;
# 最后重启mysql服务
MySQL的目录结构 | 说明 |
---|---|
bin目录 | 所有MySQL的可执行文件。如:mysql.exe |
MySQLInstanceConfig.exe | 数据库的配置向导,在安装时出现的内容 |
data目录 | 系统数据库所在的目录 |
my.ini文件 | MySQL的主要配置文件 |
D:\software\mysql\data | 用户创建的数据库所在的目录 |
这是我的my.ini配置文件,
[mysqld]
# 设置3306端口
port=3306
# 设置mysql的安装目录
basedir=D:\\software\\mysql
# 设置mysql数据库的数据的存放目录
datadir=D:\\software\\mysql\\data
# 允许最大连接数
max_connections=200
# 允许连接失败的次数。这是为了防止有人从该主机试图攻击数据库系统
max_connect_errors=10
# 服务端使用的字符集默认为UTFMB4
character-set-server=utf8mb4
# 创建新表时将使用的默认存储引擎
default-storage-engine=INNODB
# 默认使用“mysql_native_password”插件认证
default_authentication_plugin=mysql_native_password
[mysql]
# 设置mysql客户端默认字符集
default-character-set=utf8mb4
[client]
# 设置mysql客户端连接服务端时默认使用的端口
port=3306
default-character-set=utf8mb4
MySQL 源代码获取,首先,你要进入 MySQL下载界面。 这里你不要选择用默认的“Microsoft Windows”,而是要通过下拉栏,找到“Source Code”,在下面的操作系统版本里面, 选择 Windows(Architecture Independent),然后点击下载。接下来,把下载下来的压缩文件解压,我们就得到了 MySQL 的源代码。MySQL 是用 C++ 开发而成的,我简单介绍一下源代码的组成。mysql-8.0.22 目录下的各个子目录,包含了 MySQL 各部分组件的源代码
因为查询语句使用的非常的频繁,所以很多人把查询语句单拎出来一类:DQL(数据查询语言)。还有单独将COMMIT、ROLLBACK 取出来称为TCL (Transaction Control Language,事务控制语言)。
基本规则
SQL大小写规范 (建议遵守)
注释
单行注释:#注释文字(MySQL特有的方式)
单行注释:-- 注释文字(--后面必须包含一个空格。)
多行注释:/* 注释文字 */
命名规则(暂时了解)
数据导入指令
# 数据导入指令,需要用命令行
source d:\xxx.sql
# 通过FOREIGN_KEY_CHECKS解决,用法如下
set FOREIGN_KEY_CHECKS=0; #在导入前设置为不检查外键约束
set FOREIGN_KEY_CHECKS=1; #在导入后恢复检查外键约束
SELECT * FROM departments;
-- 重命名一个列,as也可以省略;别名使用双引号,以便在别名中包含空格或特 殊的字符并区分大小写
SELECT last_name AS name, commission_pct comm FROM employees;
-- 去除重复行,如果有多个列,就会去重全部列的
SELECT DISTINCT department_id FROM employees;
-- 空值参与运算
-- 空值:null ( 不等同于0, '', 'null' )实际问题的解决方案:引入IFNULL
-- 空值不等于空字符串。一个空字符串的长度是 0,而一个空值的长 度是空。而且,在 MySQL 里面,空值是占用空间的
SELECT employee_id, salary "月工资", salary * (1 + IFNULL(commission_pct, 0)) * 12 "年工资" FROM employees;
-- 着重号 ``,保证你的字段没有和保留字、数据库系统或常见方法冲突
SELECT * FROM `order`;
-- 查询常数
SELECT '小张科技' as "公司名", employee_id, last_name FROM employees;
-- 显示表中字段的详细信息
DESCRIBE employees;
DESC employees;
-- 过滤数据
SELECT employee_id, last_name, job_id, department_id FROM employeesWHERE department_id = 90;
-- DUAL 伪表
-- 算术运算符
-- 一个整数类型的值对整数进行加法和减法操作,结果还是一个整数;
-- 一个整数类型的值对浮点数进行加法和减法操作,结果是一个浮点数;
-- 在Java中, + 的左右两边如果有字符串,那么表示字符串的拼接。但是在MySQL中 + 只表示数值相加。如果遇到非数值类型,先尝试转成数值,如果转失败,就按0计算。(注:MySQL 中字符串拼接要使用字符串函数CONCAT()实现)
-- 在数学运算中,0不能用作除数,在MySQL中,一个数除以0为NULL
SELECT 100 + 0, 100 + 50 * 30, 100 - 35.5 FROM DUAL;-- 比较运算符
-- 比较的结果为真则返回1,比较的结果 为假则返回0,其他情况则返回NULL
-- 如果等号两边的值、字符串或表达式中有一个为NULL,则比较结果为NULL
SELECT 1 = 1, 1 = '1', 1 = 0, 'a' = 'a', (5 + 3) = (2 + 6), '' = NULL , NULL = NULL;
SELECT 1 = 2, 0 = 'abc', 1 = 'abc' FROM DUAL;
-- 如果等号两边的值、字符串或表达式都为字符串,则MySQL会按照字符串进行比较,其比较的 是每个字符串中字符的ANSI编码是否相等
-- 如果等号两边的值都是整数,则MySQL会按照整数来比较两个值的大小。
-- 如果等号两边的值一个是整数,另一个是字符串,则MySQL会将字符串转化为数字进行比较。
-- 如果等号两边的值、字符串或表达式中有一个为NULL,则比较结果为NULL
-- <=> 使用安全等于运算符时,两边的操作数的值都为NULL时,返回的结果为1而不是NULL,其他返回结果与等于运算符相同
SELECT 1 <=> '1', 1 <=> 0, 'a' <=> 'a', (5 + 3) <=> (2 + 6), '' <=> NULL,NULL <=> NULL FROM dual-- 不等于运算符
SELECT 1 <> 1, 1 != 2, 'a' != 'b', (3+4) <> (2+6), 'a' != NULL, NULL <> NULL;SELECT NULL IS NULL, ISNULL(NULL), ISNULL('a'), 1 IS NULL;
SELECT NULL IS NOT NULL, 'a' IS NOT NULL, 1 IS NOT NULL;
-- 最小值运算符
-- 语法格式为:LEAST(值 1 ,值 2 ,…,值n)。其中,“值n”表示参数列表中有n个值。在有两个或多个参数的情况下,返回最小值
SELECT LEAST ( 1 , 0 , 2 ), LEAST('b','a','c'), LEAST( 1 ,NULL, 2 );
-- 最大值运算符
SELECT GREATEST( 1 , 0 , 2 ), GREATEST('b','a','c'), GREATEST( 1 ,NULL, 2 );
-- BETWEEN AND运算符
-- BETWEEN运算符使用的格式通常为SELECT D FROM TABLE WHERE C BETWEEN A AND B,此时,当C大于或等于A,并且C小于或等于B时,结果为 1 ,否则结果为 0
SELECT 1 BETWEEN 0 AND 1 , 10 BETWEEN 11 AND 12 , 'b' BETWEEN 'a' AND 'c';
SELECT last_name, salary FROM employees WHERE salary BETWEEN 2500 AND 3500 ;-- IN运算符
-- IN运算符用于判断给定的值是否是IN列表中的一个值,如果是则返回 1 ,否则返回 0 。如果给定的值为NULL,或者IN列表中存在NULL,则结果为NULL
SELECT employee_id, last_name, salary, manager_id FROM employees WHERE manager_id IN (100 ,101 ,201);
-- NOT IN运算符
SELECT 'a' NOT IN ('a','b','c'), 1 NOT IN ( 2 , 3 );
-- LIKE运算符,如果给定的值或者匹配条件为NULL,则返回结果为NULL
-- "%":匹配 0 个或多个字符
-- "_":只能匹配一个字符
SELECT NULL LIKE 'abc', 'abc' LIKE NULL;
SELECT last_name FROM employees WHERE last_name LIKE '_o%';
-- 回避特殊符号的: 使用转义符 例如:将[%]转为[$%]、[]转为[$],然后再加上[ESCAPE'$']即可
-- 默认 \是转义
SELECT job_id FROM jobs WHERE job_id LIKE 'IT\_%';
SELECT job_id FROM jobs WHERE job_id LIKE 'IT$_%' escape '$';-- REGEXP运算符
-- REGEXP运算符用来匹配字符串,语法格式为:expr REGEXP 匹配条件
-- '^'匹配以该字符后面的字符开头的字符串。
-- '$'匹配以该字符前面的字符结尾的字符串。
-- '.'匹配任何一个单字符。
-- "[...]"匹配在方括号内的任何字符。例如,"[abc]"匹配"a"或"b"或"c"。为了命名字符的范围,使用一个'-'。"[a-z]"匹配任何字母,而"[0-9]"匹配任何数字。
-- '*'匹配零个或多个在它前面的字符。例如,"x*"匹配任何数量的'x'字符,"[0-9]*"匹配任何数量的数字,而"*"匹配任何数量的任何字符。
SELECT 'atguigu' REGEXP 'gu.gu', 'atguigu' REGEXP '[ab]';
逻辑运算符主要用来判断表达式的真假,在MySQL中,逻辑运算符的返回结果为1、0或者NULL
-- 逻辑非(NOT或!)运算符表示当给定的值为 0 时返回 1 ;当给定的值为非 0 值时返回 0 ;当给定的值为NULL时,返回NULL
SELECT NOT 1 , NOT 0 , NOT( 1 + 1 ), NOT! 1 , NOT NULL;
-- 逻辑与(AND或&&)运算符是当给定的所有值均为非 0 值,并且都不为NULL时,返回1 ;当给定的一个值或者多个值为 0 时则返回 0 ;否则返回NULL
SELECT 1 AND - 1 , 0 AND 1 , 0 AND NULL, 1 AND NULL;
-- 逻辑或(OR或||)运算符是当给定的值都不为NULL,并且任何一个值为非 0 值时,则返回 1 ,否则返回 0 ;当一个值为NULL,并且另一个值为非 0 值时,返回 1 ,否则返回NULL;当两个值都为NULL时,返回NULL
SELECT 1 OR - 1 , 1 OR 0 , 1 OR NULL, 0 || NULL, NULL || NULL;
-- 逻辑异或(XOR)运算符是当给定的值中任意一个值为NULL时,则返回NULL;如果两个非NULL的值都是 0 或者都不等于 0 时,则返回 0 ;如果一个值为 0 ,另一个值不为 0 时,则返回 1
SELECT 1 XOR - 1 , 1 XOR 0 , 0 XOR 0 , 1 XOR NULL, 1 XOR 1 XOR 1 , 0 XOR 0 XOR 0 ;
-- AND优先级高于OR
位运算符是在二进制数上进行计算的运算符。位运算符会先将操作数变成二进制数,然后进行位运算, 最后将计算结果从二进制变回十进制数
-- 按位与(&)运算符将给定值对应的二进制数逐位进行逻辑与运算。当给定值对应的二进制位的数值都为 1 时,则该位返回 1 ,否则返回 0 。
SELECT 1 & 10 , 20 & 30 ;
-- 按位或(|)运算符将给定的值对应的二进制数逐位进行逻辑或运算。当给定值对应的二进制位的数值有一个或两个为 1 时,则该位返回 1 ,否则返回 0
SELECT 1 | 10 , 20 | 30 ;
-- 按位异或(^)运算符将给定的值对应的二进制数逐位进行逻辑异或运算。当给定值对应的二进制位的数值不同时,则该位返回 1 ,否则返回 0
SELECT 1 ^ 10 , 20 ^ 30 ;
-- 按位取反(~)运算符将给定的值的二进制数逐位进行取反操作,即将 1 变为 0 ,将 0 变为 1 。
SELECT 10 & ~1;
-- 按位右移(>>)运算符将给定的值的二进制数的所有位右移指定的位数。右移指定的位数后,右边低位的数值被移出并丢弃,左边高位空出的位置用 0 补齐
SELECT 1 >> 2 , 4 >> 2 ;
-- 按位左移(<<)运算符将给定的值的二进制数的所有位左移指定的位数。左移指定的位数后,左边高位的数值被移出并丢弃,右边低位空出的位置用 0 补齐
SELECT 1 << 2 , 4 << 2 ;
-- 单列排序
SELECT last_name, job_id, department_id, hire_date FROM employees ORDER BY hire_date;
-- 多列排序
-- 可以使用不在SELECT列表中的列排序
-- 在对多列进行排序的时候,首先排序的第一列必须有相同的列值,才会对第二列进行排序。
-- 如果第一列数据中所有值都是唯一的,将不再对第二列进行排序
--前10条记录:
SELECT * FROM 表名 LIMIT 0,10;
SELECT * FROM 表名 LIMIT 10;
--第11至20条记录:
SELECT * FROM 表名 LIMIT 10,10;
--第21至30条记录:
SELECT * FROM 表名 LIMIT 20,10;-- MySQL 8.0中可以使用“LIMIT 3 OFFSET 4”,意思是获取从第5条记录开始后面的3条记录,和“LIMIT 4,3;”返回的结果相同。
-- SELECT * FROM table LIMIT(PageNo - 1) * PageSize, PageSize;
LIMIT 子句必须放在整个SELECT语句的最后,约束返回结果的数量可以 减少数据表的网络传输量 ,也可以提升查询效率
-- 自连接
SELECT CONCAT(worker.last_name , ' works for ', manager.last_name)
FROM employees worker, employees manager
WHERE worker.manager_id = manager.employee_id;-- 内连接与外连接
-- 内连接: 合并具有同一列的两个以上的表的行, 结果集中不包含一个表与另一个表不匹配的行,会产生笛卡尔积
-- SQL92语法
SELECT emp.employee_id, dep.department_name
FROM employee emp, department dep
WHERE emp.`department_id` = dep.`department_id`;
-- SQL99语法
SELECT emp.employee_id, dep.department_name
FROM employee emp JOIN department dep
ON emp.`department_id` = dep.`department_id`;
-- 外连接: 两个表在连接过程中除了返回满足连接条件的行以外还返回左(或右)表中不满足条件的 行 ,这种连接称为左(或右) 外连接。没有匹配的行时, 结果表中相应的列为空(NULL)。
-- 如果是左外连接,则连接条件中左边的表也称为 主表 ,右边的表称为 从表
SELECT last_name, department_name
FROM employees emp LEFT OUTER JOIN department dep
ON emp.`department_id` = dep.`department_id`;
-- 如果是右外连接,则连接条件中右边的表也称为 主表 ,左边的表称为 从表
SELECT last_name, department_name
FROM employees emp RIGHT OUTER JOIN department dep
ON emp.`department_id` = dep.`department_id`;-- UNION的使用
-- 利用UNION关键字,可以给出多条SELECT语句,并将它们的结果组合成单个结果集。
-- UNION 操作符返回两个查询的结果集的并集,去除重复记录
-- UNION ALL操作符返回两个查询的结果集的并集。对于两个结果集的重复部分,不去重
-- 执行UNION ALL语句时所需要的资源比UNION语句少
-- 查询部门编号>90或邮箱包含a的员工信息
SELECT * FROM employees WHERE email LIKE '%a%' OR department_id>90;
SELECT * FROM employees WHERE email LIKE '%a%'
UNION
SELECT * FROM employees WHERE department_id>90;
-- 查询中国用户中男性的信息以及美国用户中年男性的用户信息
SELECT id,cname FROM t_chinamale WHERE csex='男'
UNION ALL
SELECT id,tname FROM t_usmale WHERE tGender='male';
七种SQL JOINS的实现
SQL99语法的新特性(了解即可)
QL99 在 SQL92 的基础上提供了一些特殊语法,比如 NATURAL JOIN 用来表示自然连接。我们可以把 自然连接理解为 SQL92 中的等值连接。它会帮你自动查询两张连接表中所有相同的字段 ,然后进行 等值 连接(MySQL复杂链接 (左/右/全外)链接只能使用SQL99语法,SQL92不支持)
-- SQL92
SELECT employee_id,last_name,department_name
FROM employees e JOIN departments d
ON e.`department_id` = d.`department_id`
AND e.`manager_id` = d.`manager_id`;
-- SQL99
SELECT employee_id,last_name,department_name
FROM employees e NATURAL JOIN departments d;
当我们进行连接的时候,SQL99还支持使用 USING 指定数据表里的 同名字段 进行等值连接。但是只能配 合JOIN一起使用。比如:
SELECT employee_id,last_name,department_name
FROM employees e JOIN departments d
USING (department_id);-- 和下面的是一样的
SELECT employee_id,last_name,department_name
FROM employees e ,departments d
WHERE e.department_id = d.department_id;
基本函数
函数 | 用法 |
---|---|
ABS(x) | 返回x的绝对值 |
SIGN(X) | 单元格 |
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 |
角度与弧度互换函数
函数 | 用法 |
---|---|
RADIANS(x) | 将角度转化为弧度,其中,参数x为角度值 |
DEGREES(x) | 将弧度转化为角度,其中,参数x为弧度值 |
三角函数
函数 | 用法 |
---|---|
SIN(x) | 将角度转化为弧度,其中,参数x为角度值 |
ASIN(x) | 将弧度转化为角度,其中,参数x为弧度值 |
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为弧度值 |
指数与对数函数
函数 | 用法 |
---|---|
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 |
进制间的转换
函数 | 用法 |
---|---|
BIN(x) | 返回x的二进制编码 |
HEX(x) | 返回x的十六进制编码 |
OCT(x) | 返回x的八进制编码 |
CONV(x,f1,f2) | 返回f1进制数变成f2进制数 |
MySQL中,字符串的位置是从1开始的
函数 | 用法 |
---|---|
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 |
函数 | 用法 |
---|---|
CURDATE() ,CURRENT_DATE() | 返回当前日期,只包含年、月、日 |
CURTIME() , CURRENT_TIME() | 返回当前时间,只包含时、分、秒 |
NOW() / SYSDATE() / CURRENT_TIMESTAMP() / LOCALTIME() / LOCALTIMESTAMP() | 返回当前系统日期和时间 |
UTC_DATE() | 返回UTC(世界标准时间)日期 |
UTC_TIME() | 返回UTC(世界标准时间)时间 |
UNIX_TIMESTAMP() | 以UNIX时间戳的形式返回当前时间。SELECT UNIX_TIMESTAMP() - >1634348884 |
UNIX_TIMESTAMP(date) | 将时间date以UNIX时间戳的形式返回。 |
FROM_UNIXTIME(timestamp) | 将UNIX时间戳的时间转换为普通格式的时间 |
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 |
EXTRACT(type FROM date) | 返回指定日期中特定的部分,type指定返回的值 |
TIME_TO_SEC(time) | 将time转化为秒并返回结果值。转化的公式为:小时 * 3600+分钟 * 60+秒 |
SEC_TO_TIME(seconds) | 将 seconds 描述转化为包含小时、分钟和秒的时间 |
DATE_ADD(datetime, INTERVAL expr type),ADDDATE(date,INTERVAL expr type) | 返回与给定日期时间相差INTERVAL时间段的日期时间 |
DATE_SUB(date,INTERVAL expr type),SUBDATE(date,INTERVAL expr type) | 返回与date相差INTERVAL时间间隔的日期 |
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后的时间 |
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进行解析,解析为一个日期 |
EXTRACT(type FROM date)函数中type的取值与含义:
上述非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 | %% | 表示% |
GET_FORMAT函数中date_type和format_type参数取值如下
流程处理函数可以根据不同的条件,执行不同的处理流程,可以在SQL语句中实现不同的条件选择。MySQL中的流程处理函数主要包括IF()、IFNULL()和CASE()函数
函数 | 用法 |
---|---|
IF(value,value1,value2) | 如果value的值为TRUE,返回value1,否则返回value |
IFNULL(value1, value2) | 如果value1不为NULL,返回value1,否则返回value |
CASE WHEN 条件1 THEN 结果1 WHEN 条件2 THEN 结果 2 …. [ELSE resultn] END | 相当于Java的if…else if…else… |
CASE expr WHEN 常量值1 THEN 值1 WHEN 常量值1 THEN 值1 …. [ELSE 值n] END | 相当于Java的switch…case… |
加密与解密函数主要用于对数据库中的数据进行加密和解密处理,以防止数据被他人窃取。这些函数在保证数据库安全时非常有用,下面ENCODE(value,password_seed)函数与DECODE(value,password_seed)函数互为反函数
函数 | 用法 |
---|---|
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 |
MySQL中内置了一些可以查询MySQL信息的函数,这些函数主要用于帮助数据库开发或运维人员更好地对数据库进行维护工作
函数 | 用法 |
---|---|
VERSION() | 返回当前MySQL的版本号 |
CONNECTION_ID() | 返回当前MySQL服务器的连接数 |
DATABASE(),SCHEMA() | 返回MySQL命令行当前所在的数据库 |
USER(),CURRENT_USER()、SYSTEM_USER(),SESSION_USER() | 返回当前连接MySQL的用户名,返回结果格式为“主机名@用户名” |
CHARSET(value) | 返回字符串value自变量的字符集 |
COLLATION(value) | 返回字符串value的比较规则 |
MySQL中有些函数无法对其进行具体的分类,但是这些函数在MySQL的开发和运维过程中也是不容忽视的
函数 | 用法 |
---|---|
FORMAT(value,n) | 返回对数字value进行格式化后的结果数据。n表示四舍五入后保留到小数点后n位 |
CONV(value,from,to) | 将value的值进行不同进制之间的转换 |
INET_ATON(ipvalue) | 将以点分隔的IP地址转化为一个数字 |
INET_NTOA(value) | 将数字形式的IP地址转化为以点分隔的IP地址 |
BENCHMARK(n,expr) | 将表达式expr重复执行n次。用于测试MySQL处理expr表达式所耗费的时间 |
CONVERT(value USING char_code) | 将value所使用的字符编码修改为char_code |
聚合(或聚集、分组)函数,它是对一组数据进行汇总的函数,输入的是一组数据的集合,输出的是单个值。聚合函数类型:AVG()、SUM()、MAX()、MIN()、COUNT()
-- AVG和SUM函数
SELECT AVG(salary), MAX(salary),MIN(salary), SUM(salary)
FROM employees
WHERE job_id LIKE '%REP%';
-- MIN和MAX函数
SELECT MIN(hire_date), MAX(hire_date)
FROM employees;
-- COUNT函数,适用于 任意数据类型
SELECT COUNT(*)
FROM employees
WHERE department_id = 50;
-- COUNT(expr) 返回 expr不为空 的记录总数
SELECT COUNT(commission_pct)
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 值的行
可以使用GROUP BY子句将表中的数据分成若干组,WHERE一定放在FROM后面
SELECT column, group_function(column)
FROM table
[WHERE condition]
[GROUP BY group_by_expression]
[ORDER BY column];-- 在SELECT列表中所有未包含在组函数中的列都应该包含在 GROUP BY子句中
SELECT department_id, AVG(salary)
FROM employees
GROUP BY department_id ;-- 包含在 GROUP BY 子句中的列不必包含在SELECT 列表中
SELECT AVG(salary)
FROM employees
GROUP BY department_id ;-- 使用多个列分组
SELECT department_id dept_id, job_id, SUM(salary)
FROM employees
GROUP BY department_id, job_id ;-- GROUP BY中使用WITH ROLLUP
-- 当使用ROLLUP时,不能同时使用ORDER BY子句进行结果排序,即ROLLUP和ORDER BY是互相排斥的
-- 使用WITH ROLLUP关键字之后,在所有查询出的分组记录之后增加一条记录,该记录计算查询出的所有记录的总和,即统计记录数量
SELECT department_id,AVG(salary)
FROM employees
WHERE department_id > 80
GROUP BY department_id WITH ROLLUP;
过滤分组:HAVING子句、行已经被分组、使用了聚合函数、满足HAVING 子句中条件的分组将被显示、HAVING 不能单独使用,必须要跟 GROUP BY 一起使用
SELECT department_id, MAX(salary)
FROM employees
GROUP BY department_id
HAVING MAX(salary)> 10000 ;-- 非法使用聚合函数 : 不能在 WHERE 子句中使用聚合函数
WHERE和HAVING的对比
#方式 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:分页-- sql语句执行
SELECT ... FROM ... JOIN ... ON ... WHERE ... GROUP BY ... HAVING ... ORDER BY ... LIMIT...
-- 语句执行顺序
FROM -> ON -> (JOIN) -> WHERE -> GROUP BY -> HAVING -> SELECT 的字段 -> DISTINCT -> ORDER BY -> LIMIT
-- 在 SELECT 语句执行这些步骤的时候,每个步骤都会产生一个虚拟表,然后将这个虚拟表传入下一个步骤中作为输入。需要注意的是,这些步骤隐含在 SQL 的执行过程中,对于我们来说是不可见的
SQL的执行原理
SELECT 是先执行 FROM 这一步的。在这个阶段,如果是多张表联查,还会经历下面的几个步骤:
当然如果我们操作的是两张以上的表,还会重复上面的步骤,直到所有表都被处理完为止。这个过程得到是我们的原始数据。当我们拿到了查询数据表的原始数据,也就是最终的虚拟表 vt1,就可以在此基础上再进行 WHERE 阶段。在这个阶段中,会根据 vt1 表的结果进行筛选过滤,得到虚拟表 vt2。然后进入第三步和第四步,也就是 GROUP 和 HAVING 阶段。在这个阶段中,实际上是在虚拟表 vt2 的基础上进行分组和分组过滤,得到中间的虚拟表 vt3 和 vt4。
当我们完成了条件筛选部分之后,就可以筛选表中提取的字段,也就是进入到 SELECT 和 DISTINCT
阶段。首先在 SELECT 阶段会提取想要的字段,然后在 DISTINCT 阶段过滤掉重复的行,分别得到中间的虚拟表vt5- 1 和 vt5- 2 。当我们提取了想要的字段数据之后,就可以按照指定的字段进行排序,也就是 ORDER BY 阶段,得到虚拟表 vt6。最后在 vt6 的基础上,取出指定行的记录,也就是 LIMIT 阶段,得到最终的结果,对应的是虚拟表vt7。
当然我们在写 SELECT 语句的时候,不一定存在所有的关键字,相应的阶段就会省略。同时因为 SQL 是一门类似英语的结构化查询语言,所以我们在写 SELECT 语句的时候,还要注意相应的关键字顺序, 所谓底层运行的原理,就是我们刚才讲到的执行顺序。
注意事项
-- 方式一:
SELECT salary
FROM employees
WHERE last_name = 'Abel';SELECT last_name,salary
FROM employees
WHERE salary > 11000 ;-- 方式二:自连接
SELECT e2.last_name,e2.salary
FROM employees e1,employees e
WHERE e1.last_name = 'Abel'
AND e1.`salary` < e2.`salary`-- 方式三:子查询
SELECT last_name,salary
FROM employees
WHERE salary > (SELECT salaryFROM employeesWHERE last_name = 'Abel');
子查询的分类
我们按内查询的结果返回一条还是多条记录,将子查询分为单行子查询、多行子查询
我们按内查询是否被执行多次,将子查询划分为相关(或关联)子查询和不相关(或非关联)子查询。子查询从数据表中查询了数据结果,如果这个数据结果只执行一次,然后这个数据结果作为主查询的条件进行执行,那么这样的子查询叫做不相关子查询。同样,如果子查询需要执行多次,即采用循环的方式,先从外部查询开始,每次都传入子查询进行查询,然后再将结果反馈给外部,这种嵌套的执行方式就称为相关子查询
-- 查询与 141 号或 174 号员工的manager_id和department_id相同的其他员工的employee_id,manager_id,department_id
SELECT employee_id, manager_id, department_id
FROM employees
WHERE (manager_id, department_id) IN(SELECT manager_id, department_idFROM employeesWHERE employee_id IN (141,174))
AND employee_id NOT IN (141,174);-- 显式员工的employee_id,last_name和location
SELECT employee_id, last_name,(CASE department_idWHEN(SELECT department_id FROM departments WHERE location_id = 1800)THEN 'Canada' ELSE 'USA' END) location
FROM employees;
操作符 | 含义 |
---|---|
IN | 等于列表中的任意一个 |
ANY | 需要和单行比较操作符一起使用,和子查询返回的 某一个 值比较 |
ALL | 需要和单行比较操作符一起使用,和子查询返回的 所有 值比较 |
SOME | 实际上是ANY的别名,作用相同,一般常使用ANY |
-- mysql聚合函数不能嵌套使用
-- 查询平均工资最低的部门id
SELECT department_id
FROM employees
GROUP BY department_id
HAVING AVG(salary) <= ALL (SELECT AVG(salary) avg_salFROM employeesGROUP BY department_id
)-- 空值问题,包含了null
SELECT last_name
FROM employees
WHERE employee_id NOT IN (SELECT manager_idFROM employees--where manager_id is not NULL);
如果子查询的执行依赖于外部查询,通常情况下都是因为子查询中的表用到了外部的表,并进行了条件关联,因此每执行一次外部查询,子查询都要重新计算一次,这样的子查询就称之为关联子查询。相关子查询按照一行接一行的顺序执行,主查询的每一行都执行一次子查询
-- 查询员工的id,salary,按照department_name 排序
SELECT employee_id,salary
FROM employees e
ORDER BY (SELECT department_nameFROM departments dWHERE e.`department_id` = d.`department_id`
);-- 在许多 DBMS 的处理过程中,对于自连接的处理速度要比子查询快得多
-- 子查询实际上是通过未知表进行查询后的条件判断,而自连接是通过已知的自身数据表进行条件判断,因此在大部分 DBMS 中都对自连接处理进行了优化
从系统架构的层次上看,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 | 从-231到231-1的整型数据。存储大小为 4 个字节 |
CHAR(size) | 定长字符数据。若未指定,默认为 1 个字符,最大长度 255 |
VARCHAR(size) | 可变长字符数据,根据字符串实际长度保存, 必须指定长度 |
FLOAT(M,D) | 单精度,占用 4 个字节,M=整数位+小数位,D=小数位。 D<=M<=255,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 |
-- 创建数据库
CREATE DATABASE 数据库名;
-- 创建数据库并指定字符集,例如utf8mb4
CREATE DATABASE 数据库名 CHARACTER SET 字符集;
-- 判断数据库是否已经存在,不存在则创建数据库(推荐)
CREATE DATABASE IF NOT EXISTS 数据库名;
-- 如果MySQL中已经存在相关的数据库,则忽略创建语句,不再创建数据库
-- 注意,DATABASE 不能改名。一些可视化工具可以改名,它是建新库,把所有表复制到新库,再删旧库完成的-- 查看当前所有的数据库
-- 有一个S,代表多个数据库
SHOW DATABASES;
-- 查看当前正在使用的数据库,使用的一个 mysql 中的全局函数
SELECT DATABASE();
-- 查看指定库下所有的表
SHOW TABLES FROM 数据库名;
-- 查看数据库的创建信息
SHOW CREATE DATABASE 数据库名;
-- 或者
SHOW CREATE DATABASE 数据库名\G
-- 使用/切换数据库
USE 数据库名;
-- 注意:要操作表格和数据之前必须先说明是对哪个数据库进行操作,否则就要对所有对象加上“数据库名.”-- 更改数据库字符集
-- 比如:gbk、utf8等
ALTER DATABASE 数据库名 CHARACTER SET 字符集; -- 删除数据库
-- 删除指定的数据库
DROP DATABASE 数据库名;
-- 删除指定的数据库(推荐)
DROP DATABASE IF EXISTS 数据库名;
创建表必须具备CREATE TABLE权限和存储空间
-- 第一种方式创建
-- 加上了IF NOT EXISTS关键字,则表示:如果当前数据库中不存在要创建的数据表,则创建数据表;
-- 如果当前数据库中已经存在要创建的数据表,则忽略建表语句,不再创建数据表。
-- 必须指定表名,列名(或字段名),数据类型, 长度;可选约束条件,默认值
CREATE TABLE [IF NOT EXISTS] 表名(
字段1, 数据类型 [约束条件] [默认值],
字段2, 数据类型 [约束条件] [默认值],
字段3, 数据类型 [约束条件] [默认值],
......
[表约束条件]
);-- 举例
-- 创建表
CREATE TABLE emp (-- int类型emp_id INT,-- 最多保存 20 个中英文字符emp_name VARCHAR( 20 ),-- 总位数不超过 15 位salary DOUBLE,-- 日期类型birthday DATE
);
-- 查看一下表格
-- 在MySQL 8.x版本中,不再推荐为INT类型指定显示长度,并在未来的版本中可能去掉这样的语法
DESC emp;-- 举例2
CREATE TABLE dept(
-- int类型,自增
deptno INT( 2 ) AUTO_INCREMENT,
dname VARCHAR( 14 ),
loc VARCHAR( 13 ),
-- 主键
PRIMARY KEY (deptno)
);-- 第二种方式创建
-- 使用 AS subquery 选项, 将创建表和插入数据结合起来,指定的列和子查询中的列要一一对应,通过列名和默认值定义列
CREATE TABLE emp1 AS SELECT * FROM employees;
-- 创建的emp2是空表
CREATE TABLE emp2 AS SELECT * FROM employees WHERE 1 = 2 ;
-- 创建
CREATE TABLE dept
AS
SELECT employee_id, last_name, salary* 12 ANNSAL, hire_date
FROM employees
WHERE department_id = 80 ;
-- 查看一下
DESCRIBE dept80;-- 查看数据表结构
-- 在MySQL中创建好数据表之后,可以查看数据表的结构。MySQL支持使用DESCRIBE/DESC语句查看数据表结构,
-- 也支持使用SHOW CREATE TABLE语句查看数据表结构
-- 使用SHOW CREATE TABLE语句不仅可以查看表创建时的详细语句,还可以查看存储引擎和字符编码
SHOW CREATE TABLE 表名\G
-- 修改表指的是修改数据库中已经存在的数据表的结构
-- 追加一个列
ALTER TABLE 表名 ADD 【COLUMN】 字段名 字段类型 【FIRST|AFTER 字段名】;
-- 举例
ALTER TABLE dept
ADD job_id varchar( 15 );-- 修改一个列,可以修改列的数据类型,长度、默认值和位置
ALTER TABLE 表名 MODIFY 【COLUMN】 字段名 1 字段类型 【DEFAULT 默认值】【FIRST|AFTER 字段名2】;
-- 举例,对默认值的修改只影响今后对表的修改
ALTER TABLE dept
MODIFY salary double( 9 , 2 ) default 1000 ;-- 重命名一个列,使用 CHANGE old_column new_column dataType子句重命名列
ALTER TABLE 表名 CHANGE 【column】 列名 新列名 新数据类型;
-- 举例
ALTER TABLE dept
CHANGE department_name dept_name varchar( 15 );-- 删除一个列
ALTER TABLE 表名 DROP 【COLUMN】字段名
-- 举例
ALTER TABLE dept
DROP COLUMN job_id;-- 重命名表,必须是对象的拥有者
RENAME TABLE emp TO myemp;
ALTER table dept RENAME [TO] detail_dept; -- [TO]可以省略
-- 删除表
-- 在MySQL中,当一张数据表没有与其他任何数据表形成关联关系时,可以将当前数据表直接删除。
-- 数据和结构都被删除,所有正在运行的相关事务被提交,所有相关索引被删除
-- IF EXISTS的含义为:如果当前数据库中存在相应的数据表,则删除数据表;如果当前数据库中不存在相应的数据表,则忽略删除语句,不再执行删除数据表的操作。
DROP TABLE [IF EXISTS] 数据表1 [, 数据表2, ..., 数据表n];
-- 举例,DROP TABLE 语句不能回滚
DROP TABLE dept80;-- 清空表
-- TRUNCATE TABLE语句删除表中所有的数据,释放表的存储空间
-- TRUNCATE语句 不能回滚 ,而使用 DELETE 语句删除数据,可以回滚
TRUNCATE TABLE detail_dept;
-- TRUNCATE语句 不能回滚 ,而使用 DELETE 语句删除数据,可以回滚,举例
SET autocommit = FALSE;
DELETE FROM emp2;
-- TRUNCATE TABLE emp2;
SELECT * FROM emp2;
ROLLBACK;
SELECT * FROM emp2;-- TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少,但 TRUNCATE 无事务且不触发 TRIGGER,有可能造成事故,故不建议在开发代码中使用此语句
-- TRUNCATE TABLE 在功能上与不带 WHERE 子句的 DELETE 语句相同。
表删除操作将把表的定义和表中的数据一起删除,并且MySQL在执行删除操作时,不会有任何的确认信息提示,因此执行删除操时应当慎重。在删除表前,最好对表中的数据进行备份,这样当操作失误时可以对数据进行恢复,以免造成无法挽回的后果。
同样的,在使用 ALTER TABLE 进行表的基本修改操作时,在执行操作过程之前,也应该确保对数据进行完整的备份,因为数据库的改变是无法撤销的,如果添加了一个不需要的字段,可以将其删除;相同的,如果删除了一个需要的列,该列下面的所有数据都将会丢失。
在MySQL 8.0版本中,InnoDB表的DDL支持事务完整性,即DDL操作要么成功要么回滚。DDL操作回滚日志写入到data dictionary数据字典表mysql.innodb_ddl_log(该表是隐藏的表,通过show tables无法看到)中,用于回滚操作。通过设置参数,可将DDL操作日志打印输出到MySQL错误日志中。
CREATE DATABASE mytest;
USE mytest;
CREATE TABLE book1(
book_id INT ,
book_name VARCHAR( 255 )
);
SHOW TABLES;
ROP TABLE book1,book2;
-- mysql5.7 book1被删除
-- mysql8.0 没有被删除
-- VALUES的方式添加
-- 为表的所有字段按默认顺序插入数据
INSERT INTO 表名 VALUES (value1,value2,....);
-- 举例
INSERT INTO departments VALUES ( 100 , 'Finance', NULL, NULL);-- 为表的指定字段插入数据
INSERT INTO 表名(column1 [, column2, ..., columnn]) VALUES (value1 [,value2, ..., valuen]);
-- 为表的指定字段插入数据,就是在INSERT语句中只向部分字段中插入值,而其他字段的值为表定义时的默认值
INSERT INTO departments(department_id, department_name) VALUES ( 80 , 'IT');-- 同时插入多条记录
-- INSERT语句可以同时向数据表中插入多条记录,插入时指定多个值列表,每个值列表之间用逗号分隔开
INSERT INTO table_name
VALUES
(value1 [,value2, ..., valuen]),
(value1 [,value2, ..., valuen]),
......
(value1 [,value2, ..., valuen]);
-- 使用INSERT同时插入多条记录时,MySQL会返回一些在执行单行插入时没有的额外信息
-- Records:表明插入的记录条数,Duplicates:表明插入时被忽略的记录,原因可能是这些记录包含了重复的主键值,Warnings:表明有问题的数据值,例如发生数据类型转换
-- 一个同时插入多行记录的INSERT语句等同于多个单行插入的INSERT语句,但是多行的INSERT语句在处理过程中效率更高
-- VALUES也可以写成VALUE,但是VALUES是标准写法
-- 字符和日期型数据应包含在单引号中-- 将查询结果插入到表中
-- INSERT还可以将SELECT语句查询的结果插入到表中,此时不需要把每一条记录的值一个一个输入,只需要使用一条INSERT语句和一条SELECT语句组成的组合语句即可快速地从一个或多个表中向一个表中插入多行
INSERT INTO 目标表名
(tar_column1 [, tar_column2, ..., tar_columnn])
SELECT
(src_column1 [, src_column2, ..., src_columnn])
FROM 源表名
[WHERE condition]
-- 举例,子查询中的值列表应与 INSERT 子句中的列名对应
INSERT INTO emp
SELECT *
FROM employees
WHERE department_id = 90 ;
UPDATE table_name
SET column1=value1, column2=value2, ... , column=valuen
[WHERE condition]
-- 可以一次更新 多条 数据
-- 如果需要回滚数据,需要保证在DML前,进行设置: SET AUTOCOMMIT = FALSE;
-- 使用 WHERE 子句指定需要更新的数据-- 如果省略 WHERE 子句,则表中的所有数据都将被更新
UPDATE copy_emp
SET department_id = 110 ;
-- 注意约束问题,会报错
UPDATE employees SET department_id = 55 WHERE department_id = 110 ;
-- table_name指定要执行删除操作的表;“[WHERE ]”为可选参数,指定删除条件,如果没有WHERE子句,DELETE语句将删除表中的所有记录
DELETE FROM table_name [WHERE ];
-- 举例
DELETE FROM departments WHERE department_name = 'Finance';
-- 注意约束问题,会报错
DELETE FROM departments WHERE department_id = 60 ;
什么叫计算列呢?简单来说就是某一列的值是通过别的列计算得来的。例如,a列值为 1 、b列值为 2 ,c列不需要手动插入,定义a+b的结果为c的值,那么c就是计算列,是通过别的列计算得来的
-- 举例:定义数据表tb1,然后定义字段id、字段a、字段b和字段c,其中字段c为计算列,用于计算a+b的值。 首先创建测试表tb1
CREATE TABLE tb1(
id INT,
a INT,
b INT,
c INT GENERATED ALWAYS AS (a + b) VIRTUAL
);
-- 插入演示数据
INSERT INTO tb1(a,b) VALUES ( 100 , 200 );
SELECT * FROM tb1;
UPDATE tb1 SET a = 500 ;
#1、创建数据库test01_library
CREATE DATABASE IF NOT EXISTS test01_library CHARACTER SET 'utf8';#指定使用哪个数据库
USE test01_library;#2、创建表 books
CREATE TABLE books(
id INT,
name VARCHAR( 50 ),
`authors` VARCHAR( 100 ) ,
price FLOAT,
pubdate YEAR ,
note VARCHAR( 100 ),
num INT
);#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);# 4、将小说类型(novel)的书的价格都增加 5 。
UPDATE books SET price=price+ 5 WHERE note = 'novel';# 5、将名称为EmmaT的书的价格改为 40 ,并将说明改为drama。
UPDATE books SET price= 40 ,note='drama' 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 * 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 * FROM books WHERE CHAR_LENGTH(REPLACE(name,' ',''))>= 10 ;/*
16 、查询书名和类型,
其中note值为 novel显示小说,law显示法律,medicine显示医药,cartoon显示卡通,joke显示笑话
*/
SELECT name AS "书名" ,note, CASE noteWHEN 'novel' THEN '小说'WHEN 'law' THEN '法律'WHEN 'medicine' THEN '医药'WHEN 'cartoon' THEN '卡通'WHEN 'joke' THEN '笑话'END AS "类型"
FROM books;# 17、查询书名、库存,其中num值超过 30 本的,显示滞销,大于 0 并低于 10 的,显示畅销,为 0 的显示需要无货
SELECT name,num,CASEWHEN num> 30 THEN '滞销'WHEN num> 0 AND num< 10 THEN '畅销'WHEN num= 0 THEN '无货'ELSE '正常'END AS "库存状态"
FROM books;# 18、统计每一种note的库存量,并合计总量
SELECT IFNULL(note,'合计总库存量') AS note,SUM(num) FROM books GROUP BY note WITH ROLLUP;# 19、统计每一种note的数量,并合计总量
SELECT IFNULL(note,'合计总数') AS note,COUNT(*) FROM books GROUP BY note WITH ROLLUP;# 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 WHERE note = 'novel' ORDER BY price DESC LIMIT 0 , 1 ;# 23、找出书名中字数最多的一本书,不含空格
SELECT * FROM books ORDER BY CHAR_LENGTH(REPLACE(name,' ','')) DESC LIMIT 0 ,1;
类型 | 类型举例 |
---|---|
整数类型 | TINYINT、SMALLINT、MEDIUMINT、INT(或INTEGER)、BIGINT |
浮点类型 | FLOAT、DOUBLE |
定点数类型 | DECIMAL |
位类型 | BIT |
日期时间类型 | YEAR、TIME、DATE、DATETIME、TIMESTAMP |
文本字符串类型 | CHAR、VARCHAR、TINYTEXT、TEXT、MEDIUMTEXT、LONGTEXT |
枚举类型 | ENUM |
集合类型 | SET |
二进制字符串类型 | BINARY、VARBINARY、TINYBLOB、BLOB、MEDIUMBLOB、LONGBLOB |
JSON类型 | JSON对象、JSON数组 |
空间数据类型 | 单值类型:GEOMETRY、POINT、LINESTRING、POLYGON; |
集合类型:MULTIPOINT、MULTILINESTRING、MULTIPOLYGON、GEOMETRYCOLLECTION |
常见数据类型的属性,如下
MySQL关键字 | 含义 |
---|---|
NULL | 数据列可包含NULL值 |
NOT NULL | 数据列不允许包含NULL值 |
DEFAULT | 默认值 |
PRIMARY KEY | 主键 |
AUTO_INCREMENT | 自动递增,适用于整数类型 |
UNSIGNED | 无符号 |
CHARACTER SET name | 指定一个字符集 |
整数类型 | 字节 | 有符号数取值范围 | 无符号数取值范围 |
---|---|---|---|
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 |
整数类型的可选属性有三个
在 int(M) 中,M 的值跟 int(M) 所占多少存储空间并无任何关系。 int(3)、int(4)、int(8) 在磁盘上都是占用 4 bytes 的存储空间。也就是说,**int(M),必须和UNSIGNED ZEROFILL一起使用才有意义。**如果整数值超过M位,就按照实际位数存储。只是无须再用字符 0 进行填充。
TINYINT:一般用于枚举数据,比如系统设定取值范围很小且固定的场景。
SMALLINT:可以用于较小范围的统计数据,比如统计工厂的固定资产库存数量等。
MEDIUMINT:用于较大整数的计算,比如车站每日的客流量等。
INT、INTEGER:取值范围足够大,一般情况下不用考虑超限问题,用得最多。比如商品编号。
BIGINT:只有当你处理特别巨大的整数时才会用到。比如双十一的交易量、大型门户网站点击量、证券公司衍生产品持仓等。
MySQL支持的浮点数类型,分别是 FLOAT、DOUBLE、REAL(REAL默认就是 DOUBLE。如果你把 SQL 模式设定为启用“REAL_AS_FLOAT”,那 么,MySQL 就认为REAL 是 FLOAT)
对于浮点类型,在MySQL中单精度值使用 4 个字节,双精度值使用 8 个字节。
在编程中,如果用到浮点数,要特别注意误差问题(例如0. 47 + 0. 44 + 0. 19),**因为浮点数是不准确的,所以我们要避免使用“=”来判断两个数是否相等。**同时,在一些对精确度要求较高的项目中,千万不要使用浮点数,不然会导致结果错误,甚至是造成不可挽回的损失。那么,MySQL 有没有精准的数据类型呢?当然有,这就是定点数类型:DECIMAL
BIT类型中存储的是二进制值,类似 010110 。BIT类型,如果没有指定(M),默认是 1 位。这个 1 位,表示只能存 1 位的二进制值。这里(M)是表示二进制的位数,位数最小值为 1 ,最大值为 64 。
CREATE TABLE test_bit1(
f1 BIT,
f2 BIT( 5 ),
f3 BIT( 64 )
);INSERT INTO test_bit1(f1)
VALUES( 1 );#Data too long for column 'f1' at row 1
INSERT INTO test_bit1(f1)
VALUES( 2 );INSERT INTO test_bit1(f2)
VALUES( 23 );SELECT BIN(f2),HEX(f2) FROM test_bit1;
-- 使用b+ 0 查询数据时,可以直接查询出存储的十进制数据的值
类型 | 名称 | 字节 | 日期格式 | 最小值 | 最大值 |
---|---|---|---|---|---|
YEAR | 年 | 1 | YYYY或YY | 1901 | 2155 |
TIME | 时间 | 3 | HH:MM:SS | - 838 : 59 : 59 | 838 : 59 : 59 |
DATE | 日期 | 3 | YYYY-MM-DD | 1000 - 01 - 01 | 9999 - 12 - 03 |
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 : 07 UTC |
YEAR类型用来表示年份,在所有的日期时间类型中所占用的存储空间最小,只需要 1 个字节的存储空间。在MySQL中,YEAR有以下几种存储格式:
从MySQL 5. 5. 27 开始, 2 位格式的YEAR已经不推荐使用 。YEAR默认格式就是“YYYY”,没必要写成YEAR( 4 ),从MySQL 8. 0. 19 开始,不推荐使用指定显示宽度的YEAR( 4 )数据类型。
DATE类型表示日期,没有时间部分,格式为YYYY-MM-DD,其中,YYYY表示年份,MM表示月份,DD表示日期。需要 3 个字节的存储空间。在向DATE类型的字段插入数据时,同样需要满足一定的格式条件。
TIME类型用来表示时间,不包含日期部分。在MySQL中,需要 3 个字节的存储空间来存储TIME类型的数据,可以使用“HH:MM:SS”格式来表示TIME类型,其中,HH表示小时,MM表示分钟,SS表示秒。
在MySQL中,向TIME类型的字段插入数据时,也可以使用几种不同的格式。
DATETIME类型在所有的日期时间类型中占用的存储空间最大,总共需要 8 个字节的存储空间。在格式上为DATE类型和TIME类型的组合,可以表示为YYYY-MM-DD HH:MM:SS,其中YYYY表示年份,MM表示月份,DD表示日期,HH表示小时,MM表示分钟,SS表示秒。在向DATETIME类型的字段插入数据时,同样需要满足一定的格式条件
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类型的字段插入数据时,当插入的数据格式满足YY-MM-DD HH:MM:SS和YYMMDDHHMMSS时,两位数值的年份同样符合YEAR类型的规则条件,只不过表示的时间范围要小很多。如果向TIMESTAMP类型的字段插入的时间超出了TIMESTAMP类型的范围,则MySQL会抛出错误信息。
TIMESTAMP和DATETIME的区别:
-- 一般存注册时间、商品发布时间等,不建议使用DATETIME存储,而是使用时间戳,其他建议datetime
SELECT UNIX_TIMESTAMP();
MySQL中,文本字符串总体上分为CHAR、VARCHAR、TINYTEXT、TEXT、MEDIUMTEXT、
LONGTEXT、ENUM、SET等类型
字符串(文本)类型 | 特点 | 长度 | 长度范围 | 占用的存储空间 |
---|---|---|---|---|
CHAR(M) | 固定长度 | M | 0 <= M <= 255 | M个字节 |
VARCHAR(M) | 可变长度 | M | 0 <= M <= 65535 | (实际长度 + 1 ) 个字节 |
CHAR类型:
VARCHAR类型:
哪些情况使用 CHAR 或 VARCHAR 更好
情况 1 :存储很短的信息。比如门牌号码 101 ,201……这样很短的信息应该用char,因为varchar还要占个byte用于存储信息长度,本来打算节约存储的,结果得不偿失。
情况 2 :固定长度的。比如使用uuid作为主键,那用char应该更合适。因为他固定长度,varchar动态根据长度的特性就消失了,而且还要占个长度信息。
情况 3 :十分频繁改变的column。因为varchar每次存储都要有额外的计算,得到长度等工作,如果一个非常频繁改变的,那就要有很多的精力用于计算,而这些对于char来说是不需要的。
情况 4 :具体存储引擎中的情况:
在MySQL中,TEXT用来保存文本类型的字符串,总共包含 4 种类型,分别为TINYTEXT、TEXT、MEDIUMTEXT 和 LONGTEXT 类型。在向TEXT类型的字段保存和查询数据时,系统自动按照实际长度存储,不需要预先定义长度。这一点和VARCHAR类型相同
文本字符串类型 | 特点 | 长度 | 长度范围 | 占用的存储空间 |
---|---|---|---|---|
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文本类型,可以存比较大的文本段,搜索速度稍慢,因此如果不是特别大的内容,建议使用CHAR,VARCHAR来代替。还有TEXT类型不用加默认值,加了也没用。而且text和blob类型的数据删除后容易导致“空洞”,使得文件碎片比较多,所以频繁使用的表不建议包含TEXT类型字段,建议单独分出去,单独用一个表
ENUM类型也叫作枚举类型,ENUM类型的取值范围需要在定义字段时进行指定。设置字段值时,ENUM类型只允许从成员中选取单个值,不能一次选取多个值
文本字符串类型 | 长度 | 长度范围 | 占用的存储空间 |
---|---|---|---|
ENUM | L | 1 <= L <= 65535 | 1 或 2 个字节 |
CREATE TABLE test_enum(
season ENUM('春','夏','秋','冬','unknow')
);INSERT INTO test_enum
VALUES('春'),('秋');# 忽略大小写
INSERT INTO test_enum
VALUES('UNKNOW');# 允许按照角标的方式获取指定索引位置的枚举值
INSERT INTO test_enum
VALUES('1'),( 3 );# Data truncated for column 'season' at row 1
INSERT INTO test_enum
VALUES('ab');# 当ENUM类型的字段没有声明为NOT NULL时,插入NULL也是有效的
INSERT INTO test_enum
VALUES(NULL);
SET表示一个字符串对象,可以包含 0 个或多个成员,但成员个数的上限为 64 。设置字段值时,可以取取值范围内的 0 个或多个值。当SET类型包含的成员个数不同时,其所占用的存储空间也是不同的
成员个数范围(L表示实际成员个数) | 占用的存储空间 |
---|---|
1 <= L <= 8 | 1 个字节 |
9 <= L <= 16 | 2 个字节 |
17 <= L <= 24 | 3 个字节 |
25 <= L <= 32 | 4 个字节 |
33 <= L <= 64 | 8 个字节 |
SET类型在存储数据时成员个数越多,其占用的存储空间越大。注意:SET类型在选取成员时,可以一次选择多个成员,这一点与ENUM类型不同。
CREATE TABLE temp_mul(
gender ENUM('男','女'),
hobby SET('吃饭','睡觉','打豆豆','写代码')
);INSERT INTO temp_mul VALUES('男','睡觉,打豆豆'); #成功# Data truncated for column 'gender' at row 1
INSERT INTO temp_mul VALUES('男,女','睡觉,写代码'); #失败# Data truncated for column 'gender' at row 1
INSERT INTO temp_mul VALUES('妖','睡觉,写代码');#失败INSERT INTO temp_mul VALUES('男','睡觉,写代码,吃饭'); #成功
MySQL中的二进制字符串类型主要存储一些二进制数据,比如可以存储图片、音频和视频等二进制数据。MySQL中支持的二进制字符串类型主要包括BINARY、VARBINARY、TINYBLOB、BLOB、MEDIUMBLOB 和LONGBLOB类型。
BINARY与VARBINARY类型
BINARY和VARBINARY类似于CHAR和VARCHAR,只是它们存储的是二进制字符串。BINARY (M)为固定长度的二进制字符串,M表示最多能存储的字节数,取值范围是 0 ~ 255 个字符。如果未指定(M),表示只能存储 1 个字节。例如BINARY ( 8 ),表示最多能存储 8 个字节,如果字段值不足(M)个字节,将在右边填充’\ 0’以补齐指定长度。VARBINARY (M)为可变长度的二进制字符串,M表示最多能存储的字节数,总字节数不能超过行的字节长度限制 65535 ,另外还要考虑额外字节开销,VARBINARY类型的数据除了存储数据本身外,还需要 1 或 2 个字节来存储数据的字节数。VARBINARY类型必须指定(M),否则报错。
二进制字符串类型 | 特点 | 值的长度 | 占用空间 |
---|---|---|---|
BINARY(M) | 固定长度 | M( 0 <= M <= 255 ) | M个字节 |
VARBINARY(M) | 可变长度 | M( 0 <= M <= 65535 ) | M+ 1 个字节 |
BLOB类型
BLOB是一个二进制大对象,可以容纳可变数量的数据。MySQL中的BLOB类型包括TINYBLOB、BLOB、MEDIUMBLOB和LONGBLOB 4种类型,它们可容纳值的最大长度不同。可以存储一个二进制的大对象,比如图片、音频和视频等。需要注意的是,在实际工作中,往往不会在MySQL数据库中使用BLOB类型存储大对象数据,通常会将图片、音频和视频文件存储到服务器的磁盘上,并将图片、音频和视频的访问路径存储到MySQL中。
二进制字符串类型 | 值的长度 | 长度范围 | 占用空间 |
---|---|---|---|
TINYBLOB | L | 0 <= L <= 255 | L + 1 个字节 |
BLOB | L | 0 <= L <= 65535(相当于64KB) | L + 2 个字节 |
MEDIUMBLOB | L | 0 <= L <= 16777215 (相当于16MB) | L + 3 个字节 |
LONGBLOB | L | 0 <= L <= 4294967295(相当于4GB) | L + 4 个字节 |
TEXT和BLOB的使用注意事项
在使用text和blob字段类型时要注意以下几点,以便更好的发挥数据库的性能。
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。它易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。 JSON 可以将 JavaScript 对象中表示的一组数据转换为字符串,然后就可以在网络或者程序之间轻松地传递这个字符串,并在需要的时候将它还原为各编程语言所支持的数据格式。
在MySQL 5.7中,就已经支持JSON数据类型。在MySQL 8.x版本中,JSON类型提供了可以进行自动验证的JSON文档和优化的存储结构,使得在MySQL中存储和读取JSON类型的数据更加方便和高效
CREATE TABLE test_json(
js json
);
INSERT INTO test_json (js)
VALUES ('{"name":"songhk", "age":18, "address":{"province":"beijing",
"city":"beijing"}}');
-- 查询
SELECT * FROM test_json;
-- 当需要检索JSON类型的字段中数据的某个具体值时,可以使用“->”和“->>”符号
SELECT js -> '$.name' AS NAME,js -> '$.age' AS age ,js -> '$.address.province' AS province, js -> '$.address.city' AS city -> FROM test_json;
MySQL 空间类型扩展支持地理特征的生成、存储和分析。这里的地理特征表示世界上具有位置的任何东西,可以是一个实体,例如一座山;可以是空间,例如一座办公楼;也可以是一个可定义的位置,例如一个十字路口等等。MySQL中使用Geometry(几何)来表示所有地理特征。Geometry指一个点或点的集合,代表世界上任何具有位置的事物。
数据完整性(Data Integrity)是指数据的精确性(Accuracy)和可靠性(Reliability)。它是防止数据库中存在不符合语义规定的数据和防止因错误信息的输入输出造成无效操作或错误信息而提出的。为了保证数据的完整性,SQL规范以约束的方式对表数据进行额外的条件限制。从以下四个方面考虑:
约束是表级的强制规定。可以在 创建表时规定约束(通过 CREATE TABLE 语句) ,或者在 表创建之后通过 ALTER TABLE 语句规定约束
-- 注意: MySQL不支持check约束,但可以使用check约束,而没有任何效果
-- information_schema数据库名(系统库)
-- table_constraints表名称(专门存储各个表的约束)
SELECT * FROM information_schema.table_constraints
WHERE table_name = '表名称';
NOT NULL,限定某个字段/某列的值不允许为空
CREATE TABLE 表名称(字段名 数据类型,字段名 数据类型 NOT NULL,字段名 数据类型 NOT NULL
);-- 建表后修改
alter table 表名称 modify 字段名 数据类型 not null;
-- 删除非空约束
-- 去掉not null,相当于修改某个非注解字段,该字段允许为空
alter table 表名称 modify 字段名 数据类型 NULL;
-- 去掉not null,相当于修改某个非注解字段,该字段允许为空
alter table 表名称 modify 字段名 数据类型;
UNIQUE,用来限制某个字段/某列的值不能重复。
-- 建表
create table 表名称(字段名 数据类型,字段名 数据类型 unique,字段名 数据类型 unique key,字段名 数据类型
);
create table 表名称(字段名 数据类型,字段名 数据类型,字段名 数据类型,[constraint 约束名] unique key(字段名)
);-- 建表后指定唯一键约束
alter table 表名称 add unique key(字段列表);
alter table 表名称 modify 字段名 字段类型 unique;-- 关于复合唯一约束,例如
create table student_course(id int,sid int,cid int,score int,unique key(sid,cid) #复合唯一
);
-- 删除约束
-- 添加唯一性约束的列上也会自动创建唯一索引
-- 删除唯一约束只能通过删除唯一索引的方式删除
-- 删除时需要指定唯一索引名,唯一索引名就和唯一约束名一样
-- 如果创建唯一约束时未指定名称,如果是单列,就默认和列名相同;如果是组合列,那么默认和()中排在第一个的列名相同。也可以自定义唯一性约束名
-- 查看都有哪些约束
SELECT * FROM information_schema.table_constraints WHERE table_name = '表名';
ALTER TABLE USER DROP INDEX uk_name_pwd;
-- 可以通过 show index from 表名称;查看表的索引
primary key,用来唯一标识表中的一行记录
-- 创建
create table 表名称(字段名 数据类型 primary key, --列级模式字段名 数据类型,字段名 数据类型
);
create table 表名称(字段名 数据类型,字段名 数据类型,字段名 数据类型,[constraint 约束名] primary key(字段名) --表级模式
);
-- 建表后增加主键约束
-- 字段列表可以是一个字段,也可以是多个字段,如果是多个字段的话,是复合主键
ALTER TABLE 表名称 ADD PRIMARY KEY(字段列表);
-- 举例
ALTER TABLE emp5 ADD PRIMARY KEY(NAME,pwd);-- 删除主键约束
alter table 表名称 drop primary key;
-- 删除主键约束,不需要指定主键名,因为一个表只有一个主键,删除主键约束后,非空还存在
auto_increment,某个字段的值自增
-- 建表时
create table 表名称(字段名 数据类型 primary key auto_increment,字段名 数据类型 unique key not null,字段名 数据类型 unique key,字段名 数据类型 not null default 默认值,
);
-- 自增从n开始,也可以自定义步长
create table 表名称(字段名 数据类型 default 默认值 ,字段名 数据类型 unique key auto_increment,字段名 数据类型 not null default 默认值,primary key(字段名)
) auto_increment = n;-- 修改
alter table 表名称 modify 字段名 数据类型 auto_increment;
-- 给这个字段增加自增约束
alter table 表名称 modify 字段名 数据类型 auto_increment
-- 去掉auto_increment相当于删除
alter table 表名称 modify 字段名 数据类型;
在MySQL 8.0之前,自增主键AUTO_INCREMENT的值如果大于max(primary key)+1,在MySQL重启后,会重置AUTO_INCREMENT=max(primary key)+1,这种现象在某些情况下会导致业务主键冲突或者其他难以发现的问题。
MySQL 8. 0 将自增主键的计数器持久化到重做日志中。每次计数器发生改变,都会将其写入重做日志中。如果数据库重启,InnoDB会根据重做日志中的信息来初始化计数器的内存值
限定某个表的某个字段的引用完整性
例如:员工表的员工所在部门这个字段的值要参考部门表:部门表是主表,员工表是从表。例如:学生表、课程表、选课表:选课表的学生和课程要分别参考学生表和课程表,学生表和课程表是主表,选课表是从表。
-- 建表时
create table 主表名称(字段 1 数据类型 primary key,字段 2 数据类型
);create table 从表名称(字段 1 数据类型 primary key,字段 2 数据类型,[CONSTRAINT <外键约束名称>] FOREIGN KEY(从表的某个字段) references 主表名(被参考字段)
);
-- (从表的某个字段)的数据类型必须与主表名(被参考字段)的数据类型一致,逻辑意义也一样
-- (从表的某个字段)的字段名可以与主表名(被参考字段)的字段名一样,也可以不一样-- FOREIGN KEY: 在表级指定子表中的列
-- REFERENCES: 标示在父表中的列-- 建表后
ALTER TABLE 从表名 ADD [CONSTRAINT 约束名] FOREIGN KEY (从表的字段) REFERENCES 主表名(被引用字段) [on update xx][on delete xx];
对于外键约束,最好是采用: ON UPDATE CASCADE ON DELETE RESTRICT 的方式。
-- 举例
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) on update cascade on delete set null-- 把修改操作设置为级联修改等级,把删除操作设置为set null等级
);
-- 第一步先查看约束名和删除外键约束
SELECT * FROM information_schema.table_constraints WHERE table_name = '表名称';
ALTER TABLE 从表名 DROP FOREIGN KEY 外键约束名;-- 第二步查看索引名和删除索引。(注意,只能手动删除)
SHOW INDEX FROM 表名称;
ALTER TABLE 从表名 DROP INDEX 索引名;
问题 1 :如果两个表之间有关系(一对一、一对多),比如:员工表和部门表(一对多),它们之间是否一定要建外键约束?
答:不是的
问题 2 :建和不建外键约束有什么区别?
答:建外键约束,你的操作(创建表、删除表、添加、修改、删除)会受到限制,从语法层面受到限制。例如:在员工表中不可能添加一个员工信息,它的部门的值在部门表中找不到。
不建外键约束,你的操作(创建表、删除表、添加、修改、删除)不受限制,要保证数据的引用完整性,只能依靠程序员的自觉,或者是在Java程序中进行限定。例如:在员工表中,可以添加一个员工的信息,它的部门指定为一个完全不存在的部门。
问题 3 :那么建和不建外键约束和查询有没有关系?
答:没有
在 MySQL 里,外键约束是有成本的,需要消耗系统资源。对于大并发的 SQL 操作,有可能会不适合。比如大型网站的中央数据库,可能会因为外键约束的系统开销而变得非常慢。所以, MySQL 允许你不使用系统自带的外键约束,在应用层面完成检查数据一致性的逻辑。也就是说,即使你不用外键约束,也要想办法通过应用层面的附加逻辑,来实现外键约束的功能,确保数据的一致性。
阿里开发规范
【强制】不得使用外键与级联,一切外键概念必须在应用层解决。
说明:(概念解释)学生表中的 student_id 是主键,那么成绩表中的 student_id 则为外键。如果更新学生表中的 student_id,同时触发成绩表中的 student_id 更新,即为级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度。
CHECK,检查某个字段的值是否符号xx要求,一般指的是值的范围,MySQL5.7 可以使用check约束,但check约束对数据验证没有任何作用。添加数据时,没有任何错误或警告,但是MySQL 8.0中可以使用check约束了
create table employee(eid int primary key,ename varchar( 5 ),gender char check ('男' or '女')
);-- age tinyint check(age > 20 ) 或 sex char( 2 ) check(sex in('男','女'))
给某个字段/某列指定默认值,一旦设置默认值,在插入数据时,如果此字段没有显式赋值,则赋值为默认值。
-- 建表
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(字段名)
);
-- 说明:默认值约束一般不在唯一键和主键列上加
desc employee;
-- 后期添加
-- 如果这个字段原来有非空约束,你还保留非空约束,那么在加默认值约束时,还得保留非空约束,否则非空约束就被删除了
-- 同理,在给某个字段加非空约束也一样,如果这个字段原来有默认值约束,你想保留,也要在modify语句中保留默认值约束,否则就删除了
alter table 表名称 modify 字段名 数据类型 default 默认值 not null;
alter table 表名称 modify 字段名 数据类型 default 默认值;-- 删除默认值约束
-- 删除默认值约束,也不保留非空约束
alter table 表名称 modify 字段名 数据类型 ;
-- 删除默认值约束,保留非空约束
alter table 表名称 modify 字段名 数据类型 not null;
对象 | 描述 |
---|---|
表(TABLE) | 表是存储数据的逻辑单元,以行和列的形式存在,列就是字段,行就是记录 |
数据字典 | 就是系统表,存放数据库相关信息的表。系统表的数据通常由数据库系统维护,程序员通常不应该修改,只可查看 |
约束(CONSTRAINT) | 执行数据校验的规则,用于保证数据完整性的规则 |
视图(VIEW) | 一个或者多个数据表里的数据的逻辑显示,视图并不存储数据 |
索引(INDEX) | 用于提高查询性能,相当于书的目录 |
存储过程(PROCEDURE) | 用于完成一次完整的业务处理,没有返回值,但可通过传出参数将多个值传给调用环境 |
存储函数(FUNCTION) | 用于完成一次特定的计算,具有一个返回值 |
触发器(TRIGGER) | 相当于一个事件监听器,当数据库发生特定事件后,触发器被触发,完成相应的处理 |
-- 在 CREATE VIEW 语句中嵌入子查询
CREATE [OR REPLACE]
[ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}]
VIEW 视图名称 [(字段列表)]
AS 查询语句
[WITH [CASCADED|LOCAL] CHECK OPTION]
-- 精简版
CREATE VIEW 视图名称 AS 查询语句
-- 举例
CREATE VIEW empvu
AS
SELECT employee_id, last_name, salary
FROM employees WHERE department_id = 80 ;
-- 查询视图
SELECT * FROM salvu80;
-- 实际上就是我们在 SQL 查询语句的基础上封装了视图 VIEW,这样就会基于 SQL 语句的结果集形成一张虚拟表
-- 在创建视图时,没有在视图名后面指定字段列表,则视图中字段列表默认和SELECT语句中的字段列表一致。如果SELECT语句中给字段取了别名,那么视图中的字段名和别名相同-- 创建多表联合视图
-- 举例
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;
-- 利用视图对数据进行格式化
-- 我们经常需要输出某个格式的内容,比如我们想输出员工姓名和对应的部门名,对应格式为emp_name(department_name),就可以使用视图来完成数据格式化的操作
CREATE VIEW emp_depart
AS
SELECT CONCAT(last_name,'(',department_name,')') AS emp_dept
FROM employees e JOIN departments d
WHERE e.department_id = d.department_id-- 基于视图创建视图
-- 当我们创建好一张视图之后,还可以在它的基础上继续创建视图
CREATE VIEW emp_dept_ysalary
AS
SELECT emp_dept.ename,dname,year_salary
FROM emp_dept INNER JOIN emp_year_salary
ON emp_dept.ename = emp_year_salary.ename;
-- 查看数据库的表对象、视图对象
SHOW TABLES;
-- 查看视图的结构
DESC/DESCRIBE 视图名称;
-- 查看视图的属性信息
-- 查看视图信息(显示数据表的存储引擎、版本、数据行数和数据大小等)
-- 执行结果显示,注释Comment为VIEW,说明该表为视图,其他的信息为NULL,说明这是一个虚表
SHOW TABLE STATUS LIKE '视图名称'\G
-- 查看视图的详细定义信息
SHOW CREATE VIEW 视图名称;
MySQL支持使用INSERT、UPDATE和DELETE语句对视图中的数据进行插入、更新和删除操作。当视图中的数据发生变化时,数据表中的数据也会发生变化,反之亦然。
要使视图可更新,视图中的行和底层基本表中的行之间必须存在一对一的关系。另外当视图定义出现如下情况时,视图不支持更新操作:
虽然可以更新视图数据,但总的来说,视图作为虚拟表,主要用于方便查询,不建议更新视图的数据。 对视图数据的更改,都是通过对实际数据表里数据的操作来完成的
-- 修改视图
-- 使用CREATE OR REPLACE VIEW 子句 修改视图
-- 注意CREATE VIEW 子句中各列的别名应和子查询中各列相对应
CREATE OR REPLACE VIEW empvu
(id_number, name, sal, department_id)
AS
SELECT employee_id, first_name || ' ' || last_name, salary, department_id
FROM employees
WHERE department_id = 80 ;
-- 方式2
ALTER VIEW 视图名称
AS
查询语句-- 删除视图
-- 删除视图只是删除视图的定义,并不会删除基表的数据
DROP VIEW IF EXISTS 视图名称;
DROP VIEW IF EXISTS 视图名称1,视图名称2,视图名称3,...;
-- 基于视图a、b创建了新的视图c,如果将视图a或者视图b删除,会导致视图c的查询失败。这样的视图c需要手动删除或修改,否则影响使用
如果我们在实际数据表的基础上创建了视图,那么, 如果实际数据表的结构变更了,我们就需要及时对相关的视图进行相应的维护 。特别是嵌套的视图(就是在视图的基础上创建视图),维护会变得比较复杂,可读性不好,容易变成系统的潜在隐患。因为创建视图的 SQL 查询可能会对字段重命名,也可能包含复杂的逻辑,这些都会增加维护的成本。
实际项目中,如果视图过多,会导致数据库维护成本的问题。所以,在创建视图的时候,你要结合实际项目需求,综合考虑视图的优点和不足,这样才能正确使用视图,使系统整体达到最优。
含义 :存储过程的英文是 Stored Procedure。它的思想很简单,就是一组经过预先编译的 SQL 语句的封装。执行过程:存储过程预先存储在 MySQL 服务器上,需要执行的时候,客户端只需要向服务器端发出调用存储过程的命令,服务器端就可以把预先存储好的这一系列 SQL 语句全部执行。
好处 :
和视图、函数的对比 :
它和视图有着同样的优点,清晰、安全,还可以减少网络传输量。不过它和视图不同,视图是虚拟表,通常不对底层数据表直接操作,而存储过程是程序化的 SQL,可以直接操作底层数据表,相比于面向集合的操作方式,能够实现一些更复杂的数据处理。一旦存储过程被创建出来,使用它就像使用函数一样简单,我们直接通过调用存储过程名即可。相较于函数,存储过程是没有返回值的。
存储过程的参数类型可以是IN、OUT和INOUT。根据这点分类如下:
注意:IN、OUT、INOUT 都可以在一个存储过程中带多个。
CREATE PROCEDURE 存储过程名(IN|OUT|INOUT 参数名 参数类型,...)
[characteristics ...]
BEGIN
存储过程体
END
说明:
LANGUAGE SQL
| [NOT] DETERMINISTIC
| { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
| SQL SECURITY { DEFINER | INVOKER }
| COMMENT 'string'
-- 举例1
DELIMITER $
CREATE PROCEDURE select_all_data()BEGINSELECT * FROM emps;END $
DELIMITER ;-- 举例2,创建存储过程show_max_salary(),用来查看“emps”表的最高薪资值
DELIMITER //
CREATE PROCEDURE show_max_salary()LANGUAGE SQLNOT DETERMINISTICCONTAINS SQLSQL SECURITY DEFINERCOMMENT '查看最高薪资'BEGINSELECT MAX(salary) FROM emps;END //
DELIMITER ;-- 查看“emps”表的最低薪资值。并将最低薪资通过OUT参数“ms”输出
DELIMITER //
CREATE PROCEDURE show_min_salary(OUT ms DOUBLE)BEGINSELECT MIN(salary) INTO ms FROM emps;END //
DELIMITER ;-- 查看“emps”表的某个员工的薪资,并用IN参数empname输入员工姓名
DELIMITER //
CREATE PROCEDURE show_someone_salary(IN empname VARCHAR( 20 ))BEGINSELECT salary FROM emps WHERE ename = empname;END //
DELIMITER ;
-- 存储过程有多种调用方法。存储过程必须使用CALL语句调用,并且存储过程和数据库相关,如果要执行其他数据库中的存储过程,需要指定数据库名称
CALL 存储过程名(实参列表)
-- 调用in模式
CALL sp1('值');
-- 调用out模式的参数
SET @name;
CALL sp1(@name);
SELECT @name;-- 调用inout模式的参数
SET @name=值;
CALL sp1(@name);
SELECT @name;
CREATE FUNCTION 函数名(参数名 参数类型,...)
RETURNS 返回值类型
[characteristics ...]
BEGIN
函数体 #函数体中肯定有 RETURN 语句
END
在MySQL中,存储函数的使用方法与MySQL内部函数的使用方法是一样的。换言之,用户自己定义的存储函数与MySQL内部函数是一个性质的。区别在于,存储函数是用户自己定义的,而内部函数是MySQL的开发者定义的
SELECT 函数名(实参列表)-- 举例
DELIMITER //CREATE FUNCTION email_by_id(emp_id INT)
RETURNS VARCHAR( 25 )
DETERMINISTIC
CONTAINS SQL
BEGINRETURN (SELECT email FROM employees WHERE employee_id = emp_id);
END //DELIMITER ;
-- 调用
SET @emp_id = 102 ;
SELECT email_by_id( 102 );
注意:
若在创建存储函数中报错“you might want to use the less safe log_bin_trust_function_creators variable”,有两种处理方法:
SET GLOBAL log_bin_trust_function_creators = 1 ;
**关键字 | 调用语法 | 返回值 | 应用场景 | |
---|---|---|---|---|
存储过程 | PROCEDURE | CALL 存储过程() | 理解为有 0 个或多个 | 一般用于更新 |
存储函数 | FUNCTION | SELECT 函数() | 只能是一个 | 一般用于查询结果为一个值并返回时 |
-- 查看存储过程和函数的创建信息
SHOW CREATE {PROCEDURE | FUNCTION} 存储过程名或函数名
-- 举例
SHOW CREATE FUNCTION test_db.CountProc \G-- 使用SHOW STATUS语句查看存储过程和函数的状态信息
SHOW {PROCEDURE | FUNCTION} STATUS [LIKE 'pattern']
-- 举例
SHOW PROCEDURE STATUS LIKE 'SELECT%' \G-- 从information_schema.Routines表中查看存储过程和函数的信息
SELECT * FROM information_schema.Routines
WHERE ROUTINE_NAME='存储过程或函数的名'[AND ROUTINE_TYPE={'PROCEDURE|FUNCTION'}];
-- 举例:从Routines表中查询名称为CountProc的存储函数的信息
SELECT * FROM information_schema.Routines
WHERE ROUTINE_NAME='count_by_id' AND ROUTINE_TYPE = 'FUNCTION' \G-- 修改存储过程或函数,不影响存储过程或函数功能,只是修改相关特性
ALTER {PROCEDURE | FUNCTION} 存储过程或函数的名 [characteristic ...]
-- 其中,characteristic指定存储过程或函数的特性,其取值信息与创建存储过程、函数时的取值信息略有不同
{ CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
| SQL SECURITY { DEFINER | INVOKER }
| COMMENT 'string'
-- 举例
ALTER FUNCTION CountProc
READS SQL DATA
COMMENT 'FIND NAME' ;-- 删除
DROP {PROCEDURE | FUNCTION} [IF EXISTS] 存储过程或函数的名
DROP PROCEDURE CountProc;
DROP FUNCTION CountProc;
阿里开发规范
【强制】禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。
MySQL文档的系统变量:https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html
变量由系统定义,不是用户定义,属于服务器层面。启动MySQL服务,生成MySQL服务实例期间,MySQL将为MySQL服务器内存中的系统变量赋值,这些系统变量定义了当前MySQL服务实例的属性、特征。这些系统变量的值要么是编译MySQL时参数的默认值,要么是配置文件(例如my.ini等)中的参数值。
系统变量分为全局系统变量(需要添加global 关键字)以及会话系统变量(需要添加 session 关键字),有时也把全局系统变量简称为全局变量,有时也把会话系统变量称为local变量。 如果不写,默认会话级别。 静态变量(在 MySQL 服务实例运行期间它们的值不能使用 set 动态修改)属于特殊的全局系统变量。
每一个MySQL客户机成功连接MySQL服务器后,都会产生与之对应的会话。会话期间,MySQL服务实例会在MySQL服务器内存中生成与该会话对应的会话系统变量,这些会话系统变量的初始值是全局系统变量值的复制。如下图
在MySQL中有些系统变量只能是全局的,例如 max_connections 用于限制服务器的最大连接数;有些系统变量作用域既可以是全局又可以是会话,例如 character_set_client 用于设置客户端的字符集;有些系统变量的作用域只能是当前会话,例如 pseudo_thread_id 用于标记当前会话的 MySQL 连接 ID。
-- 查看系统变量
-- 查看所有全局变量
SHOW GLOBAL VARIABLES;
-- 查看所有会话变量
SHOW SESSION VARIABLES;
SHOW VARIABLES;-- 查看满足条件的部分系统变量。
SHOW GLOBAL VARIABLES LIKE '%标识符%';
-- 查看满足条件的部分会话变量
SHOW SESSION VARIABLES LIKE '%标识符%';-- 查看指定系统变量
-- 作为 MySQL 编码规范,MySQL 中的系统变量以两个“@”开头,其中“@@global”仅用于标记全局系统变量,“@@session”仅用于标记会话系统变量。
-- “@@”首先标记会话系统变量,如果会话系统变量不存在,则标记全局系统变量
-- 查看指定的系统变量的值
SELECT @@global.变量名;
-- 查看指定的会话变量的值
SELECT @@session.变量名;
SELECT @@变量名;-- 修改系统变量的值
-- 方式一修改MySQL配置文件,继而修改MySQL系统变量的值(该方法需要重启MySQL服务)
-- 方式二在MySQL服务运行期间,使用“set”命令重新设置系统变量的值
-- 为某个系统变量赋值
SET @@global.变量名=变量值;
SET GLOBAL 变量名=变量值;
-- 为某个会话变量赋值
SET @@session.变量名=变量值;
SET SESSION 变量名=变量值;
用户变量是用户自己定义的,作为 MySQL 编码规范,MySQL 中的用户变量以一个“@”开头。根据作用范围不同,又分为会话用户变量和局部变量。
-- 会话用户变量
-- 变量定义
-- 方式 1 :“=”或“:=”
SET @用户变量 = 值;
SET @用户变量 := 值;
-- 方式 2 :“:=” 或 INTO关键字
SELECT @用户变量 := 表达式 [FROM 等子句];
SELECT 表达式 INTO @用户变量 [FROM 等子句];
-- 查询
SELECT @用户变量-- 局部变量
-- 如果没有DEFAULT子句,初始值为NULL
DECLARE 变量名 类型 [default 值];
-- 赋值,一般用于赋简单的值
SET 变量名=值;
SET 变量名:=值;
-- 一般用于赋表中的字段值
SELECT 字段名或表达式 INTO 变量名 FROM 表;
-- 使用变量
SELECT 局部变量名;
使用SET GLOBAL语句设置的变量值只会临时生效。数据库重启后,服务器又会从MySQL配置文件中读取变量的默认值。 MySQL 8.0版本新增了SET PERSIST命令。例如,设置服务器的最大连接数为 1000 :
SET PERSIST global max_connections = 1000 ;
show variables like '%max_connections%';
MySQL会将该命令的配置保存到数据目录下的mysqld-auto.cnf文件中,下次启动时会读取该文件,用其中的配置来覆盖默认的配置文件。
定义条件是事先定义程序执行过程中可能遇到的问题,处理程序定义了在遇到问题时应当采取的处理方式,并且保证存储过程或函数在遇到警告或错误时能继续执行。这样可以增强存储程序处理问题的能力,避免程序异常停止运行。说明:定义条件和处理程序在存储过程、存储函数中都是支持的。
定义条件就是给MySQL中的错误码命名,这有助于存储的程序代码更清晰。它将一个错误名字和指定的错误条件关联起来。这个名字可以随后被用在定义处理程序的DECLARE HANDLER语句中。定义条件使用DECLARE语句,语法格式如下:
DECLARE 错误名称 CONDITION FOR 错误码(或错误条件)
-- 举例 定义”ERROR 1148(42000)”错误,名称为command_not_allowed-- 使用MySQL_error_code
DECLARE command_not_allowed CONDITION FOR 1148 ;-- 使用sqlstate_value
DECLARE command_not_allowed CONDITION FOR SQLSTATE '42000';
可以为SQL执行过程中发生的某种类型的错误定义特殊的处理程序。定义处理程序时,使用DECLARE语句的语法如下:
DECLARE 处理方式 HANDLER FOR 错误类型 处理语句
定义处理程序的几种方式,代码如下:
-- 方法 1 :捕获sqlstate_value
DECLARE CONTINUE HANDLER FOR SQLSTATE '42S02' SET @info='NO_SUCH_TABLE';
-- 方法 2 :捕获mysql_error_value
DECLARE CONTINUE HANDLER FOR 1146 SET @info = 'NO_SUCH_TABLE';
-- 方法 3 :先定义条件,再调用
DECLARE no_such_table CONDITION FOR 1146 ;
DECLARE CONTINUE HANDLER FOR NO_SUCH_TABLE SET @info = 'NO_SUCH_TABLE';
-- 方法 4 :使用SQLWARNING
DECLARE EXIT HANDLER FOR SQLWARNING SET @info = 'ERROR';
-- 方法 5 :使用NOT FOUND
DECLARE EXIT HANDLER FOR NOT FOUND SET @info = 'NO_SUCH_TABLE';
-- 方法 6 :使用SQLEXCEPTION
DECLARE EXIT HANDLER FOR SQLEXCEPTION SET @info = 'ERROR';
在存储过程中,定义处理程序,捕获sqlstate_value值,当遇到MySQL_error_code值为 1048 时,执行CONTINUE操作,并且将@proc_value的值设置为-1
DELIMITER //CREATE PROCEDURE InsertDataWithCondition()BEGINDECLARE duplicate_entry CONDITION FOR SQLSTATE '23000' ;DECLARE EXIT HANDLER FOR duplicate_entry SET @proc_value = - 1 ;SET @x = 1 ;INSERT INTO departments(department_name) VALUES('测试');SET @x = 2 ;INSERT INTO departments(department_name) VALUES('测试');SET @x = 3 ;END //DELIMITER ;
解决复杂问题不可能通过一个 SQL 语句完成,我们需要执行多个 SQL 操作。流程控制语句的作用就是控制存储过程中 SQL 语句的执行顺序,是我们完成复杂操作必不可少的一部分。只要是执行的程序,流程就分为三大类:
针对于MySQL 的流程控制语句主要有 3 类。注意:只能用于存储程序。
-- 分支结构之 IF
IF 表达式 1 THEN 操作 1
[ELSEIF 表达式 2 THEN 操作2]......
[ELSE 操作N]
END IF
-- 分支结构之 CASE
CASE
WHEN 条件 1 THEN 结果 1 或语句1(如果是语句,需要加分号)
WHEN 条件 2 THEN 结果 2 或语句2(如果是语句,需要加分号)
...
ELSE 结果n或语句n(如果是语句,需要加分号)
END [case](如果是放在begin end中需要加上case,如果放在select后面不需要)
-- 循环结构之LOOP
[loop_label:] LOOP循环执行的语句
END LOOP [loop_label]
-- 循环结构之WHILE
[while_label:] WHILE 循环条件 DO循环体
END WHILE [while_label];
-- 循环结构之REPEAT
[repeat_label:] REPEAT循环体的语句
UNTIL 结束循环的条件表达式
END REPEAT [repeat_label]
-- 跳转语句之LEAVE语句,可以把 LEAVE 理解为 break
LEAVE 标记名
-- 跳转语句之ITERATE语句,可以把 ITERATE 理解为 continue
ITERATE label
虽然我们也可以通过筛选条件 WHERE 和 HAVING,或者是限定返回记录的关键字 LIMIT 返回一条记录,但是,却无法在结果集中像指针一样,向前定位一条记录、向后定位一条记录,或者是随意定位到某一条记录,并对记录的数据进行处理。这个时候,就可以用到游标。游标,提供了一种灵活的操作方式,让我们能够对结果集中的每一条记录进行定位,并对指向的记录中的数据进行操作的数据结构。 游标让 SQL 这种面向集合的语言有了面向过程开发的能力。
在 SQL 中,游标是一种临时的数据库对象,可以指向存储在数据库表中的数据行指针。这里游标充当了指针的作用,我们可以通过操作游标来对数据行进行操作。MySQL中游标可以在存储过程和函数中使用。
-- 1、声明游标
DECLARE cursor_name CURSOR FOR select_statement;
-- 这个语法适用于 MySQL,SQL Server,DB2 和 MariaDB。如果是用 Oracle 或者 PostgreSQL,需要写成
DECLARE cursor_name CURSOR IS select_statement;-- 要使用 SELECT 语句来获取数据结果集,而此时还没有开始遍历数据,这里 select_statement 代表的是SELECT 语句,返回一个用于创建游标的结果集。
-- 举例
DECLARE cur_emp CURSOR FOR
SELECT employee_id,salary FROM employees;-- 2、打开游标的语法
OPEN cursor_name
-- 当我们定义好游标之后,如果想要使用游标,必须先打开游标。
-- 打开游标的时候 SELECT 语句的查询结果集就会送到游标工作区,为后面游标的逐条读取结果集中的记录做准备-- 3、使用游标(从游标中取得数据)
FETCH cursor_name INTO var_name [, var_name] ...
-- 使用 cursor_name 这个游标来读取当前行,并且将数据保存到 var_name 这个变量中,游标指针指到下一行
-- 如果游标读取的数据行有多个列名,则在 INTO 关键字后面赋值给多个变量名即可
-- 游标的查询结果集中的字段数,必须跟 INTO 后面的变量数一致 ,否则,在存储过程执行的时候,MySQL 会提示错误-- 4、关闭游标
CLOSE cursor_name
-- 如果不及时关闭, 游标会一直保持到存储过程结束 ,影响系统运行的效率-- 举例
DELIMITER //CREATE PROCEDURE get_count_by_limit_total_salary(IN limit_total_salary DOUBLE,OUT total_count INT)
BEGINDECLARE sum_salary DOUBLE DEFAULT 0 ; -- 记录累加的总工资DECLARE cursor_salary DOUBLE DEFAULT 0 ; -- 记录某一个工资值DECLARE emp_count INT DEFAULT 0 ; -- 记录循环个数-- 定义游标DECLARE emp_cursor CURSOR FOR SELECT salary FROM employees ORDER BY salary DESC;-- 打开游标OPEN emp_cursor;REPEAT-- 使用游标(从游标中获取数据)FETCH emp_cursor INTO cursor_salary;SET sum_salary = sum_salary + cursor_salary;SET emp_count = emp_count + 1 ;UNTIL sum_salary >= limit_total_salaryEND REPEAT;SET total_count = emp_count;-- 关闭游标CLOSE emp_cursor;
END //DELIMITER ;
游标是 MySQL 的一个重要的功能,为逐条读取结果集中的数据,提供了完美的解决方案。跟在应用层面实现相同的功能相比,游标可以在存储程序中使用,效率高,程序也更加简洁。但同时也会带来一些性能问题,比如在使用游标的过程中,会对数据行进行加锁,这样在业务并发量大的时候,不仅会影响业务之间的效率,还会消耗系统资源,造成内存不足,这是因为游标是在内存中进行的处理。
建议:养成用完之后就关闭的习惯,这样才能提高系统的整体效率。
MySQL从5.0.2版本开始支持触发器。MySQL的触发器和存储过程一样,都是嵌入到MySQL服务器的一段程序。触发器是由事件来触发某个操作,这些事件包括INSERT、UPDATE、DELETE事件。所谓事件就是指用户的动作或者触发某项行为。如果定义了触发程序,当数据库执行这些语句时候,就相当于事件发生了,就会自动激发触发器执行相应的操作。当对数据表中的数据执行插入、更新和删除操作,需要自动执行一些数据库逻辑时,可以使用触发器来实现。
-- 创建触发器的语法结构
CREATE TRIGGER 触发器名称
{BEFORE|AFTER} {INSERT|UPDATE|DELETE} ON 表名
FOR EACH ROW
触发器执行的语句块;
-- 表名:表示触发器监控的对象。
-- BEFORE|AFTER:表示触发的时间。BEFORE 表示在事件之前触发;AFTER 表示在事件之后触发。
-- INSERT|UPDATE|DELETE:表示触发的事件。
-- INSERT 表示插入记录时触发;
-- UPDATE 表示更新记录时触发;
-- DELETE 表示删除记录时触发。
-- 触发器执行的语句块:可以是单条SQL语句,也可以是由BEGIN…END结构组成的复合语句块。-- 举例1
-- 向test_trigger数据表插入数据之前,向test_trigger_log数据表中插入before_insert的日志信息
DELIMITER //
CREATE TRIGGER before_insert
BEFORE INSERT ON test_trigger
FOR EACH ROW
BEGININSERT INTO test_trigger_log (t_log)VALUES('before_insert');
END //
DELIMITER ;
-- 举例2
-- 在INSERT之前检查将要添加的新员工薪资是否大于他领导的薪资,如果大于领导薪资,则报sqlstate_value为’HY000’的错误,从而使得添加失败
DELIMITER //
CREATE TRIGGER salary_check_trigger
BEFORE INSERT ON employees FOR EACH ROW
BEGINDECLARE mgrsalary DOUBLE;SELECT salary INTO mgrsalary FROM employees WHERE employee_id = NEW.manager_id;IF NEW.salary > mgrsalary THENSIGNAL SQLSTATE 'HY000' SET MESSAGE_TEXT = '薪资高于领导薪资错误';END IF;
END //
DELIMITER ;-- 查看、删除触发器
-- 查看当前数据库的所有触发器的定义
SHOW TRIGGERS\G
-- 查看当前数据库中某个触发器的定义
SHOW CREATE TRIGGER 触发器名
-- 从系统库information_schema的TRIGGERS表中查询“salary_check_trigger”触发器的信息
SELECT * FROM information_schema.TRIGGERS;
-- 触发器也是数据库对象,删除触发器也用DROP语句
DROP TRIGGER IF EXISTS 触发器名称;
优点:
缺点:
注意点:
注意,如果在子表中定义了外键约束,并且外键指定了ON UPDATE/DELETE CASCADE/SET NULL子句,此时修改父表被引用的键值或删除父表被引用的记录行时,也会引起子表的修改和删除操作,此时基于子表的UPDATE和DELETE语句定义的触发器并不会被激活。
WITH cte_name (col_name1,col_name2 ...) AS (Subquery) SELECT * FROM cte_name;
Subquery代表子查询,子查询前使用WITH语句将结果集命名为cte_name,在后续的查询中即可使用cte_name进行查询。在MySQL 5.7版本上开发的应用程序如果使用了MySQL8.0 移除的特性,语句可能会失败,或者产生不同的执行结果。为了避免这些问题,对于使用了移除特性的应用,应当尽力修正避免使用这些特性,并尽可能使用替代方法。
-- 假设我现在有这样一个数据表,它显示了某购物网站在每个城市每个区的销售额
CREATE TABLE sales(id INT PRIMARY KEY AUTO_INCREMENT,city VARCHAR( 15 ),county VARCHAR( 15 ),sales_value DECIMAL
);
INSERT INTO sales(city,county,sales_value)
VALUES
('北京','海淀',10.00),
('北京','朝阳',20.00),
('上海','黄埔',30.00),
('上海','长宁',10.00);-- 现在计算这个网站在每个城市的销售总额、在全国的销售总额、每个区的销售额占所在城市销售额中的比率,以及占总销售额中的比率
-- 传统方式实现
-- 第一步,计算总销售金额,并存入临时表 a:
CREATE TEMPORARY TABLE a -- 创建临时表
SELECT SUM(sales_value) AS sales_value -- 计算总计金额
FROM sales;
-- 第二步,计算每个城市的销售总额并存入临时表 b
CREATE TEMPORARY TABLE b -- 创建临时表
SELECT city,SUM(sales_value) AS sales_value -- 计算城市销售合计
FROM sales
GROUP BY city;
-- 第三步,计算各区的销售占所在城市的总计金额的比例,和占全部销售总计金额的比例
SELECT s.city AS 城市,s.county AS 区,s.sales_value AS 区销售额,
b.sales_value AS 市销售额,s.sales_value/b.sales_value AS 市比率,
a.sales_value AS 总销售额,s.sales_value/a.sales_value AS 总比率
FROM sales s
JOIN b ON (s.city=b.city) -- 连接市统计结果临时表
JOIN a -- 连接总计金额临时表
ORDER BY s.city,s.county;-- 使用窗口函数
SELECT city AS 城市,county AS 区,sales_value AS 区销售额,
SUM(sales_value) OVER(PARTITION BY city) AS 市销售额, -- 计算市销售额
sales_value/SUM(sales_value) OVER(PARTITION BY city) AS 市比率,
SUM(sales_value) OVER() AS 总销售额, -- 计算总销售额
sales_value/SUM(sales_value) OVER() AS 总比率
FROM sales
ORDER BY city,county;-- 在这种需要用到分组统计的结果对每一条记录进行计算的场景下,使用窗口函数更好
官方文档:https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html#function_row-number
MySQL从 8. 0 版本开始支持窗口函数。窗口函数的作用类似于在查询中对数据进行分组,不同的是,分组操作会把分组的结果聚合成一条记录,而窗口函数是将结果置于每一条数据记录中。窗口函数可以分为静态窗口函数和动态窗口函数。
窗口函数总体上可以分为序号函数、分布函数、前后函数、首尾函数和其他函数,如下表:
-- 窗口函数的语法结构
函数 OVER([PARTITION BY 字段名 ORDER BY 字段名 ASC|DESC])
-- 或者
函数 OVER 窗口名 ... WINDOW 窗口名 AS ([PARTITION BY 字段名 ORDER BY 字段名 ASC|DESC])
首先创建表和插入数据
CREATE TABLE goods(id INT PRIMARY KEY AUTO_INCREMENT,category_id INT,category VARCHAR( 15 ),NAME VARCHAR( 30 ),price DECIMAL( 10 , 2 ),stock INT,upper_time DATETIME
);INSERT INTO goods(category_id,category,NAME,price,stock,upper_time)
VALUES
( 1 , '女装/女士精品', 'T恤', 39.90, 1000 , '2020-11-10 00:00:00'),
( 1 , '女装/女士精品', '连衣裙', 79.90, 2500 , '2020-11-10 00:00:00'),
( 1 , '女装/女士精品', '卫衣', 89.90, 1500 , '2020-11-10 00:00:00'),
( 1 , '女装/女士精品', '牛仔裤', 89.90, 3500 , '2020-11-10 00:00:00'),
( 1 , '女装/女士精品', '百褶裙', 29.90, 500 , '2020-11-10 00:00:00'),
( 1 , '女装/女士精品', '呢绒外套', 399.90, 1200 , '2020-11-10 00:00:00'),
( 2 , '户外运动', '自行车', 399.90, 1000 , '2020-11-10 00:00:00'),
( 2 , '户外运动', '山地自行车', 1399.90, 2500 , '2020-11-10 00:00:00'),
( 2 , '户外运动', '登山杖', 59.90, 1500 , '2020-11-10 00:00:00'),
( 2 , '户外运动', '骑行装备', 399.90, 3500 , '2020-11-10 00:00:00'),
( 2 , '户外运动', '运动外套', 799.90, 500 , '2020-11-10 00:00:00'),
( 2 , '户外运动', '滑板', 499.90, 1200 , '2020-11-10 00:00:00');
不同函数讲解
-- ====================序号函数==========================
-- ROW_NUMBER()函数
-- ROW_NUMBER()函数能够对数据中的序号进行顺序显示
-- 举例:查询 goods 数据表中每个商品分类下价格降序排列的各个商品信息
SELECT ROW_NUMBER() OVER(PARTITION BY category_id ORDER BY price DESC) AS row_num,
id, category_id, category, NAME, price, stock
FROM goods;
-- 举例:查询 goods 数据表中每个商品分类下价格最高的 3 种商品信息
SELECT *
FROM (
SELECT ROW_NUMBER() OVER(PARTITION BY category_id ORDER BY price DESC) AS row_num,
id, category_id, category, NAME, price, stock
FROM goods) t
WHERE row_num <= 3 ;-- RANK()函数
-- 使用RANK()函数能够对序号进行并列排序,并且会跳过重复的序号,比如序号为 1 、 1 、 3 。
-- 举例:使用RANK()函数获取 goods 数据表中各类别的价格从高到低排序的各商品信息。
SELECT RANK() OVER(PARTITION BY category_id ORDER BY price DESC) AS row_num,
id, category_id, category, NAME, price, stock
FROM goods;
-- 举例:使用RANK()函数获取 goods 数据表中类别为“女装/女士精品”的价格最高的 4 款商品信息
SELECT *
FROM(
SELECT RANK() OVER(PARTITION BY category_id ORDER BY price DESC) AS row_num,
id, category_id, category, NAME, price, stock
FROM goods) t
WHERE category_id = 1 AND row_num <= 4 ;-- DENSE_RANK()函数
-- DENSE_RANK()函数对序号进行并列排序,并且不会跳过重复的序号,比如序号为1 、1 、2。
-- 举例:使用DENSE_RANK()函数获取 goods 数据表中各类别的价格从高到低排序的各商品信息。
SELECT DENSE_RANK() OVER(PARTITION BY category_id ORDER BY price DESC) AS row_num,
id, category_id, category, NAME, price, stock
FROM goods;-- ============================分布函数===========================
-- PERCENT_RANK()函数
-- PERCENT_RANK()函数是等级值百分比函数。按照如下方式进行计算。其中,rank的值为使用RANK()函数产生的序号,rows的值为当前窗口的总记录数。
-- (rank - 1 ) / (rows - 1 )
-- 举例:计算 goods 数据表中名称为“女装/女士精品”的类别下的商品的PERCENT_RANK值。
-- 写法一:
SELECT RANK() OVER (PARTITION BY category_id ORDER BY price DESC) AS r,
PERCENT_RANK() OVER (PARTITION BY category_id ORDER BY price DESC) AS pr,
id, category_id, category, NAME, price, stock
FROM goods
WHERE category_id = 1 ;
-- 写法二:
SELECT RANK() OVER w AS r,
PERCENT_RANK() OVER w AS pr,
id, category_id, category, NAME, price, stock
FROM goods
WHERE category_id = 1 WINDOW w AS (PARTITION BY category_id ORDER BY price DESC);-- CUME_DIST()函数
-- CUME_DIST()函数主要用于查询小于或等于某个值的比例
-- 举例:查询goods数据表中小于或等于当前价格的比例
SELECT CUME_DIST() OVER(PARTITION BY category_id ORDER BY price ASC) AS cd,
id, category, NAME, price
FROM goods;-- ============================前后函数======================================
-- LAG(expr,n)函数
-- LAG(expr,n)函数返回当前行的前n行的expr的值。
-- 举例:查询goods数据表中前一个商品价格与当前商品价格的差值
SELECT id, category, NAME, price, pre_price, price - pre_price AS diff_price
FROM (
SELECT id, category, NAME, price,LAG(price, 1) OVER w AS pre_price
FROM goods
WINDOW w AS (PARTITION BY category_id ORDER BY price)) t;-- LEAD(expr,n)函数
-- LEAD(expr,n)函数返回当前行的后n行的expr的值
-- 举例:查询goods数据表中后一个商品价格与当前商品价格的差值
SELECT id, category, NAME, behind_price, price,behind_price - price AS diff_price
FROM(
SELECT id, category, NAME, price,LEAD(price, 1 ) OVER w AS behind_price
FROM goods WINDOW w AS (PARTITION BY category_id ORDER BY price)) t;-- ==================================首尾函数==================================
-- FIRST_VALUE(expr)函数
-- FIRST_VALUE(expr)函数返回第一个expr的值
-- 举例:按照价格排序,查询第 1 个商品的价格信息
SELECT id, category, NAME, price, stock,FIRST_VALUE(price) OVER w AS first_price
FROM goods WINDOW w AS (PARTITION BY category_id ORDER BY price);-- LAST_VALUE(expr)函数
-- LAST_VALUE(expr)函数返回最后一个expr的值
-- 举例:按照价格排序,查询最后一个商品的价格信息
SELECT id, category, NAME, price, stock,LAST_VALUE(price) OVER w AS last_price
FROM goods WINDOW w AS (PARTITION BY category_id ORDER BY price);--=================================其他函数========================
-- NTH_VALUE(expr,n)函数
-- NTH_VALUE(expr,n)函数返回第n个expr的值
-- 举例:查询goods数据表中排名第 2 和第 3 的价格信息
SELECT id, category, NAME, price,NTH_VALUE(price, 2 ) OVER w AS second_price,
NTH_VALUE(price, 3 ) OVER w AS third_price
FROM goods WINDOW w AS (PARTITION BY category_id ORDER BY price);
-- NTILE(n)函数
-- NTILE(n)函数将分区中的有序数据分为n个桶,记录桶编号
-- 举例:将goods表中的商品按照价格分为 3 组
SELECT NTILE( 3 ) OVER w AS nt,id, category, NAME, price
FROM goods WINDOW w AS (PARTITION BY category_id ORDER BY price);
窗口函数的特点是可以分组,而且可以在分组内排序。另外,窗口函数不会因为分组而减少原表中的行数,这对我们在原表数据的基础上进行统计和排序非常有用
公用表表达式(或通用表表达式)简称为CTE(Common Table Expressions)。CTE是一个命名的临时结果集,作用范围是当前语句。CTE可以理解成一个可以复用的子查询,当然跟子查询还是有点区别的,CTE可以引用其他CTE,但子查询不能引用其他子查询。所以,可以考虑代替子查询。依据语法结构和执行方式的不同,公用表表达式分为普通公用表表达式和递归公用表表达式 2 种
-- 普通公用表表达式的语法结构
WITH CTE名称
AS (子查询)
SELECT|DELETE|UPDATE 语句;-- 查询员工所在的部门的详细信息
SELECT * FROM departments
WHERE department_id IN (
SELECT DISTINCT department_id
FROM employees);-- 这个查询也可以用普通公用表表达式的方式完成:
WITH emp_dept_id
AS (SELECT DISTINCT department_id FROM employees)SELECT * FROM departments d JOIN emp_dept_id e
ON d.department_id = e.department_id;
递归公用表表达式也是一种公用表表达式,只不过,除了普通公用表表达式的特点以外,它还有自己的特点,就是 可以调用自己
WITH RECURSIVE
CTE名称 AS (子查询)
SELECT|DELETE|UPDATE 语句;
递归公用表表达式由 2 部分组成,分别是种子查询和递归查询,中间通过关键字 UNION [ALL]进行连接。这里的 种子查询,意思就是获得递归的初始值 。这个查询只会运行一次,以创建初始数据集,之后递归查询会一直执行,直到没有任何新的查询数据产生,递归返回。
案例: 针对于我们常用的employees表,包含employee_id,last_name和manager_id三个字段。如果a是b的管理者,那么,我们可以把b叫做a的下属,如果同时b又是c的管理者,那么c就是b的下属,是a的下下属。
下面我们尝试用查询语句列出所有具有下下属身份的人员信息。如果用我们之前学过的知识来解决,会比较复杂,至少要进行 4 次查询才能搞定:
如果第四步的结果集为空,则计算结束,第三步的结果集就是我们需要的下下属集了,否则就必须继续进行第四步,一直到结果集为空为止。比如上面的这个数据表,就需要到第五步,才能得到空结果集。而且,最后还要进行第六步:把第三步和第四步的结果集合并,这样才能最终获得我们需要的结果集。
如果用递归公用表表达式,就非常简单了。我介绍下具体的思路。
这里看似也是 3 步,实际上是一个查询的 3 个部分,只需要执行一次就可以了。而且也不需要用临时表保存中间结果,比刚刚的方法简单多了。
WITH RECURSIVE cte
AS
(
SELECT employee_id,last_name,manager_id, 1 AS n FROM employees WHERE employee_id = 100 -- 种子查询,找到第一代领导
UNION ALL
SELECT a.employee_id,a.last_name,a.manager_id,n+ 1 FROM employees AS a JOIN cte
ON (a.manager_id = cte.employee_id) -- 递归查询,找出以递归公用表表达式的人为领导的人)
SELECT employee_id,last_name FROM cte WHERE n >= 3 ;
总之,递归公用表表达式对于查询一个有共同的根节点的树形结构数据,非常有用。它可以不受层级的限制,轻松查出所有节点的数据。如果用其他的查询方式,就比较复杂了。
公用表表达式的作用是可以替代子查询,而且可以被多次引用。递归公用表表达式对查询有一个共同根节点的树形结构数据非常高效,可以轻松搞定其他查询方式难以处理的查询。
总之,开始设置数据库的时候,我们就需要重视数据表的设计。为了建立冗余较小、结构合理的数据库,设计数据库时必须遵循一定的规则。
在关系型数据库中,关于数据表设计的基本原则、规则就称为范式。可以理解为,一张数据表的设计结 构需要满足的某种设计标准的 级别 。要想设计一个结构合理的关系型数据库,必须满足一定的范式。
目前关系型数据库有六种常见范式,按照范式级别,从低到高分别是:第一范式(1NF)、第二范式 (2NF)、第三范式(3NF)、巴斯-科德范式(BCNF)、第四范式(4NF)和第五范式(5NF,又称完美范式)。数据库的范式设计越高阶,夯余度就越低,同时高阶的范式一定符合低阶范式的要求,满足最低要求的范式是第一范式(1NF)。在第一范式的基础上进一步满足更多规范的要求称为第二范式(2NF),其余范式以此类推。
一般来说,在关系型数据库设计中,最高也就遵循到BCNF, 普遍还是3NF。但也不绝对,有时候为了提高某些查询性能,我们还需要破坏范式规则,也就是反规范化
范式的定义会使用到主键和候选键,数据库中的键(Key)由一个或者多个属性组成。数据表中常用的几种键和属性的定义:
通常,我们也将候选键称之为“码”,把主键也称为“主码”。因为键可能是由多个属性组成的,针对单个属性,我们还可以用主属性和非主属性来进行区分。
举例
球员表(player)
:球员编号 | 姓名 | 身份证号 | 年龄 | 球队编号
球队表(team)
:球队编号 | 主教练 | 球队所在地
第一范式主要确保数据库中每个字段的值必须具有原子性,也就是说数据表中每个字段的值为不可再次拆分的最小数据单元。我们在设计某个字段的时候,对于字段X来说,不能把字段X拆分成字段X-1和字段X-2。事实上,任何的DBMS都会满足第一范式的要求,不会将字段进行拆分。
第二范式要求,在满足第一范式的基础上,还要满足数据库里的每一条数据记录,都是可唯一标识的。而且所有非主键字段,都必须完全依赖主键,不能只依赖主键的一部分。如果知道主键的所有属性的值,就可以检索到任何元组(行)的任何属性的任何值。(要求中的主键,其实可以扩展替换为候选键)
第二范式(2NF)要求实体的属性完全依赖主关键字。如果存在不完全依赖,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与元实体之间是一对多的关系
第三范式是在第二范式的基础上,确保数据表中的每一个非主键字段都和主键字段直接相关,也就是说,要求数据表中的所有非主键字段不能依赖于其他非主键字段。(即,不能存在非主属性A依赖于非主属性B,非主属性B依赖于主键C的情况,即存在“A->B->C"的决定关系)通俗地讲,该规则的意思是所有非主键属性之间不能由依赖关系,必须相互独立。
这里的主键可以扩展为候选键。
关于数据表的设计,有三个范式要遵循。
原子性
。数据库的每一列都是不可分割的原子数据项,不可再分的最小数据单元,而不能是集合、数组、记录等非原子数据项。完全依赖
。尤其在复合主键的情况下,非主键部分不应该依赖于部分主键。直接相关
,而不是间接相关范式的优点:数据的标准化有助于消除数据库中的数据冗余
,第三范式(3NF)通常被认为在性能、扩展性和数据完整性方面达到了最好的平衡。
范式的缺点:范式的使用,可能降低查询的效率
。因为范式等级越高,设计出来的数据表就越多、越精细,数据的冗余度就越低,进行数据查询的时候就可能需要关联多张表
,这不但代价昂贵,也可能使一些索引策略无效
。
范式只是提出了设计的标准,实际上设计数据表时,未必一定要符合这些标准。开发中,我们会出现为了性能和读取效率违反范式化的原则,通过增加少量的冗余或重复的数据来提高数据库的读性能,减少关联查询,join 表的次数,实现空间换取时间的目的。因此在实际的设计过程中要理论结合实际,灵活运用。
有的时候不能简单按照规范要求设计数据表,因为有的数据看似冗余,其实对业务来说十分重要。这个时候,我们就要遵循业务优先的原则,首先满足业务需求,再尽量减少冗余。
如果数据库中的数据量比较大,系统的UV和PV访问频次比较高,则完全按照MysQL的三大范式设计数据表,读数据时会产生大量的关联查询,在一定程度上会影响数据库的读性能。如果我们想对查询效率进行优化,反范式优化也是一种优化思路。此时,可以通过在数据表中增加冗余字段来提高数据库的读性能。
规范化 vs 性能
当冗余信息有价值或者能 大幅度提高查询效率 的时候,我们才会采取反范式的优化
在现实生活中,我们经常需要一些冗余信息,比如订单中的收货人信息,包括姓名、电话和地址等。每 次发生的 订单收货信息
都属于 历史快照
,需要进行保存,但用户可以随时修改自己的信息,这时保存这 些冗余信息是非常有必要的。反范式优化也常用在 数据仓库
的设计中,因为数据仓库通常存储历史数据
,对增删改的实时性要求不 强,对历史数据的分析需求强。这时适当允许数据的冗余度,更方便进行数据分析。简单总结下数据仓库和数据库在使用上的区别:
捕捉数据
,而数据仓库设计的目的在于分析数据
增删改实时性
要求强,需要存储在线的用户数据,而数据仓库存储的一般是历史数据
尽量避免冗余
,但为了提高查询效率也允许一定的冗余度
,而数据仓库在设计上更偏向采用反范式设计人们在3NF的基础上进行了改进,提出了巴斯范式(BCNF),页脚巴斯 - 科德范式(Boyce - Codd Normal Form)。BCNF被认为没有新的设计规范加入,只是对第三范式中设计规范要求更强,使得数据库冗余度更小。所以,称为是修正的第三范式,或扩充的第三范式,BCNF不被称为第四范式。
若一个关系达到了第三范式,并且它只有一个候选键,或者它的每个候选键都是单属性,则该关系自然达到BC范式。一般来说,一个数据库设符合3NF或者BCNF就可以了。
多值依赖的概念:
多值依赖
即属性之间的一对多关系,记为K—>—>A。函数依赖
事实上是单值依赖,所以不能表达属性值之间的一对多关系。平凡的多值依赖
:全集U=K+A,一个K可以对应于多个A,即K—>—>A。此时整个表就是一组一对多关系。非平凡的多值依赖
:全集U=K+A+B,一个K可以对应于多个A,也可以对应于多个B,A与B相互独立,即K—>—>A,K—>—>B。整个表有多组一对多关系,且有:"一"部分是相同的属性集合,“多”部分是相互独立的属性集合。第四范式即在满足巴斯 - 科德范式(BCNF)的基础上,消除非平凡且非函数依赖的多值依赖(即把同一表的多对多关系删除)
除了第四范式外,我们还有更高级的第五范式(又称完美范式)和域键范式(DKNF)。在满足第四范式(4NF)的基础上,消除不是由候选键所蕴含的连接依赖。如果关系模式R中的每一个连 接依赖均由R的候选键所隐含,则称此关系模式符合第五范式。
函数依赖是多值依赖的一种特殊的情况,而多值依赖实际上是连接依赖的一种特殊情况。但连接依赖不 像函数依赖和多值依赖可以由 语义直接导出 ,而是在 关系连接运算 时才反映出来。存在连接依赖的关系 模式仍可能遇到数据冗余及插入、修改、删除异常等问题。第五范式处理的是 无损连接问题 ,这个范式基本 没有实际意义 ,因为无损连接很少出现,而且难以察觉。而域键范式试图定义一个 终极范式 ,该范式考虑所有的依赖和约束类型,但是实用价值也是最小的,只存在理论研究中。
在实际工作场景中,这种由于数据表结构设计不合理,而导致的数据重复的现象并不少见。往往是系统虽然能够运行,承载能力却很差,稍微有点流量,就会出现内存不足、CPU使用率飙升的情况,甚至会导致整个项目失败
这个表中的字段很多,表里的数据量也很惊人。大量重复导致表变得庞大,效率极低。如何改?
第一范式要求:所有的字段都是基本数据类型,不可进行拆分。这里需要确认,所有的列中,每个字段只包含一种数据。这张表里,我们把“property"这一字段,拆分成”specification (规格)" 和 “unit (单位)”
第二范式要求,在满足第一范式的基础上,还要满足数据表里的每一条数据记录,都是可唯一标识的。而且所有字段,都必须完全依赖主键,不能只依赖主键的一部分
第1步,就是要确定这个表的主键。通过观察发现,字段“listnumber(单号)“+“barcode(条码)“可以唯一标识每一条记录,可以作为主键。第2步,确定好了主键以后,判断哪些字段完全依赖主键,哪些字段只依赖于主键的一部分。把只依赖于主键一部分的字段拆出去,形成新的数据表。此外,字段"supplierid(供应商编号)”“suppliername(供应商名称)”“stock(仓库)“只依赖于"listnumber(单号)”,不完全依赖于主键,所以,我们可以把"supplierid”“suppliername”“stock"这3个字段拆出去,再加上它们依赖的字段"listnumber(单号)”,就形成了一个新的表"进货单头表”。剩下的字段,会组成新的表,我们叫它"进货单明细表"
进货单头表
进货单明细表
商品信息表
我们的进货单头表,还有数据冗余的可能。因为"suppliername"依赖"supplierid",那么就可以按照第三范式的原则进行拆分了。我们就进一步拆分进货单头表,把它拆解成供货商表和进货单头表
因此,最后我们可以把进货单表拆分成下面的4个表
供货商表:
进货单头表:
进货单明细表:
商品信息表:
在开发基于数据库的信息系统的设计阶段,通常使用ER模型来描述信息需求和信息特性,帮助我们理清业务逻辑,从而设计出优秀的数据库。
ER 模型中有三个要素,分别是实体、属性和关系。
实体 ,可以看做是数据对象,往往对应于现实生活中的真实存在的个体。在 ER 模型中,用 矩形 来表 示。实体分为两类,分别是 强实体 和 弱实体 。强实体是指不依赖于其他实体的实体;弱实体是指对另 一个实体有很强的依赖关系的实体。
属性 ,则是指实体的特性。比如超市的地址、联系电话、员工数等。在 ER 模型中用 椭圆形 来表示。
关系 ,则是指实体之间的联系。比如超市把商品卖给顾客,就是一种超市与顾客之间的联系。在 ER 模 型中用 菱形 来表示。
注意:实体和属性不容易区分。这里提供一个原则:我们要从系统整体的角度出发去看,可以独立存在的是实体,不可再分的是属性。也就是说,属性不能包含其他属性。
在 ER 模型的 3 个要素中,关系又可以分为 3 种类型,分别是 一对一、一对多、多对多。
一对一 :指实体之间的关系是一一对应的,比如个人与身份证信息之间的关系就是一对一的关系。一个人只能有一个身份证信息,一个身份证信息也只属于一个人。
一对多 :指一边的实体通过关系,可以对应多个另外一边的实体。相反,另外一边的实体通过这个关系,则只能对应唯一的一边的实体。比如说,我们新建一个班级表,而每个班级都有多个学生,每个学 生则对应一个班级,班级对学生就是一对多的关系。
多对多 :指关系两边的实体都可以通过关系对应多个对方的实体。比如在进货模块中,供货商与超市之 间的关系就是多对多的关系,一个供货商可以给多个超市供货,一个超市也可以从多个供货商那里采购 商品。再比如一个选课表,有许多科目,每个科目有很多学生选,而每个学生又可以选择多个科目,这 就是多对多的关系
通过绘制 ER 模型,我们已经理清了业务逻辑,转换的原则:
综合以上内容,总结出数据表设计的一般原则:“三少一多”
RDBMS的核心在于对实体和联系的定义,也就是E-R图(Entity Relationship Diagram),数据表越少,证明实体和联系设计得越简洁,既方便理解又方便操作
字段个数越多,数据冗余的可能性越大。设置字段个数少的前提是各个字段相互独立,而不是某个字段的取值可以由其他字段计算出来。当然字段个数少是相对的,我们通常会在数据冗余和检索效率中进行平衡。
设置主键是为了确定唯一性,当一个字段无法确定唯一性的时候,就需要采用联合主键的方式(也就是用多个字段来定义一个主键)。联合主键中的字段越多,占用的索引空间越大,不仅会加大理解难度,还会增加运行时间和索引空间,因此联合主键的字段个数越少越好。
数据库的设计实际上就是定义各种表,以及各种字段之间的关系。这些关系越多,证明这些实体之间的冗余度越低,利用度越高。这样做的好处在于不仅保证了数据表之间的独立性,还能提升相互之间的关联使用率。
1、关于库
2、关于表、列
CREATE TABLE user_info (
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`user_id` bigint(11) NOT NULL COMMENT '用户id',
`username` varchar(45) NOT NULL COMMENT '真实姓名',
`email` varchar(30) NOT NULL COMMENT '用户邮箱',
`nickname` varchar(45) NOT NULL COMMENT '昵称',
`birthday` date NOT NULL COMMENT '生日',
`sex` tinyint(4) DEFAULT '0' COMMENT '性别',
`short_introduce` varchar(150) DEFAULT NULL COMMENT '一句话介绍自己,最多50个汉字',
`user_resume` varchar(300) NOT NULL COMMENT '用户提交的简历存放地址',
`user_register_ip` int NOT NULL COMMENT '用户注册时的源ip',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE
CURRENT_TIMESTAMP COMMENT '修改时间',
`user_review_status` tinyint NOT NULL COMMENT '用户资料审核状态,1为通过,2为审核中,3为未
通过,4为还未提交审核',
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_user_id` (`user_id`),
KEY `idx_username`(`username`),
KEY `idx_create_time_status`(`create_time`,`user_review_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='网站用户基本信息
3、关于索引
4、SQL编写
PowerDesigner是一款开发人员常用的数据库建模工具,用户利用该软件可以方便地制作 数据流程图 、 概念数据模型 、 物理数据模型 ,它几乎包括了数据库模型设计的全过程,是Sybase公司为企业建模和设 计提供的一套完整的集成化企业级建模解决方案。
参考:
https://www.bilibili.com/video/BV1iq4y1u7vj
https://new-wangz.github.io/categories/
13. 【建议】表中所有字段尽量都是 NOT NULL 属性,业务可以根据需要定义 DEFAULT值 。 因为使用 NULL值会存在每一行都会占用额外存储空间、数据迁移容易出错、聚合函数计算结果偏差等问 题。
14. 【建议】所有存储相同数据的 列名和列类型必须一致 (一般作为关联列,如果查询时关联列类型 不一致会自动进行数据类型隐式转换,会造成列上的索引失效,导致查询效率降低)。
15. 【建议】中间表(或临时表)用于保留中间结果集,名称以 tmp_ 开头。 备份表用于备份或抓取源表快照,名称以 bak_ 开头。中间表和备份表定期清理。 1
16. 【示范】一个较为规范的建表语句
CREATE TABLE user_info (
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`user_id` bigint(11) NOT NULL COMMENT '用户id',
`username` varchar(45) NOT NULL COMMENT '真实姓名',
`email` varchar(30) NOT NULL COMMENT '用户邮箱',
`nickname` varchar(45) NOT NULL COMMENT '昵称',
`birthday` date NOT NULL COMMENT '生日',
`sex` tinyint(4) DEFAULT '0' COMMENT '性别',
`short_introduce` varchar(150) DEFAULT NULL COMMENT '一句话介绍自己,最多50个汉字',
`user_resume` varchar(300) NOT NULL COMMENT '用户提交的简历存放地址',
`user_register_ip` int NOT NULL COMMENT '用户注册时的源ip',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE
CURRENT_TIMESTAMP COMMENT '修改时间',
`user_review_status` tinyint NOT NULL COMMENT '用户资料审核状态,1为通过,2为审核中,3为未
通过,4为还未提交审核',
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_user_id` (`user_id`),
KEY `idx_username`(`username`),
KEY `idx_create_time_status`(`create_time`,`user_review_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='网站用户基本信息
3、关于索引
4、SQL编写
PowerDesigner是一款开发人员常用的数据库建模工具,用户利用该软件可以方便地制作 数据流程图 、 概念数据模型 、 物理数据模型 ,它几乎包括了数据库模型设计的全过程,是Sybase公司为企业建模和设 计提供的一套完整的集成化企业级建模解决方案。
参考:
https://www.bilibili.com/video/BV1iq4y1u7vj
https://new-wangz.github.io/categories/
上一篇:python的安装