分组数据
数据分组
分组允许把数据分为多个逻辑组,以便能对每个组进行聚集计算。WHERE过滤指定的是行而不是分组。事实
上,WHERE没有分组的概念。
创建分组
分组是在SELECT语句的GROUP BY子句中建立的。
1  | SELECT vend_id, COUNT(*) AS num_prods FROM products  | 
上面的SELECT语句指定了两个列,vend_id和num_prods为计算字段(用COUNT(*)函数建立),GROUP BY子句指示MySQL按vend_id排序并分组数据。这导致对每个vend_id而不是整个表计算num_prods一次。
因为使用了GROUP BY,就不必指定要计算和估值的每个组了。系统会自动完成。GROUP BY子句指示MySQL分组数据,然后对每个组而不是整个结果集进行聚集。
在具体使用GROUP BY子句前,需要知道一些重要的规定:
GROUP BY子句可以包含任意数目的列。这使得能对分组进行嵌套,为数据分组提供更细致的控制。- 如果在
GROUP BY子句中嵌套了分组,数据将在最后规定的分组上进行汇总。换句话说,在建立分组时,指定的所有列都一起计算(所以不能从个别的列取回数据)。 GROUP BY子句中列出的每个列都必须是检索列或有效的表达式(但不能是聚集函数)。如果在SELECT中使用表达式,则必须在GROUP BY子句中指定相同的表达式。不能使用别名。- 除聚集计算语句外,
SELECT语句中的每个列都必须在GROUP BY子句中给出。 - 如果分组列中具有
NULL值,则NULL将作为一个分组返回。如果列中有多行NULL值,它们将分为一组。 GROUP BY子句必须出现在WHERE子句之后,ORDER BY子句之前。
ROLLUP: 使用WITH ROLLUP关键字,可以得到每个分组以及每个分组汇总级别(针对每个分组)的值,如下所示:
1  | SELECT vend_id, COUNT(*) AS num_prods FROM products  | 
过滤分组
除了能用GROUP BY分组数据外,MySQL还允许过滤分组,规定包括哪些分组,排除哪些分组。
MySQL提供了HAVING子句,HAVING非常类似于WHERE。唯一的差别是WHERE过滤行,而HAVING过滤分组。
1  | SELECT cust_id, COUNT(*) AS orders FROM orders  | 
同时使用WHERE和HAVING子句:
下面的语句,列出具有2个(含)以上、prod_price为10(含)以上的产品的vend_id:
1  | SELECT vend_id, COUNT(*) AS num_prods FROM products  | 
WHERE子句过滤所有prod_price至少为10的行。然后按vend_id分组数据,HAVING子句过滤计数为2或2以上的分组。
分组和排序
我们经常发现用GROUP BY分组的数据确实是以分组顺序输出的。但情况并不总是这样,它并不是SQL规范所要求的。
一般在使用GROUP BY子句时,应该也给出ORDER BY子句。这是保证数据正确排序的唯一方法。千万不要仅依赖GROUP BY排序数据。
下面的SELECT语句,它检索总计订单价格大于等于50的订单的order_num和总计订单价格:
1  | SELECT order_num, SUM(quantity*item_price) AS ordertotal  | 
为按总计订单价格排序输出,需要添加ORDER BY子句,如下所示:
1  | SELECT order_num, SUM(quantity*item_price) AS ordertotal  | 
SELECT子句顺序
SELECT语句中使用时必须遵循的次序:
| 字句 | 说明 | 是否必须使用 | 
|---|---|---|
| SELECT | 要返回的列或表达式 | 是 | 
| FROM | 从中检索数据的表 | 仅在从表选择数据时使用 | 
| WHERE | 行级过滤 | 否 | 
| GROUP BY | 分组说明 | 仅在按组计算聚集时使用 | 
| HAVING | 组级过滤 | 否 | 
| ORDER BY | 输出排序顺序 | 否 | 
| LIMIT | 要检索的行数 | 否 | 
使用子查询
子查询
SQL还允许创建子查询(subquery),即嵌套在其他查询中的查询。
利用子查询进行过滤
订单存储在两个表中。对于包含订单号、客户ID、订单日期的每个订单,orders表存储一行。各订单的物品存储在相关的orderitems表中。orders表不存储客户信息。它只存储客户的ID。实际的客户信息存储在customers表中。
假如需要列出订购物品TNT2的所有客户:
检索包含物品TNT2的所有订单的编号:
1
SELECT order_num FROM orderitems WHERE prod_id = 'TNT2';
检索具有前一步骤列出的订单编号(20005, 20007)的所有客户的ID:
1
SELECT cust_id FROM orders WHERE order_num IN (20005, 20007)
检索前一步骤返回的所有客户ID(10001, 10004)的客户信息:
1
SELECT cust_name, cust_contact FROM customers WHERE cust_id IN (10001, 10004);
可以将上面的WHERE字句转换为子查询:
1  | SELECT cust_name, cust_contact FROM customers  | 
执行上述SELECT语句,MySQL实际上必须执行3条SELECT语句。最里边的子查询返回order_num列表,此列表用于其外面的子查询的WHERE子句。外面的子查询返回cust_id列表,此cust_id列表用于最外层查询的WHERE子句。最外层查询确实返回所需的数据。
在WHERE子句中使用子查询能够编写出功能很强并且很灵活的SQL语句。对于能嵌套的子查询的数目没有限制,不过在实际使用时由于性能的限制,不能嵌套太多的子查询。
作为计算字段使用子查询
使用子查询的另一方法是创建计算字段。假如需要显示customers表中每个客户的订单总数。订单与相应的客户ID存储在orders表中。
为了执行这个操作,遵循下面的步骤:
- 从customers表中检索客户列表。
 - 对于检索出的每个客户,统计其在orders表中的订单数目。
 
可使用SELECT COUNT(*)对表中的行进行计数,并且通过提供一条WHERE子句来过滤某个特定的客户ID,可仅对该客户的订单进行计数。例如,下面的代码对客户10001的订单进行计数:
1  | SELECT COUNT(*) AS orders FROM orders WHERE cust_id = 10001;  | 
为了对每个客户执行COUNT(*)计算,应该将COUNT(*)作为一个子查询:
1  | SELECT cust_name, cust_state, (SELECT COUNT(*)  | 
这条SELECT 语句对customers 表中每个客户返回3 列:cust_name、cust_state和orders。orders是一个计算字段,它是由圆括号中的子查询建立的。该子查询对检索出的每个客户执行一次。
联结表
联结
SQL最强大的功能之一就是能在数据检索查询的执行中联结(join)表。
关系表
关系表的设计就是要保证把信息分解成多个表,一类数据一个表。各表通过某些常用的值互相关联。
外键: 为某个表中的一列,它包含另一个表的主键值,定义了两个表之间的关系。
为什么要使用联结
联结是一种机制,用来在一条SELECT语句中关联表,因此称之为联结。使用特殊的语法,可以联结多个表返回一组输出,联结在运行时关联表中正确的行。
创建联结
联结的创建非常简单,规定要联结的所有表以及它们如何关联即可。
1  | SELECT vend_name, prod_name, prod_price  | 
SELECT语句指定的两个列(prod_name和prod_price)在一个表中,而另一个列(vend_name)在另一个表中。FROM子句列出了两个表,分别是vendors和products。WHERE子句指示MySQL匹配vendors表中的vend_id和products表中的vend_id。
匹配的两个列以vendors.vend_id 和products.vend_id指定。这里需要这种完全限定列名,因为如果只给出vend_id,则MySQL不知道指的是哪一个(它们有两个,每个表中一个)。
完全限定列名: 在引用的列可能出现二义性时,必须使用完全限定列名(用一个点分隔的表名和列名)。如果引用一个没有用表名限制的具有二义性的列名,MySQL将返回错误。
不要忘了WHERE子句: 应该保证所有联结都有WHERE子句,否则MySQL将返回比想要的数据多得多的数据。同理,应该保证WHERE子句的正确性。不正确的过滤条件将导致MySQL返回不正确的数据。
内部联结
目前为止所用的联结称为 等值联结 (equijoin),它基于两个表之间的相等测试。这种联结也称为 内部联结 。其实,对于这种联结可以使用稍微不同的语法来明确指定联结的类型。下面的SELECT语句返回与前面例子完全相同的数据:
1  | SELECT vend_name, prod_name, prod_price  | 
两个表之间的关系是FROM子句的组成部分,以INNER JOIN 指定。在使用这种语法时,联结条件用特定的ON子句而不是WHERE子句给出。传递给ON的实际条件与传递给WHERE的相同。
使用哪种语法: ANSI SQL规范首选INNER JOIN语法。此外,尽管使用WHERE子句定义联结的确比较简单,但是使用明确的联结语法能够确保不会忘记联结条件,有时候这样做也能影响性能。
联结多个表
SQL对一条SELECT语句中可以联结的表的数目没有限制。创建联结的基本规则也相同。首先列出所有表,然后定义表之间的关系。
1  | SELECT prod_name, vend_name, prod_price, quantity  | 
此例子显示编号为20005的订单中的物品。订单物品存储在orderitems表中。每个产品按其产品ID存储,它引用products表中的产品。这些产品通过供应商ID联结到vendors表中相应的供应商,供应商ID存储在每个产品的记录中。
性能考虑: MySQL在运行时关联指定的每个表以处理联结。这种处理可能是非常耗费资源的,因此不要联结不必要的表。联结的表越多,性能下降越厉害。
创建高级联结
使用表别名
别名除了用于列名和计算字段外,SQL还允许给表名起别名。这样做有两个主要理由:
- 缩短SQL语句
 - 允许在单条SELECT语句中多次使用相同的表
 
1  | SELECT cust_name, cust_contact  | 
FROM子句中3个表全都具有别名。customers AS c建立c作为customers的别名,等等。这使得能使用省写的c而不是全名customers。
应该注意,表别名只在查询执行中使用。与列别名不一样,表别名不返回到客户机。
使用不同类型的联结
查询要求首vend_id为DTNTR的物品的供应商,然后找出这个供应商生产的其他物品。
使用子查询:
1
2
3
4
5SELECT prod_id, prod_name
FROM products
WHERE vend_id = (SELECT vend_id
FROM products
WHERE prod_id = 'DTNTR');使用联结查询:
1
2
3
4SELECT p1.prod_id, 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的第一次出现为别名p1,第二次出现为别名p2。SELECT语句使用p1前缀明确地给出所需列的全名。如果不这样,MySQL将返回错误,因为分别存在两个名为prod_id、prod_name的列。MySQL不知道想要的是哪一个列(即使它们事实上是同一个列)。WHERE(通过匹配p1中的vend_id和p2中的vend_id)首先联结两个表,然后按第二个表中的prod_id过滤数据,返回所需的数据。
外部联结
许多联结将一个表中的行与另一个表中的行相关联。但有时候会需要包含没有关联行的那些行。这种类型的联结称为 外部联结 。
下面的SELECT语句给出一个简单的内部联结。它检索所有客户及其订单:
1  | SELECT customers.cust_id, orders.order_num  | 
外部联结语法类似。为了检索所有客户,包括那些没有订单的客户,可如下进行:
1  | SELECT customers.cust_id, orders.order_num  | 
这条SELECT语句使用了关键字OUTER JOIN来指定联结的类型。但是,与内部联结关联两个表中的行不同的是,外部联结还包括没有关联行的行。在使用OUTER JOIN语法时,必须使用RIGHT或LEFT关键字指定包括其所有行的表(RIGHT指出的是OUTER JOIN右边的表,而LEFT指出的是OUTER JOIN左边的表)。上面的例子使用LEFT OUTER JOIN从FROM子句的左边表(customers表)中选择所有行。
使用带聚集函数的联结
聚集函数可以与联结一起使用。
1  | SELECT customers.cust_name, customers.cust_id, COUNT(orders.order_num)  | 
此SELECT语句使用INNER JOIN将customers和orders表互相关联。GROUP BY 子句按客户分组数据, 因此, 函数调用COUNT(orders.order_num)对每个客户的订单计数,将它作为num_ord返回。
组合查询
MySQL也允许执行多个查询(多条SELECT语句),并将结果作为单个查询结果集返回。这些组合查询通常称为 并 (union)或 复合查询 (compound query)。
有两种基本情况,其中需要使用组合查询:
- 单个查询中从不同的表返回类似结构的数据
 - 对单个表执行多个查询,按单个查询返回数据
 
组合查询和多个WHERE条件: 多数情况下,组合相同表的两个查询完成的工作与具有多个WHERE子句条件的单条查询完成的工作相同。
创建组合查询
利用UNION,可给出多条SELECT语句,将它们的结果组合成单个结果集。
假如需要prod_price小于等于5的所有物品的一个列表,而且还想包括vend_id为1001和1002生产的所有物品(不考虑价格)。
使用UNION:
1  | SELECT vend_id, prod_id, prod_price  | 
使用WHERE:
1  | SELECT vend_id, prod_id, prod_price  | 
在这个简单的例子中,使用UNION可能比使用WHERE子句更为复杂。但对于更复杂的过滤条件,或者从多个表(而不是单个表)中检索数据的情形,使用UNION可能会使处理更简单。
UNION规则
进行UNION时有几条规则需要注意:
UNION必须由两条或两条以上的SELECT语句组成,语句之间用关键字UNION分隔(因此,如果组合4条SELECT语句,将要使用3个UNION关键字)UNION中的每个查询必须包含相同的列、表达式或聚集函数(不过各个列不需要以相同的次序列出)- 列数据类型必须兼容:类型不必完全相同,但必须是DBMS可以隐含地转换的类型
 
包含或取消重复的行
UNION从查询结果集中自动去除了重复的行,如果想不取消重复的行,可使用UNION ALL而不是UNION。
1  | SELECT vend_id, prod_id, prod_price  | 
对组合查询结果排序
SELECT语句的输出用ORDER BY子句排序。在用UNION组合查询时,只能使用一条ORDER BY子句,它必须出现在最后一条SELECT语句之后。对于结果集,不存在用一种方式排序一部分,而又用另一种方式排序另一部分的情况,因此不允许使用多条ORDER BY子句。
1  | SELECT vend_id, prod_id, prod_price  | 
全文本搜索
MyISAM引擎: 支持全文检索
InnoDB: 不支持全文检索
在使用全文本搜索时,MySQL不需要分别查看每个行,不需要分别分析和处理每个词。MySQL创建指定列中各词的一个索引,搜索可以针对这些词进行。
使用全文本搜索
为了进行全文本搜索,必须索引被搜索的列,而且要随着数据的改变不断地重新索引。在对表列进行适当设计后,MySQL会自动进行所有的索引和重新索引。
启用全文本搜索支持
一般在创建表时启用全文本搜索。CREATE TABLE语句接受FULLTEXT子句,它给出被索引列的一个逗号分隔的列表。
1  | CREATE TABLE productnotes  | 
有一个名为note_text的列,为了进行全文本搜索,MySQL根据子句FULLTEXT(note_text)的指示对它进行索引。这里的FULLTEXT索引单个列,如果需要也可以指定多个列。
在定义之后,MySQL自动维护该索引。在增加、更新或删除行时,索引随之自动更新。
进行全文本搜索
在索引之后,使用两个函数Match()和Against()执行全文本搜索,其中Match()指定被搜索的列,Against()指定要使用的搜索表达式。
1  | SELECT note_text FROM productnotes  | 
Match(note_text)指示MySQL针对指定的列进行搜索,Against('rabbit')指定词rabbit作为搜索文本。
传递给Match()的值必须与FULLTEXT()定义中的相同。如果指定多个列,则必须列出它们(而且次序正确)。
全文本搜索的一个重要部分就是对结果排序。具有较高等级的行先返回。等级由MySQL根据行中词的数目、唯一词的数目、整个索引中词的总数以及包含该词的行的数目计算出来。
1  | SELECT note_text, Match(note_text) Against('rabbit')  | 
计算列(别名为rank),此列包含全文本搜索计算出的等级值。不包含词rabbit的行等级为0。
布尔文本搜索
MySQL支持全文本搜索的另外一种形式,称为布尔方式。
下表列出支持的所有布尔操作符:
| 布尔操作符 | 说明 | 
|---|---|
| + | 包含,词必须存在 | 
| - | 排除,词必须不出现 | 
| > | 包含,而且增加等级值 | 
| < | 包含,且减少等级值 | 
| () | 把词组成子表达式(允许这些子表达式作为一个组被包含、排除、排列等) | 
| ~ | 取消一个词的排序值 | 
| * | 词尾的通配符 | 
| “” | 定义一个短语(与单个词的列表不一样,它匹配整个短语以便包含或排除这个短语) | 
匹配包含heavy但不包含任意以rope开始的词的行:
1  | SELECT note_text FROM productnotes  | 
搜索匹配包含词rabbit和bait的行:
1  | SELECT note_text FROM productnotes  | 
没有指定操作符,这个搜索匹配包含rabbit和bait中的至少一个词的行:
1  | SELECT note_text FROM productnotes  | 
匹配短语rabbit bait而不是匹配两个词rabbit和bait:
1  | SELECT note_text FROM productnotes  | 
匹配rabbit和carrot,增加前者的等级,降低后者的等级:
1  | SELECT note_text FROM productnotes  | 
搜索匹配词safe和combination,降低后者的等级:
1  | SELECT note_text FROM productnotes  | 
插入数据
插入完整的行
1  | INSERT INTO customers  | 
虽然这种语法很简单,但并不安全,应该尽量避免使用。上面的SQL语句高度依赖于表中列的定义次序,并且还依赖于其次序容易获得的信息。即使可得到这种次序信息,也不能保证下一次表结构变动后各个列保持完全相同的次序。
编写INSERT语句的更安全的方法如下:
1  | INSERT INTO customers(cust_name,  | 
在表名后的括号里明确地给出了列名。在插入行时,MySQL将用VALUES列表中的相应值填入列表中的对应项。VALUES中的第一个值对应于第一个指定的列名。第二个值对应于第二个列名,如此等等。
省略列: 如果表的定义允许,则可以在INSERT操作中省略某些列。省略的列必须满足以下某个条件。
- 该列定义为允许NULL值(无值或空值)。
 - 在表定义中给出默认值。这表示如果不给出值,将使用默认值。
 
插入多个行
1  | INSERT INTO customers(cust_name,  | 
INSERT语句有多组值,每组值用一对圆括号括起来,用逗号分隔。
插入检索出的数据
INSERT还存在另一种形式,可以利用它将一条SELECT语句的结果插入表中。
1  | INSERT INTO customers(cust_id, cust_contact, cust_email, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country)  | 
INSERT SELECT中的列名: 在INSERT和SELECT语句中不一定要求列名匹配。它使用的是列的位置,因此SELECT中的第一列(不管其列名)将用来填充表列中指定的第一个列,第二列将用来填充表列中指定的第二个列,如此等等。
更新和删除数据
更新数据
为了更新(修改)表中的数据,可使用UPDATE语句。
1  | UPDATE customers SET cust_email = 'elmer@fudd.com'  | 
更新多个列:
1  | UPDATE customers SET cust_name = 'The Fudds', cust_email = 'elmer@fudd.com'  | 
为了删除某个列的值,可设置它为NULL:
1  | UPDATE customers SET cust_email = NULL  | 
删除数据
为了从一个表中删除(去掉)数据,使用DELETE语句。
1  | DELETE FROM customers WHERE cust_id = 10006;  | 
如果执行UPDATE而不带WHERE子句,则表中每个行都将用新值更新。类似地,如果执行DELETE语句而不带WHERE子句,表的所有数据都将被删除。
创建和操纵表
CREATE TABLE创建表,必须给出下列信息:
- 新表的名字,在关键字
CREATE TABLE之后给出 - 表列的名字和定义,用逗号分隔。
 
1  | CREATE TABLE customers  | 
每列的定义以列名(它在表中必须是唯一的)开始,后跟列的数据类型,表的主键可以在创建表时用PRIMARY KEY关键字指定。这里,列cust_id指定作为主键列。
使用NULL值
NULL值就是没有值或缺值。允许NULL值的列也允许在插入行时不给出该列的值。不允许NULL值的列,在插入或更新行时,该列必须有值。
主键
表中的每个行必须具有唯一的主键值。如果主键使用单个列,则它的值必须唯一。如果使用多个列,则这些列的组合值必须唯一。
主键中只能使用不允许NULL值的列。允许NULL值的列不能作为唯一标识。
使用AUTO_INCREMENT
AUTO_INCREMENT告诉MySQL,自动对该列增量。每个表只允许一个AUTO_INCREMENT列,而且它必须被索引(如,通过使它成为主键)。
指定默认值
默认值用CREATE TABLE语句的列定义中的DEFAULT关键字指定。
1  | CREATE TABLE orderitems  | 
引擎类型
MySQL有一个具体管理和处理数据的内部引擎。在使用CREATE TABLE语句时,该引擎具体创建表,而在使用SELECT语句或进行其他数据库处理时,该引擎在内部处理你的请求。多数时候,此引擎都隐藏在DBMS内,不需要过多关注它。
- InnoDB是一个可靠的事务处理引擎,它不支持全文本搜索
 - MEMORY在功能等同于MyISAM,但由于数据存储在内存(不是磁盘)中,速度很快(特别适合于临时表)
 - MyISAM是一个性能极高的引擎,它支持全文本搜索,但不支持事务处理。
 
外键不能跨引擎: 外键不能跨引擎,即使用一个引擎的表不能引用具有使用不同引擎的表的外键。
更新表
更新表
为更新表定义,可使用ALTER TABLE语句。
给表添加一个列:
1  | ALTER TABLE vendors ADD vend_phone CHAR(20);  | 
删除列:
1  | ALTER TABLE vendors DROP COLUMN vend_phone;  | 
ALTER TABLE的一种常见用途是定义外键:
1  | ALTER TABLE orderitems  | 
删除表
删除表使用DROP TABLE语句:
1  | DROP TABLE customers;  | 
重命名表
1  | RENAME TABLE customers2 TO customers;  | 
使用视图
视图
视图是虚拟的表。与包含数据的表不一样,视图只包含使用时动态检索数据的查询。
为什么使用视图
- 重用
SQL语句。 - 简化复杂的
SQL操作。在编写查询后,可以方便地重用它而不必知道它的基本查询细节。 - 使用表的组成部分而不是整个表。
 - 保护数据。可以给用户授予表的特定部分的访问权限而不是整个表的访问权限。
 - 更改数据格式和表示。视图可返回与底层表的表示和格式不同的数据。
 
使用视图
- 视图用
CREATE VIEW语句来创建。 - 使用
SHOW CREATE VIEW viewname;来查看创建视图的语句。 - 用
DROP删除视图,其语法为DROP VIEW viewname; - 更新视图时,可以先用
DROP再用CREATE,也可以直接用CREATE ORREPLACE VIEW。 
利用视图简化复杂的联结
1  | CREATE VIEW productcustomers AS  | 
这条语句创建一个名为productcustomers的视图,它联结三个表,以返回已订购了任意产品的所有客户的列表。如果执行SELECT * FROM productcustomers,将列出订购了任意产品的客户。
为检索订购了产品TNT2的客户,可如下进行:
1  | SELECT cust_name, cust_contact  | 
用视图重新格式化检索出的数据
下面的SELECT语句在单个组合计算列中返回供应商名和位置:
1  | SELECT Concat(RTrim(vend_name), '(', RTrim(vend_country), ')') AS vend_title  | 
假如经常需要这个格式的结果。不必在每次需要时执行联结,创建一个视图,每次需要时使用它即可。
1  | CREATE VIEW vendorlocations AS  | 
使用SELECT语句相查询的视图:
1  | SELECT * FROM vendorlocations;  | 
用视图过滤不想要的数据
可以定义customeremaillist视图,它过滤没有电子邮件地址的客户:
1  | CREATE VIEW customeremaillist AS  | 
使用视图与计算字段
下面SELECT语句。它检索某个特定订单中的物品,计算每种物品的总价格:
1  | SELECT order_num, prod_id, quantity, item_price,  | 
将其转换为一个视图:
1  | CREATE VIEW orderitemsexpanded AS  | 
检索订单20005的详细内容:
1  | SELECT * FROM orderitemsexpanded  | 
更新视图
视图主要用于数据检索。对视图增加或删除行,实际上是对其基表增加或删除行。
并非所有视图都是可更新的。基本上可以说,如果MySQL不能正确地确定被更新的基数据,则不允许更新(包括插入和删除)。这实际上意味着,如果视图定义中有以下操作,则不能进行视图的更新:
- 分组(使用GROUP BY和HAVING)
 - 联结
 - 子查询
 - 并
 - 聚集函数(Min()、Count()、Sum()等)
 - DISTINCT
 - 导出(计算)
 
使用存储过程
存储过程简单来说,就是为以后的使用而保存的一条或多条MySQL语句的集合。可将其视为批文件,虽然它们的作用不仅限于批处理。
使用存储过程有3个主要的好处,即简单、安全、高性能。
创建存储过程
1  | DELIMITER //  | 
此存储过程名为productpricing,用CREATE PROCEDURE productpricing()语句定义。如果存储过程接受参数,它们将在()中列举出来。此存储过程没有参数,但后跟的()仍然需要。BEGIN和END语句用来限定存储过程体,过程体本身仅是一个简单的SELECT语句。
mysql命令行客户机的分隔符: mysql命令行实用程序使用;作为语句分隔符。如果命令行实用程序要解释存储过程自身内的;字符,则它们最终不会成为存储过程的成分,这会使存储过程中的SQL出现句法错误。 解决办法是临时更改命令行实用程序的语句分隔符,DELIMITER //。为恢复为原来的语句分隔符,可使用DELIMITER ;。
使用存储过程
1  | CALL productpricing();  | 
CALL productpricing();执行刚创建的存储过程并显示返回的结果。因为存储过程实际上是一种函数,所以存储过程名后需要有()符号(即使不传递参数也需要)。
删除存储过程
存储过程在创建之后,被保存在服务器上以供使用,直至被删除。删除命令从服务器中删除存储过程。
1  | DROP PROCEDURE productpricing;  | 
使用参数
一般,存储过程并不显示结果,而是把结果返回给你指定的变量。
1  | CREATE PROCEDURE productpricing(  | 
此存储过程接受3个参数:pl存储产品最低价格,ph存储产品最高价格,pa存储产品平均价格。每个参数必须具有指定的类型,这里使用十进制值。关键字OUT指出相应的参数用来从存储过程传出一个值(返回给调用者)。MySQL支持IN(传递给存储过程)、OUT(从存储过程传出,如这里所用)和INOUT(对存储过程传入和传出)类型的参数。存储过程的代码位于BEGIN和END语句内,它们是一系列SELECT语句,用来检索值,然后保存到相应的变量(通过指定INTO关键字)。
参数的数据类型: 存储过程的参数允许的数据类型与表中使用的数据类型相同。注意,记录集不是允许的类型,因此,不能通过一个参数返回多个行和列。这就是前面的例子为什么要使用3个参数(和3条SELECT语句)的原因。
为调用此修改过的存储过程,必须指定3个变量名,如下所示:
1  | CALL productpricing(@pricelow, @pricehigh, @priceaverage);  | 
由于此存储过程要求3个参数,因此必须正好传递3个参数,不多也不少。所以,这条CALL语句给出3个参数。它们是存储过程将保存结果的3个变量的名字。
变量名: 所有MySQL变量都必须以@开始。
在调用时,这条语句并不显示任何数据。它返回以后可以显示的变量。
为了显示检索出的产品平均价格,可如下进行:
1  | SELECT @priceaverage;  | 
为了获得3个值,可使用以下语句:
1  | SELECT @priceaverage @pricehigh, @pricelow  | 
下面是另外一个例子,这次使用IN和OUT参数。ordertotal接受订单号并返回该订单的合计:
1  | CREATE PROCEDURE ordertotal(  | 
onumber定义为IN,因为订单号被传入存储过程。ototal定义为OUT,因为要从存储过程返回合计。SELECT语句使用这两个参数,WHERE子句使用onumber选择正确的行,INTO使用ototal存储计算出来的合计。
为调用这个新存储过程,可使用以下语句:
1  | CALL ordertotal(20005, @total);  | 
必须给ordertotal传递两个参数;第一个参数为订单号,第二个参数为包含计算出来的合计的变量名。
为了显示此合计,可如下进行:
1  | SELECT @total;  | 
检查存储过程
为显示用来创建一个存储过程的CREATE语句,使用SHOW CREATE PROCEDURE语句:
1  | SHOW CREATE PROCEDURE ordertotal  | 
为了获得包括何时、由谁创建等详细信息的存储过程列表,使用SHOW PROCEDURE STATUS。
使用游标
游标(cursor)是一个存储在MySQL服务器上的数据库查询,它不是一条SELECT语句,而是被该语句检索出来的结果集。在存储了游标之后,应用程序可以根据需要滚动或浏览其中的数据。
不像多数DBMS,MySQL游标只能用于存储过程(和函数)。
创建游标
游标用DECLARE语句创建。DECLARE命名游标,并定义相应的SELECT语句,根据需要带WHERE和其他子句。
1  | CREATE PROCEDURE processorders()  | 
DECLARE语句用来定义和命名游标,这里为ordernumbers。存储过程处理完成后,游标就消失(因为它局限于存储过程)。
打开和关闭游标
游标用OPEN CURSOR语句来打开:
1  | OPEN ordernumbers;  | 
在处理OPEN语句时执行查询,存储检索出的数据以供浏览和滚动。
游标处理完成后,应当使用如下语句关闭游标:
1  | CLOSE ordernumbers;  | 
CLOSE释放游标使用的所有内部内存和资源,因此在每个游标不再需要时都应该关闭。
隐含关闭: 如果你不明确关闭游标,MySQL将会在到达END语句时自动关闭它。
1  | CREATE PROCEDURE processorders()  | 
使用游标数据
在一个游标被打开后,可以使用FETCH语句分别访问它的每一行。FETCH指定检索什么数据(所需的列),检索出来的数据存储在什么地方。它还向前移动游标中的内部行指针,使下一条FETCH语句检索下一行(不重复读取同一行)。
1  | CREATE PROCEDURE processorders()  | 
其中FETCH用来检索当前行的order_num列(将自动从第一行开始)到一个名为o的局部声明的变量中。对检索出的数据不做任何处理。
循环检索数据,从第一行到最后一行:
1  | CREATE PROCEDURE processorders()  | 
FETCH是在REPEAT内,因此它反复执行直到done为真(由UNTIL done END REPEAT;规定)。为使它起作用,用一个DEFAULT 0(假,不结束)定义变量done。
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done=1;  
这条语句定义了一个CONTINUE HANDLER,它是在条件出现时被执行的代码。这里,它指出当SQLSTATE '02000'出现时,SET done=1。 SQLSTATE '02000'是一个未找到条件,当REPEAT由于没有更多的行供循环而不能继续时,出现这个条件。  
如果一切正常,可以在循环内放入任意需要的处理(在FETCH语句之后,循环结束之前)。
使用触发器
触发器是MySQL响应以下任意语句而自动执行的一条MySQL语句:
- DELETE
 - INSERT
 - UPDATE
 
其他MySQL语句不支持触发器。
只有表才支持触发器,视图不支持(临时表也不支持)。
创建触发器
触发器用CREATE TRIGGER语句创建:
1  | CREATE TRIGGER newproduct AFTER INSERT ON products  | 
CREATE TRIGGER用来创建名为newproduct的新触发器。触发器可在一个操作发生之前或之后执行,这里给出了AFTER INSERT,所以此触发器将在INSERT语句成功执行后执行。这个触发器还指定FOR EACH ROW,因此代码对每个插入行执行。在这个例子中,文本Product added将对每个插入的行显示一次。
触发器按每个表每个事件每次地定义,每个表每个事件每次只允许一个触发器。因此,每个表最多支持6个触发器(每条INSERT、UPDATE和DELETE的之前和之后)。单一触发器不能与多个事件或多个表关联,所以,如果需要一个对INSERT和UPDATE操作执行的触发器,则应该定义两个触发器。
删除触发器
1  | DROP TRIGGER newproduct;  | 
管理事务处理
事务处理
事务处理(transaction processing)可以用来维护数据库的完整性,它保证成批的MySQL操作要么完全执行,要么完全不执行。
事务处理的几个术语:
- 事务(transaction)指一组SQL语句
 - 回退(rollback)指撤销指定SQL语句的过程
 - 提交(commit)指将未存储的SQL语句结果写入数据库表
 - 保留点(savepoint)指事务处理中设置的临时占位符(placeholder),可以对它发布回退(与回退整个事务处理不同)。
 
控制事务处理
MySQL使用下面的语句来标识事务的开始:
1  | START TRANSACTION;  | 
使用ROLLBACK
MySQL的ROLLBACK命令用来回退(撤销)MySQL语句
哪些语句可以回退: 事务处理用来管理INSERT、UPDATE和DELETE语句。不能回退SELECT语句。(这样做也没有什么意义。)不能回退CREATE或DROP操作。事务处理块中可以使用这两条语句,但如果执行回退,它们不会被撤销。
使用COMMIT
一般的MySQL语句都是直接针对数据库表执行和编写的。这就是所谓的隐含提交(implicit commit),即提交(写或保存)操作是自动进行的。
但是,在事务处理块中,提交不会隐含地进行。为进行明确的提交,使用COMMIT语句。
1  | START TRANSCATION;  | 
从系统中完全删除订单20010。因为涉及更新两个数据库表orders和orderItems,所以使用事务处理块来保证订单不被部分删除。最后的COMMIT语句仅在不出错时写出更改。如果第一条DELETE起作用,但第二条失败,则DELETE不会提交(实际上,它是被自动撤销的)。
隐含事务关闭: 当COMMIT或ROLLBACK语句执行后,事务会自动关闭。
使用保留点
简单的ROLLBACK和COMMIT语句就可以写入或撤销整个事务处理。但是,只是对简单的事务处理才能这样做,更复杂的事务处理可能需要部分提交或回退。
为了支持回退部分事务处理,必须能在事务处理块中合适的位置放置占位符。这样,如果需要回退,可以回退到某个占位符。
这些占位符称为保留点。为了创建占位符,可如下使用SAVEPOINT语句:
1  | SAVEPOINT delete1;  | 
每个保留点都取标识它的唯一名字,以便在回退时,MySQL知道要回退到何处。为了回退到给出的保留点,可如下进行:
1  | ROLLBACK TO delete1;  | 
释放保留点: 保留点在事务处理完成(执行一条ROLLBACK或COMMIT)后自动释放。