MySQL基础

安装(迁移)MySQL

环境(windows

OS:windows11

MySQL: mysql-8.0.25

安装

下载MySQL或将旧机器内mysql目录内所有文件复制到新的windows11,然后把bin添加到环境变量。

然后cmd执行安装指令

1
2
#安装mysql
mysqld -install

启动或停止

启动或停止mysql服务:

1
2
3
4
5
#start
net start mysql

#stop
net stop mysql

连接

连接mysql

1
2
3
4
#登录mysql
mysql -u root -p #root 替换成你自己的用户名
#指定ip
mysql -h localhost -u root -p #适用远程登陆mysql

配置远程连接

1
2
3
4
5
6
7
use mysql;
select host,user from user;
#这时候发现root用户的host作用域为localhost
#将host修改为%,允许所有连接
update user set host='%' where user='root';
#热更新(flush privileges 命令本质上的作用是将当前user和privilige表中的用户信息/权限设置从mysql库 (MySQL数据库的内置库)中提取到内存里。 MySQL用户数据和权限有修改后,希望在"不重启MySQL服务"的情况下直接生效,那么就需要执行这个命令)
flush privileges;

常用命令

use

1
USE mysql;	#选择数据库

show

1
2
3
4
5
6
7
SHOW DATABASES;	#显示可用的数据库列表
SHOW TABLES; #显示一个数据库内可用的表的列表
SHOW COLUMNS FROM user; #显示表列,表名(user)为必须项
DESCRIBE user; #是SHOW COLUMNS FROM user;的一种快捷方式

SHOW ERRORS; #显示服务器错误信息
SHOW WARNINGS; #显示服务器警告信息

更多命令,可输入’HELP SHOW’查看。

SELECT

1. SELECT

从一个或多个表中检索信息。至少要给出两条信息:

  • 想选择什么
  • 从哪里检索

示例:检索mysql的user列

1
2
3
4
5
6
7
8
USE school;
#基本查询
#检索单个列
SELECT prod_name FROM products;
#检索单个列
SELECT prod_id, prod_name, prod_price FROM products;
#检索所有列
SELECT * FROM products;

2. 限制查询结果

1
2
3
4
5
6
7
8
9
10
11
12
13
#返回不同的行	DISTINCT
#'DISTINCT'只返回不同的vend_id的行
#'DISTINCT'应用于所有而不仅是前置他的列
SELECT DISTINCT vend_id FROM products;
#限制结果 LIMIT
#参数5:不多于5行
SELECT prod_name FROM products LIMIT 5;
#参数5,5:从第5开始,不多于5行
SELECT prod_name FROM products LIMIT 5, 5;
#由于容易弄混参数,所以从MySQL 5 开始支持另一种替代的语法,如:从行3开始取4行
SELECT prod_name FROM products LIMIT 4 OFFSET 3;
#使用完全限定的表名
SELECT products.prod_name FROM products;

3. 查询排序(ORDER BY)

SELECT检索出的数据,可使用ORDER BY字句进行排序。ORDER BY字句取一个或多个列名作为参数。

允许非检索需要的列进行排序

1
2
3
4
5
#单个列排序
SELECT prod_name FROM products ORDER BY prod_name;

#多列排序,优先级从前往后
SELECT prod_id, prod_price, prod_name FROM products ORDER BY prod_price, prod_name;

4. 指定排序方向(ORDER BY

如不指定排序方向,则默认是升序排序(也可指定升序排序, 关键字ASC,通常是不必要的,SELECT默认是升序排序)。

ORDER BY字句为了进行降序排序,必须指定DESC关键字。

需注意:DESC只作用于其前面的单个列

1
2
3
4
5
6
7
8
#单个列,降序排序
SELECT prod_name FROM products ORDER BY prod_name DESC;
#多列降序排序,优先级从前往后
SELECT prod_id, prod_price, prod_name FROM products ORDER BY prod_price DESC, prod_name DESC;
#price降序, name升序
SELECT prod_id, prod_price, prod_name FROM products ORDER BY prod_price DESC, prod_name;
#最贵的prod_name
SELECT prod_name FROM products ORDER BY prod_price DESC LIMIT 1;

使用ORDER BY字句,必须位于FROM字句之后;LIMIT字句必须位于ORDER BY字句

rank() over

排名相同的两名是并列,但是占两个名次,1 1 3 4 4 6这种

dense_rank() over

排名相同的两名是并列,共占一个名词,1 1 2 3 3 4这种

row_number() over

这个函数不需要考虑是否并列,哪怕根据条件查询出来的数值相同也会进行连续排名 1 2 3 4 5

5. 过滤数据(WHERE

只检索所需数据需指定搜索条件(也称过滤条件)。SELECT语句中,根据WHERE字句中指定的搜索条件进行过滤。WHERE字句在FROM字句后。

WHERE字句操作符

  • =
  • <>
  • !=
  • <
  • <=
  • >
  • >=
  • BETWEEN
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#price = 2.50
SELECT prod_name, prod_price FROM products WHERE prod_price = 2.50;
#name = 'fuses', MYSQL在执行匹配时默认不区分大小写
SELECT prod_name, prod_price FROM products WHERE prod_name = 'fuses';
#price <= 10,小于等于
SELECT prod_name, prod_price FROM products WHERE prod_price <= 10;
#vend_id <> 1003,不匹配检查
SELECT vend_id, prod_name FROM products WHERE vend_id <> 1003;
#vend_id != 1003,不匹配检查
SELECT vend_id, prod_name FROM products WHERE vend_id != 1003;
#prod_price BETWEEN 5 AND 10
SELECT prod_name, prod_price FROM products WHERE prod_price BETWEEN 5 AND 10;
#cust_email is NULL
SELECT cust_email, cust_id FROM customers WHERE cust_email is NULL;
  • 在同时使用将ORDER BYWHERE字句时,ORDER BY需位于WHERE后。
  • MYSQL在执行匹配时默认不区分大小写
  • 值与串类型的列进行比较,值需要限定引号
  • BETWEEN字句需指定两个值,一个开始,一个结束值

组合WHERE字句

1
2
3
4
5
6
7
8
9
10
11
12
#AND关键字:逻辑与
SELECT prod_id, prod_name, prod_price FROM products WHERE vend_id = 1003 AND prod_price <= 10;

#OR关键字:逻辑或
SELECT vend_id, prod_name FROM products WHERE vend_id = 1002 OR vend_id =1003;

#IN关键字:在这些值里
SELECT prod_name, vend_id FROM products WHERE vend_id IN (1002, 1003);

#NOT关键字:否定紧跟在后面的条件
SELECT prod_name, vend_id FROM products WHERE vend_id NOT IN (1002, 1003);

INOR操作符功能等价,但IN操作符具有如下优点:

  1. 语法更清楚更直观
  2. 计算的次序更容易管理(使用的操作符更少)
  3. 效率一般更快
  4. IN最大的优点是可以包含其他SELECT语句
1
2
#price >= 10 且由1002或1003造的产品
SELECT prod_id, prod_name, prod_price FROM products WHERE (vend_id = 1002 OR vend_id = 1003) AND prod_price >= 10;
  • 在处理SQL时,优先级AND>OR,可使用圆括号明确的分组相应的操作符。

  • 圆括号还有消除歧义的作用,在任何组合逻辑运算中,都应该使用。

6. 通配符过滤

LIKE操作符

LIKE操作符指示MySQL,后跟的搜索模式利用通配符匹配而不是直接相等匹配等进行比较。SQL支持几种通配符:%、_、

1
2
3
4
5
6
7
#%:百分号通配符,表示任意字符出现任意次。
#例:查找所有jet开头的产品
SELECT prod_id, prod_name FROM products WHERE prod_name LIKE 'jet%';
#例:匹配任何文本含anvil的值
SELECT prod_id, prod_name FROM products WHERE prod_name LIKE '%anvil%';
#例:%在中间
SELECT prod_id, prod_name FROM products WHERE prod_name LIKE 's%e';

7. 正则表达式

7.1 REGEXP关键字

1
2
3
4
5
6
7
8
9
10
11
#基本字符匹配:匹配id含有ANV的所有行
SELECT prod_id, prod_name FROM products WHERE prod_id REGEXP 'ANV';
#'.000'
SELECT prod_name FROM products WHERE prod_name REGEXP '.000';
#OR匹配
SELECT prod_name FROM products WHERE prod_name REGEXP '1000|2000';
#匹配几个字符之一:1 Ton|2 Ton|3 Ton
SELECT prod_name FROM products WHERE prod_name REGEXP '[123] Ton';
#同上
SELECT prod_name FROM products WHERE prod_name REGEXP '[1|2|3] Ton';

  • .是正则表达式的一个特殊字符,表示匹配任意一个字符

  • 3.23.4后,正则匹配默认不区分大小写。可用BINARY关键字区分大小写

    1
    2
    3
    4
    5
    #例:
    #匹配行:0
    SELECT prod_id, prod_name FROM products WHERE prod_id REGEXP BINARY 'aNV';
    #匹配行:3
    SELECT prod_id, prod_name FROM products WHERE prod_id REGEXP BINARY 'aNV';
  • ^表否定

    1
    2
    #匹配除1、2、3外的所有
    SELECT prod_name FROM products WHERE prod_name REGEXP '[^123] Ton';

7.2 匹配范围

1
2
#-可用来定义范围你:0-9
SELECT prod_name FROM products WHERE prod_name REGEXP '[0-9] Ton';

7.3 转译(匹配特殊字符)

使用\\转义特殊字符

1
2
#匹配.
SELECT prod_name FROM products WHERE prod_name REGEXP '\\.';

\\也用来引用元字符

  • \\f 换页
  • \\n 换行
  • \\r 回车
  • \\t 制表
  • \\v 纵向制表

匹配\使用\\\

7.4 匹配字符类

-[:alnum:] :任意数字和字符(同[a-zA-Z0-9]

-[:alpha:] :任意字符(同[a-zA-Z]

-[:blank:] :空格和制表(同[\\t]

-[:cntrl:]ASCII控制字符(ASCII0到31和127)

-[:digit:] :任意数字(同[0-9]

-[:graph:] :与[:print:]相同,但不包括空格

-[:lower:] :任意小写字母(同[a-z]

-[:print:] :任意可打印字符

-[:punct:] :既不在[:alnum:]也不在[:cntrl:]中的任意字符

-[:space:] :包括空格在内的任意空白字符(同[\\f\\n\\r\\t\\v]

-[:upper:] :任意大写字母(同[A-Z]

-[:xdigit:] :任意十六进制数字(同[a-fA-F0-9]

7.5 匹配多个实例(重复元字符)

  • * :0个或多个匹配
  • + :1个或多个匹配(同{1,}
  • :0个或1个匹配(同{0,1}
  • {n} :指定数目的匹配
  • {n,} :不少于指定数目的匹配
  • {n,m} :匹配数目的范围(m不超过255)
1
2
3
4
5
6
#例1
SELECT prod_name FROM products WHERE prod_name REGEXP '\\([0-9] sticks?\\)';
#例2:连在一起的4位数字
SELECT prod_name FROM products WHERE prod_name REGEXP '[[:digit:]]{4}';
#同上
SELECT prod_name FROM products WHERE prod_name REGEXP '[:digit:]{4}';

7.6 定位元字符

  • ^:文本的开始
  • $:文本的结束
  • []
  • []
1
2
#匹配以数字或.开始的串
SELECT prod_name FROM products WHERE prod_name REGEXP '^[0-9\\.]';

LIKEREGEXPLIKE匹配整个串,REGEXP匹配子串。

7.7 简单的正则表达式测试

可以在不使用数据库表,用SELECT测试正则表达式。返回值:

  • 0:没有匹配
  • 1:匹配
1
2
3
4
#返回0,因为'hello'没有数字
SELECT 'hello' REGEXP '[0-9]';
#返回1,有数字匹配。
SELECT '11' REGEXP '[0-9]';

8 创建计算字段

8.1 拼接字段 (Concat

拼接字段使用**Concat**关键字。

由两列组成的标题的简单例子

1
2
#两列组成标题
SELECT Concat(vend_name, '(', vend_country, ')') FROM vendors ORDER BY vend_name;
  • Concat拼接串,即把多个串连接起来形成一个较长的串。

  • RTrim:去除右边的空格

  • LTrim:去掉左边的空格

    1
    2
    #两列组成标题
    SELECT Concat(RTrim(vend_name), '(', RTrim(vend_country), ')') FROM vendors ORDER BY vend_name;

8.2 使用别名 (AS)

列别名使用AS关键字

  • 别名是一个字段或值的替换名

  • 别名有时也成为导出列

1
2
#使用别名
SELECT Concat(RTrim(vend_name), '(', RTrim(vend_country), ')') AS vend_title FROM vendors ORDER BY vend_name;

8.3 执行算数计算

1
2
#计算单价*数量 = 总价格
SELECT prod_id, quantity, item_price, quantity * item_price AS expanded_price FROM orderitems WHERE order_num = 20005;

9. 使用数据处理函数

9.1 文本处理函数

  • Left :返回串左边的字符

  • Length :返回串的长度

  • Locate :找出串的一个子串

  • Lower :将串转换为小写

  • LTrim :去掉串左边的空格

  • Right :返回串右边的字符

  • RTrim :去掉串右边的空格

  • Soundex :返回串的SOUNDEX值

    1
    2
    3
    #Soundex:将任何文本串转换为描述其语音表示的字母数字模式的算法
    SELECT cust_name, cust_contact FROM customers WHERE Soundex(cust_contact) = Soundex('Y Lie');

  • SubString :返回子串的字符

  • Upper :将串转换为大写

9.2 日期和时间处理函数

假如列的值包含日期和时间,我们想找出2005-09-01这个日期的所有行,这样

1
2
#因为没有跟时间段,时间段默认为00:00:00
SELECT cust_id, order_num, order_date FROM orders WHERE order_date = '2005-09-01';

image-20211201232509287

1
2
#Date, 讲字段的值转换成日期(去掉时间)再比较,匹配当天所有的行
SELECT cust_id, order_num, order_date FROM orders WHERE Date(order_date) = '2005-09-01';

image-20211201232524887

日期处理函数的意义在于此

函数一览:

  • AddDate
  • AddTime
  • CurDate
  • CurTime
  • Date
  • DateDiff
  • Date_Add
  • Date_Format
  • Day
  • DayOfWeek
  • Hour
  • Minute
  • Month
  • Now
  • Second
  • Time
  • Year

9.3 查找某个月的订单

查找某个月的订单时,可以使用Yead()和Mont()函数来处理日期的值,再分别匹配年和月。相比使用in查询时需要指定年月日(即日期的具体值)语句更简洁、容易理解。

1
2
SELECT cust_id, order_num FROM orders WHERE Date(order_date) in ('2005-09-01', '2005-09-30');
#上面的缺点是,当不知道某天有没有订单时,要不要写在in里, 不写肯定匹配不出来

image-20211201233249613

1
2
#更好的方法:
SELECT cust_id, order_num FROM orders WHERE Year(order_date) = 2005 AND Month(order_date) = 9;

image-20211201233303002

9.4 数值处理函数

数值处理函数主要用于代数、三角或几何运算

值有讽刺意味的是,在主要的DBMS的函数中,数值处理函数,是最一致最统一的函数。

函数名
Abs
Cos
Exp
Mod
Pi
Rand
Sin
Sqrt
Tan

10. 汇总数据 (聚集函数)

常常需要汇总数据,而不是把它们实际检索出来

10.1 聚集函数

MySQL给出了5个SQL聚集函数:

  • AVG:返回某列的平均值
  • COUNT:返回某列的行数
  • MAX:返回某列的最大值
  • MIN:返回某列的最小值
  • SUM:返回某列值之和

MySQL还涉及一系列标准偏差聚集函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#AVG
SELECT AVG(prod_price) AS avg_price FROM products;
SELECT AVG(prod_price) AS avg_price FROM products WHERE vend_id = 1003;
#COUNT
SELECT COUNT(*) AS num_cust FROM customers;
#具有电子邮件地址的客户计数
SELECT COUNT(cust_email) AS num_cust FROM customers;
#MAX
SELECT MAX(prod_price) AS max_price FROM products;
#MIN
SELECT MIN(prod_price) AS min_price FROM products;
#SUM
SELECT SUM(quantity) AS items_ordered FROM orderitems WHERE order_num = 20005;
#SUM也可以用来计算值
SELECT SUM(item_price * quantity) AS items_ordered FROM orderitems WHERE order_num = 20005;
  • AVG()函数将自动忽略值为NULL的行
  • 如果指定列名,COUNT()将忽略值为NULL的行。若列名是*号,则不忽略
  • MAX()将自动忽略值为NULL的行
  • MIN()将自动忽略值为NULL的行
  • SUM()将自动忽略值为NULL的行

10.2 聚集不同值(DISTINCT

聚集函数的DISTINCT被添加到MySQL 5.0.3,更低版本不可用

1
2
3
#AVG DISTINCT
SELECT AVG(DISTINCT prod_price) AS avg_price FROM products WHERE vend_id = 1003;
#上面的代码没有很好的体现应用场景,只是做一个示例。(2021/12/2)

使用DISTINCT后,平均值提高了,因为有多个物品具有相同的较低价格。

10.3 组合聚集函数

1
2
#包含多个聚合函数
SELECT COUNT(*) AS num_items, MIN(prod_price) AS min_price, MAX(prod_price) AS max_price, AVG(prod_price) AS price_avg FROM products;

10.4 小结

11. 分组数据

11.2 创建分组**GROUP BY**

分组是在SELECTGROUP BY字句中建立的。

1
2
#统计每个vend_id的行数
SELECT vend_id, COUNT(*) FROM products GROUP BY vend_id;

GROUP BY字句指示MySQL分组数据,然后对每个组而不是整个结果集进行聚集。

11.3 过滤分组 HAVING

所有的WHERE字句都可以用HAVING字句来代替,唯一的差别就是WHERE过滤行,而HAVING过滤分组(换一种理解就是:WHERE在分组前过滤,而HAVING在分组后过滤)。

1
2
3
4
5
6
7
#最后COUNT(*) >= 2:过滤两个以上的订单。
SELECT cust_id, COUNT(*) AS orders FROM orders GROUP BY cust_id HAVING COUNT(*) >= 2;

#具有两个以上价格>=10的产品的供应商
SELECT vend_id, COUNT(*) AS num_prods FROM products WHERE prod_price >= 10 GROUP BY vend_id HAVING COUNT(*) >= 2;

SELECT vend_id, COUNT(*) AS num_prods FROM products WHERE prod_price >= 10 GROUP BY vend_id HAVING COUNT(*) >= 2;

11.4 分组和排序

ORDER BY:排序产生的输出

GROUP BY:分组行,但输出可能不是分组的顺序(虽然经常是以分组的顺序给出,但情况并不总是这样,这不是mysql规范所要求的)

当要求分组数据时,并不表示需要以相同的方式排序输出.所以应该提供明确的ORDER BY语句.

1
2
3
4
#总价按照order_num汇总
SELECT order_num, SUM(quantity * item_price) AS ordertotal FROM orderitems GROUP BY order_num HAVING SUM(quantity * item_price) >= 50;
#根据总价排序
SELECT order_num, SUM(quantity * item_price) AS ordertotal FROM orderitems GROUP BY order_num HAVING SUM(quantity * item_price) >= 50 ORDER BY ordertotal;

11.5 SELECT 子句顺序

子句 说明 是否 必须使用
SELECT 要返回的列或表达式
FROM 从中检索数据的表 仅再从表选择数据时使用
WHERE 行级过滤
GROUP BY 分组说明 仅在按组计算聚集时使用
HAVING 组级过滤
ORDER BY 输出排序的顺序
LIMIT 要检索的行数
OFFSET 记录偏移量

子查询

1. 利用子查询过滤

有订单表orders、物品表orderitems、客户表customers。订单表存物品id和客户id。

分步查询

现要查询订购物品TNT2的所有客户,有如下三个步骤:

  1. 检索orderitems中TNT2对应的订单编号order_num

    1
    SELECT order_num FROM orderitems WHERE prod_id = 'TNT2';
  2. 检索前一步包含订单id的所有订单所对应的客户id

    1
    SELECT cust_id FROM orders WHERE order_num in (20005, 20007);
  3. 检索前一步返回的所有用户id信息

    1
    SELECT * FROM customers WHERE cust_id in (10001, 10004);

组合查询

将第1个查询变为子查询,组合两个查询:

在SELECT语句中,子查询总是从内向外处理。

1
SELECT cust_id FROM orders WHERE order_num in (SELECT order_num FROM orderitems WHERE prod_id = 'TNT2');

将得到的组合查询变为子查询语句,放到第3步中:

1
SELECT * FROM customers WHERE cust_id in (SELECT cust_id FROM orders WHERE order_num in (SELECT order_num FROM orderitems WHERE prod_id = 'TNT2'));

MySQL实际上必须执行3条SELECT语句。在WHERE子句中使用子查询能够编写出功能很强并且很灵活的SQL语句。

子查询嵌套层数没有限制,不过在实际的使用时由于性能限制,不宜嵌套太多子查询。

2. 作为计算字段使用子查询 (相关子查询

使用子查询的另一个方法是创建计算字段。

需求:需要显示customers表中每个客户的订单总数(订单与相应的客户id存储在orders表中):

  1. 从customers表中检索出客户id表

    1
    SELECT cust_id FROM customers;
  2. 对于每个检索出的客户id,统计其在orders表中的订单总数

    • 返回三列,orders是一个计算字段,由圆括号中的子查询建立。
    • 子查询总共执行了5次,因为检索出了5个客户id。
    • 这里使用了完全限定名orders.cust_id = customers.cust_id
    1
    SELECT cust_id, cust_state, (SELECT COUNT(*) FROM orders WHERE orders.cust_id = customers.cust_id) AS orders FROM customers ORDER BY cust_id;

相关子查询:涉及外部查询的子查询。

3. 总结(使用子查询流程

因在充分测试子查询正常时,再将其进行组合。这样虽然在构造查询语句时浪费了一些时间,但节省了以后(找出为什么查询不正常时)的大量时间,并极大提高了查询语句从一开始正常工作的可能性。

联结表

什么是联结、为什么要用联结、如何编写使用联结的SELECT语句

SQL最强大的功能之一。利用SQL的SELECT能执行的最重要的工作。

1. 关系表

假如有一个包含产品目录的数据表,每种类别物品占一行,每行中都记录一个供应商。如果一个供应商供应多个物品时,要修改这个供应商的信息,则需要修改所有包含该供应商的物品记录行。从中我们可以挖掘信息:

  • 同个供应商的信息被多次记录在不同种类物品中
  • 要修改供应商信息时,要修改多处。

同个信息在多处存储和修改,浪费时间和空间。相同数据多次出现绝对不是一件好事。此因素是关系数据库设计的基础。

关系表的设计就是要保证把信息分解成多个表,一类数据一个表。在这个例子中,可建立两个表,一个表存储供应商信息,一个表存储产品信息。供应商表中每个供应商都具有唯一标识,称为该表的主键。产品信息表除了存储产品信息表外,还存储了供应商的唯一标识,该字段(供应商表的主键)称为产品信息表的外键。

1.1 外键

外键为某个表中的一列,它包含其他表的主键值, 定义了两个表的关系。

好处:

  • 供应商信息唯一,不浪费时间和空间
  • 如果要修改供应商的信息,只需要修改供应商表中的单条记录,不用修改产品信息表。
  • 由于供应商数据唯一,显然数据是一致的,这使得处理数据更简单。

1.2 可伸缩性

可伸缩性能够适应不断增加的工作量而不失败。设计良好的数据库或应用程序称之为可伸缩性好。

2. 为什么使用联结?

分解数据为多个表能更有效的存储数据,更方便处理,且具有更大的可伸缩性。代价是怎么使用单条SELECT语句检索出数据?
使用联结。联结是一种机制,用来在SELECT中关联表,因此称之为联结。使用特殊的语法,可以联结多个表返回一组输出,联结在运行时关联表中正确的行。

3. 创建联结(等值联结

创建联结非常简单,规定要联结的所有表以及它们如何关联即可。

1
SELECT vend_name, prod_name, prod_price FROM vendors, products WHERE vendors.vend_id = products.vend_id ORDER BY vend_name, prod_name;

要检索的列vend_name和prod_name,prod_price分属不同的两个表,FROM子句列出了两个表vendors,products即要联结的两个表。

vendors.vend_id = products.vend_id这里使用了完全限定名:当引用的列具有二义性时,必须使用完全限定名,否则MYSQL将返回错误。

4. 笛卡儿积

上示例子的联结中,使用了WHERE子句规定了两个表每一行的对应关系,WHERE子句作为过滤条件,它只包含那些匹配给定条件的行。没有WHERE子句时,第一个表中的每个行将与第二个表中的每个行配对,而不管他们逻辑上是否可以配在一起。

1
SELECT vend_name, prod_name, prod_price FROM vendors, products ORDER BY vend_name, prod_name;

笛卡尔体积:由没有连接条件的表关系返回的结果为笛卡儿积。检索出的行的数目将是第一个表中的行数乘以第二个表中的行数。

应该保证所有联结都有WHERE子句,应该保证WHERE子句的正确性。

5. 内部联结

上面示例的等值联结基于两个表之间的相等测试。这种联结也称为内部联结,另外一个稍微不同的语法如下:

1
SELECT vend_name, prod_name, prod_price FROM vendors INNER JOIN products ON vendors.vend_id = products.vend_id;

虽然WHERE子句创建联结比较简单,但使用联结语法能确保不会忘记联结条件

6. 联结多个表

SQL对一条SELECT要联结的表没有数量限制,列出要联结的B表,使用WHERE语句规定表关系。

1
SELECT prod_name, vend_name, prod_price, quantity FROM orderitems, products, vendors WHERE products.vend_id = vendors.vend_id AND orderitems.prod_id = products.prod_id AND order_num = '20005';

MYSQL在运行时关联指定的每个表以处理联结。这种处理可能是非常耗资源,因此因该仔细,不要联结不必要的表。联结的表越多 ,性能下降越厉害。

将子查询改为联结查询:

1
2
3
4
5
#子查询
SELECT cust_name, cust_contact FROM customers WHERE cust_id in (SELECT cust_id FROM orders WHERE order_num in (SELECT order_num FROM orderitems WHERE prod_id = 'TNT2'));

#联结查询
SELECt cust_name, cust_contact FROM customers, orders, orderitems WHERE customers.cust_id = orders.cust_id AND orders.order_num = orderitems.order_num AND orderitems.prod_id = 'TNT2';

为执行任一给定SQL操作,一般存在不止一种方法,很少有绝对正确或绝对错误的方法。性能可能会受操作类型、表中数据量、是否存在索引或键以及其他一些条件的影响。因此有必要对不同的选择机制进行实验,以找出最适合具体情况的方法。

高级联结

1. 使用表别名

和列表名一样,表也可以使用别名。但表别名只在SELECT语句中使用,列别名可以返回到客户端,表别名只在查询执行时使用,不返回客户机。

1
SELECt cust_name, cust_contact FROM customers AS c, orders AS o, orderitems AS oi WHERE c.cust_id = o.cust_id AND o.order_num = oi.order_num AND oi. prod_id = 'TNT2';

使用别名主要理由

  • 缩短SQL语句
  • 允许在单挑SELECT语句中多次使用相同的表。

2. 自联结

一个表中,查询某件物品DTNTR的供应商的其他物品有哪些,可以使用子查询:

1
2
SELECT vend_id FROM products  WHERE prod_name = 'DTNTR';
SELECT prod_name FROM products WHERE vend_id = (SELECT vend_id FROM products WHERE prod_id = 'DTNTR');

使用联结查询:

1
SELECT p1.prod_name FROM products AS p1, products AS p2 WHERE p1.vend_id = p2.vend_id AND p2.prod_id = 'DTNTR';

此查询中需要的两个表实际上是相同的表,因此products表在FROM子句中出现了两次。虽然这是完全合法的,但对products的引用具有二义性,因为MySQL不知道你引用的是products表中的哪个实例。为了解决这个问题,可以使用表别名,将第一次出现的表命名为p1,第二次出现的表命名为p2。

虽然性能上自联结

3. 自然连接

使用联结时,应至少有一列出现在不止一个表中。标准的连接返回所有的数据,可能相同的列出现多次。

自然联结排除多次出现,使每个列只返回一次。这个排除重复列的操作系统不操作,需要sql指定,如下:

1
2
3
4
5
# 检索重复的列
SELECT c.cust_id, o.cust_id FROM customers AS c, orders AS o WHERE c.cust_id = o.cust_id;

# 通过指定检索的列,重复的列只列出一次
SELECT c.*, o.order_num FROM customers AS c, orders AS o WHERE c.cust_id = o.cust_id;

4. 外部联结

前面的联结都是将一个表中的行与另一个表中的列相关联。但有时候需要包含相关联的行,则可能需要使用外部联结来完成:

使用内部联结,检索所有客户及其订单:

1
SELECT c.cust_id, o.order_num FROM customers AS c INNER JOIN orders AS o ON c.cust_id = o.cust_id;

使用外部联结,为了检索所有用户,保护那些没有下订单的客户:

1
SELECT c.cust_id, o.order_num FROM customers AS c LEFT JOIN orders AS o ON c.cust_id = o.cust_id;

5. 带聚集函数的联结

之前的聚集函数只在单个表使用,现在对多个表使用聚集函数。

检索所有客户以及客户所下订单数:

1
2
SELECT c.cust_name, c.cust_id, COUNT(o.order_num) AS num_ord FROM customers AS c INNER JOIN orders AS o ON c.cust_id = o.cust_id GROUP BY c.cust_id;
# 此SELECT语句使用INNER JOIN将customers和orders表互相关联。GROUP BY子句按客户分组数据,因此函数调用 COUNT(orders.order_num)对每个客户的订单计数,将它作为num_ord返回。

聚集函数也可以方便地与其他联结一起使用:

1
2
# 使用LEFT JOIN 左外联结查询。对没有订单的客户也检索出来
SELECT c.cust_name, c.cust_id, COUNT(o.order_num) AS num_ord FROM customers AS c LEFT JOIN orders AS o ON c.cust_id = o.cust_id GROUP BY c.cust_id;

6. 总结

  • 注意所使用的联结类型。一般我们使用内部联结,但使用外部联
    结也是有效的。
  • 保证使用正确的联结条件,否则将返回不正确的数据。
  • 应该总是提供联结条件,否则会得出笛卡儿积。
  • 在一个联结中可以包含多个表,甚至对于每个联结可以采用不同的联结类型。虽然这样做是合法的,一般也很有用,但应该在一起测试它们前,分别测试每个联结。这将使故障排除更为简单。

组合查询(UNION)

1. UNION

可用UNION操作符来组合数条SQL查询:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 1.
SELECT vend_id, prod_id, prod_price FROM products WHERE prod_price <= 5;
# 2.
SELECT vend_id, prod_id, prod_price FROM products WHERE vend_id in (1002, 1001);

#3. 使用UNION组合1和2
SELECT vend_id, prod_id, prod_price FROM products WHERE prod_price <= 5
UNION
SELECT vend_id, prod_id, prod_price FROM products WHERE vend_id in (1002, 1001);

#4. 使用多条WHERE子句实现3
SELECT vend_id, prod_id, prod_price FROM products WHERE prod_price <= 5 OR vend_id in (1001, 1002);

使用UNION可能比使用WHERE子句更为复杂。但对于更复杂的过滤条件,或者从多个表(而不是单个表)中检索数据的情形,使用UNION可能会使处理更简单

2. 保留重复行(UNION ALL

UNION自动会去除重复的行,要想将重复的行也显示出来,使用UNION ALL

1
2
3
SELECT vend_id, prod_name, prod_price FROM products WHERE prod_price <= 5
UNION ALL
SELECT vend_id, prod_name, prod_price FROM products WHERE vend_id in (1001, 1002);

3. 排序

SELECT语句输出使用ORDER BY排序。在使用UNION组合查询时,只能使用一条ORDER BY语句,且是接在最后的SELECT语句中

1
2
3
4
SELECT vend_id, prod_name, prod_price FROM products WHERE prod_price <= 5
UNION
SELECT vend_id, prod_name, prod_price FROM products WHERE vend_id in (1001, 1002)
ORDER BY vend_id, prod_price;

ORDER BY对UNION的所有查询结果都有效。

全文本搜索

MyISAM支持全文本搜索,InnoDB不支持全文本搜索

1. 全文本搜索

LIKE关键字利用通配符可以匹配查找文本,但存在几个限制

  1. 性能:会对MySQL所有行进行检索
  2. 明确控制:不能明确地控制匹配什么,不匹配什么
  3. 智能化的结果:无法提供一种智能化选择,例如包含单个匹配的行和包含多个匹配的行(按照可能是更好的匹配来排列它们)。类似,一个特殊词的搜索将不会找出不包含该词但包含其他相关词的行。

这些限制可以用全文搜索解决。

2. 使用全文搜索

为了使用全文搜索,必须索引被搜索的列,且随着数据的改变不断的重新索引。

在索引之后,SELECT可与Match()和Against()一起使用以实际执行搜索。

启用全文本搜索支持

在创建表时启用全文本搜索:

1
2
3
4
5
6
7
8
9
CREATE TABLE productnotes
(
note_id int NOT NULL AUTO_INCREMENT,
prod_id char(10) NOT NULL,
note_date datetime NOT NULL,
note_text text NULL,
PRIMARY KEY(note_id),
FULLTEXT(note_text)
)ENGINE=MyISAM;

MySQL根据FULLTEXT(note_text)对note_text进行索引。

使用全文检索

1
2
3
4
5
6
7
#1. 全文检索
SELECT note_text FROM productnotes WHERE Match(note_text) Against('rabbit');
#Match()指定被搜索的列,Against()指定要使用的搜索表达式
#传递给 Match()的值必须与FULLTEXT()定义中的相同

#2. LIKE
SELECT note_text FROM productnotes WHERE note_text LIKE '%rabbit%';

两种方式返回结果的顺序不一样,全文搜索返回以文本匹配的良好程度排序的数据。全文件搜索的一个重要部分就是对结果排序。

1
2
SELECT note_text, Match(note_text) Against('rabbit') AS 'rank' FROM productnotes;
# 检索Match(note_text) Against('rabbit')

rank列返回包含全文本搜索计算出的等级值,由MySQL根据行中词的数目、唯一词的数目、整个索引中词的总数以及包含该词的行的数目计算出来

3. 查询扩展

使用查询扩展时,除了检索出匹配的列,还会检索相关的列。

在使用查询扩展时,MySQL对数据和 索引进行两遍扫描来完成搜索:

  • 首先,进行一个基本的全文本搜索,找出与搜索条件匹配的所有
    行;
  • 其次,MySQL检查这些匹配行并选择所有有用的词(我们将会简
    要地解释MySQL如何断定什么有用,什么无用)。
  • 再其次,MySQL再次进行全文本搜索,这次不仅使用原来的条件,
    而且还使用所有有用的词。
1
SELECT note_text FROM productnotes WHERE Match(note_text) Against('anvils' WITH QUERY EXPANSION);

4. 布尔文本搜索

即使没有FULLTEXT索引也可以使用(意味着性能低,随着数据量越多越低)

1
2
3
4
#检索包含词heavy的所有行
SELECT note_text FROM productnotes WHERE Match(note_text) Against('heavy' IN BOOLEAN MODE);
#匹配包含heavy但不包含任意以rope开始的词的行
SELECT note_text FROM productnotes WHERE Match(note_text) Against('heavy -rope*' IN BOOLEAN MODE);

image-20220406185113835

5. 全文搜索使用说明

INSERT

INSERT用来插入(或添加)行到数据库。

1. 插入完整的行

指定表名和插入到新行中的值。例子:

1
2
INSERT INTO Customers
VALUES(NULL, 'Pep E. LaPew','100 Main Street', 'Los Angeles', 'CA', '90046', 'USA', NULL, NULL);

没有输出。INSERT语句一般没有输出

如果某个列没有值,应该使用NULL填充(假设该列允许空值)。每个列必须以他们在表定义中出现的顺序填充。

这样的方法简单,但不安全,高度依赖表定义的列顺序。更安全的做法是:

1
2
#即使表结构变动,该INSERT语句也能正常工作
INSERT INTO Customers (cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email) VALUES( 'Pep E. LaPew','100 Main Street', 'Los Angeles', 'CA', '90046', 'USA', NULL, NULL);

省略列必须满足条件:

  • 该列允许空值
  • 在表定义中使用默认值。插入语句不给出值,则使用默认值。

2. 插入多行

可以使用多条INSERT语句完成,每条语句以;分号结束。

1
2
3
INSERT INTO Customers (cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email) VALUES( 'Zhang','100 Main Street', 'Los Angeles', 'CA', '90046', 'USA', NULL, NULL);
INSERT INTO Customers (cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email) VALUES( 'Li','100 Main Street', 'Los Angeles', 'CA', '90046', 'USA', NULL, NULL);
INSERT INTO Customers (cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email) VALUES( 'Liu','100 Main Street', 'Los Angeles', 'CA', '90046', 'USA', NULL, NULL);

每条语句中的列明和次序相同时,可以组合语句如下:

1
2
3
4
5
#语句有多组值,每组值用一对圆括号括起来,用逗号分隔。
INSERT INTO Customers (cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email)
VALUES( 'Yan','100 Main Street', 'Los Angeles', 'CA', '90046', 'USA', NULL, NULL),
( 'Fan','100 Main Street', 'Los Angeles', 'CA', '90046', 'USA', NULL, NULL),
( 'Xu','100 Main Street', 'Los Angeles', 'CA', '90046', 'USA', NULL, NULL);

3. 插入检索出的数据

INSERT还存在另一种形式,可以利用它将一条SELECT语句的结果插入表中。即一条INSERT语句和一条SELECT语句组成。

1
2
# INSERT SELECT
INSERT INTO Customers (cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email) SELECT cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email FROM Customers WHERE cust_name = 'Li';

这条语句将插入多少行有赖于custnew表中有多少行。如果这个表为空,则没有行被插入(也不产生错误,因为操作仍然是合法的)

SELECT语句中使用了相同的列名。但是,不一定要求列名匹配。

事实上,MySQL甚至不关心SELECT返回的列名。它使用的是列的位置,因此SELECT中的第一列(不管其列名)将用来填充表列中指定的第一个列,第二列将用来填充表列中指定的第二个列,如此等等。这对于从使用不同列名的表中导入数据是非常有用的。

UPDATE

修改表中数据,可使用UPDATE语句。可选两种方式:

  • 更新表中特定行
  • 更新表中所有行

UPDATE语句由3部分组成:

  1. 要更新的表
  2. 列名和他们的新值
  3. 确定要更新行的过滤条件

假如客户10005有了邮件地址,需要更新:

1
2
3
UPDATE customers 
SET cust_email = 'elmer@fudd.com'
WHERE cust_id = '10005';

更新多个值:

1
2
3
4
5
UPDATE customers 
SET
cust_name = 'The Fudds',
cust_email = 'elmer@fudd.com'
WHERE cust_id = '10005';

IGNORE

使用IGNORE关键字,即使列更新错误,也会忽略错误,继续更新剩余列,不回回滚。

为删除某个列的值,可以设置它为NULL(假定允许为NULL)

1
2
3
4
UPDATE customers
SET
cust_email = NULL
WHILE cust_id = '10005';

DELETE

从一个表中删除数据,使用DELETE语句。

  • 从表中删除特定的行
  • 从表中删除所有行

从表中删除一行:

1
DELETE FROM customers WHERE cust_id = 10006;

删除所有行

使用TRUNCATE TABLE而不是DELETE,TRUNCATE TABLE删除原有表再新建一个一样的表,而不是删除所有行

创建表

MySQL不仅可以操作表数据,还可以执行数据库和表的所有操作,包括表本身的创建和处理。

  • 在使用界面管理工具创建表时,工具会生成并执行相应的MySQL语句。

  • 使用SQL数据创建

1. 创建表(CREATE TABLE

使用CREATE TABLE创建表时,必须给出:

  1. 新表的名字
  2. 表列的名字和定义,用逗号分隔
1
2
3
4
5
6
7
8
9
10
CREATE TABLE IF NOT EXISTS 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(50) NULL,
#...
PRIMARY KEY(cust_id)
)ENGINE=InnoDB;

**PRIMARY KEY(cust_id)**:表的主键可以在创建时指定

在新建表时,指定的表名必须不存在,否则会保存。要覆盖一个表,SQL要求先删除该表,再创建一个新表,而不是使用CREATE TABLE覆盖。

IF NOT EXISTS:当表不存在时才创建

2. 使用NULL值

每个列是NULL列或者NOT NULL列,这种状态在创建时由表的定义规定。

NULL列允许值为空,NOT NULL列不允许值为空。如果对NOT NULL列插入了一个NULL值,则会报错

NULL与空串不一样,对NOT NULL列插入‘’空串是允许的

3. 主键

主键值必须唯一:主键使用单个列,则它的值必须唯一。如果使用多个列,则这些列的组合必须唯一。

1
2
3
4
5
#单个列为主键
PRIMARY KEY(vend_id)

#多个列为主键, 多个列用,分隔开
PRIMARY KEY(vend_id, o)

4. AUTO_INCREMENT

当一列比如订单编号为主键时,这些编号除它们是唯一的以外没有别的特殊意义。在增加一个新顾客或新订单时,需要一个新的顾客ID或订单号。这些编号可以任意,只要它们是唯一的即可。显然,使用的最简单的编号是下一个编号,所谓下一个编号是大于当前最大编号的编号。

1
cust_id int	NOT NULL AUTO_INCREMENT

AUTO_INCREMENT告诉MySQL,本列每当增加一行时自动增量

每个表只允许一个AUTO_INCREMENT列,而且它必须被索引(如,通过使它成为主键)。

last_insert_id()

使用last_insert_id()获取最后一个AUTO_INCREMENT值

1
SELECT last_insert_id();

5. 指定默认值

插入行时没有给出值,MySQL允许指定此时使用的默认值

默认值由CREATE TABLE语句列定义的DEFAULT指定

1
2
3
4
5
6
7
8
CREATE TABLE IF NOT EXISTS orderitems(
order_num int NOT NULL,
order_item int NOT NULL,
prod_id char(10) NOT NULL,
quantity int NOT NULL DEFAULT 1, #指定默认值,在未给出数量的
item_price decimal(8, 2) NOT NULL,
PRIMARY KEY(order_num, order_item)
)ENGINE=InnoDB;

6. 引擎类型

MySQL具有多种引擎,隐藏在MySQL服务内,都能执行CREATE TABLE 和SELECT等命令。

不同的引擎具有不同的功能和特性,为不同的任务选择正确的引擎能获得良好的功能和灵活性。

  • InnoDB是一个可靠的事务处理引擎,但不支持全文搜索
  • MEMORY在功能上等同于MyISAM,但由于数据存储在内存中,速度很快,适合临时表
  • MyISAM是一个性能极高的引擎,支持全文搜索但不支持事务

外键不能跨引擎:使用一个引擎的表不能引用具有使用不同引擎的表的外键

7. 更新表(ALTER TABLE

理想情况下,表存储数据后就不应该被更新。

更新表可以使用ALTER TABLE语句,必须给出以下信息:

  • 在ALTER TABLE之后给出要更新的表名(表必须存在,否则会出错
  • 所作更改的列

例子:

1
2
3
4
5
#1. 给该表增加一个vend_phone列,必须明确列的类型
ALTER TABLE vendors ADD vend_phone char(20);

#2. 删除刚刚的列:
ALTER TABLE vendors DROP COLUMN vend_phone;

定义外键

1
2
3
4
5
6
7
8
9
10
11
ALTER TABLE orderitems ADD CONSTRAINI fk_orderitems_orders
FOREIGN KEY (order_num) REFERENCES orders(order_num);

ALTER TABLE orderitems ADD CONSTRAINI fk_orderitems_products
FOREIGN KEY (prod_id) REFERENCES products(prod_id);

ALTER TABLE orders ADD CONSTRAINI fk_orders_customers
FOREIGN KEY (cust_id) REFERENCES customers(cust_id);

ALTER TABLE products ADD CONSTRAINI fk_products_vendors
FOREIGN KEY (vend_id) REFERENCES customers(vend_id);

8. 删除表(DROP TABLE

1
DROP TABLE customers2;

删除表没有确认,也不能撤销,执行这条命令将永久删除该表。

9. 重命名表(RENAME TABLE

1
2
3
4
5
6
7
8
9
#重命名单个表
RENAME TABLE customers2 TO customers3;

#重命名多个表
RENAME TABLE
customers2 TO customers3,
orders2 TO orders3,
products2 TO products3;

使用视图

MySQL 5添加了对视图的支持。

视图是虚拟的表。与包含数据的表不一样,视图只包含使用时动态检索数据的查询。

1. 何为视图

检索订购了某个产品的客户:

1
SELECT cust_name, cust_contact FROM customers, orders, orderitems WHERE customers.cust_id = orders.cust_id AND orderitems.order_num = orders.order_num AND prod_id = 'TNT2';

假如可以把整个查询包装成一个名为productcustomers的虚拟表,则可以轻松检索出相同的数据:

1
SELECT cust_name, cust_contact FROM productcustomers WHERE prod_id = 'TNT2';

这就是视图的作用。productcustomers是一个视图,它不包含表中应该有的任何列或数据,它包含的是一个SQL查询。

2. 视图作用

  1. 重用SQL语句
  2. 简化复杂的SQL操作。编写后可以方便的重用而不必知道它的基本查询细节。
  3. 使用表的组成部分而不是整个表
  4. 保护数据。可以给用户授予表的特定部分访问权限而不是整个表的访问权限
  5. 更改数据格式和表示。视图可放回与底层表的表示和格式不同的数据。

3. 规则和限制

  1. 必须唯一命名
  2. 可以创建的视图数目没有限制
  3. 创建视图必须要有足够的访问权限
  4. 视图可以嵌套
  5. ORDER BY可以用在视图中。但从该视图检索数据的SELECT中的ORDER BY将覆盖视图中的ORDER BY
  6. 视图不能索引,也不能有关联的触发器或默认值
  7. 视图可以和表一起使用(例如编写一条联结表和视图的SELECT语句。

4. 使用视图

  1. 视图用CREATE VIEW语句创建
  2. 使用SHOW CREATE VIEW viewname来查看视图的语句。
  3. 用DROP删除视图(DROP VIEW viewname
  4. 更新视图时,可以先用DROP再CREATE,也可以直接CREATE OR REPLACE VIEW。

4.1 利用视图简化复杂的联结

1
CREATE VIEW productcustomers AS SELECT cust_name, cust_contact, prod_id FROM customers, orders, orderitems WHERE customers.cust_id = orders.cust_id AND orderitems.order_num = orders.order_num;

该语句创建一个名为productcustomers的视图,联结三个表,返回已订购任意产品的客户列表。SELECT * FROM productcustomers将列出任意订购了产品的客户。

为检索订购了产品‘TNT2’的客户:

1
SELECT cust_name, cust_contact FROM productcustomers WHERE prod_id = 'TNT2';

4.2 用视图重新格式化检索数据

在单个组合计算列种返回供应商名和位置:

1
2
SELECT Concat(RTrim(vend_name), '(', RTrim(vend_country), ')') AS vend_title
FROM vendors ORDER BY vend_name;

如果经常需要这个格式化的结果,可以创建一个视图,每次需要时使用它即可。

1
CREATE VIEW vendorlocations AS SELECT Concat(RTrim(vend_name), '(', RTrim(vend_country), ')') AS vend_title FROM vendors ORDER BY vend_name;

使用:

1
SELECT * FROM vendorlocations;

4.3 用视图过滤不想要的数据

过滤没有电子邮件的客户:

1
CREATE VIEW customeremaillist AS SELECT cust_id, cust_name, cust_email FROM customers WHERE cust_email IS NOT NULL;

发送电子邮件前,需要排除没有电子邮件地址的用户。

1
SELECT * FROM customeremaillist;

4.4 使用视图与计算字段

检索某个订单种的商品,计算每种五排的总价格

1
SELECT prod_id, quantity, item_price, quantity*item_price AS expanded_price FROM orderitems WHERE order_num = 20005;

转化为视图:

1
CREATE VIEW orderitemsexpanded AS SELECT order_num, prod_id, quantity, item_price, quantity*item_price AS expanded_price FROM orderitems;

检索订单20005的详细内容:

1
SELECT * FROM orderitemsexpanded WHERE order_num = '20005';

5. 更新视图

可以使用INSERT、UPDATE和DELETE更新视图。对一个视图更新即更新其基表(对视图增加或删除行,实际上是对其基表增加或删除行)

但是并非所有视图都可更新。如果MySQL不能正确地确定被更新的基数据,则不允许更新。即如果视图定义中有如下操作,则不能进行视图的更新:

  • 分组
  • 联结
  • 子查询
  • 聚集函数(Min()、Count()、Sum()等)
  • DISTINCT
  • 导出(计算)列

换句话说很多例子的视图都是不可更新的,听上去好像一个严重的限制,但实际上不是,因为视图主要用于数据检索。

存储过程

1. 存储过程

通常一个完整的操作设计多条语句,这时可以创建存储过程,为以后的使用而保存的一条或多条MySQL语句的集合。类似批处理

2. 为什么使用存储过程

简单、安全、高性能:

  1. 将处理封装在单元中,简化复杂操作
  2. 保证数据完整性
  3. 简化对变动的管理
  4. 提高性能
  5. 编写功能更强更灵活的代码

3. 执行存储过程(CALL

CALL接受存储过程的名字以及任意参数

1
CALL productpricting(@pricelow, @pricehigh, @priceaverage);

4. 创建存储过程

返回产品的平均价格的存储过程:

1
2
3
4
CREATE PROCEDURE productpricting()
BEGIN
SELECT AVG(prod_price) AS priceaverage FROM products;
END;

该存储过程名为productpricting,()内是参数列表,即使没有参数也要列出。BEGIN和EDN用来限定存储过程体。存储过程本身是一条间的SELECT语句。

如果用命令行执行的这条存储过程创建命令,会报错。因为默认一条sql语句以分号结尾。故END;算是一条指令,解决办法可以临时更改分隔符:

1
2
3
4
5
6
7
8
9
DELIMITER //
CREATE PROCEDURE productpricting()
BEGIN
SELECT AVG(prod_price) AS priceaverage FROM products;
END;
DELIMITER ; //

#记得之后把分割符修改回分号
DELIMITER ;

使用该存储过程:

1
CALL productpricting();

5. 删除存储过程

1
DROP PROCEDURE productpricing;

6. 使用参数

一般,存储过程并不显示结果,而是把结果返回给指定的变量:

1
2
3
4
5
6
7
8
9
10
CREATE PROCEDURE productpricing(
OUT p1 DECIMAL(8, 2),
OUT ph DECIMAL(8, 2),
OUT pa DECIMAL(8, 2)
)
BEGIN
SELECT Min(prod_price) INTO p1 FROM products;
SELECT Max(prod_price) INTO ph FROM products;
SELECT Avg(prod_price) INTO pa FROM products;
END;

此存储过程接收三个参数:p1存储最低价格,ph存储最高价格,pa存储平均价格。每个参数必须指定类型,这里使用十进制。关键字OUT指出相应的参数用来存储传出的值。

MySQL支持IN(传递参数值给存储过程)、OUT(从存储过程存入值到参数)和INOUT(对存储过程传入和传出)。

记录集不是允许类型。为调用此存储过程,必须指定3个变量名:

1
2
3
4
#所有MySQL变量都必须以@开始
CALL productpricing(@pricelow, @pricehigh, @priceaverage);
#显示检索的平均价格
SELECT @priceaverage;

IN的列子:查询订单号并返回订单合计价格:

1
2
3
4
5
6
7
8
9
10
11
12
#创建
CREATE PROCEDURE ordertotal(
IN onumber INT,
OUT total DECIMAL(8, 2)
)
BEGIN
SELECT SUM(item_price * quantity) FROM orderitems WHERE order_num = onumber INTO total;
END;

#使用
CALL ordertotal(20009, @total);
SELECT @total;

7. 建立智能的存储过程

前面的存储过程都是简单的语句,在包含业务规则和只能处理时,它的威力才真正显现出来。

获取订单合计,对合计增加营业税(只针对某些顾客):

  1. 获得合计
  2. 把营业税有条件地添加到合计
  3. 返回合计(带或不带税)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
CREATE PROCEDURE ordertotal(
IN onumber INT,
IN taxable BOOLEAN,
OUT ototal DECIMAL(8, 2)
)COMMENT 'Obtain order total, optionally adding tax' #COMMET非必须
BEGIN
#声明变量total
DECLARE total DECIMAL(8, 2);
#tax,默认6
DECLARE taxrate INT DEFAULT 6;
#检索和
SELECT SUM(item_price * quantity) FROM orderitems WHERE order_num = onumber INTO total;
#是否加税
IF taxable THEN
SELECT total + (total/100 * taxrate) INTO total;
END IF;

#保存到参数ototal里
SELECT total INTO ototal;

END;

使用:

1
2
3
4
5
6
CALL ordertotal(20005, 0, @total);
SELECT @total;


CALL ordertotal(20005, 1, @total);
SELECT @total;

8. 检查存储过程

1
SHOW CREATE PROCEDURE ordertotal;

游标

游标只能用于存储过程。游标可以在检索出来的行中前进或后退一行或多行。是一个存储在MySQL服务器上的数据库查询,不是一条SELECT语句,而是被该语句检索出来的结果集。

1. 创建游标(DECLARE

1
2
3
4
5
6
7
8
9
10
11
CREATE PROCEDURE processorders()
BEGIN
DECLARE ordernumbers CURSOR
FOR
SELECT order_num FROM orders;

#打开游标
OPEN ordernumbers;
#关闭游标
CLOSE ordernumbers;
END;

但对检索出的数据什么也没做。DECLARE语句用来定义和命名游标,存储过程处理完后,游标就消失(局限于存储过程)。

2. 打开和关闭游标(OPEN CURSOR、CLOSE CURSOR

定义游标后可以打开游标

1
OPEN ordernumbers;

游标处理完成后,关闭游标:

1
CLOSE ordernumbers;

如果不明确关闭游标,MySQL将在到达END语句时自动关闭它。

3. 使用游标数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CREATE PROCEDURE processorders()
BEGIN
#定义变量o
DECLARE o INT;

#定义游标
DECLARE ordernumbers CURSOR
FOR
SELECT order_num FROM orders;

#打开游标
OPEN ordernumbers;
#获取order number
FETCH ordernumbers INTO o;
#关闭游标
CLOSE ordernumbers;
END;

FETCH检索当前行的order_num列(自动从第一行开始)到一个名为o的局部声明变量中,对检索的数据不做任何处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
CREATE PROCEDURE processorders()
BEGIN
#定义变量
DECLARE done BOOLEAN DEFAULT 0;
DECLARE o INT;

#定义游标
DECLARE ordernumbers CURSOR
FOR
SELECT order_num FROM orders;
#定义continue handle
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;
#打开游标
OPEN ordernumbers;
#获取order number
#循环处理所有行
REPEAT
FETCH ordernumbers INTO o;
#结束循环
UNTIL done END REPEAT;
#关闭游标
CLOSE ordernumbers;
END;

但与前一个例子不一样的是,这个例子中的FETCH是在REPEAT内,因此它反复执行直到done为真(由UNTILdone END REPEAT;规定)

CONTINUE HANDLER在条件出现时执行代码。当SQLSTATE ‘02000’出现时,SET done=1。SQLSTATE ‘02000’是一个未找到条件,当REPEAT由于没有更多的行供循环而不能继续时,出现这个条件。

对取出的数据进行某种实际的处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
CREATE PROCEDURE processorders()
BEGIN
#定义变量
DECLARE done BOOLEAN DEFAULT 0;
DECLARE o INT;
DECLARE t DECIMAL(8, 2);

#定义游标
DECLARE ordernumbers CURSOR
FOR
SELECT order_num FROM orders;
#定义continue handle
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;
#创建一个表如果不存在的话
CREATE TABLE IF NOT EXISTS ordertotals(order_num INT, total DECIMAL(8,2));
#打开游标
OPEN ordernumbers;
#获取order number
#循环处理所有行
REPEAT
#获取订单的order_num
FETCH ordernumbers INTO o;
#调用另一存储过程获取该order_num的合计
CALL ordertotal(o, 1, t);
#将数据插入表中
INSERT INTO ordertotals(order_num, total) VALUES(o, t);
#结束循环
UNTIL done END REPEAT;
#关闭游标
CLOSE ordernumbers;
COMMIT;
END;

使用:

1
2
CALL processorders;
SELECT * FROM ordertotals;

触发器

当某条语句在事件发生时自动执行,例如:

  • 每当增加一个顾客到数据库时,检查号码格式、州的缩写
  • 每订购一个产品时,都从库存数量中减去订购的数量
  • 无论何时删除一行,都在某个存档中保留一个副本。

以上都需要在某个表发生更改时自动处理,这就是触发器。触发器时MySQL响应以下任意语句自动执行的MySQL语句:

  • DELETE
  • INSERT
  • UPDATE

其他语句不支持触发器。

1. 创建触发器(CREATE TRIGGER

需要给出4条信息:

  1. 唯一的触发器名(触发器必须在每个表中唯一,但不是在每个数据库中唯一。但其他数据中是数据库唯一。
  2. 触发器关联的表;
  3. 触发器应响应的活动(DELTE、INSERT、UPDATE
  4. 触发器何时执行
1
2
CREATE TRIGGER newproduct AFTER INSERT ON products
FOR EACH ROW SELECT 'Product added' INTO @tips;

AFTER ISNERT指出在INSERT语句成功执行后执行。

FRO EACH ROW:对每个插入行执行

文本’Product added‘对每个插入的行显示一次

数量限制

每个表最多支持6个触发器(每条INSERT、UPDATE、DELETE的之前和之后)。单一触发器不能与多个事件或表相连。

如果BEFFOR触发器失败,则不执行请求的操作。

如果BEFFORE触发器或SQL语句本身失败,将不执行AFTER触发器。

2. 删除触发器

1
DROP TRIGGER newproduct;

3. 使用触发器

3.1 INSERT触发器

  • 在INSERT触发器内,可引用一个名为NEW的虚拟表,访问被插入的行
  • 在BEFORE INSERT触发器中,NEW中的值也可以被更新
  • 对于AUTO_INCREMENT,NEW在INSERT执行之前包含0,在INSERT执行之后包含新的自动生成的值
1
2
3
4
5
CREATE TRIGGER neworder AFTER INSERT ON orders
FOR EACH ROW SELECT NEW.order_num;
#MySQL5后不允许触发器返回任何结果,修改为:
CREATE TRIGGER neworder AFTER INSERT ON orders
FOR EACH ROW SELECT NEW.order_num INTO @ordernum;

为测试这个触发器,试着插入一行:

1
INSERT INTO orders (order_date, cust_id) VALUES(Now(), 10001);

3.2 DELETE触发器

  • 在DELETE触发器代码内,可引用一个名为OLD的虚拟表,访问被删除的行
  • OLD中的值全部只读,不能更新
1
2
3
4
5
CREATE TRIGGER deleteorder BEFORE INSERT ON orders FOR EACH ROW 
BEGIN
INSERT INTO archive_orders(order_num, order_date, cust_id)
VALUES(OLD.order_num, OLD.order_date, OLD.cust_id);
END;

使用BEFORE DELETE优点:如果由于某种原因,订单不能存档,DELETE本身将被放弃。

3.3 UPDATE触发器

  • 可以引用OLD虚拟表访问以前的值,NEW虚拟表访问新值
  • BEFORE UPDATE触发器中,NEW值也可能被更新
  • OLD中的值全部都是只读的,不能更新
1
2
CREATE TRIGGER updatevendor BEFORE UPDATE ON vendos
FOR EACH ROW SET NEW.vend_state = Upper(NEW.vend_state);

管理事务处理

1. 事务处理

事务处理可以用来维护数据库的完整性,保证成批的MySQL操作要么完全执行,要么完全不执行。

  • 事务:指一组SQL语句。
  • 回退:指撤销指定的SQL语句的过程。
  • 提交:指将为存储的SQL语句结果写入数据库表。
  • 保留点:事务处理中设置的临时占位符。

2. 控制事务处理

使用下面的语句来表示事务的开始:

1
START TRANSACTION;

2.1 回退(ROLLBACK

MySQL使用ROLLBACK命令来回退MySQL语句:

1
2
3
4
5
6
SELECT * FROM ordertotals;
START TRANSACTION;
DELETE FROM ordertotals;
SELECT * FROM ordertotals;
ROLLBACK;
SELECT * FROM ordertotals;

不能对SELECT语句回退(没有意义)。不能回退REATE和DROP操作,它们不会被撤销

2.2 提交(COMMIT

一般MySQL语句都是直接对数据库表执行和编写(隐含提交)。在事务处理中,提交不会隐含进行,要明确使用COMMIT语句:

1
2
3
4
START TRANSACTION;
DELETE FROM orderitems WHERE order_num = 20010;
DELETE FROM orders WHERE order_num = 20010;
COMMIT;

涉及两个表,使用事务处理来保证订单不被部分删除。

2.3 使用保留点

为了支持回退部分事务处理,必须能在事务处理块中某个合适的位置放置占位符,如果需要回退,可以回退到某个占位符。创建占位符可以使用:

1
SAVEPOINT delete1; #delete1 唯一标识

回退到保留点:

1
ROLLBACK TO delete1;

2.4 更改默认的提交行为

默认的MySQL行为是自动提交所有更改。为指示MySQL不自动提交,需要:

1
SET autocommit = 0;

设置autocommit为0(假)。autocommit标志是针对每个连接而不是服务器的。

全球化和本地化

1. 字符集和校对顺序

  • 字符集:字母和符号的集合
  • 编码:某个字符集成员的内部表示
  • 校对:规定字符如何比较的指令

安全管理

1. 访问控制

需要给用户提供他们所需的访问权,且仅提供他们所需的访问权,这就是访问控制。管理访问控制需要创建和管理用户账号。

2. 管理用户

MySQL用户账号和信息存储在名为mysql和MySQL的数据库中。直接访问它之一是获取所有用户账号列表:

1
2
USE mysql;
SELECT 'user' FROM user;

2.1 创建用户账号

1
CREATE USER ben IDENTIFIED BY 'p@$$w0rd';

创建用户账号时不一定需要口令。这个列子用IDENTIFIED BY ’p@$$W0rd’给出了一个口令。

2.2 删除用户账号

1
DROP USER ben;

2.3 设置访问权限

为看到赋予用户账号的权限使用SHOW GRANTS FRO

1
SHOW GRANTS FOR root;

输出结果显示用户bforta有一个权限USAGE ON。USAGE表示根本没有权限(我知道,这不很直观),所以,此结果表示在任意数据库和任意表上对任何东西没有权限。

设置权限,使用GRANT语句。至少给出以下信息:

  • 要授予的权限
  • 被授予访问权限的数据库或表
  • 用户名
1
GRANT SELECT ON  crashcourse.* TO ben;

授予用户在crashcourse的所有表上使用SELECT。

撤销指定权限,要撤销的权限必须存在,否则会出错

1
REVOKE SELECT ON crashcourse.* FROM ben;

权限层次:

  • 整个服务器GRANT ALL和REVOKE ALL
  • 真个数据可, 使用ON DATABASE.*;
  • 特定的表,使用ON databases.table;
  • 特定的列
  • 特定的存储过程

3. 更改口令(密码

更改用户口令,使用SET PASSWORD

1
SET PASSWORD FRO ben = Password('n3w p@$$0rd');

新口令必须传递到Password()函数进行加密。

设置自己的口令:在不指定用户时,SET PASSWORD更新当前登录用户的口令

1
SET PASSWORD = Password('root');

数据库维护

改善性能


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!