数据库是往全栈倒退不得不跨过的一道坎,大家不可避免会学到用到相干常识,最近查资料的时候发现网上很多内容要么就特地深,要么不成体系,对一些心愿浅尝辄止仅仅是应用一下的人不太敌对。最近刚好有机会学到 MySQL,集中一些工夫学习了一下 MySQL 同时做了一些笔记,每个概念根本都有代码示例,每一行都是在下手打,读者能够间接复制了代码到命令行中运行,心愿对大家有所帮忙~
本文介绍的常识都不是特地深,指标用户是对 MySQL 零根底或弱根底的小伙伴们,能够帮忙对 MySQL 建设一些概念,至多碰到相干问题晓得怎么去百度,也不会碰到后端给的数据库文件看不懂。
对于 Docker 和 CentOS 相干常识不理解的小伙伴能够看看 <手摸手带你 Docker 从入门到实际> 和 <半小时搞会 CentOS 入门必备基础知识> 两篇文章,反正 Docker 和 CentOS 也早晚会用到
所有代码保留在 Github 上,能够自行 Clone 下来浏览和执行。
CentOS 版本: 7.6
MySQL 版本:8.0.21
下面这个脑图能够加文末公众号回复 「mysql脑图」 获取 xmind 源文件。
1. 什么是数据库
数据库是一个以某种有组织的形式存储的数据汇合,能够将其设想为一个文件柜。
1.1 根本信息
MySQL 数据库隶属于MySQL AB公司,总部位于瑞典,后被 oracle 收买。是目前最风行的关系型数据库。
长处:
- 成本低:凋谢源代码,个别能够收费试用;
- 性能高:执行很快;
- 简略:很容易装置和应用。
1.2 MySQL 装置
MySQL 倡议应用 Docker 装置,几行命令就装置好了,参见 <手摸手带你 Docker 从入门到实际> - 装置 MySQL
我这里的命令是:
# 创立mysql容器docker run -d -p 3307:3306 -e MYSQL_ROOT_PASSWORD=888888 \-v /Users/sherlocked93/Personal/configs/mysql.d:/etc/mysql/conf.d \-v /Users/sherlocked93/Personal/configs/data:/var/lib/mysql \--name localhost-mysql mysql# 创立好之后进入 mysql 容器:docker exec -it localhost-mysql bash# 登录 mysqlmysql -u root -p888888
如果你机子上装置了 navicate,能够参考一下上面这个配置
抉择 New Connection 之后填一下配置:
就能够看到你数据库外面的内容了。
就能够啦,成果如下图:
不必 Docker 能够去官网 MySQL Community Server 下载对应版本的 MySQL 安装包,Community Server 社区版本是不要钱的,下载安装结束也能够,根本始终下一步就行了。
废话少说,上面间接开始常识灌体!
2. MySQL 简略应用
2.1 数据库相干术语
数据库相干的概念和术语:
- 数据库(database) 保留有组织的数据的容器;
- 表(table) 某种特定类型数据的结构化清单;
- 模式(schema) 对于数据库和表的布局及个性的信息;
- 列(column) 表中的一个字段,所有表都是由一个或多个列组成的;
- 数据类型(datatype) 所答应的数据的类型;
- 行(row) 表中的一个记录;
- 主键(primary key) 一列(或一组列),其值可能惟一辨别表中每个行;
- 外键(foreign key) 表中的一列,它蕴含另一个表的主键值,定义了两个表之间的关系。
- 子句(clause) SQL 语句由子句形成,有些子句是必须的,而有的是可选的。比方 select 语句的 from 子句。
2.2 主键
主键的概念非常重要,它惟一标识表中每行的单个或者多个列称为主键。主键用来示意一个特定的行。
尽管并不总是都须要主键,但应尽量保障每个表都定义有主键,以便于当前的数据操纵和治理。没有主键,无奈将不同的行辨别开来,更新或删除表中特定行很艰难。
表中的任何列都能够作为主键,只有它满足以下条件:
- 任意两行都不具备雷同的主键值;
- 每个行都必须具备一个主键值(主键列不容许 NULL 值)。
在应用多列作为主键时,上述条件必须利用到形成主键的所有列,所有列值的组合必须是惟一的(单个列的值能够不惟一)。
几个广泛认可的最好习惯为:
- 不更新主键列中的值;
- 不重用主键列的值;
- 不在主键列中应用可能会更改的值。
2.3 语法标准
语法标准:
- 输出
help
或\h
获取帮忙; - 不辨别大小写,但倡议关键字大写,表名、列名小写;
- 每条命令最好应用分号
;
或\g
结尾,仅按 Enter 不执行命令; - 每条命令依据须要,能够进行缩进、换行;
- 用
#
结尾进行多行正文,用/* ... */
进行多行正文; - 输出
quit
或exit
推出 MySQL 命令行;
语法特点:
- 大小写不敏感;
- 能够写在一行或多行,能够分成多行以便于浏览和调试;
- 关键字不能被缩写也不能分行;
- 各子句个别分行写;
- 推介应用缩进进步语句的可读性;
常见的简略命令:
mysql -u root -p # –h 主机名 –u 用户名 -P 端口号 –p明码,留神-p跟明码之间不能加空格其余能够加能够不加select version(); # 查看 mysql 服务版本show databases; # 查看所有数据库,留神最初有 screate database [库名]; # 创立库use [库名]; # 关上指定的库show tables; # 查看以后库的所有表 show tables from [库名]; # 查看其余库的所有表 desc [表名]; # 查看表构造 create table [表名] ( # 创立表 列名 列类型, 列名 列类型,);drop database [库名]; # 删除库 drop table [表名]; # 删除表 exit; # 退出
2.4 创立表并填充数据
首先咱们整点数据,不便前面的代码演示。
mysql -u root -p888888 # 输出用户名明码,进入mysql命令行
而后在 Github 下载文件 create.sql 并运行(也能够间接复制文件里的内容到 MySQL 命令行中执行)。
如果你用的是 navicate,在上一章创立到 localhost-mysql 的连贯后,运行一下即可:
同理运行另一个文件 populate.sql,填充每个表中的数据。
运行之后在命令行中 show tables
就能够看到库中的表了,如下图:
2.5 关系表
简略介绍一下刚刚创立好的表。
为了数据分类解决,顾客 customers、供应商 vendors、订单 orders、订单信息 orderitems、产品记录 productnotes、产品 products 表别离存储不同的信息。
比方供应商信息表 vendors 总每个供应商都有一个惟一标识,也就是主键 vend_id,而 products 产品表的每个产品也有一个主键 prod_id,还有一个字段 vend_id 供应商 ID 和供应商表中的 vend_id 一一对应,这就是外键。
如果你心愿通过产品 ID 查到对应的供应商信息,那么就通过外键来找到另一个表中的信息。外键防止了每个产品都反复保留供应商的详细信息,只有保留供应商的 ID 就行,当供应商信息变了,比方邮箱、地址变更,也不必挨个改每一行的数据,只需更改供应商表中对应供应商信息。
这样做的益处:
- 供应商信息不反复,从而不浪费时间和空间;
- 如果供应商信息变动,能够只更新 vendors 表中的单个记录,相干表中的数据不必改变;
- 因为数据无反复,显然数据是统一的,这使得解决数据更简略。
可伸缩性(scale),可能适应一直减少的工作量而不失败。设计良好的数据库或应用程序称之为可伸缩性好(scale well)。
2.6 数据类型
MySQL 数据类型定义了列中能够存储什么数据以及该数据怎么存储的规定。
数值型
整型:Tinyint
、Smallint
、Mediumint
、Int
(Integer
)、Bigint
,能够为无符号和有符号,默认有符号。
- 如果不设置有无符号默认是有符号,如果想设置无符号,能够增加
unsigned
关键字; - 如果插入的数值超出了整型的范畴,会报 out of range 异样,并且插入临界值;
- 如果不设置长度,会有默认的长度。
小数
- 定点数:
dec(M,D)
、decimal(M,D)
- 浮点数:
float(M, D)
、double(M, D)
M 为整数部位+小数部位,D 为小数部位,M 和 D 都能够省略。如果是 decimal
,则 M 默认为 10,D 默认为 0。
字符型
- 较短的文本:
char(n)
、varchar(n)
中的 n 代表字符的个数,不代表字节个数。 - 较长的文本:
text
(长文本数据)、blob
(较长的二进制数据)。 binary
、varbinary
用于保留较短的二进制。enum
用于保留枚举。set
用于保留汇合。
日期和工夫类型
date
格局 YYYY-MM-DD,保留日期;time
格局 HH:MM:SS,保留工夫;year
格局 YYYY,保留年;datetime
格局 YYYY-MM-DD HH:MM:SS,保留日期+工夫,范畴1000-9999
,不受时区印象;timestamp
工夫戳,格局保留日期+工夫,范畴1970-2038
,受时区影响;
3. 检索数据 select
用来查问的 select
语句大略是最罕用的了,用来从一个或多个表中检索信息,一条 select
语句必须至多给出两条信息:想抉择什么、从什么中央抉择。
# 根本语法select [查问列表] from [表名];# 查问单个/多个/所有字段select cust_name from customers;select cust_name,cust_city,cust_address from customers;select `select` from customers; # 如果某字段是关键字,能够用 ` 符号包起来select * from customers; # * 示意所有 # 查问常量值/表达式/函数select 100;select 'zhangsan';select 100%98;select version();
3.1 去重 distinct
查问进去的后果可能有多个反复值,能够应用 distinct
关键字来去重
select order_num from orderitems; # 有一些反复值select distinct order_num from orderitems; # 将反复值去重
3.2 限度后果 limit
select 语句返回所有匹配的行,它们可能是指定表中的每个行。为了返回第一行或前几行,可应用 limit
子句。
limit m
示意返回找出的前 m 行,limit m,n
示意返回第 m 行开始的 n 行,也能够应用 limit m offset n
含意和后面一样。
留神,检索进去的第一行的索引为 0 行。
3.3 齐全限定表名与列名
在某些状况下,语句可能应用齐全限定的列明与表名:
select orderitems.order_num from mysql_demo1.orderitems;# 下面这句等价于select order_num from orderitems;
4. 排序检索数据 order by
上一章从 orderitems
这个表中检索的数据是没有排序的,个别状况下返回的程序是在底层表中呈现的程序。能够通过 order by
子句来对检索后的数据进行排序。
能够用 asc
、desc
关键字来指定排序方向。order by asc
升序、order by desc
降序,不写默认是升序。
order by
子句中能够反对单个字段、多个字段、表达式、函数、别名,个别放在句子的最初面,除了 limit
之外。
select * from orderitems order by item_price; # 按item_price升序排列# 先按 quantity 升序排列,quantity 的值一样时按 item_price 的值升序排列select * from orderitems order by quantity,item_price; # 先按 quantity 降序排列,quantity 的值一样时按 item_price 的值升序排列select * from orderitems order by quantity desc,item_price; # 找到最贵订单select * from orderitems order by item_price desc limit 1;
5. 过滤数据 where
在 from
子句后应用 where
关键字能够减少筛选条件,过滤数据。
# 根本语法select [查问列表] from [表名] where [筛选条件] order by [排序条件];
按条件表达式来筛选 >
、=
、<
、>=
、<=
、!=
、<>
、<=>
# 找出产品价格为 2.5 的产品名字select prod_name, prod_price from products where prod_price=2.5;# 找出产品价格小于等于 10 的产品名字,并按产品价格降序排列select prod_name, prod_price from products where prod_price <= 10 order by prod_price desc;# 找到供应商 id 不为 1003 的产品,!= 和 <> 含意一样,都是不等于select vend_id, prod_name from products where vend_id <> 1003;
5.1 范畴查看 between and
应用 between ... and ...
指定所需范畴的开始值和完结值,能够达到范畴查问的成果。
留神 between and
左右数字是按小大的程序的,调过来不行。
# 查问产品价格在 3 到 10 内的产品select prod_name, prod_price from products where prod_price between 3 and 10;# 独自应用 and 也能够打到这个成果select prod_name, prod_price from products where prod_price <= 10 and prod_price >= 3;
5.2 空值查看 is (not) null
创立表时,能够指定某些列能够不蕴含值,即能够为 null
,null
示意无值 no value,这与字段蕴含 0、空字符串或仅仅蕴含空格不同。
应用 is null
或 is not null
能够用来判断一个值是否为 null
。
阐明:
- 等于
=
和不等于<>
、!=
不能用来判断null
,只能用is null
和is not null
来判断null
<=>
平安等于号能够用来判断null
# 找出顾客中邮箱不是 null 的顾客信息select * from customers where cust_email is not null;# 应用平安等于号来判断 null 也是能够的select * from customers where cust_email <=> null;
5.3 逻辑与操作符 and
操作符(operator) 用来联结或扭转 where 子句中的子句的关键字,也称为逻辑操作符(logical operator)。
前文提到了 and
操作符,通过这个操作符能够减少限定条件:
# 找出供应商为 1003 提供的价格小于等于 10 的产品select * from products where vend_id = 1003 and prod_price <= 10;
5.4 逻辑或操作符 or
or
操作符和 and
操作符相同,这是逻辑或操作符,返回匹配任一条件的行:
# 找出id为 1003 或 1001 的供应商select * from products where vend_id = 1003 or vend_id = 1001;
在 and
和 or
同时呈现时,会优先解决 and
,比方这句:
select * from products where vend_id = 1001 or vend_id = 1003 and prod_price >= 10;
这句会先解决 and
,示意 vend_id
为 1003 且 prod_price
大于等于 10 的产品,或者 vend_id
为 1001 的产品。
遇到这种状况,能够通过减少圆括号:
select * from products where (vend_id = 1001 or vend_id = 1003) and prod_price >= 10;
这样检索的后果就是 vend_id
为 1001 或 1003 的产品里,所有 prod_price
大于等于 10 的产品列表了。
任何时候应用具备 and
和 or
操作符的 where
子句,都应该应用圆括号明确地分组操作符。不要过分依赖默认计算秩序,即便它的确是你想要的货色也是如此,而且应用圆括号能打消歧义,减少可读性。
5.5 范畴操作符 in (set)
应用 in
操作符能够指定条件范畴,范畴中的每个条件都能够进行匹配。in
要匹配的值放在其后的圆括号中:
# 找出id为 1003 或 1001 的供应商select * from products where vend_id in (1001, 1003);
in
操作符能够用 or
来取代,在以下状况倡议应用 in
:
- 在选项比拟多时,
in
更分明且更直观; - 应用
in
时,计算的秩序更容易治理(因为应用的操作符更少); in
个别比or
操作符清单执行更快;in
的最大长处是能够蕴含其余select
语句,使得可能更动静地建设where
子句。
5.6 逻辑否操作符 not
not
否操作符能够和后面的 in
和 between and
一起应用,示意对范畴取反:
# 找出id不为 1001 1003 的产品select * from products where vend_id not in (1001, 1003);# 抉择产品价格不在 5 到 15 之间的产品select * from products where prod_price not between 5 and 15;
5.7 like 操作符
比方想找出名称中蕴含 anvil
的所有产品,能够通过 like
操作符来进行搜寻。like
示意前面跟的搜寻模式应用通配符匹配而不是间接相等匹配。
操作符 %
最常应用的通配符是 %
操作符,%
示意任意多个字符,包含没有字符。
# 找出产品名字以 jet 结尾的产品select * from products where prod_name like 'jet%';# 找出产品名中含有 on 的产品select * from products where prod_name like '%on%';# 找出产品名以 s 结尾,以 e 完结的产品select * from products where prod_name like 's%e';
留神,%
是无奈匹配 null
的。
操作符 _
_
示意任意单个字符。
select * from products where prod_name like '_ ton anvil';
另外,转译应用 \
,比方 \_
# 找到形容中有 % 的产品select * from products where prod_desc like '%\%%';
留神:
- 不要适度应用通配符。如果其余操作符能达到雷同的目标,应该应用其余操作符。
- 在的确须要应用通配符时,除非相对有必要,否则不要把它们用在搜寻模式的开始处。把通配符置于搜寻模式的开始处,搜寻起来是最慢的。
5.8 正则表达式 regexp
对于正则表达式,能够先简略看一下 「正则表达式必知必会」 这篇博客。
应用 regexp
关键字来示意匹配其后的正则表达式:
# 找到产品名以 1000 或 anvil 结尾的产品select * from products where prod_name regexp '1000|anvil$';
正则表达式中转译应用 \\
,比方心愿查找 .
这个字符而不是正则中的 .
通配符,应用 \\.
,为了转移 \
这个字符,应用 \\\
# 找到产品名以 . 字符结尾的产品select * from products where prod_name regexp '^\\.';
6. 计算字段
有时候咱们须要间接从数据库中检索出转换、计算或格式化过的数据,而不是检索出数据,而后再在客户机应用程序或报告程序中从新格式化,这时咱们就须要计算字段了。
6.1 别名 as
查问进去的虚构表格能够起一个别名,不便了解,可读性好,另外如果查问的字段有重名的状况,能够应用别名 as
来辨别开来。
# 应用 as 关键字select cust_name as name from customers;# as 关键字也能够间接省略select cust_name name from customers;# 能够给不同字段别离起别名select cust_name name, cust_city location from customers;
6.2 拼接 concat
想把多个字段连接成一个字段,能够应用到拼接字段函数 concat
:
# 将供应商的名字和地点拼接好后返回,并命名为 vendselect concat(vend_name, '(', vend_country, ')') vend from vendors;
留神两头如果有任何一个数据为 null
,拼接的后果也是 null
。
所以对某些可能为 null
的字段要应用 ifnull
函数判断一下,第一个参数为要判断的字段,第二个参数是如果是 null
心愿返回的后果:
# 将顾客信息拼接起来select concat(cust_name, '(', ifnull(cust_email, '-'), ')') customerInfo from customers;
如果表中的数据前后有空格,能够应用 rtrim()
函数去除左边空格,ltrim()
去除右边空格,或者 trim()
去除前后空格:
# 将顾客信息处理后拼接起来select concat(rtrim(vend_name), '(', trim(vend_country), ')') vend from vendors;
6.3 算术计算 +-*/
根本的算术运算符在 select
语句中也是反对的:
# 计算订单每种总额,并依照总金额降序排列select prod_id as id, quantity, quantity*item_price as totalPrice from orderitems order by totalPrice desc;
根本运算符加减乘除都是反对的 +
、 -
、 *
、 /
。
7. 数据处理函数
后面介绍的去除数据首位空格的 trim()
函数就是数据处理函数,除此之外还有多种其余类型的数据处理函数:
- 用于解决文本串的文本函数,如删除或填充值,转换值为大写或小写。
- 用于在数值数据上进行算术操作的数值函数,如返回绝对值,进行代数运算。
- 用于解决日期和工夫值并从这些值中提取特定成分的日期和工夫函数,例如,返回两个日期之差,查看日期有效性等。
- 零碎函数,如返回用户登录信息,查看版本细节。
在不理解如何应用一个函数的时候,能够应用 help
命令,比方 help substr
就能够获取 substr
的应用形式和示例。
7.1 字符函数
函数 | 阐明 |
---|---|
left() 、right() | 返回串右边、左边的字符 |
length() | 返回串长度 |
lower() 、upper() | 返回串的小写、大写 |
rtrim() 、ltrim() 、trim() | 去除左边、右边、两边的空格 |
locate() | 找出一个串的子串 |
soundex() | 返回串的 sundex 值 |
substring() | 返回子串的字符 |
subset() | 返回子串的字符(和 substring 应用形式不一样) |
instr() | 返回子串第一次呈现的索引,没有返回 0 |
replace() | 字符串替换 |
lpad() 、rpad() | 左填充、右填充 |
示例:
# upper、lower 将姓变大写,名变小写,而后拼接select concat(upper(last_name), lower(first_name)) 姓名 from employees;# 姓名中首字符大写,其余字符小写而后用_拼接,显示进去select concat(upper(substr(last_name, 1, 1)), '_', lower(substr(last_name, 2))) from employees;# substr 截取字符串,sql 中索引从 1 开始而不是0select substr('hello world', 3); # llo worldselect substr('hello world', 2, 3); # ell# instr 返回子串第一次呈现的索引,没找到返回 0select instr('abcdefg', 'cd'); # 3# trim 减去字符串首尾的空格或指定字符select trim(' hello '); # helloselect trim('aa' from 'aaabaabaaaaaa'); # abaab# lpad 用指定的字符实现左填充指定长度select lpad('he', 5, '-'); # ---he# rpad 用指定的字符实现左填充指定长度select rpad('he', 5, '-*'); # he-*-# replace 替换select replace('abcabcabc', 'bc', '--'); # a--a--a--
7.2 数学函数
函数 | 阐明 |
---|---|
round() | 四舍五入 |
ceil() | 向上取整 |
floor() | 向下取整 |
truncate() | 保留几位小数 |
mod() | 取余 |
abs() | 返回绝对值 |
rand() | 返回一个随机数 |
示例:
# round 四舍五入,第二个参数是小数点后保留的位数select round(-1.55); # -2select round(1.446, 2); # 1.45# ceil 向上取整select ceil(1.001); # 2select ceil(1.000); # 1select ceil(-1.001); # -1# floor 向下取整select floor(1.001); # 1select floor(1.000); # 1select floor(-1.001); # -2# truncate 小数点后截断几位select truncate(1.297, 1); # 1.2select truncate(1.297, 2); # 1.29# mod 取余和%同理,符号与被除数一样select mod(10, -3); # 1select mod(-10, -3); # -1select mod(-10, 3); # -1select 10%3; # 1
7.3 日期函数
函数 | 阐明 |
---|---|
now() | 返回以后零碎日期和工夫 |
curate() 、current_date | 返回以后零碎日期,不包含工夫 |
curtime() 、current_time | 返回以后工夫,不包含日期 |
year() 、month() 、day() 、hour() 、minute() 、second() | 获取工夫指定局部,年、月、日、小时、分钟、秒 |
str_todate() | 将日期格局的字符转换成指定格局的日期 |
date_format() | 将日期转换为指定格局字符 |
示例:
# now 返回以后零碎日期和工夫select now(); # 2020-07-08 12:29:56# curdate,current_date 返回以后零碎日期,不包含工夫select curdate(); # 2020-07-08# curtime,current_time 返回以后工夫,不包含日期select curtime(); # 12:29:56# year... 获取工夫指定局部,年、月、日、小时、分钟、秒select year(now()); # 2020select month(now()); # 7select monthname(now()); # Julyselect day(now()); # 8select dayname(now()); # Wednesdayselect hour(now()); # 12select minute(now()); # 29select second(now()); # 56select month(order_date) from orders;# str_to_date 将日期格局的字符转换成指定格局的日期select str_to_date('1-9-2021', '%d-%c-%Y'); # 2020-09-01select * from orders where order_date = str_to_date('2005-09-01', '%Y-%m-%d');# date_format 将日期转换成指定格局的字符select date_format(now(), '%Y年%m月%d日'); # 2020年09月01日select order_num orderId,date_format(order_date, '%Y/%m') orderMonth from orders;
日期格局符:
格局符 | 性能 |
---|---|
%Y | 四位年份 |
%y | 两位年份 |
%m | 月份(01,02,...12) |
%c | 月份(1,2,...12) |
%d | 日(01,02,...) |
%e | 日(1,2,...) |
%H | 小时(24小时制) |
%h | 小时(12小时制) |
%i | 分钟(00,01,...59) |
%s | 秒(00,01,...59) |
7.4 汇集函数
汇集函数(aggregate function) 运行在行组上,计算和返回单个值的函数。
函数 | 阐明 |
---|---|
avg() | 返回某列的平均值 |
count() | 返回某列的行数 |
max() 、min() | 返回某列最大值、最小值(疏忽 null 值) |
sum() | 返回某列之和(疏忽 null 值) |
示例:
# 计算产品价格平均值select avg(prod_price) as avgPrice from products;# 计算供应商id为 1003 提供的产品的平均价格select avg(prod_price) as avgPrice from products where vend_id = 1003;# 计算价格最大的产品价格select max(prod_price) as maxPrice from products;# 计算顾客总数select count(*) from customers;# 计算具备 email 的顾客数select count(cust_email) from cutomers;# 计算产品价格总和select sum(prod_price) from products;# 计算订单为 20005 的订单总额 select sum(item_price * quantity) totalPrice from orderitems where order_num = 20005;# 计算产品具备的不同的价格的平均数select avg(distinct prod_price) avgPrice from products where vend_id = 1003;# 同时计算产品总数、价格最小值、最大值、平均数select count(*) prodNums, min(prod_price) priceMin, max(prod_price) priceMax, avg(prod_price) priceAvg from products;
8. 分组数据
之前的汇集函数都是在 where
子句查问到的所有数据根底上进行的计算,比方查问某个供应商的产品平均价格,但如果心愿别离返回每个供应商提供的产品的平均价格,该怎么解决呢。这该用到分组了,分组容许把数据分为多个逻辑组,以便能对每个组进行汇集计算。
8.1 创立分组 group by
应用 group by
子句能够批示 MySQL 按某个数据排序并分组数据,而后对每个组而不是整个后果集进行汇集。
# 别离查问每个供应商提供的产品种类数select vend_id, count(*) num_prods from products group by vend_id;# 查问每个供应商提供的产品的平均价格select vend_id, avg(prod_price) avg_price from products group by vend_id;
留神:
group by
子句能够蕴含任意数目的列。这使得能对分组进行嵌套,为数据分组提供更粗疏的管制。- 如果在
group by
子句中嵌套了分组,数据将在最初规定的分组上进行汇总。换句话说,在建设分组时,指定的所有列都一起计算(所以不能从个别的列取回数据)。 group by
子句中列出的每个列都必须是检索列或无效的表达式(但不能是汇集函数)。如果在select
中应用表达式,则必须在group by
子句中指定雷同的表达式。不能应用别名。- 除汇集计算语句外,
select
语句中的每个列都必须在group by
子句中给出。 - 如果分组列中具备 null 值,则 null 将作为一个分组返回。如果列中有多行 null 值,它们将分为一组。
group by
子句必须呈现在where
子句之后,order by
子句之前。
8.2 过滤分组 having
除了能用 group by
分组数据外,MySQL 还容许应用 having
关键字过滤分组,指定包含哪些分组,排除哪些分组。
语法程序如下:
# 语法程序select [查问列表] from [表名] where [筛选条件] group by [分组列表] having [分组后筛选] order by [排序列表] limit [要检索行数];
where
过滤没有分组的概念,指定的是行而不是分组,针对分组的过滤应用 having
子句。事实上,目前为止所学过的所有类型的 where
子句都能够用 having
来代替。
对于 having
和 where
的差异,这里有另一种了解办法,where
在数据分组前进行过滤,having
在数据分组后进行过滤。where
排除的行不包含在分组中,这可能会扭转计算值,从而影响 having
子句中基于这些值过滤掉的分组。
能用分组前筛选 where
的,优先思考分组前筛选。
# 找到提供大于 2 个产品的供应商,并列出其提供的产品数量,这里应用 having 来过滤掉产品数不大于2的供应商select vend_id, count(*) prodCount from products group by vend_id having prodCount > 2;# 找到供应商提供的商品平均价格大于 10 的供应商,并且按平均价格降序排列select vend_id, avg(prod_price) avgPrice from products group by vend_id having avgPrice > 10 order by avgPrice desc;
9. 子查问
子查问(subquery),嵌套在其余查问中的查问。
9.1 应用子查问进行过滤
当一个查问语句中又嵌套了另一个残缺的 select
语句,则被嵌套的 select
语句称为子查问或内查问,里面的 select
语句称为主查问或外查问。
之前所有查问都是在同一张表中的,如果咱们想获取的信息扩散在两张甚至多张表呢,比方要从订单表 orders 中获取顾客 ID,而后用顾客 ID 去顾客表 custormers 找到对应顾客信息。
# 首先在 orderitems 表中找到产品 TNT2 对应的订单编号select order_num from orderitems where prod_id = 'TNT2'# 而后在 orders 表中找到订单编号对应的顾客 idselect cust_id from orders where order_num in ( select order_num from orderitems where prod_id = 'TNT2'); # 而后去 customers 表中找到顾客 id 对应的顾客名字select cust_id, cust_name from customers where cust_id in ( select cust_id from orders where order_num in ( select order_num from orderitems where prod_id = 'TNT2'));
这里实际上有三条语句,最里边的子查问返回订单号列表,此列表用于其里面的子查问的 where
子句。里面的子查问返回顾客 ID 列表,此顾客 ID 列表用于最外层查问的 where 子句。最外层查问最终返回所需的数据。
对于能嵌套的子查问的数目没有限度,不过在理论应用时因为性能的限度,不能嵌套太多的子查问。
9.2 相干子查问
相干子查问(correlated subquery) 波及内部查问的子查问。
应用子查问的另一办法是创立计算字段。如果须要显示 customers 表中每个顾客的订单总数。订单与相应的顾客 ID 存储在 orders 表中。
# 首先找到用户 ID 对应的订单数量select count(*) from orders where cust_id = 10003;# 而后将其作为一个 select 子句,将用户 id select cust_name, cust_state, ( select count(*) from orders where orders.cust_id = customers.cust_id) as order_count from customers order by order_count desc;
留神到下面这个 where orders.cust_id = customers.cust_id
,这种类型的子查问叫做相干子查问,任何时候只有列名可能有多义性,就必须应用齐全限定语法(表名和列名由一个句点分隔)。
10. 联结表
如果要查的数据扩散在多个表中,如何应用单条 select
语句查到数据呢,应用联结能够做到。
联结是一种机制,用来在一条 select
语句中关联表,因而称之为联结。应用非凡的语法,能够联结多个表返回一组输入,联结在运行时关联表中正确的行。
保护援用完整性 :在应用关系表时,仅在关系列中插入非法的数据十分重要。如果在 products 表中插入领有没有在 vendors 表中呈现的供应商 ID 的供应商生产的产品,则这些产品是不可拜访的,因为它们没有关联到某个供应商。
为避免这种状况产生,可批示 MySQL 只容许在 products 表的供应商 ID 列中呈现非法值(即呈现在 vendors 表中的供应商)。 这就是保护援用完整性,它是通过在表的定义中指定主键和外键来实现的。
10.1 创立联结
联结的创立非常简单,规定要联结的所有表以及它们如何关联即可。
# 列出产品的供应商及其价格select vend_name, prod_name, prod_price from vendors, products where vendors.vend_id = products.vend_id order by prod_price desc;
这里在 where
前面用齐全限定列名形式指定 MySQL 匹配 vender 表的 vend_id 列和 products 表的 vend_id 字段。
当援用的列可能有歧义时,必须应用齐全限定列名的形式,因为 MySQL 不晓得你指的是哪个列。
在联结两个表时,实际上做的是将一个表的每一行与另一个表的每一行配对,所以 where
子句作为过滤条件,过滤出只蕴含指定联结条件的列 where vendors.vend_id = products.vend_id
,没有 where 子句,将返回两个表的长度乘积个字段,这叫笛卡尔积(cartesian product),能够运行一下这句看看:
# 返回两个表长度乘积行select vend_name, prod_name, prod_price from vendors, products;
所有联结应该总是应用联结条件,否则会得出笛卡尔积。
10.2 联结多个表
一条 select
语句也能够联结多个表,比方须要把某个订单的产品信息、订单信息、供应商信息都列出来,要找的产品信息扩散在供应商、产品、订单信息三个表中。
# 将订单 20005 的产品信息、订单信息、供应商信息找进去select prod_name, vend_name, prod_price, quantityfrom orderitems, products, vendorswhere products.vend_id = vendors.vend_id and orderitems.prod_id = products.prod_id and order_num = 20005;
这里应用 and
来连贯多个联结条件,定义了 3 个表之间用什么作为关联。
留神:MySQL 在运行时关联多个表以解决联结可能是十分消耗资源的,不要联结不必要的表。联结的表越多,性能降落越厉害。
这里能够应用联结来实现 9.1 节的例子,之前是应用子查问来实现的,从订单表 orders 中获取顾客 ID,而后用顾客 ID 去顾客表 custormers 找到对应顾客信息。
# 应用联结来实现 9.1 的例子select customers.cust_id, cust_namefrom orders, customers, orderitemswhere orders.order_num = orderitems.order_num and customers.cust_id = orders.cust_id and prod_id = 'TNT2'; # 因为三个表中只有一个表有 prod_id,所以不须要限定表名
这里提一句,不仅仅列能够起别名,表也能够起,用法跟列的别名一样:
# 把后面这个句子起别名select c.cust_id, cust_namefrom orders o, customers c, orderitems oiwhere o.order_num = oi.order_num and c.cust_id = o.cust_id and prod_id = 'TNT2';
这样不仅仅能够缩短 SQL 语句,也容许在单条 select
语句中屡次应用雷同的表,同时起的别名不仅能够用在 select
子句,也能够应用在 where
、order by
子句以及语句的其余局部。
10.3 外部联结 inner join
目前为止所用的联结称为等值联结(equijoin),它基于两个表之间的相等测试,也称为外部联结。其实,对于这种联结能够应用略微不同的语法来明确指定联结的类型。上面的 select
语句返回与后面例子完全相同的数据:
# 列出产品的供应商及其价格select vend_name, prod_name, prod_pricefrom vendors inner join products on vendors.vend_id = products.vend_id;
这里的联结条件应用 on
子句而不是 where
,这两种语法都能够达到成果。只管应用 where
子句定义联结确实比较简单,但应用明确的联结语法可能确保不会遗记联结条件,有时候这样做也能影响性能。
10.4 自联结
比方某个产品呈现了品质问题,当初心愿找出这个产品的供应商提供的所有产品信息。依照之前介绍的子查问,咱们能够先找到对应产品的供应商,而后找到具备这个供应商 ID 的产品列表:
# 先找到产品 ID 为 TNT1 的供应商 ID,而后找到对应供应商 ID 提供的产品列表select prod_id, prod_name, vend_idfrom productswhere vend_id in ( select vend_id from products where prod_id = 'TNT1');
应用子查问的确能够实现,应用联结也能够做到,这就是自联结:
# 自联结select p1.prod_id, p1.prod_name, p1.vend_idfrom products p1, products p2where p1.vend_id = p2.vend_id and p2.prod_id = 'TNT1';
自联结查问的两个表是同一个表,因而 products 表须要别离起别名,以作为辨别,而且 select
子句中呈现的列名也须要限定表明,因为两个表都呈现了雷同的字段。
自联结通常作为内部语句用来代替从雷同表中检索数据时应用的子查问语句。尽管最终的后果是雷同的,但有时候解决联结远比解决子查问快得多。应该试一下两种办法,以确定哪一种的性能更好。
10.5 天然联结
无论何时对表进行联结,应该至多有一个列出当初不止一个表中(被联结的列)。规范的联结返回所有数据,甚至雷同的列屡次呈现。天然联结排除屡次呈现,使每个列只返回一次。
天然联结就是你只抉择那些惟一的列,这个别是通过对表应用通配符,对所有其余表的列应用明确的子集来实现的。
# 自抉择惟一的通配符只对第一个表应用。所有其余列明确列出,所以没有反复的列被检索进去。select v.*, p.prod_idfrom vendors v, products pwhere v.vend_id = p.vend_id;
10.6 内部链接 outer join
有些状况下,联结蕴含了那些在相干表中没有关联行的行,这种类型的联结称为内部联结。
比方:
- 对每个顾客下了多少订单进行计数,包含那些至今尚未下订单的顾客;
- 列出所有产品以及订购数量,包含没有人订购的产品;
- 计算均匀销售规模,包含那些至今尚未下订单的顾客。
此时联结须要蕴含哪些没有关联行的那些行。
比方检索所有用户,及其所下的订单,没有订单的也要列举进去:
# 外部联结,查找用户对应的订单select c.cust_id, o.order_numfrom customers c inner join orders o on c.cust_id = o.cust_id; # 左内部联结,将没有下单过的顾客行也列出来select c.cust_id, o.order_numfrom customers c left outer join orders o on c.cust_id = o.cust_id; # 右内部联结,列出所有订单及其顾客,这样没下单过的顾客就不会被列举进去select c.cust_id, o.order_numfrom customers c right outer join orders o on c.cust_id = o.cust_id;
在应用 outer join
语法时,必须应用 right
或 left
关键字指定包含其所有行的表。right
指出的是 outer join
左边的表,而 left
指出的是 outer join
右边的表。下面应用 left outer join
从 from
子句的右边表 custermers 中抉择所有行。为了从左边的表中抉择所有行,应该应用 right outer join
。
左内部联结可通过颠倒 from
或 where
子句中表的程序转换为右内部联结,具体用哪个看你不便。
10.7 应用带汇集函数的联结
比方想检索一个顾客下过的订单数量,即便没有也要写 0,此时应用分组和 count
汇集函数来统计数量:
# 找到每个顾客所下订单的数量,并降序排列select c.cust_id, c.cust_name, count(o.order_num) count_ordersfrom customers c left outer join orders o on c.cust_id = o.cust_idgroup by c.cust_idorder by count_orders desc;
因为即便顾客没有下单,也要在后果里,所以把顾客表放在右边,用左内部联结。
11. 组合查问
MySQL 容许执行多条select语句,并将后果作为单个查问后果集返回。这些组合查问通常称为并(union)或复合查问(compound query)。
有两种状况须要应用组合查问:
- 在单个查问中从不同的表返回相似构造的数据;
- 对单个表执行多个查问,按单个查问返回数据。
少数状况下,组合查问能够应用具备多个 where
子句条件的单条查问代替。具体场景能够尝试一下这两种形式,看看对特定的查问哪一种性能更好。
11.1 创立组合查问 union
当查问后果来自于多张表,但多张表之间没有关联,这个时候往往应用组合查问。在每条 select
语句之间放上关键字 union
即可。
# 比方须要列出商品价格小于等于 10 而且是供应商 ID 为 1005 或 1003 的产品信息select prod_id, prod_name, prod_price, vend_id from products where prod_price <= 10unionselect prod_id, prod_name, prod_price, vend_id from products where vend_id in (1005, 1003);# 实际上这句也能够通过 where 语句代替select prod_id, prod_name, prod_price from products where prod_price <= 10 or vend_id in (1005, 1003);
- 有些状况下,比方更简单的过滤条件、须要从多个表中检索数据的状况下,应用
union
可能会更简略。 union
每个查问必须蕴含雷同的列、表达式、汇集函数,不过每个列不须要以雷同的秩序列出。- 列数据类型必须兼容,类型不用完全相同,但必须是数据库管理系统能够隐式的转换。
- 组合查问的排序
order by
只能呈现在最初一条select
语句之后,不能对不同的select
语句别离排序。
11.2 蕴含或勾销反复的行 union (all)
两行 union
离开的语句可能会返回反复的行,但后面那个例子理论后果却并没有蕴含反复行,这是因为 union
关键字主动去除了反复的行,如果不心愿去重,能够应用 union all
关键字。
# 不去重反复行select prod_id, prod_name, prod_price, vend_id from products where prod_price <= 10union allselect prod_id, prod_name, prod_price, vend_id from products where vend_id in (1005, 1003);
如果须要呈现反复行,此时无奈应用 where
关键字来达成同样的成果了。
12. 数据的增删改
后面说的都是数据的查问,这一章将所以说数据的增删改。
12.1 数据插入 insert into
数据插入应用 insert
关键字,它能够插入一行、多行数据,也能够插入某些查问的后果。
# 插入一条数据到顾客表中insert into customersvalues (null, 'Zhang San', '001 street', 'ShangHai', 'SH', '666666', 'ZH', null, null);
这里插入一条数据到顾客表中,存储到每个表列中的数据须要在 values
子句中给出,依照表在创立的时候的程序顺次给出。如果某个列没值就给 null。尽管第一条数据对应 cust_id 列的属性是 not null
的,然而这个列是 auto_increment 也就是自增的,MySQL 会主动疏忽你给出的 null 并将值主动减少再填充。
但应用下面 values
子句这种形式并不平安,因为这种形式注入数据齐全靠输出数据的程序,如果表构造变动,就会导致输出数据错位。
平安的数据插入方式是这样的:
# 平安但繁琐的插入方式insert into customers(cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email)values ('Zhang San', '001 street', 'ShangHai', 'SH', '666666', 'ZH', null, null);
这里在前一个括号给出了前面括号中数据对应的列名,这样的话即便表构造或者程序发生变化,也能正确插入数据。
能够看到列 cust_id 被省略了,当满足上面条件时,列能够省略:
- 列定义为容许 null 值;
- 表定义时这个列给出了默认值,示意如果不给值则应用默认值。
如果不能省略却省略了,会报错。
insert
操作可能很耗时,特地是有很多索引须要更新时,而且它可能升高期待解决的 select 语句的性能。如果数据检索是最重要的,你能够通过在 insert
和 into
之间增加关键字 low_priority
,升高 insert
语句的优先级,这也同样实用于下文提到的 update
和 delete
语句。
12.2 插入多个行
下面介绍的 insert
语句能够一次插入一个行,如果想一次插入多个行,每次都列出列名就比拟繁琐了,能够应用上面这种形式:
# 插入多个行insert into customers(cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email)values ('Zhang San', '001 street', 'ShangHai', 'SH', '666666', 'ZH', null, null), ('Li Si', '002 street', 'BeiJing', 'BJ', '878787', 'ZH', null, '123123@163.com');
values
子句前面持续用括号将字段括起来增加新行,两头加个逗号。这能够进步数据库解决的性能,因为单条 insert
语句解决多个插入比应用多条 insert
语句快。
12.3 插入检索出的数据 insert select
insert
能够将一条 select
语句的后果插入表中,这就是 insert select
。比方你想将另一个表中查问的数据插入到这个表中:
# 从别的表中找出数据,并插入 customers 表中insert into customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email)select id, name, address, city, state, zip, country, contact, emailfrom custnew;
select
的新行也能够省略 cust_id,这样 insert
的时候也是能够主动生成新的 cust_id 的。另外能够看到 select 语句的列名跟 insert into
语句后的列名并不对应,这也没关系,因为 insert select
应用的是地位对应,select 语句返回的第一列对应 cust_id,第二列对应 cust_name,顺次对应。select
语句后也能够退出 where
子句进行过滤。
12.4 批改数据 update
update
语句用来批改表中的数据,应用 update
的时候肯定要小心,不要忘了增加 where
子句,因为一不小心就会更新表中所有行。
# 更新 id 为 10005 的用户的信息update customers set cust_email = '888@qq.com' where cust_id = 10005;
如果这里没有应用 where
子句,update
将会更新这个表中的所有行的 cust_email 字段,所以肯定要留神。
要删除某行某列的值,能够将值批改为 null。
更新多个字段的形式也很简略:
# 更新多个字段update customersset cust_email = '666@qq.com', cust_contact = 'S Zhang'where cust_id = 10005;
如果用 update
语句更新多行,并且在更新这些行中的一行或多行时出一个现谬误,则整个 update
操作被勾销 (谬误产生前更新的所有行被复原到它们原来的值)。为即便是产生谬误,也持续进行更新,能够在 update
后应用 ignore
关键字。
update
语句能够应用子查问,用 select
语句检索出的数据来更新列数据。
12.5 删除数据 delete
delete
语句能够用来从表中删除特定的行或者所有行。应用 delete
语句的时候要小心,不要忘了增加 where
子句,因为一不小心就会删除表中所有行。
# 删除顾客表中顾客 id 为 10008 的行delete from customers where cust_id = 10008;
如果将 where
子句去掉,那么就是删除这个表中的所有行,但不是删除这个表,删除表应用的是另一个语句 drop
。另外删除一个表中所有行更快的语句是 truncate table
,因为 delete
是逐行删除数据,而 truncate
是删除原来的表从新建个表。
留神,在应用 update
和 delete
之前,应该十分小心,因为 MySQL 没有撤销,应用之前倡议先应用 select
进行测试,避免 where
子句不正确导致数据失落。
13. 创立和操作表
13.1 创立表 create table
咱们能够关上之前为了整点数据执行的 create.sql 文件看看,用 VSCode/Webstorm/Navivate/文本 都能关上,这个文件除了正文的第一行就是创立表语句:
CREATE TABLE customers( cust_id int NOT NULL AUTO_INCREMENT, cust_name char(50) NOT NULL , cust_address char(50) NULL , cust_city char(50) NULL , cust_state char(5) NULL , cust_zip char(10) NULL , cust_country char(50) NULL DEFAULT 'ZH', # 指定默认值 cust_contact char(50) NULL , cust_email char(255) NULL , PRIMARY KEY (cust_id) # 指定主键) ENGINE=InnoDB;
从这里能够看到 create table
的格局。
如果要在一个表不存在时创立,应该在表名前、create table
后加上 if not exists
。这样会先检查表名是否已存在,并且在不存在时进行创立。
对于 auto_increment
,每个表只能有一个 auto_increment
,而且它必须被索引。当你应用 insert
语句插入一个新的值,后续主动增量将从这个值从新开始减少。如果一个表创立新的列须要失去最 auto_increment
的值,能够应用 last_insert_id()
来获取最初自增的值。
下面创立语句的列名后 null
示意这个列在插入和批改时容许不给出值,如果是 not null
,那么在插入或批改时就必须给值,否则会报错。默认为 null
,如果不显式的给出 not null
,则会默认为 null
。
primary key
批示主键的值,在插入时主键值必须是不反复的,主键也能够是多个字段 primary key (cust_id, cust_name)
用逗号离开。作为主键的列是不能允许 null 的。
default
关键字能够指定默认值,如果插入行没有指定默认值,那么将默认应用默认值。
最初的 engine
字段指定了不同的引擎,以下是 MySQL 反对的几个罕用的引擎:
- InnoDB 牢靠的事务处理引擎,不反对全文搜寻。
- MEMORY 性能等同于 MyISAM,但因为数据存储在内存,速度很快,适宜于长期表。
- MyISAM 默认是这个,性能高,反对全文搜寻,不反对事务。
依据不同须要能够抉择不同引擎。
13.2 批改表 alter table
批改表应用 alter table
语句,个别状况下,当表中开始存储数据后,就不应该再批改表。所以表在设计时须要大量工夫来思考,尽量在前期不对表进行大的改变。
# 给供应商表减少一列 vend_phone 字段alter table vendors add vend_phone char(20) default 12212341234; # 删除这个增加的 vend_phone 字段alter table vendors drop column vend_phone;
alter table
常常用来定义外键 foreign key
,用于限度两个表的关系,保障该字段的值必须来自于主表的关联列的值。外键具备放弃数据完整性和一致性的机制,对业务解决有着很好的校验作用。
外键用来在从表增加外键束缚,用于援用主表中某列的值,比方学生表的业余编号,员工表的部门编号,员工表的工种编号,产品表的供应商编号等等。
能够从 create.sql 文件最上面看到外键的例子,这里列举一行:
# 将订单信息表的 order_num 设置为订单表的外键alter table orderitems add constraint fk_orderitems_orders foreign key (order_num) references orders (order_num);
设置外键之后,如果外键曾经有对应数据,就不能间接删除主表的这个外键行了:
# 间接删除外键行报错,不容许删delete from orders where order_num = 20009;# 先删除 orderitems 中的关联行再删除 orders 中的外键行,就能够删了delete from orders where order_num = 20009;delete from orderitems where order_num = 20009;
所以在插入数据时,先插入主表,再插入从表。删除数据时,先删除从表,再删除主表。
留神:应用 alter table
要小心,最好在改变之前做一个残缺的备份,数据库表的更改不能撤销。如果不小心减少了不须要的列,可能无奈删除它们,如果删除了不该删除的列,可能就失落了数据。
13.3 删除表 drop table
删除一个表能够应用 drop table
关键字。
drop table customers2;
删除表没有确认,也没有撤销,执行后将永恒删除该表。
如果删除时不存在这个表会报错,能够在 drop table
关键字后加上 if exists
,这样数据库会先查看这个指标表是不是存在:
# 删除一个表,如果没加 if exists 表又不存在则会报错drop table if exists customers2;
创立新表时,指定的表名必须不存在,否则会报错,所以在创立前也能够执行这个句子。
13.4 重命名表 rename table
重命名一个表能够应用 rename table
关键字。
# 重命名一个表rename table customers to customers2;# 重命名多个表rename table customers to customers2, vendors to vendors2;
14. 视图
视图是虚构存在的表,行和列的数据来自定义视图的查问中应用的表,并且是在应用视图时动静生成的,只保留 SQL 逻辑,不保留查问后果。
14.1 创立视图 create view
比如说当初要查问购买了 TNT2 产品的顾客信息,按之前介绍的常识应用联结从三个表中查找:
# 找到购买了 TNT2 的顾客信息select cust_name, cust_contact, cust_email, prod_idfrom customers c, orders o, orderitems oiwhere c.cust_id = o.cust_id and o.order_num = oi.order_num and prod_id = 'TNT2';
那如果当初要换成找到购买了另一个产品的顾客信息呢,从新写一遍查问语句仿佛有点反复。程序员永远不做反复的事,如果有一个虚构表,名为 prod_cust,而后应用 select * from prod_cust where prod_id = 'TNT1'
就能够轻松找到对应的行了,这就是视图。
# 创立视图create view prod_cust asselect cust_name, cust_contact, cust_email, prod_idfrom customers c, orders o, orderitems oiwhere c.cust_id = o.cust_id and o.order_num = oi.order_num; # 应用视图查问购买了产品 TNT2 的顾客信息select cust_name, cust_email from prod_custwhere prod_id = 'TNT2';
应用起来挺简略的,能够依据须要编写出可重复使用的视图,不便查问。
视图的应用如下:
create view prod_cust as ...; # 创立视图show create view prod_cust; # 查看创立视图的语句drop view prod_cust; # 删除视图create or replace view prod_cust as ...; # 更新视图
如果要批改视图,能够先删除再新建,也能够间接 create or replace view
,如果存在则会替换,如果不存在则会新建。
视图并不间接蕴含数据,而是一个 SQL 查问。视图和一般表的关系,就像长期组建的歌唱团和一般班级的关系。
但也因为视图不蕴含数据,每次都要从新执行,所以如果应用视图的场景比较复杂比方嵌套视图等,那么你会发现性能降落的厉害,所以在大量应用视图的场景可能须要关注性能问题。
视图创立后,能够像应用表一样应用视图,对视图进行 select
、过滤、排序、联结等等操作。
应用视图能够:
- 复用 SQL 语句。
- 简化简单的 SQL 操作。在编写查问后,能够不便地重用它而不用晓得它的根本查问细节。
- 应用表的组成部分而不是整个表。
- 爱护数据。能够给用户授予表的特定局部的拜访权限而不是整个表的拜访权限。
- 更改数据格式和示意。视图可返回与底层表的示意和格局不同的数据。
顺便说一句,创立视图之后,show tables
也会显示视图,所以你能够通过上面形式查问所有基表或者视图:
# 显示以后database中不包含视图的所有基表select table_namefrom information_schema.tableswhere table_schema = 'mysql_demo1' and table_type = 'BASE TABLE';# 显示以后database中的所有视图select table_namefrom information_schema.tableswhere table_schema = 'mysql_demo1' and table_type = 'VIEW';
14.2 应用视图从新格式化检索出的数据
比方某个场景,常常会应用到一些格式化的数据,那么就能够应用视图把数据格式化的模式先拼接好:
# 常常用到的供应商信息,能够先组装成视图create or replace view vend_infos asselect vend_id, concat(vend_name, '(', vend_city, ', ', vend_country, ')') vend_infofrom vendors;# 应用视图间接拿到拼好的供应商信息select prod_id, prod_name, vend_infofrom products, vend_infoswhere products.vend_id = vend_infos.vend_id;
14.3 应用视图过滤不想要的数据
比方某个场景,须要找到邮箱地址不为 null 的顾客下的订单:
# 找到 email 不是 null 的顾客下的订单select order_num, cust_name, cust_emailfrom customers, orderswhere customers.cust_id = orders.cust_id and cust_email is not null;
然而另一个场景又须要找到邮箱地址不为 null 的顾客购买的所有商品列表,此时咱们能够应用视图,把邮箱地址不为 null 的顾客创立为一个视图,在其余场景应用:
# 创立邮箱地址不为 null 的顾客的视图create view cust_has_email asselect * from customerswhere cust_email is not null;# 找到 email 不是 null 的顾客下的订单select order_num, cust_name, cust_emailfrom cust_has_email c, orders owhere c.cust_id = o.cust_id;# 找到 email 不是 null 的顾客购买的所有商品列表select c.cust_name, c.cust_email, c.cust_name, prod_namefrom cust_has_email c, orders o, orderitems oi, products pwhere c.cust_id = o.cust_id and oi.order_num = o.order_num and p.prod_id = oi.prod_id;
能够看到视图这里就齐全被当成了一个表来和其余表一起应用。
14.4 应用视图与计算字段
视图对于简化计算字段的应用很有用,比方心愿查找 20008 的订单的订单总额:
# 查找 20008 订单的订单总额select order_num, sum(quantity * item_price) sum_pricefrom orderitemswhere order_num = 20008;
那么心愿查找另一个订单总额时,能够应用视图来革新一下:
# 查找订单总额视图create or replace view sum_orders asselect order_num, sum(quantity * item_price) sum_pricefrom orderitems group by order_num;# 找另一个订单的总金额select order_num, sum_pricefrom sum_orderswhere order_num = 20009;
看到视图非常好用,理论应用中按需应用视图能够极大不便数据库操作。
14.5 更新视图
视图也是能够应用 insert
、update
、delete
更新数据的,尽管视图只是一个 SQL 句子而不是理论数据。
更新视图的数据会更新其基表,但并非所有视图都能够更新的,如果数据库不冷确定被更新的基数据,则不容许更新。比方分组、联结、子查问、并、汇集函数、distinct
等等。
# 比方给下面的 email 不为 null 的视图增加一行数据,插入会胜利insert into cust_has_email(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact)values (10010, 'Zhang San', '001 street', 'ShangHai', 'SH', '9999', 'ZH', 'Li S');# 给查找订单总额视图减少一行会失败,因为这里有分组,数据库不晓得在哪插入insert into sum_orders(order_num, order_item, prod_id, quantity, item_price)values (20009, 5, 'OL1', 2, 8.99);
不过个别,应该把视图用于检索数据 select
,而不是增删改数据 insert
、update
、delete
。
15. 存储过程
后面介绍的大部分 SQL 语句都是对一个或者多个表的单个查问,然而理论状况下一个残缺的操作可能是由多个语句组合而成的,比方思考上面这个下单流程:
- 为了解决订单,须要核查以保障库存中有相应的物品。
- 如果库存有物品,这些物品须要预约以便不将它们再卖给别的人,并且要缩小可用的物品数量以反映正确的库存量。
- 库存中没有的物品须要订购,须要与供应商进行一些交互。
- 对于哪些物品入库(并且能够立刻发货)和哪些物品退订,须要告诉相应的客户。
能够说存储过程就是数据库 SQL 语言层面上的代码封装和重用,能够回传值,也能够承受参数。能够将其视为批文件,但作用不仅限于批处理。
存储过程简略、平安、高性能。不过有些数据库管理员会限度存储过程的创立权限,只容许用户应用,但不容许用户创立存储过程。
15.1 创立存储过程 create procedure
创立存储过程应用 create procedure
,能够设置参数,存储过程体应用 begin ... end
分隔开,调用应用 call
# 创立一个计算平均价格的存储过程create procedure product_pricing(vend_id int)begin select avg(prod_price) as priceaverage from products where products.vend_id = vend_id;end;# 查看创立存储过程的语句show create procedure product_pricing;# 调用存储过程查问平均价格call product_pricing(1002);
这里的存储过程应用了参数,也能够不应用参数,和其余语言中的函数相似。
15.2 删除存储过程 drop procedure
删除应用 drop
关键字,如果不存在这个存储过程会报错,此时能够减少 if exists
关键字:
# 删除存储过程 drop procedure product_pricing;# 先查看再删除drop procedure if exists product_pricing;
15.3 应用参数
变量(variable) 内存中一个特定的地位,用来长期存储数据。
存储过程输出了 4 个参数,一个输出参数,还有三个用来存储的参数,每个参数用 in
(传递给存储过程)、out
(从存储过程传出)、inout
(对存储过程传入和传出)指定参数。
MySQL 中的变量都必须以 @ 开始,存储过程中检索失去的值应用 into
保留到相应变量,之后能够就能够查问到变量中存储的值了。
# 存储过程输入输出参数create procedure product_pricing( in vend_id int, out min_price decimal(8, 2), out max_price decimal(8, 2), out avg_price decimal(8, 2))begin select min(prod_price) into min_price from products where products.vend_id = vend_id; select max(prod_price) into max_price from products where products.vend_id = vend_id; select avg(prod_price) into avg_price from products where products.vend_id = vend_id;end;# 调用存储过程查问产品平均价格call product_pricing(1002, @minprice, @maxprice, @avgprice);# 查问刚刚输入的变量select @minprice, @maxprice, @avgprice;
再试个例子,应用存储过程计算出指定订单号的总价,并输入到变量中:
# 计算指定订单号的总价格,并输入到变量中create procedure order_pricing( in order_num int, out total_price decimal(8, 2))begin select sum(quantity * item_price) into total_price from orderitems where orderitems.order_num = order_num;end;# 计算订单 20005 的总价call order_pricing(20005, @totalprice);# 查问总价select @totalprice;
15.4 应用条件语句
存储过程也能够应用 if (条件) then ... elseif (条件) then ... else
语句,比方当初要计算折扣后的商品价格,总商品数量 3 件 8 折,4 件 7 折,这里应用存储过程:
# 首先为了不便前面计算订单总金额,创立一个查问订单总金额的视图create or replace view sum_order_price asselect sum(quantity * item_price) as total_price, order_numfrom orderitems group by order_num;# 总商品数量 3 件 8 折,4 件 7 折,计算折扣后的产品价格,create procedure total_discount_price( in order_num int, out discount_price decimal(8, 2)) begin # 创立一个变量保留商品总数 declare prod_count int; # 计算该订单号的商品总件数 select sum(quantity) into prod_count from orderitems where orderitems.order_num = order_num; # 小于 3 件无折扣 if prod_count < 3 then select total_price into discount_price from sum_order_price o where o.order_num = order_num; # 等于 3 件 8 折 elseif prod_count = 3 then select total_price * 0.8 into discount_price from sum_order_price o where o.order_num = order_num; # 大于等于 3 件 7 折 else select total_price * 0.7 into discount_price from sum_order_price o where o.order_num = order_num; end if;end;# 调用存储过程查问折扣后的金额call total_discount_price(20005, @discountprice);select @discountprice;
这个例子中咱们应用了一个长期变量 prod_count,计算出该订单总件数之后将其赋到这个长期变量中,而后在之后的 if else
条件语句中对其进行判断,再通过视图计算出总金额,最初保留给输入变量。
16. 游标
有时,须要在检索进去的行中后退或后退一行或多行,这就是应用游标的起因。游标(cursor)是一个存储在 MySQL 服务器上的数据库查问,它不是一条 select
语句,而是被该语句检索进去的后果集。在存储了游标之后,利用能够依据须要滚动或浏览其中的数据。
游标次要用于交互式利用,其中用户须要滚动屏幕上的数据,并对数据进行浏览或做出更改。
MySQL 中的游标只能用于存储过程或函数。
游标解决分为上面几个步骤:
- 申明游标 declare:没有检索数据,只是定义要应用的
select
语句; - 关上游标 open:关上游标以供应用,用上一步定义的
select
语句把数据理论检索进去; - 检索游标 fetch:对于填有数据的游标,依据须要取出(检索)各行;
- 敞开游标 close:在完结游标应用时,必须敞开游标,如果你不敞开游标,MySQL 将在达到
end
语句时敞开游标。
上面间接看例子:
drop procedure if exists process_orders;# 应用游标将每个订单的理论价格填写到一个表中create procedure process_orders()begin # 定义本地变量 declare o_num int; # 循环中的订单号变量 declare d_price decimal(8, 2); # 循环中的理论价格变量 declare done boolean default false; # 终止条件布尔值 # 定义游标 declare order_numbers cursor for select order_num from orders; # 终止条件,当没有更多行供循环时满足 not found,此时给 done 赋值 true declare continue handler for not found set done = true; # 没有则创立一个新的表,用来存订单的理论价格 create table if not exists ordertotals ( order_num int, order_price decimal(8, 2), primary key (order_num) ); open order_numbers; # 关上游标 # 在循环中顺次给表 ordertotals 填充行 repeat fetch order_numbers into o_num; call total_discount_price(o_num, d_price); insert into ordertotals(order_num, order_price) values (o_num, d_price); until done end repeat; # 满足 done 为真值则跳出循环 close order_numbers;end;call process_orders();select * from ordertotals;
首先应用 declare
定义了几个局部变量,这几个变量用来存两头值,其中默认值为 false 的 done
是循环的终止条件,将在前面的 repeat
语句中用来作为判断是否持续循环的标记位,当 repeat
没有更多行供循环时满足 not found
,此时给 done
赋值 true 终止循环。在循环体中应用上一章的存储过程给表 ordertotals 填充计算的订单理论价格。
declare
语句是有程序的,局部变量须要在句柄之后定义,句柄必须在游标之后定义,否则会报错。
除了 repeat
循环外,MySQL 还反对 loop
循环、while
循环,根本大同小异,能够本人查问学习一下。
17. 触发器
如果你想要某些语句在事件产生时主动执行,能够思考触发器。
- 只能够响应
delete
、insert
、update
语句; - 只有表反对触发器,长期表和视图不反对;
17.1 创立触发器 create trigger
创立触发器应用 create trigger
关键字,格局如下:
# 语法create trigger [触发器名][after/before] [insert/update/delete] on [表名]for each row begin [sql语句]end;# 删除drop trigger [if exists] [触发器名];
for each row
示意对每个插入行执行触发器。只有表
# 创立一个触发器,在新的产品插入时给长期变量赋值create trigger newproduct after insert on products for each row select 'Product added' into @newprod;# 插入一条语句试试insert into products(prod_id, vend_id, prod_name, prod_price, prod_desc)values ('XP2000', 1005, 'JetPack 200', 55, 'JetPack 200, multi-use');select @newprod;
17.2 应用触发器
触发器要审慎应用,因为触发器是针对每一行的,对增删改十分频繁的表上切记不要应用触发器,因为会十分耗费资源。
insert 触发器
insert
触发器内能够通过拜访名为new
的虚构表拜访被插入的行;before insert
语句中能够通过更改new
虚构表中的值来批改插入行的数据;- 对于
auto_increment
自增列,在before
中new
中的值为 0,在after
中为主动生成的自增值。
# 插入用户后获取这个新用户主动生成的的 ID 并且赋值给长期变量create trigger newcust after insert on customers for each row select new.cust_id into @newcust_after;# 插入用户insert into customers(cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email)values ('Zhang San', '001 street', 'ShangHai', 'SH', '666666', 'ZH', null, null);# 查看新值select @newcust_after;
before
常常被用于数据验证。
delete 触发器
delete
触发器内能够通过拜访名为old
的虚构表来拜访被删除的行;old
虚构表中的字段都是只读的,不能批改。
drop trigger if exists deletecustomer;# 创立触发器,当从顾客表中删除时将删除的数据插入到另一个存档表中create trigger deletecustomer before delete on customers for each rowbegin insert into customers2(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email) values (old.cust_id, old.cust_name, old.cust_address, old.cust_city, old.cust_state, old.cust_zip, old.cust_country, old.cust_contact, old.cust_email);end;# 删除刚刚创立的顾客数据delete from customers where cust_id = 10013;# 查问一下存档表中的顾客数据是否存在select * from customers2;
这里应用 before
而不是 after
的起因是,如果因为某种原因顾客信息不能存档,delete
操作将会放弃,防止信息失落。
update 触发器
update
触发器内能够通过拜访名为old
的虚构表拜访更新前的值,拜访名为new
的虚构表来拜访更新后的值;before update
触发器中,new
中的值是能够被批改的;old
虚构表中的字段都是只读的,不能批改。
drop trigger if exists updatecustomer;# 应用触发器,将每次更新的 cust_country 转化为大写create trigger updatecustomer before update on customers for each rowbegin set new.cust_country = upper(new.cust_country);end;# 更改数据update customers set cust_country ='zh' where cust_id = 10005;
18. 治理事务处理
事务处理(transaction processing)能够用来保护数据库的完整性,它保障成批的 MySQL 操作要么齐全执行,要么齐全不执行。
举个例子,如果咱们要转账给他人,首先把他人账户减少钱,再把咱们账户上钱扣除,如果两头呈现问题,那么麻烦就大了。
或者在以后数据库中,如果咱们要增加一个订单信息,分为上面几步:
- 查看数据库中是否存在相应的客户(从customers表查问),如果不存在则增加这个用户信息。
- 检索顾客的 ID,cust_id。
- 增加一行订单信息到 orders 表,把它与顾客 ID 关联。
- 检索 orders 表中赋予的新订单 ID,order_id。
- 对于订购的每个物品在 orderitems 表中增加一行,通过检索进去的 ID 把它与 orders 表关联,以及通过产品 ID 与 products 表关联。
如果产生了某种数据库故障(超出磁盘限度、平安限度、表锁等),阻止了一个残缺的流程,会呈现什么状况 。如果故障呈现在 1 和 2 之间,这没什么关系,因为一个顾客没有订单信息是非法的,如果呈现在 3 和 4 之间,那么就会呈现一个空的订单,这个订单没有蕴含的产品信息,这很重大,如果呈现在 5 时,增加 orderitems 过程中呈现问题,那么可能呈现订单信息不残缺的状况,也很重大。
应用事务能够防止这个状况,如果两头产生了问题,那么则回退到某个平安的状态。
18.1 事务处理
那么应用事务如何解决这个过程呢:
- 查看数据库中是否存在相应的顾客,如果不存在则增加这个用户信息;
- 提交顾客信息;
- 检索顾客的 ID;
- 增加一行到 orders 表;
- 如果在增加行到 orders 表时呈现故障,回退;
- 检索 orders 表中赋予的新订单 ID;
- 对于订购的每项物品,增加新行到 orderitems 表;
- 如果在增加新行到 orderitems 时呈现故障,回退所有增加的 orderitems 行和 orders 行;
- 提交订单信息。
这里有几个概念:
- 事务(transaction)指一组 SQL 语句;
- 回退(rollback)指撤销指定 SQL 语句的过程;
- 提交(commit)指将未存储的 SQL 语句后果写入数据库表;
- 保留点(savepoint)指事务处理中设置的长期占位符,你能够对它公布回退(与回退整个事务处理不同)。
18.2 管制事务处理
应用回退 rollback
start transaction
标识事务开始,应用 rollback
能够进行回退从 start
到 rollback
两头的所有语句。
# 开始事务start transaction; delete from customers where cust_id > 10005; # 删除几个行select * from customers;# 应用 rollback 回滚 delete 语句rollback;
rollback
能够回退 insert
、update
、delete
语句,但不能回退 create
、drop
语句,事务处理块中能够应用这两个语句,但 rollback
无成果。
应用提交 commit
MySQL 中用户的任何一个更新操作(写操作)都被视为一个事务,这就是所谓的隐含提交(implicit commit),相当于 MySQL 帮你在后盾提交了。
能够针对每个连贯应用 set autocommit=0
来设置 MySQL 不主动提交更改,设置之后,每个 SQL 语句或者语句块所在的事务都须要显式 commit
能力提交事务。
但在事务处理块中,提交不会隐含地进行,须要你本人来显式的调用:
# 删除订单详情表总中 20007 相干订单详情,再删除订单表中的 20007start transaction;delete from orderitems where order_num = 20007;delete from orders where order_num = 20007;commit;
因为设计到订单和订单详情,所以应用事务来保障订单是残缺的被删除,而不是局部删除。如果两个 delete
语句中产生了谬误,那么 commit
将不会被执行。
在 commit
或 rollback
执行时,事务会被主动敞开。
应用保留点 savepoint
之前的 rollback
、commit
只能对整个事务处理块整体提交或回滚,某些简单场景下可能要局部回滚或者局部复原,比方之前例子,如果订单信息减少失败,可能要回滚到增加用户信息后。
此时能够应用保留点,这样在产生问题时回滚到保留点处即可。保留点应用比较简单:
# 创立保留点savepoint sav1;# 回滚到保留点rollback to sav1;
保留点能够应用多一点,当在事务实现时,他们将会被主动开释,也能够应用 release savpoint
来手动开释。
19. 平安治理
19.1 访问控制
对数据库来说,用户应该对他们须要的数据具备适当的拜访权,既不能多也不能少。
- 少数用户只须要对表进行读和写,多数用户须要能创立和删除表;
- 某些用户须要读表,但可能不须要更新表;
- 你可能想容许用户增加数据,但不容许他们删除数据;
- 某些用户(管理员)可能须要解决用户账号的权限,但少数用户不须要;
- 你可能想让用户通过存储过程拜访数据,但不容许他们间接拜访数据;
- 你可能想依据用户登录的地点限度对某些性能的拜访。
给不同的用户提供不同的拜访权,这就是访问控制。
对于 root 登陆的应用须要非常审慎小心,仅在相对须要时才应用,不应在日常 MySQL 操作中应用 root 账户。
19.2 治理用户
用户信息存储在 MySQL 的 mysql 库中:
# 查看用户列表use mysql;select user from user;
创立用户账号:
# 创立用户及其明码create user zhangsan identified by '888888';# 更改用户名rename user zhangsan to lisi;# 删除用户drop user if exists lisi;
设置权限用 grant
关键字:
# 显示用户张三的权限show grants for zhangsan;# 设置权限,容许张三在 mysql_demo1 库上应用 selectgrant select on mysql_demo1.* to zhangsan;# 撤销权限,撤销张三在 mysql_demo1 库上应用 select 的权限revoke select on mysql_demo1.* from zhangsan;
权限设置和用户设置还有很多内容,不是本文的重点,能够百度一下或者看文档。
20. 数据库保护
20.1 备份数据
数据库也是常常须要备份的,能够应用以下办法:
- 应用命令行实用程序 mysqldump 转储所有数据库内容到某个内部文件。在进行惯例备份前这个实用程序应该失常运行,以便能正确地备份转储文件。
- 用命令行实用程序 mysqlhotcopy 从一个数据库复制所有数据,但并非所有数据库引擎都反对这个实用程序。
- 能够应用 MySQL 的
backup table
或select into outfile
转储所有数据到某个内部文件。这两条语句都承受将要创立的零碎文件名,此系统文件必须不存在,否则会出错。数据能够用restore table
来还原。
20.2 数据库保护
analyze table
用来查看键是否正确。check table
用来针对许多问题对表进行查看。- 针对 MyISAM 表拜访产生不正确和不统一的问题,能够应用
repair table
来修复。 - 如果从一个表中删除大量数据,应该应用
optimize table
来发出所用空间,从而优化表的性能。
20.3 查看日志
谬误日志 Error Log
记录 Mysql 运行过程中的 Error、Warning、Note 等信息,零碎出错或者某条记录出问题能够查看谬误日志。
通过 show variables like "log_error";
来查看谬误日志寄存的地位。
日常日志 General Query Log
记录包含查问、批改、更新等的每条语句。
通过 show global variables like "%genera%";
查看日常日志寄存的中央,如果 general_log 是 off 则不能查问,能够通过 set global general_log=on;
关上查问,而后 tail -f /var/lib/mysql/VM-0-17-centos.log;
来查看。
二进制日志 Binary Log
蕴含一些事件,形容了数据库的改变,如建表、数据改变等,次要用于备份复原、回滚操作等。
能够通过 show variables like "%log_bin%";
来查看 Binlog 存在哪,会有多个文件,应用 show master logs;
能够看到查看所有 Binlog 日志列表,格局是 bingo.000008
这样,当 Binlog 日志写满或者数据库重启会产生新文件,应用 flush logs
能够手动产生新文件,Binlog 非常重要,产生问题要回滚用 Binlog 就能够了。
迟缓查问日志 Slow Query Log
记录执行迟缓的任何查问,在优化数据库时比拟有用。
能够通过 show variables like "%slow%";
来查看迟缓日志寄存的中央。
21. 改善性能
性能是数据库永恒的谋求,对于性能有以下 Tips:
- 数据库对硬件是有肯定要求的,在老旧主机上运行天然远不如专用服务器上。
- MySQL 有很多配置,比方内存调配、缓存区大小等,纯熟应用后通过调整配置能够取得更好的性能体现。查看配置能够应用
show variables;
和show status;
查看配置 - 实现同样性能的不同语句有不同的性能体现,能够找到性能更好的办法。
- 尽量不检索不须要的数据,比方能
select param
,就不要select *
。 - 个别组合查问比在
select
中应用or
快。 - 个别
fulltext
比like
快。
网上的帖子大多深浅不一,甚至有些前后矛盾,在下的文章都是学习过程中的总结,如果发现错误,欢送留言指出,如果本文帮忙到了你,别忘了点赞反对一下哦,你的点赞是我更新的最大能源!~
参考文档:
- 《MySQL必知必会》
- MySQL 中文文档
PS:本文收录在在下的博客 Github - SHERlocked93/blog 系列文章中,欢送大家关注我的公众号 前端下午茶
,间接搜寻即可增加或者点这里增加,继续为大家推送前端以及前端周边相干优质技术文,共同进步,一起加油~
另外能够退出「前端下午茶交换群」微信群,微信搜寻 sherlocked_93
加我好友,备注加群,我拉你入群~