MySQL必知必会笔记(2)

警告
本文最后更新于 2023-04-16,文中内容可能已过时,请谨慎使用。
MySQL必知必会
豆瓣评分
8.3
《MySQL必知必会》MySQL是世界上最受欢迎的数据库管理系统之一。书中从介绍简单的数据检索开始,逐步深入一些复杂的内容,包括联结的使用、子查询、正则表达式和基于全文本的搜索、存储过程、游标、触发器、表约束,等等。通过重点突出的章节,条理清晰、系统而扼要地讲述了读者应该掌握的知识,使他们不经意间立刻功力大增。

最常用的数据类型是串数据类型。它们存储串,如名字、地址、电话号码、邮政编码等。有两种基本的串类型,分别为定长串和变长串(参见表1)。定长串的处理速度比变长串快。

表1. 串数据类型
数据类型 说明
char 1~255个字符的定长串。它的长度必须在创建时指定,否则MySQL假定为CHAR(1)
varchar 长度可变,最多不超过255字节。如果在创建时指定为varchar(n),则可存储0n个字符的变长串(其中n≤255)
enum 接受最多65535个串组成的一个预定义集合的某个串
text 最大长度为64K的变长文本
tinytext text相同,但最大长度为255字节
mediumtext text相同,但最大长度为16K
longtext text相同,但最大长度为4GB
set 接受最多65535个串组成的一个预定义集合的零个或多个串
有符号和无符号

所有数值数据类型(除BITBOOLEAN外)都可以有符号或无符号。

有符号数值列可以存储正或负的数值,无符号数值列只能存储正数。

默认情况为有符号,但如果你知道自己不需要存储负值,可以使用UNSIGNED关键字,这样做将允许你存储两倍大小的值。

表2. 数值数据类型
数据类型 说明
bit 位字段,1~64位
boolean(或bool) 布尔值,取01
decimal(或dec) 精度可变的浮点值
double 双精度浮点值
float 单精度浮点值
bigint 整数值,支持9223372036854775808~9223372036854775807(如果是UNSIGNED,为0~18446744073709551615)的数
integer(或int) 整数值,支持-2147483648~2147483647(如果是UNSIGNED,为0~4294967295)的数
mediumint 整数值,支持-8388608~8388607(如果是UNSIGNED,为0~16777215)的数
smallint 整数值,支持-32768~32767(如果是UNSIGNED,为0~65535)的数
tinyint 整数值,支持-128~127(如果为UNSIGNED,为0~255)的数
real 4字节的浮点值
表3. 日期和时间数据类型
数据类型 说明
date 表示1000-01-01~9999-12-31的日期,格式为YYYY-MM-DD
datetime datetime的组合
timestamp 功能和datetime相同(但范围较小,1970-01-01 00:00:01~2038-01-19 03:14:07)
time 格式为HH:MM:SS
year 用2位数字表示,范围是70(1970年)~69(2069年),用4位数字表示,范围是1901年~2155
表4. 二进制数据类型
数据类型 说明
BLOB Blob最大长度为64KB
MEDIUMBLOB Blob最大长度为16MB
LONGBLOB Blob最大长度为4GB
TINYBLOB Blob最大长度为255字节

mysql8保留字官方文档

并非所有引擎都支持全文本搜索。

两种最常使用的引擎为InnoDBMyISAM,前者支持全文本搜索,而后者不支持。

全文本搜索不区分大小写。

全文本搜索会计算出一个等级值,匹配文本越靠前,等级值越大,返回的结果中越靠前

如果指定多个搜索项,则包含多数匹配词的那些行将具有比包含较少词(或仅有一个匹配)的那些行更高的等级值。

# 1.支持全文本搜索
# 建表时指定要进行索引的列以支持全文本搜索
# MySQL根据子句FULLTEXT(note_text)的指示对它进行索引。
# 这里的FULLTEXT索引单个列,如果需要也可以指定多个列。
# 不要在导入数据时使用FULLTEXT,更新索引需要时间,可以在导入数据后再修改表
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;

# 2.进行全文本搜索
# 在索引之后,使用两个函数Match()和Against()执行全文本搜索
# Match()指定被搜索的列
# Against()指定要使用的搜索表达式。Against('rabbit')指定词rabbit作为搜索文本
select note_text from productnotes where match(note_text) against('rabbit');

# 上面的简单任务也可以使用like语句完成
select note_text from productnotes where note_text like '%rabbit%';

# rank是MySQL版本8.0.2中定义的MySQL保留字!!!不能使用rank作为列名
# 当字段名与MySQL保留字冲突时,可以用字符''将字段名括起来
# 或者改为其他名字,比如as rank1等
# 计算全文本搜索结果的等级值
select note_text, match(note_text) against('rabbit') as 'rank' from productnotes; 
# 全文本搜索排除等级为0的行,并且搜索结果按照等级值降序排列

# 3.查询拓展
# 进行一个简单的全文本搜索,没有查询扩展
select note_text from productnotes where match(note_text) against('anvils');
# 相同的搜索,这次使用查询扩展
select note_text from productnotes where match(note_text) against('anvils' with query expansion);
表5. 全文本布尔操作符
布尔操作符 说明
+ 包含,词必须存在
- 排除,词必须不出现
> 包含,而且增加等级值
< 包含,且减少等级值
() 把词组成子表达式(允许这些表达式作为一个组被包含、排除、排列等)
~ 取消一个词的排序值
* 词尾的通配符
"" 定义一个短语(与单个词的列表不一样,它匹配整个短语一边包含或排除这个短语)

在布尔方式中,不按等级值降序排序返回的行。

# 全文本搜索检索包含词heavy的所有行
# 关键字IN BOOLEAN MODE,实际上没有指定布尔操作符,其结果与没有指定布尔方式的结果相同
select note_text from productnotes where match(note_text) against('heavy' in boolean mode);
# -rope* 排除包含以rope开始的词(如ropes)的行
select note_text from productnotes where match(note_text) against('heavy -rope*' in boolean mode);

# 匹配包含词rabbit和bait的行
select note_text from productnotes where match(note_text) against('+rabbit +bait' in boolean mode);

# 不指定操作符,搜索匹配包含rabbit和bait中的至少一个词的行
select note_text from productnotes where match(note_text) against('rabbit bait' in boolean mode);

# 搜索匹配短语rabbit bait而不是匹配两个词rabbit和bait。 
select note_text from productnotes where match(note_text) against('"rabbit bait"' in boolean mode);

# 匹配rabbit和carrot,增加前者的等级,降低后者的等级
select note_text from productnotes where match(note_text) against('>rabbit <carrot' in boolean mode);

# 必须匹配词safe和combination,降低后者的等级
select note_text from productnotes where match(note_text) against('+safe +(<combination)' in boolean mode);
  • 在索引全文本数据时,短词被忽略且从索引中排除。短词定义为那些具有3个或3个以下字符的词(如果需要,这个数目可以更改)。
  • MySQL带有一个内建的非用词(stopword)列表,这些词在索引全文本数据时总是被忽略。如果需要,可以覆盖这个列表(stopword列表详见这里)。
  • 许多词出现的频率很高,搜索它们没有用处(返回太多的结果)。因此,MySQL规定了一条50%规则,如果一个词出现在50%以上的行中,则将它作为一个非用词忽略。50%规则不用于in boolean mode
  • 如果表中的行数少于3行,则全文本搜索不返回结果(因为每个词或者不出现,或者至少出现在50%的行中)。
  • 忽略词中的单引号。例如,don't索引为dont
  • 不具有词分隔符(包括日语和汉语)的语言不能恰当地返回全文本搜索结果。
  • 如前所述,仅在MyISAM数据库引擎中支持全文本搜索。
  • 暂不支持邻近操作符。

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

插入可以用几种方式使用:

  • 插入完整的行
  • 插入行的一部分
  • 插入多行
  • 插入某些查询的结果
-- 插入完整的行 
# 插入一个新客户到customers表
# 简单但不安全,如果原来表列结构调整,会有问题 
insert into customers values(null,'Pep E. LaPew','100 Main Street','Los Angeles','CA','90046','USA',NULL,NULL);
# 表明括号内明确列名,更安全,稍繁琐 
# 可以忽略某些列,比如cust_id
# 忽略的列必须满足下面某个条件
# 1.该列定义为允许NULL值(无值或空值)。
# 2.在表定义中给出默认值。这表示如果不给出值,将使用默认值。
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);

-- 插入多个行 
# 方法1: 提交多个insert 语句
insert into customers(cust_name,cust_address,cust_city,cust_state,cust_zip,cust_country)
values('Pep E. LaPew','100 Main Street','Los Angeles','CA','90046','USA');
insert into customers(cust_name,cust_address,cust_city,cust_state,cust_zip,cust_country)
values('M. Martian','42 Galaxy Way','New York','NY','11213','USA');
# 方法2: 只要每条INSERT语句中的列名(和次序)相同,可以如下组合各语句
# 单条INSERT语句有多组值,每组值用一对圆括号括起来,用逗号分隔

insert into customers(cust_name,cust_address,cust_city,cust_state,cust_zip,cust_country)
values('Pep E. LaPew','100 Main Street','Los Angeles','CA','90046','USA'),('M. Martian','42 Galaxy Way','New York','NY','11213','USA');

-- 插入检索出的数据
-- INSERT还存在另一种形式,可以利用它将一条SELECT语句的结果插入表中。
-- 建立一张custom_new表
CREATE TABLE `custnew` (
    `cust_id` int(11) NOT NULL AUTO_INCREMENT,
    `cust_name` char(50) NOT NULL,
    `cust_address` char(50) DEFAULT NULL,
    `cust_city` char(50) DEFAULT NULL,
    `cust_state` char(5) DEFAULT NULL,
    `cust_zip` char(10) DEFAULT NULL,
    `cust_country` char(50) DEFAULT NULL,
    `cust_contact` char(50) DEFAULT NULL,
    `cust_email` char(255) DEFAULT NULL,
    PRIMARY KEY (`cust_id`)
) ENGINE=InnoDB;
# 将customers表的数据导入custom_new表中
# 这里省略了custs_id,这样MySQL就会重新生成新值。
insert into custnew (cust_contact,cust_email,cust_name,cust_address,cust_city,cust_state,cust_zip,cust_country)
select cust_contact,cust_email,cust_name,cust_address,cust_city,cust_state,cust_zip,cust_country from customers;
不要省略WHERE子句
在使用UPDATE时一定要注意细心。因为稍不注意,就会更新表中所有行。
IGNORE关键字
如果用UPDATE语句更新多行,并且在更新这些行中的一行或多行时出一个现错误,则整个UPDATE操作被取消(错误发生前更新的所有行被恢复到它们原来的值)。为了即使是发生错误,也继续进行更新,可使用IGNORE关键字,如下所示:UPDATE IGNORE customers...
-- update语句 : 删除或更新指定列 
# 更新某个列,比如客户10005现在有了电子邮件地址
update customers set cust_email = 'elmer@fudd.com' where cust_id = 10005;
# 更新多个列 
UPDATE customers 
SET cust_name = 'The Fudds',
    cust_email = 'elmer@fudd.com'
WHERE cust_id = 10005;

# 删除某个列的值,可设置它为NULL(假如表定义允许NULL值)
update customers set cust_email = null where cust_id = 10005;

-- delete 语句: 删除整行而不是某列 
# 从customers表中删除一行
delete from customers where cust_id = 10006;

-- 删除custnew表中所有行
delete from custnew;

-- truncate table语句 
# 如果想从表中删除所有行,不要使用DELETE,可使用TRUNCATE TABLE语句
# TRUNCATE实际是删除原来的表并重新创建一个表,而不是逐行删除表中的数据
# 删除cust_new表
TRUNCATE TABLE custnew; 
  • 除非确实打算更新和删除每一行,否则绝对不要使用不带WHERE子句的UPDATEDELETE语句。
  • 保证每个表都有主键。
  • 在对UPDATEDELETE语句使用wHERE子句前,应该先用SELECT进行测试,保证它过滤的是正确的记录,以防编写的WHERE子句不正确。
  • 使用强制实施引用完整性的数据库,这样MySQL将不允许删除具有与其他表相关联的数据的行。
注意

如果你仅想在一个表不存在时创建它,应该在表名前给出IF NOT EXISTS

这样做不检查已有表的模式是否与你打算创建的表模式相匹配。它只是查看表名是否存在,并且仅在表名不存在时创建它。

关于NULL值

NULL值就是没有值或缺值。允许NULL值的列也允许在插入行时不给出该列的值。不允许NULL值的列不接受该列没有值的行,换句话说,在插入或更新行时,该列必须有值。

NULL值不等于''(空字符串)

MySQL中默认列允许NULL值

AUTO_INCREMENT告诉MySQL,本列每当增加一行时自动增量。每次执行一个INSERT操作时,MySQL自动对该列增量,给该列赋予下一个可用的值。这样给每个行的某列分配一个唯一的列值,从而该列可以用作主键。每个列只允许一个AUTO_INCREMENT列,而且它必须被索引

覆盖AUTO_INCREMENT
如果一个列被指定为AUTO_INCRE-MENT,如何使用特殊的值?你可以简单地在INSERT语句中指定一个值,只要它是唯一的(至今尚未使用过)即可,该值将被用来替代自动生成的值。后续的增量将开始使用该手工插入的值

如果在插入行时没有给出值,MySQL允许指定此时使用的默认值。如orderitems指定quantity列的值默认为1

CREATE TABLE 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;
注意
与大多数DBMS不一样,MySQL不允许使用函数作为默认值,它只支持常量。

MySQL与其他DBMS不一样,它具有多种引擎。它打包多个引擎,这些引擎都隐藏在MySQL服务器内,全都能执行CREATE TABLESELECT等命令。

# 查看默认使用的引擎类型
SHOW VARIABLES LIKE 'default_storage_engine%';
  • InnoDB是一个可靠的事务处理引擎,它不支持全文本搜索 (默认使用该引擎)
  • MEMORY在功能等同于MyISAM,但由于数据存储在内存(不是磁盘)中,速度很快(特别适合于临时表);
  • MyISAM是一个性能极高的引擎,它支持全文本搜索,但不支持事务处理。
外键不能跨引擎
混用引擎类型有一个大缺陷。外键不能跨引擎,即使用一个引擎的表不能引用具有使用不同引擎的表的外键。
-- 新建customers表
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(5)   NULL ,
  cust_zip     char(10)  NULL ,
  cust_country char(50)  NULL ,
  cust_contact char(50)  NULL ,
  cust_email   char(255) NULL ,
  PRIMARY KEY (cust_id)
) ENGINE=InnoDB;

-- 更新表 alter table 
# 给vendors表增加一个名为vend_phone的列
alter table vendors 
add vend_phone char(20);
# 删除刚刚添加的列
alter table vendors
drop column vend_phone;

# ALTER TABLE的一种常见用途是定义外键
# 以下为书本配套文件create.sql中定义外键的语句 
# 使用ALTER TABLE语句前记得做好备份! ! !
ALTER TABLE orderitems ADD CONSTRAINT fk_orderitems_orders FOREIGN KEY (order_num) REFERENCES orders (order_num);
ALTER TABLE orderitems ADD CONSTRAINT fk_orderitems_products FOREIGN KEY (prod_id) REFERENCES products (prod_id);
ALTER TABLE orders ADD CONSTRAINT fk_orders_customers FOREIGN KEY (cust_id) REFERENCES customers (cust_id);
ALTER TABLE products ADD CONSTRAINT fk_products_vendors FOREIGN KEY (vend_id) REFERENCES vendors (vend_id);

-- 删除表
# 删除customers2表(假设它存在),删除整张表而不单单是其内容
drop table customers2;

-- 重命名表 
# 使用RENAME TABLE语句可以重命名一个表 (假设存在下述表)
rename table customers2 to customers;
# 对多个表重命名(假设存在下述表)
rename table backup_customers to customer,
			 backup_vendors to vendors,
             backup_products to products;
关于视图

视图是一种虚拟表。它们包含的不是数据而是根据需要检索数据的查询。

视图提供了一种MySQL的SELECT语句层次的封装,可用来简化数据处理以及重新格式化基础数据或保护基础数据。

  • 与表一样,视图必须唯一命名(不能给视图取与别的视图或表相同的名字)。
  • 对于可以创建的视图数目没有限制。
  • 为了创建视图,必须具有足够的访问权限。这些限制通常由数据库管理人员授予。
  • 视图可以嵌套,即可以利用从其他视图中检索数据的查询来构造一个视图。
  • ORDER BY可以用在视图中,但如果从该视图检索数据SELECT中也 含有ORDER BY,那么该视图中的ORDER BY将被覆盖。
  • 视图不能索引,也不能有关联的触发器或默认值。
  • 视图可以和表一起使用。例如,编写一条联结表和视图的SELECT语句。
/*视图提供了一种MySQL的SELECT语句层次的封装,可用来简化数据处理以及重新格式化基础数据或保护基础数据。 */ 

-- 创建视图 create view
-- 查看已创建视图的语句 show create view viewname
-- 删除视图 drop view viewname
-- 更新视图(有下面两种方式)
   -- 1. 先drop后create 
   -- 2. 直接使用create or repalce view

# 创建一个名为productcustomers的视图
# 保存了订购了任意产品的所有客户数据
create view productcustomers as
select cust_name,cust_contact,prod_id
from customers,orders,orderitems
where customers.cust_id = orders.cust_id
and orders.order_num = orderitems.order_num;
# 检索订购了产品TNT2的客户
select cust_name,cust_contact from productcustomers where prod_id = 'TNT2';

# 用视图重新格式化检索出的数据
# (来自第10章)在单个组合计算列中返回供应商名和位置
select concat(rtrim(vend_name),' (',rtrim(vend_country),')') as vend_title from vendors order by vend_name;
# 若经常使用上述格式组合,可以创建视图 
create view vendorlocations as
select concat(rtrim(vend_name),' (',rtrim(vend_country),')') as vend_title from vendors order by vend_name;
# 检索出以创建所有邮件标签的数据
select * from vendorlocations;

# 用视图过滤不想要的数据
# 定义customeremaillist视图,它过滤没有电子邮件地址的客户
create view customeremaillist as 
select cust_id,cust_name,cust_email from customers
where cust_email is not null;
select * from customeremaillist;

# 使用视图与计算字段
# (来自第10章)检索某个特定订单中的物品,计算每种物品的总价格
select prod_id,quantity,item_price,quantity*item_price as expanded_price from orderitems where order_num = 20005;
# 将其转换为一个视图
create view orderitemsexpanded as 
select order_num,prod_id,quantity,item_price,quantity*item_price as expanded_price from orderitems;
# 创建视图的时候select添加了列名order_num,否则无法按照order_num进行过滤查找 
select * from orderitemsexpanded where order_num = 20005;
将视图用于检索
视图中虽然可以更新数据,但是有很多的限制。一般情况下,最好将视图作为查询数据的虚拟表,而不要通过视图更新数据
  • GROUP BY
  • HAVING;
  • 表的连接;
  • 子查询;
  • 并(UNIONor UNION ALL);
  • 聚集函数(Min()count()Sum()等);
  • DISTINCT;
  • 导出(计算)列。

如果视图有以上操作,则不能进行更新!

  一组可编程的函数,是为了完成特定功能的SQL语句集,经编译创建并保存在数据库中,用户可通过指定存储过程的名字并给定参数(需要时)来调用执行。

注意
使用命令行创建存储过程时,如果要避免存储过程中的;MySQL语句末尾的;发生解释冲突,解决办法是临时更改命令行中的语句分隔符,比如使用//进行分隔
DELIMITER // # 临时修改语句分隔符
CREATE PROCEDURE productpricing()
BEGIN
	SELECT Avg(prod_price) AS priceaverage
    FROM products;
END //
DELIMITER ; # 修改回去

正常创建

 # 存储过程定义
 # ()中可以定义传入的参数
CREATE PROCEDURE productpricing()
# 存储过程体
BEGIN 
	SELECT Avg(prod_price) AS priceaverage
    FROM products;
END
call productpricing();
-- 删除存储过程,请注意:没有使用后面的(),只给出存储过程名。
drop procedure productpricing;
-- 当存储过程不存在进行删除时不报错可以使用下面的语句
drop procedure if exists productpricing;

MySQL支持IN(传递给存储过程)、OUT(从存储过程传出,如这里所用)和INOUT(对存储过程传入和传出)类型的参数。

关键字OUT指出相应的参数用来从存储过程传出一个值(返回给调用者)。

存储过程的代码位于BEGIN和END语句内,如前所见,它们是一系列SELECT语句,用来检索值,然后保存到相应的变量(通过指定INTO关键字)。

带参数版的productpricing:调用之后返回3个值,分别是产品的最低价格、最高价格、平均价格。

create procedure productpricing(
	out pl decimal(8,2), # 返回的结果保留两位小数
	out ph decimal(8,2), 
	out pa decimal(8,2)
)
begin
	select min(prod_price) into pl from products;
    select max(prod_price) into ph from products;
	select avg(prod_price) into pa from products;
end
# 所有MySQL变量必须以@开头
# 使用三个变量保存调用传出的结果
call productpricing(@pricelow,@pricehigh,@priceaverage);

# 查看结果
select @pricelow,@pricehigh,@priceaverage;
# 使用IN和OUT参数,存储过程ordertotal接受订单号并返回该订单的合计

create procedure ordertotal(
	in onumber int,   				# onumber定义为IN,因为订单号被传入存储过程
    out ototal decimal(8,2)			# ototal为OUT,因为要从存储过程返回合计
)
begin
	select sum(item_price*quantity) from orderitems 
    where order_num = onumber
    into ototal;
end
# 传入订单号获得总价
call ordertotal(20005,@total);
# 显示总价
select @total;
-- 存储过程名:ordertotal
-- 参数: onumber: 订单号
-- 		taxable:值为1时需要纳税,为0时不需要
-- 		ototal:最后的合计

create procedure ordertotal(
	in onumber int,
	in taxable boolean,
    out ototal decimal(8,2)
) comment 'obtain order total, optionally adding tax'
begin
	-- 定义局部变量total
    declare total decimal(8,2);
    -- 定义局部变量税率(局部变量支持默认值)
    declare taxrate int default 6;
    -- 根据输入变量onumber获得订单合计,传递给局部变量total
	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

# 调用上述存储过程,不加税 
call ordertotal(20005,0,@total);
select @total;
# 调用上述存储过程,加税 
call ordertotal(20005,1,@total);
select @total;
# 显示创建某个存储过程的CREATE语句
show create procedure ordertotal;

# 获得包括何时、由谁创建等详细信息的存储过程列表
# 该语句会列出MySQL中所有存储过程
show procedure status;

# 过滤模式:只展示ordertotal这个存储过程
show procedure status like 'ordertotal';
注意
只能用于存储过程不像多数DBMSMySQL游标只能用于存储过程(和函数)。

游标(cursor),是一个存储在MySQL服务器上的数据库查询,游标不是一条SELECT语句,而是被该语句检索出来的结果集;可以看做是指向查询结果集的指针;通过cursor,就可以一次一行的从结果集中把行拿出来处理。

游标的处理过程(4步)

  1. 声明游标(declare):declare 游标名称 cursor for table(或查询的结果集);
  2. 打开游标(open):open 游标名称
  3. 检索游标(fetch):利用游标获取对应行的数据,fetch 游标名称 into 局部变量
  4. 关闭游标(close):close 游标名称

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

-- 创建、打开、关闭游标 
# 定义名为ordernumbers的游标,检索所有订单
# 游标定义在存储过程中,存储过程处理完成之后,游标就消失了
create procedure processorders()
begin
	-- decalre the cursor 声明游标 
	declare ordernumbers cursor
    for
    select order_num from orders;

	-- open the cursor 打开游标
	open ordernumbers;
	-- close the cursor 关闭游标
	close ordernumbers;
end

-- 使用游标数据的几个例子
# 例1:检索当前行的order_num列,对数据不做实际处理
create procedure processorders()
begin
	--  声明局部变量
    declare o int;
    -- 声明游标 
	declare ordernumbers cursor
    for
    select order_num from orders;
	-- 打开游标
	open ordernumbers;
    -- 获得订单号 
    fetch ordernumbers into o;
    /*fetch检索当前行的order_num列(将自动从第一行开始)到一个名为o的局部声明变量中。
    对检索出的数据不做任何处理。*/
	-- 关闭游标
	close ordernumbers;
end

# 例2:循环检索数据,从第一行到最后一行,对数据不做实际处理
create procedure processorders()
begin
	-- 声明局部变量
    declare done boolean default false;
    declare o int;
	--  声明游标 
	declare ordernumbers cursor
	for
    select order_num from orders;
  	-- 
    declare continue handler for sqlstate '02000' set done = true;
    -- SQLSTATE '02000'是一个未找到条件,当REPEAT由于没有更多的行供循环而不能继续时,出现这个条件。
    -- 打开游标
	open ordernumbers;
    -- 遍历所有行 
    repeat
    -- 获得订单号 
    	fetch ordernumbers into o;
    -- FETCH在REPEAT内,因此它反复执行直到done为真
    until done end repeat;
  	-- 关闭游标
	close ordernumbers;
end 

# 例3:循环检索数据,从第一行到最后一行,对取出的数据进行某种实际的处理
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;
    -- declare continue handler
    declare continue handler for sqlstate '02000' set done = 1;
    -- 新建表以保存数据(订单号和总价)
    create table if not exists ordertotals
    (order_num int,total decimal(8,2));
    -- 打开游标
    open ordernumbers;
    -- 遍历所有行
    repeat
    -- 获取订单号
    fetch ordernumbers into o;
    -- 计算订单金额
    call ordertotal(o,1,t);  # 直接调用之前创建的ordertotal存储过程
    -- 将订单号、金额插入表ordertotals内
    insert into ordertotals(order_num,total) values(o,t);
	-- end of loop
    until done end repeat;
	--  关闭游标
	close ordernumbers;
end 
# 调用存储过程 precessorders()
call processorders();
# 输出结果
select * from ordertotals;

触发器是与表事件相关的特殊的存储过程,它的执行不是由程序调用,也不是手工启动,而是由事件来触发,比如当对一个表进行操作(INSERTDELETEUPDATE)时就会激活它执行。简单理解为:你执行一条sql语句,这条sql语句的执行会自动去触发执行其他的sql语句。

navicate查看表上的触发器

1.点击数据库

2.双击表名,查看表数据

3.使用快捷键ctrl+d打开表设计,点击触发器选项卡

创建触发器必须给出以下4条信息:

  • 唯一的触发器名

  • 触发器关联的表

  • 触发器应该响应的活动(INSERTDELETEUPDATE)

  • 触发器何时执行(处理之前或之后)

仅支持表
只有表才支持触发器,视图不支持(临时表也不支持)。

触发器按每个表每个事件每次地定义,**每个表每个事件每次只允许一个触发器。**因此,每个表最多支持6个触发器(每条INSERTUPDATEDELETE的之前和之后)。单一触发器不能与多个表和多个事件关联。

触发器使用create trigger语句创建,比如

create trigger newproduct after insert on products 
for each row 
begin
	select 'product added' into @new_pro;
end

删除触发器

drop trigger newproduct;
  • INSERT触发器代码内,可引用一个名为NEW的虚拟表,访问被插入的行
  • BEFORE INSERT触发器中,NEW中的值也可以被更新(允许更改被插入的值)
  • 对于AUTO_INCREMENT列,NEWINSERT执行之前包含0,在INSERT执行之后包含新的自动生成值
# order表的order_num列是AUTO_INCREMENT列
create trigger neworder after insert on orders
for each row 
begin
	select new.order_num into @order_num;
end
  • DELETE触发器代码内,你可以引用一个名为OLD的虚拟表,访问被删除的行;
  • OLD中的值全都是只读的,不能更新。
# 创建一个备份表
CREATE TABLE archive_orders
(
  order_num  int      NOT NULL ,
  order_date datetime NOT NULL ,
  cust_id    int      NOT NULL ,
  PRIMARY KEY (order_num)
) ENGINE=InnoDB;

# 使用OLD保存将要被删除的行到一个备份表中 
create trigger deleteorder 
before delete 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); # 引用一个名为OLD的虚拟表,访问被删除的行
end 

delete from orders where order_num=20005
  • UPDATE触发器代码中,你可以引用一个名为OLD的虚拟表访问以前(UPDATE语句前)的值,引用一个名为NEW的虚拟表访问新更新的值;
  • BEFORE UPDATE触发器中,NEW中的值可能也被更新(允许更改将要用于UPDATE语句中的值);
  • OLD中的值全都是只读的,不能更新。
# update触发器
# 在更新vendors表中的vend_state值时,插入前先修改为大写格式 
create trigger updatevendor before update on vendors 
for each row set new.vend_state = upper(new.vend_state);
# 更新1001供应商的州为china
update vendors set vend_state = 'china' where vend_id =1001;
# 查看update后数据,1001供应商对应的vend_state自动更新为大写的CHINA
select vend_state from vendors where vend_id=1001;
  • 触发器的执行是自动的。如果INSERTUPDATEDELETE语旬能够执行,则相关的触发器也能执行。
  • 使用触发器预处理来保证数据的一致性(大小写、格式等)。在触发器中执行这种类型的处理的优点是它总是进行这种处理,而且是透明地进行,与客户机应用无关。
  • 触发器的一种非常有意义的使用是创建存档。使用触发器,把更改(如果需要,甚至还有之前和之后的状态)记录到另一个表非常容易。
  • 不能从触发器内调用存储过程。所需的存储过程代码需要复制到触发器内。
事务处理支持
并非所有引擎都支持明确的事务处理管理。MyISAM和 InnoDB是两种最常使用的引擎。前者不支持明确的事务处理管理,而后者支持。

事务处理是一种机制,用来管理必须成批执行的MySQL操作,以保证数据库不包含不完整的操作结果。利用事务处理,可以保证一组操作不会中途停止,它们或者作为整体执行,或者完全不执行(除非明确指示)。如果没有错误发生,整组语句提交给(写到)数据库表。如果发生错误,则进行回退(撤销)以恢复数据库到某个已知且安全的状态。

  • 事务(transaction):指一组SQL语句
  • 回退(rollback):指撤销指定SQL语句的过程
  • 提交(commit):指将未存储的SQL语句结果写入数据库表
  • 保留点(savepoint):指事务处理中设置的临时占位符(place-holder),你可以对它发布回退(与回退整个事务处理不同)。
-- 控制事务处理
# 开始事务及回退 
select * from ordertotals;   # 查看ordertotals表显示不为空
start transaction;  		 # 开始事务处理 
delete from ordertotals;     # 删除ordertotals表中所有行
select * from ordertotals;   # 查看ordertotals表显示为空
rollback;				     # rollback语句回退 
select * from ordertotals;   # rollback后,再次查看ordertotals表显示不为空
哪些语句可以回退
事务处理用来管理INSERTUPDATEDELETE语句。你不能回退SELECT语句(这样做也没有什么意义)。也不能回退CREATEDROP操作。事务处理块中可以使用这两条语句,但如果你执行回退,它们不会被撤销。

一般的MySQL语旬都是直接针对数据库表执行和编写的。这就是所谓的隐含提交(implicit commit),即提交(写或保存)操作是自动进行的。 但是,在事务处理块中,提交不会隐含地进行。为进行明确的提交,使用COMMIT语句,如下所示

# commit 提交 
start transaction;
delete from orderitems where order_num = 20010;
delete from orders where order_num = 20010;
commit;   # 仅在上述两条语句不出错时写出更改 

为了支持回退部分事务处理,必须能在事务处理块中合适的位置放置占位符。这样,如果需要回退,可以回退到某个占位符。 这些占位符称为保留点。为了创建占位符,可如下使用SAVEPOINT语句:

# savepoint 保留点 
# 创建保留点
savepoint delete1;
# 回退到保留点 
rollback to delete1;
# 保留点在执行一条ROLLBACK或COMMIT语句后会自动释放
# 也可使用release savepoint手动释放保留点
release savepoint delete1;

可以在MySQL中设置任意多的保留点,越多越好。

默认的MySQL行为是自动提交所有修改,也就是执行一条SQL语句,所做的修改立即生效!

-- 更改默认的提交行为 
set autocommit = 0;  # 设置autocommit为0(假)指示MySQL不自动提交更改,不管有没有COMMIT语句
  • 字符集为字母和符号的集合

  • 编码为某个字符集成员的内部表示

  • 校对为规定字符如何比较的指令

-- 字符集和校对顺序
# 查看所支持的字符集完整列表
show character set;
# 查看所支持校对的完整列表,以及它们适用的字符集
show collation;
# 确定所用系统的字符集和校对
show variables like 'character%';
show variables like 'collation%';
# 以_ci结尾的校对不区分大小写,以_cs结尾的校对区分大小写
# 使用带子句的CREATE TABLE,给表指定字符集和校对
create table mytable
(
	column1 int,
    column2 varchar(10)
)default character set hebrew 
collate hebrew_general_ci;
# 除了能指定字符集和校对的表范围外,MySQL还允许对每个列设置它们
create table mytable
(
	column1 int,
    column2 varchar(10),
    column3 varchar(10) character set latin1 collate latin1_general_ci
)default character set hebrew 
collate hebrew_general_ci;
# 校对collate在对用ORDER BY子句排序时起重要的作用
# 如果要用与创建表时不同的校对顺序排序,可在SELECT语句中指定备用的校对顺序
select * from customers order by lastname,firstname collate latin1_general_cs;
select的其他collate子句
除了这里看到的在ORDER BY子句中使用以外,COLLATE还可以用于GROUP BYHAVING、聚集函数、别名等。

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

-- 管理用户
# 需要获得所有用户账号列表时
# mysql数据库有一个名为user的表,它包含所有用户账号。user表有一个名为user的列
use mysql;
select user from user;
-- 创建用户账号 
# 使用create user
# 用户名: ben 密码: passwd
create user ben identified by 'passwd';
# 重命名一个用户账号
rename user ben to bforta;
# 删除用户账号 
drop user bforta;
# 查看赋予用户账号的权限
show grants for bforta;
# 允许用户在(crashcourse数据库的所有表)上使用SELECT,只读
grant select on crashcourse.* to bforta;
# 重新查看赋予用户账号的权限,发生变化 
show grants for bforta;
# 撤销特定的权限
revoke select on crashcourse.* from bforta;
# 简化多次授权
grant select,insert on crashcourse.* to bforta;

-- 更改口令,新口令必须传递到Password()函数进行加密。
set password for bforta = 'newpasswd'; 
# 原来课本中使用的password()加密函数,在8.0版本中已经移除 
# password() :This function was removed in MySQL 8.0.11.
-- 如果不指定用户名,直接修改当前登录用户的口令 
set password = 'newpasswd';
表6. mysqldump常见参数
参数名 缩写 含义 默认值
--user -u 连接服务器所使用的用户名
--password -p 连接所用的用户密码
--host -h MySQL服务端地址
--port -P MySQL端口号
--databases -B 指定要备份的数据库
--tables 指定要备份的表
--all-databases -A 备份MySQL服务器上的所有数据库
--routines -R 导出存储过程以及自定义函数 未开启
--triggers 导出触发器 开启
--no-data -d 不导出任何数据,只导出数据库表结构
# 导出db数据库下的db_table表到test.sql
mysqldump -u root -p db db_table > test.sql
# 导出db数据库下的所有表到test.sql
mysqldump -u root -p db > test.sql
# 导出db1、db2数据库到test.sql
mysqldump -u root -p --databases db1 db2 > test.sql
# 导出db1的table_1表和table_2表到test.sql
mysqldump -u root -p --databases db1 --tables table_1 table_2 > test.sql
# 只导出db1数据库的所有表结构不导出数据
mysqldump -u root -p --databases db1 --no-data > test.sql
# 导出db1的表+视图+存储过程+表结构
mysqldump -u root -p --databases db1 -R > test.sql

导入数据库文件

source ~/test.sql

MySQL提供了一系列的语句,可以用来保证数据库正确和正常运行。

# 用于检查表的键状态是否正确
analyze table orders;
# 检查表是否存在错误 
check table orders,orderitems;
# 回收删除大量数据表的空间
optimize table orders;

MySQL中,可以使用IF/ELSE语句来实现条件判断。IF/ELSE语句的一般格式如下:

IF(condition, statement_if_true, statement_if_false)

这里的condition是一个布尔表达式,如果它的值为TRUE,则执行statement_if_true,否则执行statement_if_false

例如,假设你有一个名为users的表,其中包含了用户的姓名和年龄。如果要根据用户的年龄来决定他们所属的年龄段,可以使用如下的IF/ELSE语句:

SELECT name, age,
    IF(age < 18, 'Underage', IF(age < 65, 'Adult', 'Senior')) AS age_group
FROM users;

在这个例子中,我们首先判断用户的年龄是否小于18岁,如果是,就返回字符串’Underage’;否则,我们继续判断用户的年龄是否小于65岁,如果是,则返回字符串’Adult’, 否则返回’Senior’。

MySQL中,可以使用CASE语句来进行条件判断,它的一般格式如下:

CASE expression
    WHEN value1 THEN result1
    WHEN value2 THEN result2
    ...
    ELSE else_result
END

在这个语句中,expression是要比较的表达式,value1value2等是可能的值,result1result2等是对应的结果。如果表达式与某个值匹配,则返回该值对应的结果;如果没有匹配,则返回else_result

例如,假设你有一个名为people的表,其中包含了姓名和性别。如果要将查询结果中性别的’f’和’m’改为女性和男性,可以使用如下的CASE语句:

SELECT NAME,
	( CASE sex WHEN 'f' THEN '女性' WHEN 'm' THEN '男性' END ) AS sex
FROM
	people

需要注意的是,在MySQL中,CASE语句还可以有其他形式的写法,例如使用简化的语法:

CASE 
    WHEN condition1 THEN result1
    WHEN condition2 THEN result2
    ...
    ELSE else_result
END

这种写法与上面的语法类似,只是省略了表达式expression,直接在WHEN子句中写入条件。

例如,假设你有一个名为orders的表,其中包含了用户的购买金额和日期。如果要根据订单金额的不同,将订单分成三个等级(低级、中级、高级),可以使用如下的CASE语句:

SELECT amount, date,
    CASE 
        WHEN amount < 1000 THEN 'Low'
        WHEN amount >= 1000 AND amount <= 5000 THEN 'Medium'
        ELSE 'High'
    END AS order_level
FROM orders;

在这个例子中,我们首先判断订单金额是否小于1000元,如果是,就返回字符串’Low’;如果不是,我们继续判断订单金额是否在1000元到5000元之间,如果是,则返回字符串’Medium’,否则返回’High’。


相关文章