mysql必会必知
了解SQL
什么是数据库
数据库是一个以某种有组织的方式存储的数据集合。理解数据库的一种最简单的办法是将其想象为一个文件柜。此文件柜是一个存放数据的物理位置,不管数据是什么以及如何组织的。
数据库(database): 保存有组织的数据的容器(通常是一个文件或一组文件)。
注意:人们通常用数据库这个术语来代表他们使用的数据库软件。这是不正确的,它是引起混淆的根源。确切地说,数据库软件应称为DBMS(数据库管理系统)。数据库是通过DBMS创建和操纵的容器。数据库可以是保存在硬设备上的文件,但也可以不是。在很大程度上说,数据库究竟是文件还是别的什么东西并不重要,因为你并不直接访问数据库;你使用的是DBMS,它替你访问数据库。
表
表是一种结构化的文件,可用来存储某种特定类型的数据。
表(table): 某种特定类型数据的结构化清单。
数据库中的每个表都有一个名字,用来标识自己。此名字是唯一的,这表示数据库中没有其他表具有相同的名字。
表名的唯一性取决于多个因素,如数据库名和表名等的结合。这表示,虽然在相同数据库中不能两次使用相同的表名,但在不同的数据库中却可以使用相同的表名。
表具有一些特性,这些特性定义了数据在表中如何存储,如可以存储什么样的数据,数据如何分解,各部分信息如何命名,等等。描述表的这组信息就是所谓的
模式
,模式可以用来描述数据库中特定的表以及整个数据库(和其中表的关系)。
模式(schema) :关于数据库和表的布局及特性的信息。
列和数据类型
表由列组成。列中存储着表中某部分的信息。
列(column): 表中的一个字段。所有表都是由一个或多个列组成的。
数据库中每个列都有相应的数据类型。数据类型定义列可以存储的数据种类。例如,如果列中存储的为数字(或许是订单中的物品数),则相应的数据类型应该为数值类型。如果列中存储的是日期、文本、注释、金额等,则应该用恰当的数据类型规定出来。
数据类型(datatype): 所容许的数据的类型。每个表列都有相应的数据类型,它限制(或容许)该列中存储的数据。
数据类型限制可存储在列中的数据种类(例如,防止在数值字段中录入字符值)。数据类型还帮助正确地排序数据,并在优化磁盘使用方面起重要的作用。因此,在创建表时必须对数据类型给予特别的关注。
行
行(row): 表中的一个记录。
你可能听到用户在提到行( row)时称其为数据库记录( record)。在很大程度上,这两个术语是可以互相替代的,但从技术上说,行才是正确的术语。
主键
表中每一行都应该有可以唯一标识自己的一列(或一组列)。
主键(primary key): 一列(或一组列),其值能够唯一区分表中每个行。
唯一标识表中每行的这个列(或这组列)称为主键。主键用来表示一个特定的行。没有主键,更新或删除表中特定行很困难,因为没有安全的方法保证只涉及相关的行。
表中的任何列都可以作为主键,只要它满足以下条件:
- 任意两行都不具有相同的主键值;
- 每个行都必须具有一个主键值(主键列不允许NULL值)。
主键通常定义在表的一列上,但这并不是必需的,也可以一起使用多个列作为主键。在使用多列作为主键时,上述条件必须应用到构成主键的所有列,所有列值的组合必须是唯一的(但单个列的值可以不唯一)。
什么是SQL
SQL是结构化查询语言(Structured Query Language)的缩写。 SQL是一种专门用来与数据库通信的语言。
SQL有如下的优点 :
- SQL不是某个特定数据库供应商专有的语言。几乎所有重要的DBMS都支持SQL,所以,学习此语言使你几乎能与所有数据库打交道。
- SQL简单易学。它的语句全都是由描述性很强的英语单词组成,而且这些单词的数目不多。
- SQL尽管看上去很简单,但它实际上是一种强有力的语言,灵活使用其语言元素,可以进行非常复杂和高级的数据库操作。
DBMS专用的SQL:SQL不是一种专利语言,而且存在一个标准委员会,他们试图定义可供所有DBMS使用的SQL语法,但事实上任意两个DBMS实现的SQL都不完全相同。
本书讲授的SQL是专门针对MySQL的,虽然书中所讲授的多数语法也适用于其他DBMS,但不要认为这些SQL语法是完全可移植的。
MySQL简介
什么是MySQL
正如所述,数据的所有存储、检索、管理和处理实际上是由数据库软件——DBMS( 数据库管理系统)完成的。 MySQL是一种DBMS,即它是一种数据库软件。
客户机—服务器软件
DBMS可分为两类:一类为基于共享文件系统的DBMS,另一类为基于客户机—服务器的DBMS。
前者(包括诸如Microsoft Access和FileMaker) 用于桌面用途,通常不用于高端或更关键的应用。
MySQL、 Oracle以及Microsoft SQL Server等数据库是基于客户机—服务器的数据库。客户机—服务器应用分为两个不同的部分。 服务器部分是负责所有数据访问和处理的一个软件。这个软件运行在称为数据库服务器的计算机上。
与数据文件打交道的只有服务器软件。关于数据、数据添加、删除和数据更新的所有请求都由服务器软件完成。这些请求或更改来自运行客户机软件的计算机。
客户机是与用户打交道的软件。例如,如果你请求一个按字母顺序列出的产品表,则客户机软件通过网络提交该请求给服务器软件。服务器软件处理这个请求,根据需要过滤、丢弃和排序数据;然后把结果送回到你的客户机软件。
因为为了使用MySQL,你需要访问运行MySQL服务器软件的计算机和发布命令到MySQL的客户机软件的计算机。
- 服务器软件为MySQL DBMS。
- 客户机可以是MySQL提供的工具、脚本语言(如Perl)、 Web应用开发语言(如ASP、 ColdFusion、 JSP和PHP)、程序设计语言(如C、 C++、 Java)等。
MySQL工具
MySQL是一个客户机—服务器DBMS,因此,为了使用MySQL,需要有一个客户机,即你需要用来与MySQL打交道(给MySQL提供要执行的命令)的一个应用。
mysql命令行实用程序
每个MySQL安装都有一个名为mysql的简单命令行实用程序。
熟悉mysql命令行实用程序:即使你选择使用后面描述的某个图形工具,也应该保证熟悉mysql命令行实用程序,因为它是你可以安全地依靠的一个总是会被给出的客户机(因为它是核心MySQL安装的一部分)。
MySQL Administrator
MySQL Administrator( MySQL管理器)是一个图形交互客户机,用来简化MySQL服务器的管理。
MySQL Query Browser
MySQL Query Browser为一个图形交互客户机,用来编写和执行MySQL命令。
检索数据
SELECT语句
select 关键字 列名 from 表名
检索单个列
// 检索products 表中 prod_name 列的数据
select prod_name from products
检索多个列
// 检索products 表中 prod_name prod_id prod_price 列的数据
SELECT prod_name,prod_id,prod_price FROM products
检索所有列
// 检索products 所有列的数据
select * from products
检索不同的行
检索结果中,列有重复值,使用DISTINCT关键字,此关键字指示MySQL只返回不同的值;
SELECT DISTINCT vend_id FROM products
注意:不能部分使用DISTINCT:
DISTINCT关键字应用于所有列而不仅是前置它的列。如果给出SELECT DISTINCT vend_id,prod_price,除非指定的两个列都不同,否则所有行都将被检索出来。
限制结果
SELECT语句返回所有匹配的行,它们可能是指定表中的每个行。为了返回第一行或前几行,可使用LIMIT子句。
SELECT prod_name from products LIMIT 5
带一个值的LIMIT:从第一行开始,给出的数为返回的行数。
带两个值的LIMIT:第一个数为开始位置,第二个数为要检索的行数。
使用完全限定的表名
在SQL不仅可以通过列名引用列。 也可能会使用完全限定的名字来引用列(同时使用表名和列字)。
SELECT products.prod_name from products
因为不同表,可能有同名列
排序检索数据
子句(clause) :SQL语句由子句构成,有些子句是必需的,而有的是可选的。一个子句通常由一个关键字和所提供的数据组
成。子句的例子有SELECT语句的FROM子句,为了明确地排序用SELECT语句检索出的数据,可使用ORDER BY子句。
ORDER BY子句取一个或多个列的名字,据此对输出进行排序。
SELECT prod_name from products ORDER BY prod_name
通过非选择列进行排序:通常, ORDER BY子句中使用的列将是为显示所选择的列。但是,实际上并不一定要这样,用非检索的列排序数据是完全合法的。
按多个列排序
// 检索结果 首先按,然后再按价格排序
SELECT prod_name , prod_id, prod_price from products ORDER BY prod_name,prod_price
指定排序方向
数据排序不限于升序排序(从A到Z)。这只是默认的排序顺序,还可以使用ORDER BY子句以降序(从Z到A)顺序排序。为了进行降序排序,必须指定DESC关键字。
SELECT prod_name , prod_id, prod_price from products ORDER BY prod_id DESC
DESC关键字只应用到直接位于其前面的列名。
SELECT prod_name , prod_id, prod_price from products ORDER BY prod_id DESC ,prod_name
在上例中,只对prod_id列指定DESC,对prod_name列不指定。因此,prod_id列以降序排序,而prod_name列(在每个ID内)仍然按标准的升序排序。
在多个列上降序排序 如果想在多个列上进行降序排序, 必须对每个列指定DESC关键字。
与DESC相反的关键字是ASC(ASCENDING), 在升序排序时可以指定它。但实际上, ASC没有多大用处,因为升序是默认的(如果既不指定ASC也不指定DESC,则假定为ASC)。
ORDER BY子句的位置:在给出ORDER BY子句时,应该保证它位于FROM子句之后。如果使用LIMIT,它必须位于ORDER BY
之后。使用子句的次序不对将产生错误消息。
过滤数据
使用WHERE子句
数据库表一般包含大量的数据,很少需要检索表中所有行。
只检索所需数据需要指定搜索条件( search criteria) ,搜索条件也称为过滤条件( filtercondition) 。
在SELECT语句中,数据根据WHERE子句中指定的搜索条件进行过滤。WHERE子句在表名(FROM子句)之后给出。
SELECT prod_name ,prod_price from products WHERE prod_price = 5.99
WHERE子句的位置 : 在同时使用ORDER BY和WHERE子句时,应该让ORDER BY位于WHERE之后, 否则将会产生错误。
WHERE子句操作符
MySQL支持一下列出的所有条件操作符。
操作符 | 说明 |
---|---|
= | 等于(默认不区分大小写) |
<> | 不等于 |
!= | 不等于 |
< | 小于 |
<= | 小于等于 |
> | 大于 |
>= | 大于等于 |
BETWEEN | 在指定的两个值之间 |
何时使用引号:在WHERE子句中使用的条件,会看到有的值括在单引号内(如'fuses'),而有的值未括起来。单引号用来限定字符串。如果将值与串类型的列进行比较,则需要限定引号。用来与数值列进行比较的值不用引号。
范围值检查
SELECT prod_name ,prod_price from products WHERE prod_price BETWEEN 5 AND 10
在使用BETWEEN时,必须指定两个值—所需范围的低端值和高端值。这两个值必须用AND关键字分隔。 BETWEEN匹配范围中所有的值,包括指定的开始值和结束值。
空值检查
在创建表时,表设计人员可以指定其中的列是否可以不包含值。在一个列不包含值时,称其为包含空值NULL。
NULL 无值(no value),它与字段包含0、空字符串或仅仅包含空格不同。
SELECT语句有一个特殊的WHERE子句,可用来检查具有NULL值的列。这个WHERE子句就是IS NULL子句。
SELECT prod_name ,prod_price from products WHERE prod_price IS NULL
NULL与不匹配 : 在通过过滤选择出不具有特定值的行时,你可能希望返回具有NULL值的行。但是,不行。因为null具有
特殊的含义,数据库不知道它们是否匹配,所以在匹配过滤或不匹配过滤时不返回它们(具有Null的行)。因此,在过滤数据时,一定要验证返回数据中确实给出了被过滤列具有NULL的行。
数据过滤
组合WHERE子句
在过滤数据中介绍的所有WHERE子句在过滤数据时使用的都是单一的条件。为了进行更强的过滤控制, MySQL允许给出多个WHERE子句。这些子句可以两种方式使用:以AND子句的方式或OR子句的方式使用。
操作符(operator):用来联结或改变WHERE子句中的子句的关键字。也称为逻辑操作符( logical operator) 。
AND操作符
为了通过不止一个列进行过滤,可使用AND操作符给WHERE子句附加条件。
SELECT prod_id,prod_price,prod_name FROM products WHERE vend_id = 1003 and prod_id <= 10
还可以添加多个过滤条件,每添加一条就要使用一个AND。
OR操作符
OR操作符与AND操作符不同,它指示MySQL检索匹配任一条件的行。
SELECT prod_id,prod_price,prod_name FROM products WHERE vend_id = 1003 or prod_id <= 10
OR :WHERE子句中使用的关键字,用来表示检索匹配任一给定条件的行。
计算次序
WHERE可包含任意数目的AND和OR操作符。允许两者结合以进行复杂和高级的过滤。
SELECT prod_price,prod_name,vend_id FROM products WHERE vend_id = 1002 OR vend_id = 1003 AND prod_price >= 10
SQL(像多数语言一样)在处理OR操作符前,优先处理AND操作符。
当SQL看到上述WHERE子句时,它理解为由供应商1003制造的任何价格为10美元(含)以上的产品,或者由供应商1002制造的任何产品,而不管其价格如何。换句话说,由于AND在计算次序中优先级更高,操作符被错误地组合了。
解决优先级的方法是使用圆括号明确地分组相应的操作符。
SELECT prod_price,prod_name,vend_id FROM products WHERE (vend_id = 1002 OR vend_id = 1003) AND prod_price >= 10
圆括号具有较AND或OR操作符高的计算次序, DBMS首先过滤圆括号内的OR条件。
这时, SQL语句变成了选择由供应商1002或1003制造的且价格都在10美元(含)以上的任何产品,这正是我们所希望的。
在WHERE子句中使用圆括号:任何时候使用具有AND和OR操作符的WHERE子句,都应该使用圆括号明确地分组操作符。
不要过分依赖默认计算次序,即使它确实是你想要的东西也是如此。使用圆括号没有什么坏处,它能消除歧义。
IN操作符
圆括号在WHERE子句中还有另外一种用法。 IN操作符用来指定条件范围,范围中的每个条件都可以进行匹配。 IN取合法值的由逗号分隔的清单,全都括在圆括号中。
SELECT prod_price,prod_name,vend_id FROM products WHERE vend_id IN (1002,1003) ORDER BY prod_name
此SELECT语句检索供应商1002和1003制造的所有产品。 IN操作符后跟由逗号分隔的合法值清单,整个清单必须括在圆括号中。
注意:IN匹配的是括号中的具体指而不是范围,IN操作符完成与OR相同的功能;
为什么要使用IN操作符?其优点具体如下:
- 在使用长的合法选项清单时, IN操作符的语法更清楚且更直观。
- 在使用IN时,计算的次序更容易管理(因为使用的操作符更少)。
- IN操作符一般比OR操作符清单执行更快。
- IN的最大优点是可以包含其他SELECT语句,使得能够更动态地建立WHERE子句。详见:使用子查询。
NOT操作符
WHERE子句中的NOT操作符有且只有一个功能,那就是否定它之后所跟的任何条件。
SELECT prod_price,prod_name,vend_id FROM products WHERE vend_id NOT IN (1002,1003) ORDER BY prod_name
对于简单的WHERE子句,使用NOT确实没有什么优势。
但在更复杂的子句中, NOT是非常有用的。例如,在与IN操作符联合使用时, NOT使找出与条件列表不匹配的行非常简单。
MySQL中的NOT:MySQL支持使用NOT 对 IN 、 BETWEEN 和EXISTS子句取反,这与多数其他DBMS允许使用NOT对各种条件取反有很大的差别。
用通配符进行过滤
LIKE操作符
前面介绍的所有操作符都是针对已知值进行过滤的。不管是匹配一个还是多个值,测试大于还是小于已知值,或者检查某个范围的值,共同点是过滤中使用的值都是已知的。但是,这种过滤方法并不是任何时候都好用。
通配符(wildcard) :用来匹配值的一部分的特殊字符。
利用通配符可创建比较特定数据的搜索模式。
搜索模式(search pattern): 由字面值、通配符或两者组合构成的搜索条件。
通配符本身实际是SQL的WHERE子句中有特殊含义的字符, SQL支持几种通配符。
为在搜索子句中使用通配符,必须使用LIKE操作符。
LIKE指示MySQL,后跟的搜索模式利用通配符匹配而不是直接相等匹配进行比较。
百分号( %)通配符
最常使用的通配符是百分号(%)。在搜索串中, %表示任何字符出现任意次数。
例如,为了找出所有以词jet起头的产品,可使用以下SELECT语句:
SELECT prod_price,prod_name,vend_id FROM products WHERE prod_name LIKE "jet%";
此例子使用了搜索模式'jet%'。在执行这条子句时,将检索任意以jet起头的词。 %告诉MySQL接受jet之后的任意字符,不管它有多少字符。
区分大小写:根据MySQL的配置方式,搜索可以是区分大小写的。如果区分大小写, 'jet%'与JetPack 1000将不匹配。
通配符可在搜索模式中任意位置使用,并且可以使用多个通配符。
下面的例子使用两个通配符,它们位于模式的两端:
SELECT prod_price,prod_name,vend_id FROM products WHERE prod_name LIKE "%anvil%";
搜索模式'%anvil%'表示匹配任何位置包含文本anvil的值,而不论它之前或之后出现什么字符。
重要的是要注意到,除了一个或多个字符外, %还能匹配0个字符。 %代表搜索模式中给定位置的0个、 1个或多个字符。
注意尾空格: 尾空格可能会干扰通配符匹配。
例如,在保存词anvil 时 , 如果它后面有一个或多个空格 ,则子句WHERE prod_name LIKE '%anvil'将不会匹配它们,因为在最后的l后有多余的字符。解决这个问题的一个简单的办法是在搜索模式最后附加一个%。一个更好的办法是使用函数(参见:使用数据处理函数)去掉首尾空格。
注意NULL :虽然似乎%通配符可以匹配任何东西,但有一个例外,即NULL。即使是WHERE prod_name LIKE '%'也不能匹配用值NULL作为产品名的行。
下划线( _)通配符
另一个有用的通配符是下划线(_)。下划线的用途与%一样,但下划线只匹配单个字符而不是多个字符。
使用通配符的技巧
正如所见, MySQL的通配符很有用。
但这种功能是有代价的:通配符搜索的处理一般要比前面讨论的其他搜索所花时间更长。这里给出一些使用通配符要记住的技巧。
- 不要过度使用通配符。如果其他操作符能达到相同的目的,应该使用其他操作符。
- 在确实需要使用通配符时,除非绝对有必要,否则不要把它们用在搜索模式的开始处。把通配符置于搜索模式的开始处,搜索起来是最慢的。
- 仔细注意通配符的位置。如果放错地方,可能不会返回想要的数据。
用正则表达式进行搜索
正则表达式是用来匹配文本的特殊的串(字符集合)。
使用MySQL正则表达式
MySQL用WHERE子句对正则表达式提供了初步的支持,允许你指定正则表达式,过滤SELECT检索出的数据。
仅为正则表达式语言的一个子集:如果你熟悉正则表达式, 需要注意: MySQL仅支持多数正则表达式实现的一个很小的子集。
基本字符匹配
SELECT prod_name from products WHERE prod_name REGEXP "1000" ORDER BY prod_name
除关键字LIKE被REGEXP替代外,这条语句看上去非常像使用LIKE的语句;
它告诉MySQL: REGEXP后所跟的东西作为正则表达式(与文字正文1000匹配的一个正则表达式)处理。
SELECT prod_name from products WHERE prod_name REGEXP ".000" ORDER BY prod_name
这里使用了正则表达式.000。 .是正则表达式语言中一个特殊的字符。它表示匹配任意一个字符,因此, 1000和2000都匹配且返回。
当然,这个特殊的例子也可以用LIKE和通配符来完成 ;
LIKE与REGEXP 在LIKE和REGEXP之间有一个重要的差别。
请看以下两条语句:
SELECT prod_name from products WHERE prod_name LINK "1000" ORDER BY prod_name
SELECT prod_name from products WHERE prod_name REGEXP "1000" ORDER BY prod_name
如果执行上述两条语句,会发现第一条语句不返回数据,而第二条语句返回一行。为什么?
LIKE匹配整个列值。如果被匹配的文本在列值中出现(仅仅是包含文本,不是列值等于文本), LIKE将不会找到它,相应的行也不被返回(除非使用通配符)。
而REGEXP在列值内进行匹配,如果被匹配的文本在列值中出现, REGEXP将会找到它,相应的行将被返回。这是一个非常重要的差别。
匹配不区分大小写: MySQL中的正则表达式匹配(自版本3.23.4后)不区分大小写(即,大写和小写都匹配)。为区分大小写,可使用BINARY关键字,如WHERE prod_name REGEXP BINARY 'JetPack .000'。
进行OR匹配
为搜索两个串之一(或者为这个串,或者为另一个串),使用|;
SELECT prod_name from products WHERE prod_name REGEXP "1000|2000" ORDER BY prod_name
语句中使用了正则表达式1000|2000。 |为正则表达式的OR操作符。它表示匹配其中之一,因此1000和2000都匹配并返回。
使用|从功能上类似于在SELECT语句中使用OR语句, 多个OR条件可并入单个正则表达式。
两个以上的OR条件:可以给出两个以上的OR条件。例如,'1000 | 2000 | 3000'将匹配1000或2000或3000。
匹配几个字符之一
如果你只想匹配特定的字符 ,可通过指定一组用[和]括起来的字符来完成 ;
SELECT prod_name from products WHERE prod_name REGEXP "[123] Ton" ORDER BY prod_name
这里,使用了正则表达式[123] Ton。 [123]定义一组字符,它的意思是匹配1或2或3,因此, 1 ton和2 ton都匹配且返回(没有3 ton)。
正如所见, [ ]是另一种形式的OR语句。 事实上,正则表达式[123]Ton为[1|2|3]Ton的缩写,也可以使用后者。但是,需要用[]来定义OR语句查找什么。为更好地理解这一点,请看下面的例子:
SELECT prod_name from products WHERE prod_name REGEXP "1|2|3 Ton" ORDER BY prod_name
输出:
prod_name 1 ton anvil 2 ton anvil JetPack 1000 JetPack 2000 TNT (1 stick) 这并不是期望的输出。两个要求的行被检索出来,但还检索出了另外3行。之所以这样是由于MySQL假定你的意思是'1'或'2'或'3 ton'。除非把字符 | 括在(使用 [ ])一个集合中,否则它将应用于整个串。
上例中由于没有使用 [ ],mysql检索的是prod_name列中,含有1,或者2,或者3的行;
字符集合也可以被否定,即,它们将匹配除指定字符外的任何东西。为否定一个字符集,在集合的开始处放置一个^即可。因此,尽管[123]匹配字符1、 2或3,但[^123]却匹配除这些字符外的任何东西。
匹配范围
集合可用来定义要匹配的一个或多个字符。例如,下面的集合将匹配数字0到9:
[0123456789]
为简化这种类型的集合,可使用-来定义一个范围。下面的式子功能上等同于上述数字列表:
[0-9]
范围不限于完整的集合, [1-3]和[6-9]也是合法的范围。此外,范围不一定只是数值的, [a-z]匹配任意字母字符。
SELECT prod_name from products WHERE prod_name REGEXP "[1-5] Ton" ORDER BY prod_name
这里使用正则表达式[1-5] Ton。 [1-5]定义了一个范围,这个表达式意思是匹配1到5。
匹配特殊字符
正则表达式语言由具有特定含义的特殊字符构成。我们已经看到.、 []、|和-等,还有其他一些字符。
为了匹配特殊字符,必须用\为前导。 \\-表示查找-, \\.表示查找.。
这种处理就是所谓的转义( escaping),正则表达式内具有特殊意义的所有字符都必须以这种方式转义。这包括.、 |、 []以及迄今为止使用过的其他特殊字符。
\\也用来引用元字符(具有特殊含义的字符),如下表。
元字符 | 说明 |
---|---|
\\f | 换页 |
\\n | 换行 |
\\r | 回车 |
\\t | 制表 |
\\v | 纵向制表 |
匹配\: 为了匹配反斜杠( \)字符本身,需要使用\\。
匹配字符类
为更方便工作,可以使用预定义的字符集,称为字符类( character class)。
类 | 说明 |
---|---|
[:alnum:] | 任意字母和数字(同[a-zA-Z0-9]) |
[:alpha:] | 任意字符(同[a-zA-Z]) |
[:blank:] | 空格和制表(同[\t]) |
[:cntrl:] | ASCII控制字符( ASCII 0到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]) |
匹配多个实例
目前为止使用的所有正则表达式都试图匹配单次出现。如果存在一个匹配,该行被检索出来,如果不存在,检索不出任何行。但有时需要对匹配的数目进行更强的控制。
例如,你可能需要寻找所有的数,不管数中包含多少数字,或者你可能想寻找一个单词并且还能够适应一个尾随的s(如果存在),等等。
正则表达式重复元字符表如下:
元字符 | 说明 |
---|---|
* | 0个或多个匹配 |
+ | 1个或多个匹配(等于{1,}) |
? | 0个或1个匹配(等于{0,1}) |
指定数目的匹配 | |
不少于指定数目的匹配 | |
匹配数目的范围( m不超过255) |
定位符
目前为止的所有例子都是匹配一个串中任意位置的文本。为了匹配特定位置的文本,需要使用一下列出的定位符。
元符号 | 说明 |
---|---|
^ | 文本的开始 |
$ | 文本的结尾 |
[[:<:]] | 词的开始 |
[[:>:]] | 词的结尾 |
的双重用途:有两种用法。在集合中(用[和]定义),用它来否定该集合,否则,用来指串的开始处。
使REGEXP起类似LIKE的作用:
前面说过, LIKE和REGEXP的不同在于,
LIKE匹配整个串而REGEXP匹配子串
。利用定位符,通过用^开始每个表达式,用$结束每个表达式,可以使REGEXP的作用与LIKE一样。
创建计算字段
计算字段
存储在数据库表中的数据一般不是应用程序所需要的格式。
比如:
物品订单表存储物品的价格和数量,但不需要存储每个物品的总价格(用价格乘以数量即可)。为打印发票,需要物品的总价格。
需要根据表数据进行总数、平均数计算或其他计算。
在上述每个例子中,存储在表中的数据都不是应用程序所需要的。我们需要直接从数据库中检索出转换、计算或格式化过的数据;而不是检索出数据,然后再在客户机应用程序或报告程序中重新格式化。
这就是计算字段发挥作用的所在了。
与前面各章介绍过的列不同,计算字段并不实际存在于数据库表中。计算字段是运行时在SELECT语句内创建的。
字段(field) :基本上与列( column) 的意思相同,经常互换使用,不过数据库列一般称为列,而术语字段通常用在计算字段的连接上。
重要的是要注意到,只有数据库知道SELECT语句中哪些列是实际的表列,哪些列是计算字段。从客户机(如应用程序)的角度来看,计算字段的数据是以与其他列的数据相同的方式返回的。
拼接字段
拼接(concatenate): 将值联结到一起构成单个值。
在MySQL的SELECT语句中,可使用Concat()函数来拼接两个列。
如下需求:
vendors表包含供应商名和位置信息。假如要生成一个供应商报表,需要在供应商的名字中按照name(location)这样的格式列出供应商的位置.
SELECT CONCAT(vend_name, '(' ,vend_country, ')' ) FROM vendors ORDER BY vend_name
Concat()拼接串,即把多个串连接起来形成一个较长的串。Concat()需要一个或多个指定的串,各个串之间用逗号分隔。
使用别名
以上的例子中,新计算列默认没有列名;
为了解决这个问题, SQL支持列别名。 别名( alias) 是一个字段或值的替换名。别名用AS关键字赋予。
SELECT CONCAT(vend_name, '(' ,vend_country, ')' ) as vend_titel FROM vendors ORDER BY vend_name
AS vend_title。它指示SQL创建一个包含指定计算的名为vend_title的计算字段。从输出中可以看到,结果与以前的相同,但现在列名为vend_title,任何客户机应用都可以按名引用这个列,就像它是一个实际的表列一样。
别名的其他用途 : 别名还有其他用途。常见的用途包括在实际的表列名包含不符合规定的字符(如空格)时重新命名它,在原来的名字含混或容易误解时扩充它,等等。
导出列 :别名有时也称为导出列( derived column),不管称为什么,它们所代表的都是相同的东西。
执行算术计算
计算字段的另一常见用途是对检索出的数据进行算术计算。
如下需求:
订单号20005中的所有物品,每种商品的总价(商品数x订购单价)
SELECT prod_id,quantity,item_price, quantity*item_price AS expanded_price FROM orderitems WHERE order_num = 20005
MySQL支持以下列出的基本算术操作符。此外,圆括号可用来区分优先顺序。
操作符 | 说明 |
---|---|
+ | 加 |
- | 减 |
* | 乘 |
/ | 除 |
如何测试计算 :虽然SELECT通常用来从表中检索数据,但可以省略FROM子句以便简单地访问和处理表达式。例如, SELECT 3*2;将返回6, SELECT Trim('abc');将返回abc,而SELECT Now()利用Now()函数返回当前日期和时间。
通过这些例子,可以明白如何根据需要使用SELECT进行试验。
使用数据处理函数
函数
与其他大多数计算机语言一样, SQL支持利用函数来处理数据。函数一般是在数据上执行的,它给数据的转换和处理提供了方便。
使用函数
大多数SQL实现支持以下类型的函数。
- 用于处理文本串(如删除或填充值,转换值为大写或小写)的文本函数。
- 用于在数值数据上进行算术操作(如返回绝对值,进行代数运算)的数值函数。
- 用于处理日期和时间值并从这些值中提取特定成分(例如,返回两个日期之差,检查日期有效性等)的日期和时间函数。
- 返回DBMS正使用的特殊信息(如返回用户登录信息,检查版本细节)的系统函数。
文本处理函数
常用的文本处理函数。
函数 | 说明 |
---|---|
Left() | 返回串左边的字符 |
Length() | 返回串的长度 |
Locate() | 找出串的一个子串 |
Lower() | 将串转换为小写 |
LTrim() | 去掉串左边的空格 |
Right() | 返回串右边的字符 |
RTrim() | 去掉串右边的空格 |
Soundex() | 返回串的SOUNDEX值 |
SubString() | 返回子串的字符 |
Upper() | 将串转换为大写 |
日期和时间处理函数
日期和时间采用相应的数据类型和特殊的格式存储,以便能快速和有效地排序或过滤,并且节省物理存储空间。
常用的日期和时间处理函数。
函数 | 说明 |
---|---|
AddDate | 增加一个日期(天、周等) |
AddTime | 增加一个时间(时、分等) |
CurDate | 返回当前日期 |
CurTime | 返回当前时间 |
Date | 返回日期时间的日期部分 |
DateDiff | 计算两个日期之差 |
Date_Add | 高度灵活的日期运算函数 |
Date_Format | 返回一个格式化的日期或时间串 |
Day | 返回一个日期的天数部分 |
DayOfWeek | 对于一个日期,返回对应的星期几 |
Hour | 返回一个时间的小时部分 |
Minute | 返回一个时间的分钟部分 |
Month | 返回一个日期的月份部分 |
Now | 返回当前日期和时间 |
Second | 返回一个时间的秒部分 |
Time | 返回一个日期时间的时间部分 |
Year | 返回一个日期的年份部分 |
用日期进行过滤需要注意一些别的问题和使用特殊的MySQL函数。
首先需要注意的是MySQL使用的日期格式。无论你什么时候指定一个日期,不管是插入或更新表值还是用WHERE子句进行过滤,日期必须为格式yyyy-mm-dd。因此, 2005年9月1日,给出为2005-09-01。虽然其他的日期格式可能也行,但这是首选的日期格式,因为它排除了多义性(如,04/05/06是2006年5月4日或2006年4月5日或2004年5月6日或……)。
如下例子:
检索出一个订单记录,该订单记录的order_date为2005-09-01。
SELECT cust_id , order_num FROM orders WHERE order_date = "2005-09-01"
使用WHERE order_date = '2005-09-01'可靠吗?
order_date的数据类型为datetime。这种类型存储日期及时间值。
样例表中的值全都具有时间值00:00:00,但实际中很可能并不总是这样。
比如 , 存储的 order_date 值为2005-09-01 11:30:05,则WHERE order_date = '2005-09-01'失败。即使给出具有该日期的一行,也不会把它检索出来,因为WHERE匹配失败。
解决办法是指示MySQL仅将给出的日期与列中的日期部分进行比较,而不是将给出的日期与整个列值进行比较。
为此,必须使用Date()函数。 Date(order_date)指示MySQL仅提取列的日期部分,更可靠的SELECT语句为:
SELECT cust_id , order_num FROM orders WHERE DATE (order_date) = "2005-09-01"
如果要的是日期请使用Date() : 如果你想要的仅是日期,则使用Date()是一个良好的习惯,即使你知道相应的列只包含日期也是如此。这样,如果由于某种原因表中以后有日期和时间值,你的SQL代码也不用改变。当然,也存在一个Time()函数,在你只想要时间时应该使用它。
还有一种日期比较需要说明。如果你想检索出2005年9月下的所有订单,怎么办?
有几种解决办法,其中之一如下所示:
SELECT cust_id , order_num FROM orders WHERE DATE (order_date) BETWEEN "2005-09-01" AND "2005-09-03"
还有另外一种办法(一种不需要记住每个月中有多少天或不需要操心闰年2月的办法):
SELECT cust_id , order_num FROM orders WHERE YEAR(order_date) = 2005 AND MONTH(order_date) = 9
Year()是一个从日期(或日期时间)中返回年份的函数。类似,Month()从日期中返回月份。因此, WHERE Year(order_date)= 2005 AND Month(order_date) = 9检索出order_date为2005年9月的所有行。
数值处理函数
数值处理函数仅处理数值数据。这些函数一般主要用于代数、三角或几何运算,因此没有串或日期—时间处理函数的使用那么频繁。
常用数值处理函数
函数 | 说明 |
---|---|
Abs() | 返回一个数的绝对值 |
Cos() | 返回一个角度的余弦 |
Exp() | 返回一个数的指数值 |
Mod() | 返回除操作的余数 |
Pi() | 返回圆周率 |
Rand() | 返回一个随机数 |
Sin() | 返回一个角度的正弦 |
Sqrt() | 返回一个数的平方根 |
Tan() | 返回一个角度的正切 |
汇总数据
聚集函数
我们经常需要汇总数据而不用把它们实际检索出来,为此MySQL提供了专门的函数。
聚集函数( aggregate function): 运行在行组上,计算和返回单个值的函数。
SQL聚集函数:
函数 | 说明 |
---|---|
AVG() | 返回某列的平均值 |
COUNT() | 返回某列的行数 |
MAX() | 返回某列的最大值 |
MIN() | 返回某列的最小值 |
SUM() | 返回某列值之和 |
AVG()函数
AVG()通过对表中行数计数并计算特定列值之和,求得该列的平均值。 AVG()可用来返回所有列的平均值,也可以用来返回特定列或行的平均值。
例子:使用AVG()返回products表中所有产品的平均价格
SELECT AVG(prod_price) AS avg_price FROM products
例子:AVG()也可以用来确定特定列或行的平均值。下面的例子返回特定供应商所提供产品的平均价格:
SELECT AVG(prod_price) AS avg_price FROM products WHERE vend_id = 1003
只用于单个列 AVG():只能用来确定特定数值列的平均值,而且列名必须作为函数参数给出。为了获得多个列的平均值,必须使用多个AVG()函数。
NULL值 :AVG()函数忽略列值为NULL的行。
COUNT()函数
COUNT()函数进行计数。可利用COUNT()确定表中行的数目或符合特定条件的行的数目。
COUNT()函数有两种使用方式。
- 使用COUNT(*)对表中行的数目进行计数, 不管表列中包含的是空值( NULL)还是非空值。
- 使用COUNT(column)对特定列中具有值的行进行计数,忽略NULL值。
例子:返回customers表中客户的总数;统计所有行,不管行中的列是否有数据;
SELECT COUNT(*) AS count_num FROM customers
例子:只对具有电子邮件地址的客户计数;只统计cust_email中有数据的行;
SELECT COUNT(cust_email) AS count_num FROM customers
MAX()函数
MAX()返回指定列中的最大值。 MAX()要求指定列名,如下所示:
SELECT MAX(prod_price) AS max_price FROM products
NULL值 :MAX()函数忽略列值为NULL的行。
MIN()函数
MIN()的功能正好与MAX()功能相反,它返回指定列的最小值。与MAX()一样, MIN()要求指定列名,如下所示:
SELECT MIN(prod_price) AS min_price FROM products
NULL值: MIN()函数忽略列值为NULL的行。
SUM()函数
SUM()用来返回指定列值的和(总计)。
SELECT SUM(quantity) as items_ordered FROM orderitems WHERE order_num = 20005
SUM()也可以用来合计计算值。在下面的例子中,合计每项物品的item_price*quantity,得出总的订单金额:
SELECT SUM(quantity*item_price) as total_price FROM orderitems WHERE order_num = 20005
在多个列上进行计算 :如本例所示,利用标准的算术操作符,所有聚集函数都可用来执行多个列上的计算。
NULL值 :SUM()函数忽略列值为NULL的行。
聚集不同值
以上5个聚集函数都可以如下使用:
- 对所有的行执行计算,指定ALL参数或不给参数(因为ALL是默认行为);
- 只包含不同的值,指定DISTINCT参数。
下面的例子使用AVG()函数返回特定供应商提供的产品的平均价格。
它与上面的SELECT语句相同,但使用了DISTINCT参数,因此平均值只考虑各个不同的价格:
SELECT AVG(DISTINCT prod_price) AS avg_price FROM products WHERE vend_id = 1003
注意 :如果指定列名,则DISTINCT只能用于COUNT()。DISTINCT不能用于COUNT(*),因此不允许使用COUNT( DISTINCT),否则会产生错误。类似地, DISTINCT必须使用列名,不能用于计算或表达式。
将DISTINCT用于MIN()和MAX() :虽然DISTINCT从技术上可用于MIN()和MAX(),但这样做实际上没有价值。一个列中的最小值和最大值不管是否包含不同值都是相同的。
组合聚集函数
目前为止的所有聚集函数例子都只涉及单个函数。
但实际上SELECT语句可根据需要包含多个聚集函数。请看下面的例子:
SELECT AVG(prod_price) AS avg_price , COUNT(*) AS num_items, MIN(prod_price) AS min_price,MAX(prod_price) AS max_price FROM products
分组数据
数据分组
目前为止的所有计算都是在表的所有数据或匹配特定的WHERE子句的数据上进行的。
下面的例子返回供应商1003提供的产品数目:
SELECT COUNT(*) AS num_prods FROM products WHERE vend_id = 1003
但如果要返回每个供应商提供的产品数目怎么办?或者返回只提供单项产品的供应商所提供的产品,或返回提供10个以上产品的供应商怎么办?
这就是分组显身手的时候了。
分组允许把数据分为多个逻辑组,以便能对每个组进行聚集计算。
创建分组
分组是在SELECT语句的GROUP BY子句中建立的。
SELECT COUNT(*) AS num_prods ,vend_id FROM products GROUP BY vend_id
上面的SELECT语句指定了两个列, vend_id包含产品供应商的ID,num_prods为计算字段(用COUNT(*)函数建立)。
GROUP BY子句指示MySQL按vend_id排序并分组数据。这导致对每个vend_id而不是整个表计算num_prods一次。从输出中可以看到,供应商1001有3个产品,供应商1002有2个产品,供应商1003有7个产品,而供应商1005有2个产品。
因为使用了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子句之前。
过滤分组
除了能用GROUP BY分组数据外, MySQL还允许过滤分组,规定包括哪些分组,排除哪些分组。
例如,可能想要列出至少有两个订单的所有顾客。为得出这种数据,必须基于完整的分组而不是个别的行进行过滤。
我们已经看到了WHERE子句的作用。但是,在这个例子中WHERE不能完成任务,因为WHERE过滤指定的是行而不是分组。事实上, WHERE没有分组的概念。
MySQL为此目的提供了另外的子句,那就是HAVING子句。
HAVING非常类似于WHERE。事实上,目前为止所学过的所有类型的WHERE子句都可以用HAVING来替代。唯一的差别是WHERE过滤行,而HAVING过滤分组。
例如:列出至少有两个订单的所有顾客
SELECT COUNT(*) AS orders ,cust_id FROM orders GROUP BY cust_id HAVING COUNT(*) >= 2
最后一行增加了HAVING子句,它过滤COUNT(*) >=2(两个以上的订单)的那些分组。
分组和排序
虽然GROUP BY和ORDER BY经常完成相同的工作,但它们是非常不同的。
以下列出了它们之间的差别:
ORDER BY | GROUP BY |
---|---|
排序产生的输出 | 分组行。但输出可能不是分组的顺序 |
任意列都可以使用(甚至非选择的列也可以使用) | 只可能使用选择列或表达式列,而且必须使用每个选择列表达式 |
不一定需要 | 如果与聚集函数一起使用列(或表达式),则必须使用 |
SELECT子句顺序
下表列出了在SELECT语句中使用时必须遵循的次序,列出迄今为止所学过的子句。
子句 | 说明 | 是否必须使用 |
---|---|---|
SELECT | 要返回的列或表达式 | 是 |
FROM | 从中检索数据的表 | 仅在从表选择数据时使用 |
WHERE | 行级过滤 | 否 |
GROUP BY | 分组说明 | 仅在按组计算聚集时使用 |
HAVING | 组级过滤 | 否 |
ORDER BY | 输出排序顺序 | 否 |
LIMIT | 要检索的行数 | 否 |
使用子查询
迄今为止我们所看到的所有SELECT语句都是简单查询,即从单个数据库表中检索数据的单条语句。
SQL还允许创建子查询( subquery) ,即嵌套在其他查询中的查询。
利用子查询进行过滤
有如下表:
对于包含订单号、客户ID、订单日期的每个订单, orders表存储一行。
各订单的物品存储在相关的orderitems表中。 orders表不存储客户信息。它只存储客户的ID。实际的客户信息存储在customers表中。
例子:需要列出订购物品TNT2的所有客户,步骤如下:
- 检索包含物品TNT2的所有订单的编号。
- 检索具有前一步骤列出的订单编号的所有客户的ID。
- 检索前一步骤返回的所有客户ID的客户信息。
- 查询点单编号
SELECT order_num FROM orderitems WHERE prod_id = 'TNT2'
- 查询用户ID
SELECT cust_id FROM orders WHERE order_num in (20005,20007)
- 查询用户信息
SELECT cust_name,cust_contact FROM customers WHERE cust_id in (10001,10004)
组合查询:
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语句中,子查询总是从内向外处理。
列必须匹配 在WHERE子句中使用子查询(如这里所示),应该保证SELECT语句具有与WHERE子句中相同数目的列。通常,
子查询将返回单个列并且与单个列匹配,但如果需要也可以使用多个列。
子查询和性能: 这里给出的代码有效并获得所需的结果。
但是,使用子查询并不总是执行这种类型的数据检索的最有效的方法。可参考:连接表。
作为计算字段使用子查询
使用子查询的另一方法是创建计算字段。假如需要显示customers表中每个客户的订单总数。
订单与相应的客户ID存储在orders表中。
为了执行这个操作,遵循下面的步骤:
- 从customers表中检索客户列表。
- 对于检索出的每个客户,统计其在orders表中的订单数目。
SELECT cust_name,cust_state,(
SELECT COUNT(*) FROM orders WHERE orders.cust_id = customers.cust_id
) AS orders FROM customers ORDER BY cust_name
orders是一个计算字段,它是由圆括号中的子查询建立的。该子查询对检索出的每个客户执行一次。
子查询中的WHERE子句与前面使用的WHERE子句稍有不同,因为它使用了完全限定列名;
这种类型的子查询称为相关子查询。任何时候只要列名可能有多义性,就必须使用这种语法(表名和列名由一个句点分隔)。
逐渐增加子查询来建立查询: 用子查询测试和调试查询很有技巧性,特别是在这些语句的复杂性不断增加的情况下更是如此。 用子查询建立(和测试)查询的最可靠的方法是逐渐进行,这与MySQL处理它们的方法非常相同。首先,建立和测试最内层的查询。然后,用硬编码数据建立和测试外层查询,并且仅在确认它正常后才嵌入子查询。这时,再次测试它。对于要增加的每个查询,重复这些步骤。这样做仅给构造查询增加了一点点时间,但节省了以后(找出查询为什么不正常)的大量时间,并且极大地提高了查询一开始就正常工作的可能性。
联结表
联结
SQL最强大的功能之一就是能在数据检索查询的执行中联结(join)表。
在能够有效地使用联结前,必须了解关系表以及关系数据库设计的一些基础知识。
关系表
表与表之间存在某种关系,由外键相关联。
外键(foreign key): 外键为某个表中的一列,它包含另一个表的主键值,定义了两个表之间的关系。
为什么要使用联结
如果数据存储在多个表中,怎样用单条SELECT语句检索出数据?
答案是使用联结。简单地说,联结是一种机制,用来在一条SELECT语句中关联表,因此称之为联结。使用特殊的语法,可以联结多个表返回一组输出,联结在运行时关联表中正确的行。
维护引用完整性: 重要的是,要理解联结不是物理实体。换句话说,它在实际的数据库表中不存在。联结由MySQL根据需
要建立,它存在于查询的执行当中。在使用关系表时,仅在关系列中插入合法的数据非常重要。回到这里的例子,如果在products表中插入拥有非法供应商ID
(即没有在vendors表中出现)的供应商生产的产品,则这些产品是不可访问的,因为它们没有关联到某个供应商。为防止这种情况发生,可指示MySQL只允许在products表的供应商ID列中出现合法值(即出现在vendors表中的供应商)。这就是维护引用完整性,它是通过在表的定义中指定主键和外键来实现的。(参见:创建和操作表)
创建联结
联结的创建非常简单,规定要联结的所有表以及它们如何关联即可。
SELECT vend_name, prod_name, prod_price
FROM vendors ,products
WHERE vendors.vend_id = products.vend_id
ORDER BY vend_name,prod_name
与以前的SELECT语句不一样,这条语句的FROM子句列出了两个表,分别是vendors和products。它们就是这条SELECT语句联结的两个表的名字。这两个表用WHERE子句正确联结, WHERE子句指示MySQL匹配vendors表中的vend_id和products表中的vend_id。
可 以 看 到 要 匹 配 的 两 个 列 以 vendors.vend_id 和 products.vend_id指定。这里需要这种完全限定列名,因为如果只给出vend_id,则MySQL不知道指的是哪一个(它们有两个,每个表中一个)。
完全限定列名:在引用的列可能出现二义性时,必须使用完全限定列名(用一个点分隔的表名和列名)。如果引用一个没有用表名限制的具有二义性的列名, MySQL将返回错误。
WHERE子句的重要性
利用WHERE子句建立联结关系似乎有点奇怪,但实际上,有一个很充分的理由。请记住,在一条SELECT语句中联结几个表时,相应的关系是在运行中构造的。在数据库表的定义中不存在能指示MySQL如何对表进行联结的东西。你必须自己做这件事情。在联结两个表时,你实际上做的是将第一个表中的每一行与第二个表中的每一行配对。 WHERE子句作为过滤条件,它只包含那些匹配给定条件(这里是联结条件)的行。
没有WHERE子句,第一个表中的每个行将与第二个表中的每个行配对,而不管它们逻辑上是否可以配在一起。
笛卡儿积(cartesian product) :由没有联结条件的表关系返回的结果为笛卡儿积。检索出的行的数目将是第一个表中的行数乘以第二个表中的行数。
内部联结
目前为止所用的联结称为等值联结(equijoin),它基于两个表之间的相等测试。这种联结也称为内部联结。
其实,对于这种联结可以使用稍微不同的语法来明确指定联结的类型。
SELECT
vend_name,
prod_name,
prod_price
FROM
vendors
INNER JOIN products ON vendors.vend_id = products.vend_id
此语句中的SELECT与前面的SELECT语句相同,但FROM子句不同。这里,两个表之间的关系是FROM子句的组成部分,以INNER JOIN指定。在使用这种语法时,联结条件用特定的ON子句而不是WHERE子句给出。传递给ON的实际条件与传递给WHERE的相同。
联结多个表
SQL对一条SELECT语句中可以联结的表的数目没有限制。创建联结的基本规则也相同。
SELECT
vend_name,
prod_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在运行时关联指定的每个表以处理联结。这种处理可能是非常耗费资源的,因此应该仔细,不要联结不必要的表。联结的表越多,性能下降越厉害
创建高级联结
使用表别名
别名除了用于列名和计算字段外, SQL还允许给表名起别名。这样做有两个主要理由:
- 缩短SQL语句;
- 允许在单条SELECT语句中多次使用相同的表。
SELECT
cust_name,
cust_contact
FROM
customers AS c,
orders AS o,
orderitems AS oi
WHERE
c.cust_id = o.cust_id
AND oi.order_num = o.order_num
AND prod_id = 'TNT2'
在此例子中,表别名只用于WHERE子句。但是,表别名不仅能用于WHERE子句,它还可以用于SELECT的列表、 ORDER BY子句以及语句的其他部分。
应该注意,表别名只在查询执行中使用。与列别名不一样,表别名不返回到客户机。
使用不同类型的联结
之前已经了解了内部联结或等值联结( equijoin) 的简单联结。
现在来看3种其他联结,它们分别是自联结、自然联结和外部联结。
自联结
使用表别名的主要原因之一是能在单条SELECT语句中不止一次引用相同的表。
例子:查找生成某商品(其ID为DTNTR)的的生产商生产的其他商品;
方法1(子查询):
SELECT
prod_id,
prod_name
FROM
products
WHERE
vend_id = ( SELECT vend_id FROM products WHERE prod_id = 'DTNTR' )
方法2(自联结):
SELECT
p1.prod_id,
p2.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表中的哪个实例。
为解决此问题,使用了表别名。 products的第一次出现为别名p1,第二次出现为别名p2。现在可以将这些别名用作表名。
例如, SELECT语句使用p1前缀明确地给出所需列的全名。如果不这样, MySQL将返回错误,因为分别存在两个名为prod_id、 prod_name的列。 MySQL不知道想要的是哪一个列(即使它们事实上是同一个列)。 WHERE(通过匹配p1中的vend_id和p2中的vend_id)首先联结两个表,然后按第二个表中的prod_id过滤数据,返回所需的数据。
用自联结而不用子查询: 自联结通常作为外部语句用来替代从相同表中检索数据时使用的子查询语句。
虽然最终的结果是相同的,但有时候处理联结远比处理子查询快得多。应该试一下两种方法,以确定哪一种的性能更好。
自然联结
无论何时对表进行联结,应该至少有一个列出现在不止一个表中(被联结的列)。
标准的联结(前一章中介绍的内部联结)返回所有数据,甚至相同的列多次出现。 自然联结排除多次出现,使每个列只返回一次。
怎样完成这项工作呢?答案是,系统不完成这项工作,由你自己完成它。自然联结是这样一种联结,其中你只能选择那些唯一的列。这一般是*通过对表使用通配符(SELECT ),对所有其他表的列使用明确的子集来完成的。
SELECT
c.*,
o.order_num,
o.order_date,
oi.prod_id
FROM
customers AS c,
orders AS o,
orderitems AS oi
WHERE
c.cust_id = o.cust_id
AND oi.order_item = o.order_num
AND prod_id = 'FB'
在这个例子中,通配符只对第一个表使用。所有其他列明确列出,所以没有重复的列被检索出来。
外部联结
许多联结将一个表中的行与另一个表中的行相关联。但有时候会需要包含没有关联行的那些行。
例如,可能需要使用联结来完成以下工作:
- 对每个客户下了多少订单进行计数,包括那些至今尚未下订单的客户;
- 列出所有产品以及订购数量,包括没有人订购的产品;
- 计算平均销售规模,包括那些至今尚未下订单的客户。
在上述例子中,联结包含了那些在相关表中没有关联行的行。这种类型的联结称为外部联结。
下面的SELECT语句给出一个简单的内部联结。它检索所有客户及其订单:
SELECT
customers.cust_id,
orders.order_num
FROM
customers INNER JOIN orders ON customers.cust_id = orders.cust_id
外部联结语法类似。为了检索所有客户,包括那些没有订单的客户,可如下进行:
SELECT
customers.cust_id,
orders.order_num
FROM
customers
LEFT OUTER JOIN orders ON customers.cust_id = orders.cust_id
与内部联结关联两个表中的行不同的是,外部联结还包括没有关联行的行。在使用OUTER JOIN语法时,必须使用RIGHT或LEFT关键字指定包括其所有行的表( RIGHT指出的是OUTER JOIN右边的表,而LEFT指出的是OUTER JOIN左边的表)。
上面的例子使用LEFT OUTER JOIN从FROM子句的左边表(customers表)中选择所有行。
左外连接(Left Outer Join)
左外连接从左表(左侧表)中选取所有的行,以及右表中与左表匹配的行。如果右表中没有匹配的行,则在结果集中用 NULL 值填充右表的列。
右外连接(Right Outer Join)
右外连接从右表(右侧表)中选取所有的行,以及左表中与右表匹配的行。如果左表中没有匹配的行,则在结果集中用 NULL 值填充左表的列。
使用带聚集函数的联结
虽然至今为止聚集函数的所有例子只是从单个表汇总数据,但这些函数也可以与联结一起使用。
如果要检索所有客户及每个客户所下的订单数,下面使用了COUNT()函数的代码可完成此工作:
SELECT
customers.cust_id,
customers.cust_id,
COUNT(orders.order_num) AS num_ord
FROM
customers
INNER JOIN orders ON customers.cust_id = orders.cust_id
GROUP BY customers.cust_id
此SELECT语句使用INNER JOIN将customers和orders表互相关联。
GROUP BY 子句按客户分组数据 , 因此,函数调用 COUNT(orders.order_num)对每个客户的订单计数,将它作为num_ord返回。
使用联结和联结条件
在总结关于联结的这两章前,有必要汇总一下关于联结及其使用的某些要点。
- 注意所使用的联结类型。一般我们使用内部联结,但使用外部联结也是有效的。
- 保证使用正确的联结条件,否则将返回不正确的数据。
- 应该总是提供联结条件,否则会得出笛卡儿积。
- 在一个联结中可以包含多个表,甚至对于每个联结可以采用不同的联结类型。虽然这样做是合法的,一般也很有用,但应该在一起测试它们前,分别测试每个联结。这将使故障排除更为简单。
组合查询
多数SQL查询都只包含从一个或多个表中返回数据的单条SELECT语句。 MySQL也允许执行多个查询(多条SELECT语句),并将结果作为单个查询结果集返回。这些组合查询通常称为并( union) 或复合查询(compound query)。
有两种基本情况,其中需要使用组合查询:
- 在单个查询中从不同的表返回类似结构的数据;
- 对单个表执行多个查询,按单个查询返回数据。
组合查询和多个WHERE条件: 多数情况下,组合相同表的两个查询完成的工作与具有多个WHERE子句条件的单条查询完成的工作相同。换句话说,任何具有多个WHERE子句的SELECT语句都可以作为一个组合查询给出,在以下段落中可以看到这一点。这两种技术在不同的查询中性能也不同。因此,应该试一下这两种技术,以确定对特定的查询哪一种性能更好。
创建组合查询
可用UNION操作符来组合数条SQL查询。利用UNION,可给出多条SELECT语句,将它们的结果组合成单个结果集。
使用UNION
UNION的使用很简单。所需做的只是给出每条SELECT语句,在各条语句之间放上关键字UNION。
例子:需要价格小于等于5的所有物品的一个列表,而且还想包括供应商1001和1002生产的所有物品(不考虑价格)。当然,可以利用WHERE子句来完成此工作,不过这次我们将使用UNION。
单条sql语句:
SELECT
vend_id,
prod_id,
prod_price
FROM
products
WHERE
prod_price <= 5
SELECT
vend_id,
prod_id,
prod_price
FROM
products
WHERE
vend_id IN (1001,1002)
组合语句如下:
SELECT
vend_id,
prod_id,
prod_price
FROM
products
WHERE
vend_id IN ( 1001, 1002 ) UNION
SELECT
vend_id,
prod_id,
prod_price
FROM
products
WHERE
prod_price <= 5
这条语句由前面的两条SELECT语句组成,语句中用UNION关键字分隔。 UNION指示MySQL执行两条SELECT语句,并把输出组合成单个查询结果集。
作为参考,这里给出使用多条WHERE子句而不是使用UNION的相同查询:
SELECT
vend_id,
prod_id,
prod_price
FROM
products
WHERE
prod_price <= 5
OR vend_id IN (1001,1002)
在这个简单的例子中,使用UNION可能比使用WHERE子句更为复杂。但对于更复杂的过滤条件,或者从多个表(而不是单个表)中检索数据的情形,使用UNION可能会使处理更简单。
UNION规则
在进行并时有几条规则需要注意 :
- UNION必须由两条或两条以上的SELECT语句组成,语句之间用关键字UNION分隔(因此,如果组合4条SELECT语句,将要使用3个UNION关键字)。
- UNION中的每个查询必须包含相同的列、表达式或聚集函数(不过各个列不需要以相同的次序列出)。
- 列数据类型必须兼容:类型不必完全相同,但必须是DBMS可以隐含地转换的类型(例如,不同的数值类型或不同的日期类型)。
包含或取消重复的行
上面的例子中,第一条SELECT语句返回4行,第二条SELECT语句返回5行。但在用UNION组合两条SELECT语句后,只返回了8行而不是9行。
UNION从查询结果集中自动去除了重复的行(它的行为与单条SELECT语句中使用多个WHERE子句条件一样 ) ;
这是UNION的默认行为,但是如果需要,可以改变它。事实上,如果想返回所有匹配行,可使用UNION ALL而不是UNION。
SELECT
vend_id,
prod_id,
prod_price
FROM
products
WHERE
vend_id IN ( 1001, 1002 ) UNION ALL
SELECT
vend_id,
prod_id,
prod_price
FROM
products
WHERE
prod_price <= 5
UNION与WHERE: 本章开始时说过, UNION几乎总是完成与多个WHERE条件相同的工作。 UNION ALL为UNION的一种形式,它完成WHERE子句完成不了的工作。
如果确实需要每个条件的匹配行全部出现(包括重复行),则必须使用UNION ALL而不是WHERE。
对组合查询结果排序
SELECT语句的输出用ORDER BY子句排序。在用UNION组合查询时,只能使用一条ORDER BY子句,它必须出现在最后一条SELECT语句之后。
对于结果集,不存在用一种方式排序一部分,而又用另一种方式排序另一部分的情况,因此不允许使用多条ORDER BY子句。
SELECT
vend_id,
prod_id,
prod_price
FROM
products
WHERE
vend_id IN ( 1001, 1002 ) UNION ALL
SELECT
vend_id,
prod_id,
prod_price
FROM
products
WHERE
prod_price <= 5
ORDER BY
vend_id,
prod_price
组合不同的表:为使表述比较简单,本章例子中的组合查询使用的均是相同的表。
但是其中使用UNION的组合查询可以应用不同的表。
全文本搜索
理解全文本搜索
并非所有引擎都支持全文本搜索 : MySQL支持几种基本的数据库引擎。并非所有的引擎都支持本书所描述的全文本搜索。两个最常使用的引擎为MyISAM和InnoDB,前者支持全文本搜索,而后者不支持。
这就是为什么虽然本书中 创 建 的 多 数 样 例 表 使 用 InnoDB , 而有一个样例表(productnotes表)却使用MyISAM的原因。如果你的应用中需要全文本搜索功能,应该记住这一点。
之前介绍了LIKE关键字,它利用通配操作符匹配文本(和部分文本)。使用LIKE,能够查找包含特殊值或部分值的行(不管这些值位于列内什么位置)。
使用正则表达式,可以编写查找所需行的非常复杂的匹配模式。
虽然这些搜索机制非常有用,但存在几个重要的限制。
- 性能——通配符和正则表达式匹配通常要求MySQL尝试匹配表中所有行(而且这些搜索极少使用表索引)。因此,由于被搜索行数不断增加,这些搜索可能非常耗时。
- 明确控制——使用通配符和正则表达式匹配,很难(而且并不总是能)明确地控制匹配什么和不匹配什么。例如,指定一个词必须匹配,一个词必须不匹配,而一个词仅在第一个词确实匹配的情况下才可以匹配或者才可以不匹配。
- 智能化的结果——虽然基于通配符和正则表达式的搜索提供了非常灵活的搜索,但它们都不能提供一种智能化的选择结果的方法。例如,一个特殊词的搜索将会返回包含该词的所有行,而不区分包含单个匹配的行和包含多个匹配的行(按照可能是更好的匹配来排列它们)。类似,一个特殊词的搜索将不会找出不包含该词但包含其他相关词的行。
所有这些限制以及更多的限制都可以用全文本搜索来解决。在使用全文本搜索时, MySQL不需要分别查看每个行,不需要分别分析和处理每个词。 MySQL创建指定列中各词的一个索引,搜索可以针对这些词进行。这样, MySQL可以快速有效地决定哪些词匹配(哪些行包含它们),哪些词不匹配,它们匹配的频率,等等。
使用全文本搜索
为了进行全文本搜索,必须索引被搜索的列,而且要随着数据的改变不断地重新索引。在对表列进行适当设计后, MySQL会自动进行所有的索引和重新索引。
在索引之后, SELECT可与Match()和Against()一起使用以实际执行搜索。
启用全文本搜索支持
一般在创建表时启用全文本搜索。 CREATE TABLE语句(参见:创建和操作表)接受FULLTEXT子句,它给出被索引列的一个逗号分隔的列表。
下面的CREATE语句演示了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 KEY (`note_text`)
) ENGINE=MyISAM
这些列中有一个名为note_text的列,为了进行全文本搜索, MySQL根据子句FULLTEXT(note_text)的指示对它进行索引。这里的FULLTEXT索引单个列,如果需要也可以指定多个列。
在定义之后, MySQL自动维护该索引。在增加、更新或删除行时,索引随之自动更新。
可以在创建表时指定FULLTEXT,或者在稍后指定(在这种情况下所有已有数据必须立即索引)。
不要在导入数据时使用FULLTEXT :更新索引要花时间,虽然不是很多,但毕竟要花时间。如果正在导入数据到一个新表,此时不应该启用FULLTEXT索引。应该首先导入所有数据,然后再修改表, 定义FULLTEXT。 这样有助于更快地导入数据(而且使索引数据的总时间小于在导入每行时分别进行索引所需的总时间)。
进行全文本搜索
在索引之后,使用两个函数Match()和Against()执行全文本搜索,其中Match()指定被搜索的列, Against()指定要使用的搜索表达式。
SELECT
note_text
FROM
productnotes
WHERE
MATCH ( note_text ) Against (
"rabbit")
此SELECT语句检索单个列note_text。由于WHERE子句,一个全文本搜索被执行。 Match(note_text)指示MySQL针对指定的列进行搜索, Against('rabbit')指定词rabbit作为搜索文本。由于有两行包含词rabbit,这两个行被返回。
使用完整的Match()说 明 :传递给Match()的值必须与FULLTEXT()定义中的相同。如果指定多个列,则必须列出它们(而且次序正确)。
搜索不区分大小写 :除非使用BINARY方式,否则全文本搜索不区分大小写。
利用link查询:
SELECT
note_text
FROM
productnotes
WHERE
note_text LIKE '%rabbit%'
述两条SELECT语句都不包含ORDER BY子句。后者(使用LIKE)以不特别有用的顺序返回数据。
前者(使用全文本搜索)返回以文本匹配的良好程度排序的数据。两个行都包含词rabbit,但包含词rabbit作为第3个词的行的等级比作为第20个词的行高。这很重要。全文本搜索的一个重要部分就是对结果排序。具有较高等级的行先返回(因为这些行很可能是你真正想要的行)。
排序多个搜索项 :如果指定多个搜索项,则包含多数匹配词的那些行将具有比包含较少词(或仅有一个匹配)的那些行高的等级值。
正如所见,全文本搜索提供了简单LIKE搜索不能提供的功能。而且,由于数据是索引的,全文本搜索还相当快。
使用查询扩展
查询扩展用来设法放宽所返回的全文本搜索结果的范围。
考虑下面的情况。你想找出所有提到anvils的注释。只有一个注释包含词anvils,但你还想找出可能与你的搜索有关的所有其他行,即使它们不包含词anvils。
这也是查询扩展的一项任务。在使用查询扩展时, MySQL对数据和索引进行两遍扫描来完成搜索:
- 首先,进行一个基本的全文本搜索,找出与搜索条件匹配的所有行;
- 其次, MySQL检查这些匹配行并选择所有有用的词(我们将会简要地解释MySQL如何断定什么有用,什么无用)。
- 再其次, MySQL再次进行全文本搜索,这次不仅使用原来的条件,而且还使用所有有用的词。
利用查询扩展,能找出可能相关的结果,即使它们并不精确包含所查找的词。
SELECT
note_text
FROM
productnotes
WHERE
MATCH ( note_text ) Against (
"anvils" WITH QUERY EXPANSION)
这次返回了7行。
第一行包含词anvils,因此等级最高。
第二行与anvils无关,但因为它包含第一行中的两个词( customer和recommend),所以也被检索出来。
第3行也包含这两个相同的词,但它们在文本中的位置更靠后且分开得更远,因此也包含这一行,但等级为第三。第三行确实也没有涉及anvils(按它们的产品名)。
正如所见,查询扩展极大地增加了返回的行数,但这样做也增加了你实际上并不想要的行的数目。
行越多越好 :表中的行越多(这些行中的文本就越多),使用查询扩展返回的结果越好。
布尔文本搜索
MySQL支持全文本搜索的另外一种形式,称为布尔方式( boolean mode)。以布尔方式,可以提供关于如下内容的细节:
- 要匹配的词;
- 要排斥的词(如果某行包含这个词,则不返回该行,即使它包含其他指定的词也是如此);
- 排列提示(指定某些词比其他词更重要,更重要的词等级更高);
- 表达式分组;
- 另外一些内容。
即使没有FULLTEXT索引也可以使用 : 布尔方式不同于迄今为止使用的全文本搜索语法的地方在于 , 即使没有定义FULLTEXT索引,也可以使用它。但这是一种非常缓慢的操作(其性能将随着数据量的增加而降低)。
例子:全文本搜索检索包含词heavy的所有行(有两行)
SELECT
note_text
FROM
productnotes
WHERE
MATCH ( note_text ) Against (
"heavy" IN BOOLEAN MODE)
其中使用了关键字IN BOOLEAN MODE,但实际上没有指定布尔操作符,因此,其结果与没有指定布尔方式的结果相同。
为了匹配包含heavy但不包含任意以rope开始的词的行, 可使用以下查询:
SELECT
note_text
FROM
productnotes
WHERE
MATCH ( note_text ) Against (
"heavy -rope*" IN BOOLEAN MODE)
这次只返回一行。这一次仍然匹配词heavy,但-rope*明确地指示MySQL排除包含rope*(任何以rope开始的词,包括ropes)的行,这就是为什么上一个例子中的第一行被排除的原因。
我们已经看到了两个全文本搜索布尔操作符-和*, - 排除一个词,而*是截断操作符(可想象为用于词尾的一个通配符)。
以下列出支持的所有布尔操作符:
布尔操作符 | 说明 |
---|---|
+ | 包含,词必须存在 |
- | 排除,词必须不出现 |
> | 包含,而且增加等级值 |
< | 包含,且减少等级值 |
() | 把词组成子表达式(允许这些子表达式作为一个组被包含、排除、排列等) |
~ | 取消一个词的排序值 |
* | 词尾的通配符 |
"" | 定义一个短语(与单个词的列表不一样,它匹配整个短语以便包含或排除这个短语) |
下面举几个例子,说明某些操作符如何使用:
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中的至少一个词的行。
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 <carrot" IN BOOLEAN MODE)
匹配rabbit和carrot,增加前者的等级,降低后者的等级。
SELECT
note_text
FROM
productnotes
WHERE
MATCH ( note_text ) Against (
"+safe +(<combination)" IN BOOLEAN MODE)
这个搜索匹配词safe和combination,降低后者的等级。
排列而不排序 : 在布尔方式中,不按等级值降序排序返回的行
全文本搜索的使用说明
给出关于全文本搜索的某些重要的说明:
- 在索引全文本数据时,短词被忽略且从索引中排除。短词定义为那些具有3个或3个以下字符的词(如果需要,这个数目可以更改)。
- MySQL带有一个内建的非用词( stopword)列表,这些词在索引全文本数据时总是被忽略。如果需要,可以覆盖这个列表(请参阅MySQL文档以了解如何完成此工作)。
- 许多词出现的频率很高,搜索它们没有用处(返回太多的结果)。因此, MySQL规定了一条50%规则,如果一个词出现在50%以上的行中,则将它作为一个非用词忽略。 50%规则不用于IN BOOLEANMODE。
- 如果表中的行数少于3行,则全文本搜索不返回结果(因为每个词或者不出现,或者至少出现在50%的行中)。
- 忽略词中的单引号。例如, don't索引为dont。
- 不具有词分隔符(包括日语和汉语)的语言不能恰当地返回全文本搜索结果。
- 如前所述,仅在MyISAM数据库引擎中支持全文本搜索。
插入数据
INSERT是用来插入(或添加)行到数据库表的。插入可以用几种方式使用:
- 插入完整的行;
- 插入行的一部分;
- 插入多行;
- 插入某些查询的结果。
插入完整的行
把数据插入表中的最简单的方法是使用基本的INSERT语法,它要求指定表名和被插入到新行中的值。
INSERT INTO customers
VALUES
(
NULL,
"pep E.niehao",
"100 niehao",
"los niehao",
"ca",
"90046",
"usa",
NULL,
NULL
)
此例子插入一个新客户到customers表。存储到每个表列中的数据在VALUES子句中给出,对每个列必须提供一个值。如果某
个列没有值(如上面的cust_contact和cust_email列),应该使用NULL值(假定表允许对该列指定空值)。各个列必须以它们在表定义中出现的次序填充。第一列cust_id也为NULL。这是因为每次插入一个新行时,该列由MySQL自动增量。你不想给出一个值(这是MySQL的工作),又不能省略此列(如前所述,必须给出每个列),所以指定一个NULL值(它被MySQL忽略, MySQL在这里插入下一个可用的cust_id值)。
虽然这种语法很简单,但并不安全,应该尽量避免使用。
上面的SQL语句高度依赖于表中列的定义次序,并且还依赖于其次序容易获得的信息。
即使可得到这种次序信息,也不能保证下一次表结构变动后各个列保持完全相同的次序。
因此,编写依赖于特定列次序的SQL语句是很不安全的。如果这样做,有时难免会出问题。
编写INSERT语句的更安全(不过更烦琐)的方法如下:
INSERT INTO customers ( cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_name )
VALUES
(
"pep E.niehao",
"100 niehao",
"los niehao",
"ca",
"90046",
"usa",
NULL,
NULL
)
此例子完成与前一个INSERT语句完全相同的工作,但在表名后的括号里明确地给出了列名。
你会发现cust_id的NULL值是不必要的,cust_id列并没有出现在列表中,所以不需要任何值。
省略列:
如果表的定义允许,则可以在INSERT操作中省略某些列。省略的列必须满足以下某个条件。
- 该列定义为允许NULL值(无值或空值)。
- 在表定义中给出默认值。这表示如果不给出值,将使用默认值。
提高整体性能:
数据库经常被多个客户访问,对处理什么请求以及用什么次序处理进行管理是MySQL的任务。 INSERT操作可能很耗时(特别是有很多索引需要更新时),而且它可能降低等待处理的SELECT语句的性能。
如果数据检索是最重要的(通常是这样),则你可以通过在INSERT和INTO之间添加关键字LOW_PRIORITY,指示MySQL
降低INSERT语句的优先级,如下所示:INSERT LOW_PRIORITY INTO
这个方法也同样适用于 UPDATE 和 DELETE 语句。
插入多个行
可以使用多条INSERT语句,甚至一次提交它们,每条语句用一个分号结束;
INSERT INTO customers
VALUES
( "pep E.niehao", "100 niehao", "los niehao", "ca", "90046", "usa", NULL, NULL );
INSERT INTO customers
VALUES
( "pep E.niehao", "100 niehao", "los niehao", "ca", "90046", "usa", NULL, NULL );
或者,只要每条INSERT语句中的列名(和次序)相同,可以如下组合各语句:
INSERT INTO customers ( cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_name )
VALUES
( "pep E.niehao", "100 niehao", "los niehao", "ca", "90046", "usa", NULL, NULL ),
( "pep E.niehao", "100 niehao", "los niehao", "ca", "90046", "usa", NULL, NULL ),
提高INSERT的性能:此技术可以提高数据库处理的性能,因为MySQL用单条INSERT语句处理多个插入比使用多条INSERT语句快。
插入检索出的数据
INSERT一般用来给表插入一个指定列值的行。
但是, INSERT还存在另一种形式,可以利用它将一条SELECT语句的结果插入表中。
这就是所谓的INSERT SELECT,顾名思义,它是由一条INSERT语句和一条SELECT语句组成的。
例子:假如你想从另一表中合并客户列表到你的customers表。 不需要每次读取一行,然后再将它用INSERT插入,可以如下进行:
INSERT TO customers ( cust_id, cust_contact, cust_email, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country ) SELECT
cust_id,
cust_contact,
cust_email,
cust_name,
cust_address,
cust_city,
cust_state,
cust_zip,
cust_country
FROM
custnew
这个例子使用INSERT SELECT从custnew中将所有数据导入customers。 SELECT语句从custnew检索出要插入的值,而不
是列出它们。 SELECT中列出的每个列对应于customers表名后所跟的列表中的每个列。这条语句将插入多少行有赖于custnew表中有多少行。如果这个表为空,则没有行被插入
INSERT SELECT中SELECT语句可包含WHERE子句以过滤插入的数据。
INSERT SELECT中的列名 :
为简单起见,这个例子在INSERT和SELECT语句中使用了相同的列名。但是,不一定要求列名匹配。
事实上, MySQL甚至不关心SELECT返回的列名。它使用的是列的位置,因此SELECT中的第一列(不管其列名)将用来填充表列中指定的第一个列,第二列将用来填充表列中指定的第二个列,如此等等。这对于从使用不同列名的表中导入数据是非常有用的。
更新和删除数据
更新数据
为了更新(修改)表中的数据,可使用UPDATE语句。可采用两种方式使用UPDATE:
- 更新表中特定行;
- 更新表中所有行
不要省略WHERE子句:在使用UPDATE时一定要注意细心。因为稍不注意,就会更新表中所有行。
基本的UPDATE语句由3部分组成,分别是:
- 要更新的表;
- 列名和它们的新值;
- 确定要更新行的过滤条件。
UPDATE customers
SET cust_email = "elmer@fudd.com"
WHERE
cust_id = 10005
多列更新
UPDATE customers
SET cust_email = "elmer@fudd.com",
cust_name = "ceshi"
WHERE
cust_id = 10005
在UPDATE语句中使用子查询: UPDATE语句中可以使用子查询,使得能用SELECT语句检索出的数据更新列数据。
关于子查询及使用的更多内容,参见:使用子查询
IGNORE关键字 :
如果用UPDATE语句更新多行,并且在更新这些行中的一行或多行时出一个现错误,则整个UPDATE操作被取消(错误发生前更新的所有行被恢复到它们原来的值)。
为即使是发生错误,也继续进行更新,可使用IGNORE关键字,如下所示:UPDATE IGNORE customers…
为了删除某个列的值,可设置它为NULL(假如表定义允许NULL值)。
删除数据
为了从一个表中删除(去掉)数据,使用DELETE语句。可以两种方式使用DELETE:
- 从表中删除特定的行;
- 从表中删除所有行。
不要省略WHERE子句: 在使用DELETE时一定要注意细心。因为稍不注意,就会错误地删除表中所有行。
DELETE customers
FROM
cust_id = 10006
DELETE不需要列名或通配符。
DELETE删除整行而不是删除列。为了删除指定的列,请使用UPDATE语句。
删除表的内容而不是表 :DELETE语句从表中删除行,甚至是删除表中所有行。但是, DELETE不删除表本身。
更快的删除 :如果想从表中删除所有行,不要使用DELETE。可使用TRUNCATE TABLE语句,它完成相同的工作,但速度更
快( TRUNCATE实际是删除原来的表并重新创建一个表,而不是逐行删除表中的数据)。
更新和删除的指导原则
前一节中使用的UPDATE和DELETE语句全都具有WHERE子句,这样做的理由很充分。如果省略了WHERE子句,则UPDATE或DELETE将被应用到表中所有的行。换句话说,如果执行UPDATE而不带WHERE子句,则表中每个行都将用新值更新。类似地,如果执行DELETE语句而不带WHERE子句,表的所有数据都将被删除。
使用UPDATE或DELETE时所遵循的习惯:
- 除非确实打算更新和删除每一行,否则绝对不要使用不带WHERE子句的UPDATE或DELETE语句。
- 保证每个表都有主键(如果忘记这个内容,请参见:连结表),尽可能像WHERE子句那样使用它(可以指定各主键、多个值或值的范围)。
- 在对UPDATE或DELETE语句使用WHERE子句前,应该先用SELECT进行测试,保证它过滤的是正确的记录,以防编写的WHERE子句不正确。
- 使用强制实施引用完整性的数据库(关于这个内容,请参见:连结表),这样MySQL将不允许删除具有与其他表相关联的数据的行。
创建和操纵表
创建表
MySQL不仅用于表数据操纵,而且还可以用来执行数据库和表的所有操作,包括表本身的创建和处理。
一般有两种创建表的方法:
- 使用具有交互式创建和管理表的工具(GUI工具);
- 表也可以直接用MySQL语句操纵。
为了用程序创建表,可使用SQL的CREATE TABLE语句。
表创建基础
为利用CREATE TABLE创建表,必须给出下列信息:
- 新表的名字,在关键字CREATE TABLE之后给出;
- 表列的名字和定义,用逗号分隔。
CREATE TABLE语句也可能会包括其他关键字或选项,但至少要包括表的名字和列的细节。
CREATE TABLE `customers` (
`cust_id` int(11) 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;
从上面的例子中可以看到,表名紧跟在CREATE TABLE关键字后面。
实际的表定义(所有列)括在圆括号之中。各列之间用逗号分隔。这个表由9列组成。
每列的定义以列名(它在表中必须是唯一的)开始,后跟列的数据类型。
表的主键可以在创建表时用PRIMARY KEY关键字指定。这里,列cust_id指定作为主键列。
整条语句由右圆括号后的分号结束 。
处理现有的表: 在创建新表时,指定的表名必须不存在,否则将出错。如果要防止意外覆盖已有的表, SQL要求首先手工删
除该表,然后再重建它,而不是简单地用创建表语句覆盖它。如果你仅想在一个表不存在时创建它,应该在表名后给出IFNOT EXISTS。这样做不检查已有表的模式是否与你打算创建
的表模式相匹配。它只是查看表名是否存在,并且仅在表名不存在时创建它。
使用NULL值
NULL值就是没有值或缺值。允许NULL值的列也允许在插入行时不给出该列的值。不允许NULL值的列不接受该列没有值的行,换句话说,在插入或更新行时,该列必须有值。
每个表列或者是NULL列,或者是NOT NULL列,这种状态在创建时由表的定义规定。
CREATE TABLE `orders` (
`order_num` int(11) NOT NULL AUTO_INCREMENT,
`order_date` datetime NOT NULL,
`cust_id` int(11) NOT NULL,
PRIMARY KEY (`order_num`),
) ENGINE=InnoDB
这条语句创建本书中所用的orders表。
orders包含3个列,分别是订单号、订单日期和客户ID。所有3个列都需要,因此每个列的定义都含有关键字NOT NULL。这将会阻止插入没有值的列。如果试图插入没有值的列,将返回错误,且插入失败。
理解NULL : 不要把NULL值与空串相混淆。 NULL值是没有值,它不是空串。如果指定''(两个单引号,其间没有字符),这
在NOT NULL列中是允许的。空串是一个有效的值,它不是无值。 NULL值用关键字NULL而不是空串指定。
主键再介绍
正如所述,主键值必须唯一。
即,表中的每个行必须具有唯一的主键值。如果主键使用单个列,则它的值必须唯一。如果使用多个列,则这些列的组合值必须唯一。
CREATE TABLE例子都是用单个列作为主键。其中主键用以下的类似的语句定义:
PRIMARY KEY (vend_id),
为创建由多个列组成的主键,应该以逗号分隔的列表给出各列名,如下所示:
PRIMARY KEY (order_num,order_item),
订单号(order_num列)和订单物品(order_item列)的组合是唯一的,从而作为主键。
主键可以在创建表时定义, 或者在创建表之后定义。
主键和NULL值:主键为其值唯一标识表中每个行的列。主键中只能使用不允许NULL值的列。允许NULL值的列不能作为唯一标识。
使用AUTO_INCREMENT
AUTO_INCREMENT告诉MySQL,本列每当增加一行时自动增量。
每次执行一个INSERT操作时, MySQL自动对该列增量(从而才有这个关键字AUTO_INCREMENT),给该列赋予下一个可用的值。这样给每个行分配一个唯一的cust_id,从而可以用作主键值。
`order_id` int(11) NOT NULL AUTO_INCREMENT,
每个表只允许一个AUTO_INCREMENT列,而且它必须被索引(如,通过使它成为主键)。
覆盖AUTO_INCREMENT :
如果一个列被指定为AUTO_INCREMENT,则它需要使用特殊的值吗?你可以简单地在INSERT语句中指定一个值,只要它是唯一的(至今尚未使用过)即可,该值将被用来替代自动生成的值。后续的增量将开始使用该手工插入的值。
确定AUTO_INCREMENT值 :
让MySQL生成(通过自动增量)主键的一个缺点是你不知道这些值都是谁。
考虑这个场景:你正在增加一个新订单。这要求在orders表中创建一行, 然后在orderitms表中对订购的每项物品创建一行。 order_num在orderitems表中与订单细节一起存储。这就是为什么orders表和orderitems表为相互关联的表的原因。这显然要求你在插入orders行之后,插入orderitems行之前知道生成的order_num。
那么,如何在使用AUTO_INCREMENT列时获得这个值呢?可使用last_insert_id()函数获得这个值,如下所示:
select last_insert_id()
此语句返回最后一个AUTO_INCREMENT值,然后可以将它用于后续的MySQL语句。
指定默认值
如果在插入行时没有给出值, MySQL允许指定此时使用的默认值。默认值用CREATE TABLE语句的列定义中的DEFAULT关键字指定。
CREATE TABLE `orderitems` (
`order_num` int(11) NOT NULL,
`order_item` int(11) NOT NULL,
`prod_id` char(10) NOT NULL,
`quantity` int(11) NOT NULL DEFAULT 1,
`item_price` decimal(8,2) NOT NULL,
PRIMARY KEY (`order_num`,`order_item`),
) ENGINE=InnoDB
这条语句创建包含组成订单的各物品的orderitems表(订单本身存储在orders表中)。
quantity列包含订单中每项物品的数量。在此例子中,给该列的描述添加文本DEFAULT 1指示MySQL,在未给出数量的情况下使用数量1。
不允许函数:与大多数DBMS不一样, MySQL不允许使用函数作为默认值,它只支持常量。
引擎类型
迄今为止使用的CREATE TABLE语句全都以ENGINE=InnoDB语句结束。
与其他DBMS一样, MySQL有一个具体管理和处理数据的内部引擎。
在你使用CREATE TABLE语句时,该引擎具体创建表,而在你使用SELECT语句或进行其他数据库处理时,该引擎在内部处理你的请求。
多数时候,此引擎都隐藏在DBMS内,不需要过多关注它。
但MySQL与其他DBMS不一样,它具有多种引擎。它打包多个引擎,这些引擎都隐藏在MySQL服务器内,全都能执行CREATE TABLE和SELECT等命令。
当然,你完全可以忽略这些数据库引擎。如果省略ENGINE=语句,则使用默认引擎(很可能是MyISAM),多数SQL语句都会默认使用它。
但并不是所有语句都默认使用它,这就是为什么ENGINE=语句很重要的原因。
以下是几个需要知道的引擎:
- InnoDB是一个可靠的事务处理引擎(参见:管理事务处理),它不支持全文本搜索;
- MEMORY在功能等同于MyISAM, 但由于数据存储在内存(不是磁盘)中,速度很快(特别适合于临时表);
- MyISAM是一个性能极高的引擎,它支持全文本搜索(参见:全文本检索),但不支持事务处理。
外键不能跨引擎 :混用引擎类型有一个大缺陷。外键(用于强制实施引用完整性)不能跨引擎,即使用一个引擎的表不能引用具有使用不同引擎的表的外键。
那么, 你应该使用哪个引擎?这有赖于你需要什么样的特性。 MyISAM由于其性能和特性可能是最受欢迎的引擎。但如果你不需要可靠的事务处理,可以使用其他引擎。
更新表
为更新表定义,可使用ALTER TABLE语句。
但是,理想状态下,当表中存储数据以后,该表就不应该再被更新。在表的设计过程中需要花费大量时间来考虑,以便后期不对该表进行大的改动。
为了使用ALTER TABLE更改表结构,必须给出下面的信息:
- 在ALTER TABLE之后给出要更改的表名(该表必须存在,否则将出错);
- 所做更改的列表。
这条语句给vendors表增加一个名为vend_phone的列,必须明确其数据类型。
ALTER TABLE vendors
ADD vend_phone CHAR(20)
删除该列:
ALTER TABLE vendors
DROP COLUMN vend_phone
ALTER TABLE的一种常见用途是定义外键。
ALTER TABLE orders
ADD CONSTRAINT fk_order_customer FOREIGN KYE ( cust_id )
REFERENCES customers (cust_id)
复杂的表结构更改一般需要手动删除过程,它涉及以下步骤:
- 用新的列布局创建一个新表;
- 使用INSERT SELECT语句(关于这条语句的详细介绍,请参见:插入数据)从旧表复制数据到新表。如果有必要,可使用转换函数和计算字段;
- 检验包含所需数据的新表;
- 重命名旧表(如果确定,可以删除它);
- 用旧表原来的名字重命名新表;
- 根据需要,重新创建触发器、存储过程、索引和外键。
小心使用ALTER TABLE : 使用ALTER TABLE要极为小心,应该在进行改动前做一个完整的备份(模式和数据的备份)。数据
库表的更改不能撤销,如果增加了不需要的列,可能不能删除它们。类似地,如果删除了不应该删除的列,可能会丢失该列中的所有数据。
删除表
删除表(删除整个表而不是其内容)非常简单,使用DROP TABLE语句即可:
DROP TABLE customers2
这条语句删除customers 2表(假设它存在)。删除表没有确认,也不能撤销,执行这条语句将永久删除该表。
重命名表
使用RENAME TABLE语句可以重命名一个表:
RENAME TABLE customers2 TO customers
RENAME TABLE所做的仅是重命名一个表。
使用视图
视图
视图是虚拟的表。与包含数据的表不一样,视图只包含使用时动态检索数据的查询。
SELECT
cust_name,
cust_contact
FROM
customers AS c,
orders AS o,
orderitems AS oi
WHERE
c.cust_id = o.cust_id
AND oi.order_num = o.order_num
AND prod_id = 'TNT2'
此查询用来检索订购了某个特定产品的客户。
任何需要这个数据的人都必须理解相关表的结构,并且知道如何创建查询和对表进行联结。为了检索其他产品(或多个产品)的相同数据,必须修改最后的WHERE子句。
现在,假如可以把整个查询包装成一个名为productcustomers的虚拟表,则可以如下轻松地检索出相同的数据:
SELECT
cust_name,
cust_contact
FROM
productcustomers
WHERE
prod_id = 'TNT2'
这就是视图的作用。 productcustomers是一个视图,作为视图,它不包含表中应该有的任何列或数据,它包含的是一个SQL查询(与上面用以正确联结表的相同的查询)。
为什么使用视图
我们已经看到了视图应用的一个例子。下面是视图的一些常见应用。
- 重用SQL语句。
- 简化复杂的SQL操作。在编写查询后,可以方便地重用它而不必知道它的基本查询细节。
- 使用表的组成部分而不是整个表。
- 保护数据。可以给用户授予表的特定部分的访问权限而不是整个表的访问权限。
- 更改数据格式和表示。视图可返回与底层表的表示和格式不同的数据。
在视图创建之后,可以用与表基本相同的方式利用它们。可以对视图执行SELECT操作,过滤和排序数据,将视图联结到其他视图或表,甚至能添加和更新数据。
重要的是知道视图仅仅是用来查看存储在别处的数据的一种设施。
视图本身不包含数据,因此它们返回的数据是从其他表中检索出来的。
在添加或更改这些表中的数据时,视图将返回改变过的数据。
性能问题 :因为视图不包含数据,所以每次使用视图时,都必须处理查询执行时所需的任一个检索。如果你用多个联结和过滤创建了复杂的视图或者嵌套了视图,可能会发现性能下降得很厉害。因此,在部署使用了大量视图的应用前,应该进行测试。
视图的规则和限制
下面是关于视图创建和使用的一些最常见的规则和限制
- 与表一样,视图必须唯一命名(不能给视图取与别的视图或表相同的名字)。
- 对于可以创建的视图数目没有限制。
- 为了创建视图,必须具有足够的访问权限。这些限制通常由数据库管理人员授予。
- 视图可以嵌套,即可以利用从其他视图中检索数据的查询来构造一个视图。
- ORDER BY可以用在视图中,但如果从该视图检索数据SELECT中也含有ORDER BY,那么该视图中的ORDER BY将被覆盖。
- 视图不能索引,也不能有关联的触发器或默认值。
- 视图可以和表一起使用。例如,编写一条联结表和视图的SELECT语句。
使用视图
在理解什么是视图(以及管理它们的规则及约束)后,我们来看一下视图的创建。
- 视图用CREATE VIEW语句来创建。
- 使用SHOW CREATE VIEW viewname;来查看创建视图的语句。
- 用DROP删除视图,其语法为DROP VIEW viewname;。
- 更新视图时,可以先用DROP再用CREATE,也可以直接用CREATE ORREPLACE VIEW。如果要更新的视图不存在,则第2条更新语句会创建一个视图;如果要更新的视图存在,则第2条更新语句会替换原有视图。
利用视图简化复杂的联结
视图的最常见的应用之一是隐藏复杂的SQL,这通常都会涉及联结。
CREATE VIEW productcustomers AS
SELECT
cust_name,
cust_contact,
prod_id
FROM
customers AS c,
orders AS o,
orderitems AS oi
WHERE
c.cust_id = o.cust_id
AND oi.order_num = o.order_num
这条语句创建一个名为productcustomers的视图, 它联结三个表,以返回已订购了任意产品的所有客户的列表。如果执行SELECT * FROM productcustomers,将列出订购了任意产品的客户。
创建可重用的视图 创建不受特定数据限制的视图是一种好办法。
用视图重新格式化检索出的数据
视图的另一常见用途是重新格式化检索出的数据。
SELECT
CONCAT( vend_name, '(', vend_country, ')' ) AS vend_titel
FROM
vendors
ORDER BY
vend_name
现在,假如经常需要这个格式的结果。不必在每次需要时执行联结,创建一个视图,每次需要时使用它即可。
CREATE VIEW vendorlocations AS SELECT
CONCAT( vend_name, '(', vend_country, ')' ) AS vend_titel
FROM
vendors
ORDER BY
vend_name
用视图过滤不想要的数据
视图对于应用普通的WHERE子句也很有用 。
WHERE子句与WHERE子句 :如果从视图检索数据时使用了一条WHERE子句,则两组子句(一组在视图中,另一组是传递给视图的)将自动组合。
使用视图与计算字段
视图对于简化计算字段的使用特别有用。
更新视图
迄今为止的所有视图都是和SELECT语句使用的。然而,视图的数据能否更新?答案视情况而定。
通常,视图是可更新的(即,可以对它们使用INSERT、 UPDATE和DELETE)。更新一个视图将更新其基表(可以回忆一下,视图本身没有数据)。如果你对视图增加或删除行,实际上是对其基表增加或删除行。
但是,并非所有视图都是可更新的。基本上可以说,如果MySQL不能正确地确定被更新的基数据,则不允许更新(包括插入和删除)。这实际上意味着,如果视图定义中有以下操作,则不能进行视图的更新:
- 分组(使用GROUP BY和HAVING);
- 联结;
- 子查询;
- 并;
- 聚集函数( Min()、 Count()、 Sum()等);
- DISTINCT;
- 导出(计算)列。
将视图用于检索:一般,应该将视图用于检索( SELECT语句)而不用于更新( INSERT、 UPDATE和DELETE)。
使用存储过程
存储过程
使用的大多数SQL语句都是针对一个或多个表的单条语句。
并非所有操作都这么简单,经常会有一个完整的操作需要多条语句才能完成。
例如:
- 为了处理订单,需要核对以保证库存中有相应的物品。
- 如果库存有物品,这些物品需要预定以便不将它们再卖给别的人,并且要减少可用的物品数量以反映正确的库存量。
- 库存中没有的物品需要订购,这需要与供应商进行某种交互。
- 关于哪些物品入库(并且可以立即发货)和哪些物品退订,需要通知相应的客户。
执行这个处理需要针对许多表的多条MySQL语句。此外,需要执行的具体语句及其次序也不是固定的,它们可能会(和将)根据哪些物品在库存中哪些不在而变化。
那么,怎样编写此代码?可以单独编写每条语句,并根据结果有条件地执行另外的语句。
在每次需要这个处理时(以及每个需要它的应用中)都必须做这些工作。
另一种方式:可以创建存储过程。存储过程简单来说,就是为以后的使用而保存的一条或多条MySQL语句的集合。
可将其视为批文件,虽然它们的作用不仅限于批处理。
为什么要使用存储过程
下面列出一些主要的理由:
- 通过把处理封装在容易使用的单元中,简化复杂的操作(正如前面例子所述)。
- 由于不要求反复建立一系列处理步骤,这保证了数据的完整性。如果所有开发人员和应用程序都使用同一(试验和测试)存储过程,则所使用的代码都是相同的。这一点的延伸就是防止错误。需要执行的步骤越多,出错的可能性就越大。防止错误保证了数据的一致性。
- 简化对变动的管理。如果表名、列名或业务逻辑(或别的内容)有变化,只需要更改存储过程的代码。使用它的人员甚至不需要知道这些变化。
换句话说,使用存储过程有3个主要的好处,即简单、安全、高性能。显然,它们都很重要。不过,在将SQL代码转换为存储过程前,也必须知道它的一些缺陷。
- 一般来说,存储过程的编写比基本SQL语句复杂,编写存储过程需要更高的技能,更丰富的经验。
- 你可能没有创建存储过程的安全访问权限。许多数据库管理员限制存储过程的创建权限,允许用户使用存储过程,但不允许他们创建存储过程。
使用存储过程
执行存储过程
MySQL称存储过程的执行为调用,因此MySQL执行存储过程的语句为CALL。 CALL接受存储过程的名字以及需要传递给它的任意参数。
CALL productpricing (
@pricelow,
@pricehigh,
@priceeaverage
)
存储过程可以显示结果,也可以不显示结果
创建存储过程
例子:一个返回产品平均价格的存储过程。
CREATE PROCEDURE `productpricing`( )
BEGIN
SELECT AVG(prod_price) AS priceaverage
FROM products;
END
存储过程名为productpricing,用CREATE PROCEDURE productpricing()语句定义。
如果存储过程接受参数,它们将在()中列举出来。此存储过程没有参数,但后跟的()仍然需要。
BEGIN和END语句用来限定存储过程体,过程体本身仅是一个简单的SELECT语句(使用第12章介绍的Avg()函数)。
使用这个存储过程如下:
CALL productpricing ()
ALL productpricing();执行刚创建的存储过程并显示返回的结果。
因为存储过程实际上是一种函数,所以存储过程名后需要有()符号(即使不传递参数也需要)。
删除存储过程
为删除刚创建的存储过程,可使用以下语句:
DROP PROCEDURE productpricing
仅当存在时删除:如果指定的过程不存在,则DROP PROCEDURE将产生一个错误。
当过程存在想删除它时(如果过程不存在也不产生错误)可使用DROP PROCEDURE IF EXISTS。
使用参数
一般,存储过程并不显示结果,而是把结果返回给你指定的变量。
CREATE DEFINER=`root`@`localhost` 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
此存储过程接受3个参数: pl存储产品最低价格, ph存储产品最高价格, pa存储产品平均价格。
每个参数必须具有指定的类型,这里使用十进制值。
关键字OUT指出相应的参数用来从存储过程传出一个值(返回给调用者)。 MySQL支持IN(传递给存储过程)、 OUT(从存储过程传出,如这里所用)和INOUT(对存储过程传入和传出)类型的参数。
存储过程的代码位于BEGIN和END语句内,如前所见,它们是一系列SELECT语句,用来检索值,然后保存到相应的变量(通过指定INTO关键字)。
为调用此修改过的存储过程,必须指定3个变量名,如下所示:
CALL productpricing (
@pricelow,
@pricehigh,
@priceeaverage
)
由于此存储过程要求3个参数,因此必须正好传递3个参数,不多也不少。所以,这条CALL语句给出3个参数。它们是存储过程将保存结果的3个变量的名字。
变量名: 所有MySQL变量都必须以@开始。
在调用时,这条语句并不显示任何数据。它返回以后可以显示(或在其他处理中使用)的变量。
建立智能存储过程
迄今为止使用的所有存储过程基本上都是封装MySQL简单的SELECT语句。虽然它们全都是有效的存储过程例子,但它们所能完成的工作你直接用这些被封装的语句就能完成。
只有在存储过程内包含业务规则和智能处理时,它们的威力才真正显现出来。
考虑这个场景。你需要获得与以前一样的订单合计,但需要对合计增加营业税,不过只针对某些顾客(或许是你所在州中那些顾客)。那么,你需要做下面几件事情:
- 获得合计(与以前一样);
- 把营业税有条件地添加到合计;
- 返回合计(带或不带税)。
存储过程的完整工作如下:
CREATE DEFINER=`root`@`localhost` PROCEDURE `ordertotal`(
IN onumber INT,
IN taxable BOOLEAN,
OUT ototal DECIMAL(8,2)
)
COMMENT 'obtain order total ,optionaly adding tax'
BEGIN
DECLARE total DECIMAL(8,2);
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;
SELECT total INTO ototal;
END
此存储过程有很大的变动。首先,增加了注释(前面放置--)。
在存储过程复杂性增加时,这样做特别重要。添加了另外一个参数taxable,它是一个布尔值(如果要增加税则为真,否则为假)。
在存储过程体中,用DECLARE语句定义了两个局部变量。 DECLARE要求指定变量名和数据类型,它也支持可选的默认值(这个例子中的taxrate的默认被设置为6%)。 SELECT语句已经改变,因此其结果存储到total(局部变量)而不是ototal。 IF语句检查taxable是否为真,如果为真,则用另一SELECT语句增加营业税到局部变量total。最后,用另一SELECT语句将total(它增加或许不增加营业税)保存到ototal。
COMMENT关键字: 本例子中的存储过程在CREATE PROCEDURE语句中包含了一个COMMENT值。它不是必需的,但如果给出,将在SHOW PROCEDURE STATUS的结果中显示。
IF语句 :这个例子给出了MySQL的IF语句的基本用法。 IF语句还支持ELSEIF和ELSE子句(前者还使用THEN子句,后者不
使用)。
检查存储过程
为显示用来创建一个存储过程的CREATE语句,使用SHOW CREATEPROCEDURE语句:
SHOW CREATE PROCEDURE ordertotal
为了获得包括何时、由谁创建等详细信息的存储过程列表, 使用SHOW PROCEDURE STATUS。
限制过程状态结果 : SHOW PROCEDURE STATUS列出所有存储过程。为限制其输出,可使用LIKE指定一个过滤模式,例如:
SHOW PROCEDURE STATUS LIKE 'ordertotal';
使用游标
游标
由前几章可知, MySQL检索操作返回一组称为结果集的行。这组返回的行都是与SQL语句相匹配的行(零行或多行)。使用简单的SELECT语句,例如,没有办法得到第一行、下一行或前10行,也不存在每次一行地处理所有行的简单方法(相对于成批地处理它们)。
有时,需要在检索出来的行中前进或后退一行或多行。这就是使用游标的原因。
游标( cursor) 是一个存储在MySQL服务器上的数据库查询,它不是一条SELECT语句,而是被该语句检索出来的结果集。
在存储了游标之后,应用程序可以根据需要滚动或浏览其中的数据。
游标主要用于交互式应用,其中用户需要滚动屏幕上的数据,并对数据进行浏览或做出更改。
只能用于存储过程:不像多数DBMS, MySQL游标只能用于存储过程(和函数)。
使用游标
使用游标涉及几个明确的步骤:
- 在能够使用游标前,必须声明(定义)它。这个过程实际上没有检索数据,它只是定义要使用的SELECT语句。
- 一旦声明后,必须打开游标以供使用。这个过程用前面定义的SELECT语句把数据实际检索出来。
- 对于填有数据的游标,根据需要取出(检索)各行。
- 在结束游标使用时,必须关闭游标。
在声明游标后,可根据需要频繁地打开和关闭游标。在游标打开后,可根据需要频繁地执行取操作。
创建游标
游标用DECLARE语句创建。 DECLARE命名游标,并定义相应的SELECT语句,根据需要带WHERE和其他子句。
例如,下面的语句定义了名为ordernumbers的游标, 使用了可以检索所有订单的SELECT语句。
CREATE PROCEDURE `productpricing`( )
BEGIN
DECLARE ordernumber CURSOR
FOR
SELECT order_num FROM orders;
END;
这个存储过程并没有做很多事情, DECLARE语句用来定义和命名游标,这里为ordernumbers。 存储过程处理完成后,游标就消失(因为它局限于存储过程)。
在定义游标之后,可以打开它。
打开和关闭游标
游标用OPEN CURSOR语句来打开
OPEN ordernumbers
在处理OPEN语句时执行查询,存储检索出的数据以供浏览和滚动
游标处理完成后,应当使用如下语句关闭游标:
CLOSE ordernumbers
CLOSE释放游标使用的所有内部内存和资源,因此在每个游标不再需要时都应该关闭。
在一个游标关闭后,如果没有重新打开,则不能使用它。但是,使用声明过的游标不需要再次声明,用OPEN语句打开它就可以了。
隐含关闭:如果你不明确关闭游标, MySQL将会在到达END语句时自动关闭它。
下面是前面例子的修改版本:
CREATE PROCEDURE `processorder`( )
BEGIN
DECLARE ordernumbers CURSOR
FOR
SELECT order_num FROM orders;
OPEN ordernumber
CLOSE ordernumbers
END;
这个存储过程声明、打开和关闭一个游标。但对检索出的数据什么也没做。
使用游标数据
在一个游标被打开后,可以使用FETCH语句分别访问它的每一行。FETCH指定检索什么数据(所需的列),检索出来的数据存储在什么地方。它还向前移动游标中的内部行指针,使下一条FETCH语句检索下一行(不重复读取同一行)。
第一个例子从游标中检索单个行(第一行):
CREATE PROCEDURE `processorder`( )
BEGIN
DELETE o INT
DECLARE ordernumbers CURSOR
FOR
SELECT order_num FROM orders;
OPEN ordernumber
FETCH ordernumber INTO o
CLOSE ordernumbers
END;
其中FETCH用来检索当前行的order_num列(将自动从第一行开始)到一个名为o的局部声明的变量中。对检索出的数据不做任何处理。
在下一个例子中,循环检索数据,从第一行到最后一行:
CREATE PROCEDURE `processorder` ( ) BEGIN
DECLARE done BOOLEAN DEFAULT 0
DECLARE o INT
DECLARE ordernumbers CURSOR
FOR
SELECT order_num FROM orders;
DECLARE CONTINUE HANDLER FOR SQLSTATE "02000" SET done = 1;
OPEN ordernumber
REPEAT
FETCH ordernumber INTO o;
UNTIL done END REPEAT;
CLOSE ordernumbers;
END;
这个例子使用FETCH检索当前order_num到声明的名为o的变量中。
但与前一个例子不一样的是,这个例子中的FETCH是在REPEAT内,因此它反复执行直到done为真(由UNTIL done END REPEAT;规定)。为使它起作用,用一个DEFAULT 0(假,不结束)定义变量done。那么, done怎样才能在结束时被设置为真呢?答案是用以下语句
DECLARE CONTAINS HANDLER FOR SQLSTATE "02000" SET done =1
这条语句定义了一个CONTINUE HANDLER,它是在条件出现时被执行的代码。
这里, 它指出当SQLSTATE '02000'出现时, SET done=1。 SQLSTATE '02000'是一个未找到条件, 当REPEAT由于没有更多的行供循环而不能继续时,出现这个条件。
DECLARE语句的次序 : DECLARE语句的发布存在特定的次序。用DECLARE语句定义的局部变量必须在定义任意游标或句柄
之前定义,而句柄必须在游标之后定义。不遵守此顺序将产生错误消息。
下面是游标存储过程样例:
CREATE PROCEDURE `processorder` ( ) 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 FOR SQLSTATE "02000" SET done = 1;
CREATE TABLE IF NOT EXISTS ordertotals(order_num INT, total DECIMAL(8,2));
OPEN ordernumber
REPEAT
FETCH ordernumber INTO o;
CALL ordertotal(o, 1 ,t);
INSERT INTO ordertotals(order_num,total) VALUES (o, t);
UNTIL done END REPEAT;
CLOSE ordernumbers;
END;
在这个例子中,我们增加了另一个名为t的变量(存储每个订单的合计)。 此存储过程还在运行中创建了一个新表(如果它不存在的话), 名为ordertotals。 这个表将保存存储过程生成的结果。 FETCH像以前一样取每个order_num,然后用CALL执行另一个存储过程(我们在前一章中创建)来计算每个订单的带税的合计(结果存储到t)。最后,用INSERT保存每个订单的订单号和合计。
使用触发器
触发器
MySQL语句在需要时被执行,存储过程也是如此。但是,如果你想要某条语句(或某些语句)在事件发生时自动执行,怎么办呢?例如:
- 每当增加一个顾客到某个数据库表时,都检查其电话号码格式是否正确,州的缩写是否为大写;
- 每当订购一个产品时,都从库存数量中减去订购的数量;
- 无论何时删除一行,都在某个存档表中保留一个副本。
所有这些例子的共同之处是它们都需要在某个表发生更改时自动处理。这确切地说就是触发器。
触发器是MySQL响应以下任意语句而自动执行的一条MySQL语句(或位于BEGIN和END语句之间的一组语句):
- DELETE;
- INSERT;
- UPDATE
其他MySQL语句不支持触发器。
创建触发器
在创建触发器时,需要给出4条信息:
- 唯一的触发器名;
- 触发器关联的表;
- 触发器应该响应的活动( DELETE、 INSERT或UPDATE);
- 触发器何时执行(处理之前或之后)。
保持每个数据库的触发器名唯一 :在MySQL 5中,触发器名必须在每个表中唯一,但不是在每个数据库中唯一。这表示同一
数据库中的两个表可具有相同名字的触发器。这在其他每个数据库触发器名必须唯一的DBMS中是不允许的,而且以后的
MySQL版本很可能会使命名规则更为严格。因此,现在最好是在数据库范围内使用唯一的触发器名。
触发器用CREATE TRIGGER语句创建。
CREATE TRIGGER newproduct AFTER ON products FOR EACH ROW
SELECT
'Product added'
CREATE TRIGGER用来创建名为newproduct的新触发器。触发器可在一个操作发生之前或之后执行,这里给出了AFTER INSERT,所以此触发器将在INSERT语句成功执行后执行。这个触发器还指定FOR EACH ROW,因此代码对每个插入行执行。在这个例子中,文本Product added将对每个插入的行显示一次。
仅支持表 :只有表才支持触发器,视图不支持(临时表也不支持)。
触发器按每个表每个事件每次地定义,每个表每个事件每次只允许一个触发器。因此,每个表最多支持6个触发器(每条INSERT、 UPDATE和DELETE的之前和之后)。单一触发器不能与多个事件或多个表关联,所以,如果你需要一个对INSERT和UPDATE操作执行的触发器,则应该定义两个触发器。
触发器失败 : 如果BEFORE触发器失败,则MySQL将不执行请求的操作。此外,如果BEFORE触发器或语句本身失败, MySQL将不执行AFTER触发器(如果有的话)。
删除触发器
为了删除一个触发器,可使用DROP TRIGGER语句,如下所示:
DROP TRIGGER newporduct;
触发器不能更新或覆盖。为了修改一个触发器,必须先删除它,然后再重新创建。
使用触发器
INSERT触发器
INSERT触发器在INSERT语句执行之前或之后执行。需要知道以下几点:
- 在INSERT触发器代码内,可引用一个名为NEW的虚拟表,访问被插入的行;
- 在BEFORE INSERT触发器中, NEW中的值也可以被更新(允许更改被插入的值);
- 对于AUTO_INCREMENT列, NEW在INSERT执行之前包含0,在INSERT执行之后包含新的自动生成值。
下面举一个例子(一个实际有用的例子)。 AUTO_INCREMENT列具有MySQL自动赋予的值。
CREATE TRIGGER neworder AFTER INSERT ON orders FOR EACH ROW
SELECT
NEW.order_num;
此代码创建一个名为neworder的触发器,它按照AFTER INSERTON orders执行。在插入一个新订单到orders表时, MySQL生
成一个新订单号并保存到order_num中。触发器从NEW. order_num取得这个值并返回它。此触发器必须按照AFTER INSERT执行,因为在BEFOREINSERT语句执行之前,新order_num还没有生成。对于orders的每次插入使用这个触发器将总是返回新的订单号。
BEFORE或AFTER?: 通常,将BEFORE用于数据验证和净化(目的是保证插入表中的数据确实是需要的数据)。
DELETE触发器
DELETE触发器在DELETE语句执行之前或之后执行。需要知道以下两点:
- 在DELETE触发器代码内,你可以引用一个名为OLD的虚拟表,访问被删除的行;
- OLD中的值全都是只读的,不能更新。
下面的例子演示使用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);
END;
在任意订单被删除前将执行此触发器。它使用一条INSERT语句将OLD中的值(要被删除的订单)保存到一个名为archive_orders的存档表中(为实际使用这个例子,你需要用与orders相同的列创建一个名为archive_orders的表)。
使用BEFORE DELETE触发器的优点(相对于AFTER DELETE触发器来说)为,如果由于某种原因,订单不能存档, DELETE本身将被放弃。
多语句触发器 :正如所见,触发器deleteorder使用BEGIN和END语句标记触发器体。这在此例子中并不是必需的,不过也没有害处。使用BEGIN END块的好处是触发器能容纳多条SQL语句(在BEGIN END块中一条挨着一条)。
UPDATE触发器
UPDATE触发器在UPDATE语句执行之前或之后执行。需要知道以下几点:
- 在UPDATE触发器代码中,你可以引用一个名为OLD的虚拟表访问以前( UPDATE语句前)的值,引用一个名为NEW的虚拟表访问新更新的值;
- 在BEFORE UPDATE触发器中, NEW中的值可能也被更新(允许更改将要用于UPDATE语句中的值);
- OLD中的值全都是只读的,不能更新。
下面的例子保证州名缩写总是大写(不管UPDATE语句中给出的是大写还是小写):
CREATE TRIGGER updatevendor BEFORE UPDATE ON vendor FOR EACH ROW
SET
NEW.vend_state = Upper(NEW.vend_state)
每次更新一个行时, NEW.vend_state中的值(将用来更新表行的值)都用Upper(NEW.vend_state)替换。
关于触发器的进一步介绍
我们再介绍一些使用触发器时需要记住的重点:
- 创建触发器可能需要特殊的安全访问权限,但是,触发器的执行是自动的。如果INSERT、 UPDATE或DELETE语句能够执行,则相关的触发器也能执行。
- 应该用触发器来保证数据的一致性(大小写、格式等)。在触发器中执行这种类型的处理的优点是它总是进行这种处理,而且是透明地进行,与客户机应用无关。
- 触发器的一种非常有意义的使用是创建审计跟踪。使用触发器,把更改(如果需要,甚至还有之前和之后的状态)记录到另一个表非常容易。
- 遗憾的是, MySQL触发器中不支持CALL语句。这表示不能从触发器内调用存储过程。所需的存储过程代码需要复制到触发器内。
管理事务处理
事务处理
并非所有引擎都支持事务处理:MySQL支持几种基本的数据库引擎。正如本章所述,并非所有引擎都支持明确的事务处理管理。 MyISAM和InnoDB是两种最常使用的引擎。前者不支持明确的事务处理管理,而后者支持。
如果你的应用中需要事务处理功能,则一定要使用正确的引擎类型。
事务处理(transaction processing)可以用来维护数据库的完整性,它保证成批的MySQL操作要么完全执行,要么完全不执行。
前面章中使用的orders表就是一个很好的例子。订单存储在orders和orderitems两个表中: orders存储实际的订单,而orderitems存储订购的各项物品。这两个表使用称为主键的唯一ID互相关联。这两个表又与包含客户和产品信息的其他表相关联。
给系统添加订单的过程如下。
- 检查数据库中是否存在相应的客户(从customers表查询),如果不存在,添加他/她。
- 检索客户的ID。
- 添加一行到orders表,把它与客户ID关联。
- 检索orders表中赋予的新订单ID。
- 对于订购的每个物品在orderitems表中添加一行,通过检索出来的ID把它与orders表关联(以及通过产品ID与products表关联)。
现在,假如由于某种数据库故障(如超出磁盘空间、安全限制、表锁等)阻止了这个过程的完成。
这里就需要使用事务处理了。事务处理是一种机制,用来管理必须成批执行的MySQL操作,以保证数据库不包含不完整的操作结果。
利用事务处理,可以保证一组操作不会中途停止,它们或者作为整体执行,或者完全不执行(除非明确指示)。
如果没有错误发生,整组语句提交给(写到)数据库表。如果发生错误,则进行回退(撤销)以恢复数据库到某个已知且安全的状态。
在使用事务和事务处理时,有几个关键词汇反复出现。下面是关于事务处理需要知道的几个术语:
- 事务(transaction)指一组SQL语句;
- 回退(rollback)指撤销指定SQL语句的过程;
- 提交(commit)指将未存储的SQL语句结果写入数据库表;
- 保留点( savepoint)指事务处理中设置的临时占位符( placeholder),你可以对它发布回退(与回退整个事务处理不同)。
控制事务处理
管理事务处理的关键在于将SQL语句组分解为逻辑块,并明确规定数据何时应该回退,何时不应该回退。
MySQL使用下面的语句来标识事务的开始:
START TRANSACTION
使用ROLLBACK
MySQL的ROLLBACK命令用来回退(撤销) MySQL语句。
SELECT * FROM ordertotals;
START TRANSACTION;
DELETE FROM ordertotals;
SELECT * FROM ordertotals;
ROLLBACK;
SELECT * FROM ordertotals;
这个例子从显示ordertotals表的内容开始。首先执行一条SELECT以显示该表不为空。然后开始一个事务处理,用一条DELETE语句删除ordertotals中的所有行。另一条SELECT语句验证ordertotals确实为空。这时用一条ROLLBACK语句回退START TRANSACTION之后的所有语句,最后一条SELECT语句显示该表不为空。
显然, ROLLBACK只能在一个事务处理内使用(在执行一条STARTTRANSACTION命令之后)。
哪些语句可以回退? : 事务处理用来管理INSERT、 UPDATE和DELETE语句。
你不能回退SELECT语句。(这样做也没有什么意义。)
你不能回退CREATE或DROP操作。事务处理块中可以使用这两条语句,但如果你执行回退,它们不会被撤销。
使用COMMIT
一般的MySQL语句都是直接针对数据库表执行和编写的。这就是所谓的隐含提交( implicit commit),即提交(写或保存)操作是自动进行的。
但是,在事务处理块中,提交不会隐含地进行。为进行明确的提交,使用COMMIT语句,如下所示:
START TRANSACTION;
DELETE FROM orderitems WHERE order_num = 20010;
DELETE FROM orders WHERE order_num = 20010;
COMMIT;
在这个例子中,从系统中完全删除订单20010。因为涉及更新两个数据库表orders和orderItems,所以使用事务处理块来保证订单不被部分删除。最后的COMMIT语句仅在不出错时写出更改。
如果第一条DELETE起作用,但第二条失败,则DELETE不会提交(实际上,它是被自动撤销的)。
隐含事务关闭 : 当COMMIT或ROLLBACK语句执行后,事务会自动关闭(将来的更改会隐含提交)。
使用保留点
简单的ROLLBACK和COMMIT语句就可以写入或撤销整个事务处理。
但是,只是对简单的事务处理才能这样做,更复杂的事务处理可能需要部分提交或回退。
例如,前面描述的添加订单的过程为一个事务处理。如果发生错误,只需要返回到添加orders行之前即可,不需要回退到customers表(如果存在的话)。
为了支持回退部分事务处理,必须能在事务处理块中合适的位置放置占位符。这样,如果需要回退,可以回退到某个占位符。
这些占位符称为保留点。为了创建占位符,可如下使用SAVEPOINT语句:
SAVEPOINT delete1
每个保留点都取标识它的唯一名字,以便在回退时, MySQL知道要回退到何处。为了回退到本例给出的保留点,可如下进行:
ROLLBACK TO delete1
保留点越多越好 :可以在MySQL代码中设置任意多的保留点,越多越好。为什么呢?因为保留点越多,你就越能按自己的意愿灵活地进行回退。
释放保留点 : 保留点在事务处理完成(执行一条ROLLBACK或COMMIT)后自动释放。自MySQL 5以来,也可以用RELEASE
SAVEPOINT明确地释放保留点。
更改默认的提交行为
正如所述,默认的MySQL行为是自动提交所有更改。换句话说,任何时候你执行一条MySQL语句,该语句实际上都是针对表执行的,而且所做的更改立即生效。
更改默认行为可以使用一下命令:
SET autocommit=0
autocommit标志决定是否自动提交更改,不管有没有COMMIT语句。设置autocommit为0(假)指示MySQL不自动提交更改
(直到autocommit被设置为真为止)。
标志为连接专用 : autocommit标志是针对每个连接而不是服务器的。
全球化和本地化
字符集和校对顺序
数据库表被用来存储和检索数据。
不同的语言和字符集需要以不同的方式存储和检索。因此, MySQL需要适应不同的字符集(不同的字母和字符),适应不同的排序和检索数据的方法。
在讨论多种语言和字符集时,将会遇到以下重要术语:
- 字符集:为字母和符号的集合;
- 编码:为某个字符集成员的内部表示;
- 校对:为规定字符如何比较的指令。
校对为什么重要:排序英文正文很容易,对吗?或许不。考虑词APE、 apex和Apple。它们处于正确的排序顺序吗?这有赖于你是否想区分大小写。使用区分大小写的校对顺序,这些词有一种排序方式,使用不区分大小写的校对顺序有另外一种排序方式。这不仅影响排序(如用ORDER BY排序数据),还影响搜索(例如 ,寻找 apple 的 WHERE子 句 是 否能 找到APPLE)。在使用诸如法文à或德文ö这样的字符时,情况更复杂,在使用不基于拉丁文的字符集(日文、希伯来文、俄文等)时,情况更为复杂。
在MySQL的正常数据库活动(SELECT、 INSERT等)中,不需要操心太多的东西。使用何种字符集和校对的决定在服务器、数据库和表级进行。
使用字符集和校对顺序
MySQL支持众多的字符集。为查看所支持的字符集完整列表,使用以下语句:
SHOW CHARACTER SET ;
这条语句显示所有可用的字符集以及每个字符集的描述和默认校对。
为了查看所支持校对的完整列表,使用以下语句:
SHOW COLLATION
此语句显示所有可用的校对,以及它们适用的字符集。可以看到有的字符集具有不止一种校对。
例如, latin1对不同的欧洲语言有几种校对,而且许多校对出现两次,一次区分大小写(由_cs表示),一次不区分大小写(由_ci表示)。
通常系统管理在安装时定义一个默认的字符集和校对。
此外,也可以在创建数据库时,指定默认的字符集和校对。为了确定所用的字符集和校对,可以使用以下语句:
SHOW VARIABLES LIKE 'character%';
SHOW VARIABLES LIKE 'collation%';
实际上,字符集很少是服务器范围(甚至数据库范围)的设置。
不同的表,甚至不同的列都可能需要不同的字符集,而且两者都可以在创建表时指定。
为了给表指定字符集和校对,可使用带子句的CREATE TABLE
CREATE TABLE `myTable` (
`columnn1` INT
`columnn2` VARCHAR(10)
) DEFAULT CHARACTER SET hebrew
COLLATE hebrew_general_ci;
此语句创建一个包含两列的表,并且指定一个字符集和一个校对顺序。
这个例子中指定了CHARACTER SET和COLLATE两者。一般, MySQL如下确定使用什么样的字符集和校对。
- 如果指定CHARACTER SET和COLLATE两者,则使用这些值。
- 如果只指定CHARACTER SET,则使用此字符集及其默认的校对(如SHOW CHARACTER SET的结果中所示)。
- 如果既不指定CHARACTER SET,也不指定COLLATE,则使用数据库默认。
除了能指定字符集和校对的表范围外, MySQL还允许对每个列设置它们,如下所示:
CREATE TABLE `myTable` (
`columnn1` INT,
`columnn2` VARCHAR(10),
`colum3` VARCHAR(10) CHARACTER SET latin1 COLLATE latin1_general_ci
) DEFAULT CHARACTER SET hebrew
COLLATE hebrew_general_ci;
这里对整个表以及一个特定的列指定了CHARACTER SET和COLLATE。
如前所述,校对在对用ORDER BY子句检索出来的数据排序时起重要的作用。
如果你需要用与创建表时不同的校对顺序排序特定的SELECT语句,可以在SELECT语句自身中进行:
SELECT
*
FROM
customers
ORDER BY
lastname,
firstname COLLATE latin1_general_cs
此SELECT使用COLLATE指定一个备用的校对顺序(在这个例子中,为区分大小写的校对)。这显然将会影响到结果排序的次序。
SELECT的其他COLLATE子句 :除了这里看到的在ORDER BY子句中使用以外, COLLATE还可以用于GROUP BY、 HAVING、聚集函数、别名等。
安全管理
访问控制
MySQL服务器的安全基础是: 用户应该对他们需要的数据具有适当的访问权,既不能多也不能少。换句话说,用户不能对过多的数据具有过多的访问权。
考虑以下内容:
- 多数用户只需要对表进行读和写,但少数用户甚至需要能创建和删除表;
- 某些用户需要读表,但可能不需要更新表;
- 你可能想允许用户添加数据,但不允许他们删除数据;
- 某些用户(管理员)可能需要处理用户账号的权限,但多数用户不需要;
- 你可能想让用户通过存储过程访问数据,但不允许他们直接访问数据;
- 你可能想根据用户登录的地点限制对某些功能的访问。
这些都只是例子,但有助于说明一个重要的事实,即你需要给用户提供他们所需的访问权,且仅提供他们所需的访问权。这就是所谓的访问控制,管理访问控制需要创建和管理用户账号。
管理用户
MySQL用户账号和信息存储在名为mysql的MySQL数据库中。一般不需要直接访问mysql数据库和表(你稍后会明白这一点),但有时需要直接访问。需要直接访问它的时机之一是在需要获得所有用户账号列表时。为此,可使用以下代码:
USE mysql
SELECT user FROM user;
mysql数据库有一个名为user的表, 它包含所有用户账号。 user表有一个名为user的列,它存储用户登录名。
创建用户账号
为了创建一个新用户账号,使用CREATE USER语句,如下所示:
CREATE USER ben IDENTIFIED BY "p@$$w0rd";
CREATE USER创建一个新用户账号。在创建用户账号时不一定需要口令,不过这个例子用IDENTIFIED BY 'p@$$wOrd'给出了一个口令。
指定散列口令 : IDENTIFIED BY指定的口令为纯文本, MySQL将在保存到user表之前对其进行加密。为了作为散列值指定口令,使用IDENTIFIED BY PASSWORD。
使用GRANT或INSERT : GRANT语句也可以创建用户账号,但一般来说CREATE USER是最清楚和最简单的句子。此外, 也可以通过直接插入行到user表来增加用户, 不过为安全起见,一般不建议这样做。 MySQL用来存储用户账号信息的表(以及表模式等)极为重要,对它们的任何毁坏都可能严重地伤害到MySQL服务器。因此,相对于直接处理来说,最好是用标记和函数来处理这些表。
为重新命名一个用户账号,使用RENAME USER语句,如下所示:
RENAME USER ben TO bforta;
删除用户账号
为了删除一个用户账号(以及相关的权限),使用DROP USER语句,如下所示:
DROP USER bforta;
设置访问权限
在创建用户账号后,必须接着分配访问权限。新创建的用户账号没有访问权限。它们能登录MySQL,但不能看到数据,不能执行任何数据库操作。
为看到赋予用户账号的权限,使用SHOW GRANTS FOR,如下所示:
SHOW GRANTS FOR bforta ;
输出结果显示用户bforta有一个权限USAGE ON *.*。 USAGE表示根本没有权限(我知道,这不很直观),所以,此结果表示在任意数据库和任意表上对任何东西没有权限。
为设置权限,使用GRANT语句。 GRANT要求你至少给出以下信息:
- 要授予的权限;
- 被授予访问权限的数据库或表;
- 用户名
GRANT SELECT ON crashcourse.* TO bforta
此GRANT允许用户在crashcourse.*( crashcourse数据库的所有表)上使用SELECT。 通过只授予SELECT访问权限,用户bforta对crashcourse数据库中的所有数据具有只读访问权限。
GRANT的反操作为REVOKE,用它来撤销特定的权限。下面举一个例子:
REVOKE SELECT ON crashcourse.* FROM bforta
这条REVOKE语句取消刚赋予用户bforta的SELECT访问权限。 被撤销的访问权限必须存在,否则会出错。
GRANT和REVOKE可在几个层次上控制访问权限:
- 整个服务器,使用GRANT ALL和REVOKE ALL;
- 整个数据库,使用ON database.*;
- 特定的表,使用ON database.table;
- 特定的列;
- 特定的存储过程。
以下列出可以授予或撤销的每个权限:
权限 | 说明 |
---|---|
ALL | 除GRANT OPTION外的所有权限 |
ALTER | 使用ALTER TABLE |
ALTER ROUTINE | 使用ALTER PROCEDURE和DROP PROCEDURE |
CREATE | 使用CREATE TABLE |
CREATE ROUTINE | 使用CREATE PROCEDURE |
CREATE TEMPORARY TABLES | 使用CREATE TEMPORARY TABLE |
CREATE USER | 使用CREATE USER、 DROP USER、 RENAME USER和REVOKE ALL PRIVILEGES |
CREATE VIEW | 使用CREATE VIEW |
DELETE | 使用DELETE |
DROP | 使用DROP TABLE |
EXECUTE | 使用CALL和存储过程 |
FILE | 使用SELECT INTO OUTFILE和LOAD DATA INFILE |
GRANT OPTION | 使用GRANT和REVOKE |
INDEX | 使用CREATE INDEX和DROP INDEX |
INSERT | 使用INSERT |
LOCK TABLES | 使用LOCK TABLES |
PROCESS | 使用SHOW FULL PROCESSLIST |
RELOAD | 使用FLUSH |
REPLICATION CLIENT | 服务器位置的访问 |
REPLICATION SLAVE | 由复制从属使用 |
SELECT | 使用SELECT |
SHOW DATABASES | 使用SHOW DATABASES |
SHOW VIEW | 使用SHOW CREATE VIEW |
SHUTDOWN | 使用mysqladmin shutdown(用来关闭MySQL) |
SUPER | 使用CHANGE MASTER、 KILL、 LOGS、 PURGE、 MASTER和SET GLOBAL。还允许mysqladmin调试登录 |
UPDATE | 使用UPDATE |
USAGE | 无访问权限 |
更改口令
为了更改用户口令,可使用SET PASSWORD语句。新口令必须如下加密:
SET PASSWORD FOR bforta = PASSWORD("xxxx")
SET PASSWORD更新用户口令。新口令必须传递到Password()函数进行加密。
SET PASSWORD还可以用来设置你自己的口令:
SET PASSWORD = PASSWORD("xxxx")
在不指定用户名时, SET PASSWORD更新当前登录用户的口令。
数据库维护
备份数据
像所有数据一样, MySQL的数据也必须经常备份。由于MySQL数据库是基于磁盘的文件,普通的备份系统和例程就能备份MySQL的数据。但是,由于这些文件总是处于打开和使用状态,普通的文件副本备份不一定总是有效。
下面列出这个问题的可能解决方案。
- 使用命令行实用程序mysqldump转储所有数据库内容到某个外部文件。在进行常规备份前这个实用程序应该正常运行,以便能正确地备份转储文件。
- 可用命令行实用程序mysqlhotcopy从一个数据库复制所有数据(并非所有数据库引擎都支持这个实用程序)。
- 可以使用MySQL的BACKUP TABLE或SELECT INTO OUTFILE转储所有数据到某个外部文件。这两条语句都接受将要创建的系统文件名,此系统文件必须不存在,否则会出错。数据可以用RESTORETABLE来复原。
首先刷新未写数据 : 为了保证所有数据被写到磁盘(包括索引数据),可能需要在进行备份前使用FLUSH TABLES语句。
进行数据库维护
MySQL提供了一系列的语句,可以(应该)用来保证数据库正确和正常运行。
以下是你应该知道的一些语句 :
- ANALYZE TABLE,用来检查表键是否正确。 ANALYZE TABLE返回如下所示的状态信息:
- CHECK TABLE用来针对许多问题对表进行检查。 在MyISAM表上还对索引进行检查。 CHECK TABLE支持一系列的用于MyISAM表的方式。CHANGED检查自最后一次检查以来改动过的表。 EXTENDED执行最彻底的检查, FAST只检查未正常关闭的表, MEDIUM检查所有被删除的链接并进行键检验, QUICK只进行快速扫描。如下所示, CHECK TABLE发现和修复问题
- 如果MyISAM表访问产生不正确和不一致的结果,可能需要用REPAIR TABLE来修复相应的表。这条语句不应该经常使用,如果需要经常使用,可能会有更大的问题要解决。
- 如果从一个表中删除大量数据,应该使用OPTIMIZE TABLE来收回所用的空间,从而优化表的性能 。
查看日志文件
MySQL维护管理员依赖的一系列日志文件。主要的日志文件有以下几种:
- 错误日志。它包含启动和关闭问题以及任意关键错误的细节。此日志通常名为hostname.err,位于data目录中。此日志名可用--log-error命令行选项更改。
- 查询日志。它记录所有MySQL活动,在诊断问题时非常有用。此日志文件可能会很快地变得非常大,因此不应该长期使用它。此日志通常名为hostname.log,位于data目录中。此名字可以用--log命令行选项更改。
- 二进制日志。它记录更新过数据(或者可能更新过数据)的所有语句。此日志通常名为hostname-bin,位于data目录内。此名字可以用--log-bin命令行选项更改。注意, 这个日志文件是MySQL5中添加的,以前的MySQL版本中使用的是更新日志。
- 缓慢查询日志。顾名思义,此日志记录执行缓慢的任何查询。这个日志在确定数据库何处需要优化很有用。此日志通常名为hostname-slow.log , 位 于 data 目 录 中 。 此 名 字 可 以 用--log-slow-queries命令行选项更改。
在使用日志时,可用FLUSH LOGS语句来刷新和重新开始所有日志文件。
改善性能
数据库管理员把他们生命中的相当一部份时间花在了调整、试验以改善DBMS性能之上。在诊断应用的滞缓现象和性能问题时,性能不良的数据库(以及数据库查询)通常是最常见的祸因。
可以看出,下面的内容并不能完全决定MySQL的性能。我们只是想回顾一下前面各章的重点,提供进行性能优化探讨和分析的一个出发点。
- 首先, MySQL(与所有DBMS一样)具有特定的硬件建议。在学习和研究MySQL时,使用任何旧的计算机作为服务器都可以。但对用于生产的服务器来说,应该坚持遵循这些硬件建议。
- 一般来说,关键的生产DBMS应该运行在自己的专用服务器上。
- MySQL是用一系列的默认设置预先配置的,从这些设置开始通常是很好的。但过一段时间后你可能需要调整内存分配、缓冲区大小等。(为查看当前设置,可使用SHOW VARIABLES;和SHOWSTATUS;。)
- MySQL一个多用户多线程的DBMS,换言之,它经常同时执行多个任务。如果这些任务中的某一个执行缓慢,则所有请求都会执行缓慢。如果你遇到显著的性能不良,可使用SHOW PROCESSLIST显示所有活动进程(以及它们的线程ID和执行时间)。你还可以用KILL命令终结某个特定的进程(使用这个命令需要作为管理员登录)。
- 总是有不止一种方法编写同一条SELECT语句。 应该试验联结、并、子查询等,找出最佳的方法。
- 使用EXPLAIN语句让MySQL解释它将如何执行一条SELECT语句。
- 一般来说,存储过程执行得比一条一条地执行其中的各条MySQL语句快。
- 应该总是使用正确的数据类型。
- 决不要检索比需求还要多的数据。换言之,不要用SELECT *(除非你真正需要每个列)。
- 有的操作(包括INSERT)支持一个可选的DELAYED关键字,如果使用它,将把控制立即返回给调用程序,并且一旦有可能就实际执行该操作。
- 在导入数据时,应该关闭自动提交。你可能还想删除索引(包括FULLTEXT索引),然后在导入完成后再重建它们。
- 必须索引数据库表以改善数据检索的性能。确定索引什么不是一件微不足道的任务,需要分析使用的SELECT语句以找出重复的WHERE和ORDER BY子句。如果一个简单的WHERE子句返回结果所花的时间太长,则可以断定其中使用的列(或几个列)就是需要索引的对象。
- 你的SELECT语句中有一系列复杂的OR条件吗?通过使用多条SELECT语句和连接它们的UNION语句,你能看到极大的性能改进。
- 索引改善数据检索的性能,但损害数据插入、删除和更新的性能。如果你有一些表,它们收集数据且不经常被搜索,则在有必要之前不要索引它们。(索引可根据需要添加和删除。)
- LIKE很慢。一般来说,最好是使用FULLTEXT而不是LIKE。
- 数据库是不断变化的实体。一组优化良好的表一会儿后可能就面目全非了。由于表的使用和内容的更改,理想的优化和配置也会改变。
- 最重要的规则就是,每条规则在某些条件下都会被打破。
MySQL语句的语法
在阅读语句语法时,应该记住以下约定 :
- |符号用来指出几个选择中的一个,因此, NULL | NOT NULL表示或者给出NULL或者给出NOT NULL。
- 包含在方括号中的关键字或子句(如[like this])是可选的。
- 既没有列出所有的MySQL语句,也没有列出每一条子句和选项。
ALTER TABLE
ALTER TABLE用来更新已存在表的模式。为了创建新表,应该使用CREATE TABLE。
COMMIT
COMMIT用来将事务处理写到数据库。
CREATE INDEX
CREATE INDEX用于在一个或多个列上创建索引。
CREATE PROCEDURE
CREATE PROCEDURE用于创建存储过程。
CREATE TABLE
CREATE TABLE用于创建新数据库表。为更新已经存在的表的结构,使用ALTER TABLE。
CREATE USER
CREATE USER 用于向系统中添加新的用户账户。
CREATE VIEW
CREATE VIEW用来创建一个或多个表上的新视图。
DELETE
DELETE从表中删除一行或多行。
DROP
DROP永久地删除数据库对象(表、视图、索引等)。
INSERT
INSERT给表增加一行。
INSERT SELECT
INSERT SELECT插入SELECT的结果到一个表。
ROLLBACK
ROLLBACK用于撤销一个事务处理块。
SAVEPOINT
SAVEPOINT为使用ROLLBACK语句设立保留点。
SELECT
SELECT用于从一个或多个表(视图)中检索数据。
START TRANSACTION
START TRANSACTION表示一个新的事务处理块的开始。
UPDATE
UPDATE更新表中一行或多行。
MySQL数据类型
数据类型是定义列中可以存储什么数据以及该数据实际怎样存储的基本规则。
数据类型用于以下目的:
- 数据类型允许限制可存储在列中的数据。例如,数值数据类型列只能接受数值。
- 数据类型允许在内部更有效地存储数据。可以用一种比文本串更简洁的格式存储数值和日期时间值。
- 数据类型允许变换排序顺序。如果所有数据都作为串处理,则1位于10之前,而10又位于2之前(串以字典顺序排序,从左边开始比较,一次一个字符)。作为数值数据类型,数值才能正确排序。
在设计表时,应该特别重视所用的数据类型。使用错误的数据类型可能会严重地影响应用程序的功能和性能。更改包含数据的列不是一件小事(而且这样做可能会导致数据丢失)。
串数据类型
最常用的数据类型是串数据类型。它们存储串,如名字、地址、电话号码、邮政编码等。有两种基本的串类型,分别为定长串和变长串 。
定长串接受长度固定的字符串,其长度是在创建表时指定的。
定长列不允许多于指定的字符数目。它们分配的存储空间与指定的一样多。 因此, 如果串Ben存储到30个字符的名字字段,则存储的是30个字符, CHAR属于定长串类型。
变长串存储可变长度的文本。有些变长数据类型具有最大的定长,而有些则是完全变长的。不管是哪种,只有指定的数据得到保存(额外的数据不保存) TEXT属于变长串类型。
既然变长数据类型这样灵活,为什么还要使用定长数据类型?回答是因为性能。 MySQL处理定长列远比处理变长列快得多。此外, MySQL不允许对变长列(或一个列的可变部分)进行索引。这也会极大地影响性能。
串数据类型列表:
数据类型 | 说明 |
---|---|
CHAR | 1~255个字符的定长串。它的长度必须在创建时指定,否则MySQL假定为CHAR(1) |
ENUM | 接受最多64 K个串组成的一个预定义集合的某个串 |
LONGTEXT | 与TEXT相同,但最大长度为4 GB |
MEDIUMTEXT | 与TEXT相同,但最大长度为16 K |
SET | 接受最多64个串组成的一个预定义集合的零个或多个串 |
TEXT | 最大长度为64 K的变长文本 |
TINYTEXT | 与TEXT相同,但最大长度为255字节 |
VARCHAR | 长度可变,最多不超过255字节。如果在创建时指定为VARCHAR(n),则可存储0到n个字符的变长串(其中n≤255) |
使用引号 :不管使用何种形式的串数据类型,串值都必须括在引号内(通常单引号更好)。
数值数据类型
数值数据类型存储数值。 MySQL支持多种数值数据类型,每种存储的数值具有不同的取值范围。显然,支持的取值范围越大,所需存储空间越多。此外,有的数值数据类型支持使用十进制小数点(和小数),而有的则只支持整数。下面的表列出了常用的MySQL数值数据类型。
有符号或无符号 : 所有数值数据类型(除BIT和BOOLEAN外)都可以有符号或无符号。有符号数值列可以存储正或负的数值,无符号数值列只能存储正数。默认情况为有符号,但如果你知道自己不需要存储负值,可以使用UNSIGNED关键字,这样做将允许你存储两倍大小的值。
数据类型 | 说明 |
---|---|
BIT | 位字段, 1~64位。(在MySQL 5之前, BIT在功能上等价于TINYINT |
BIGINT | 整数值,支持9223372036854775808~9223372036854775807 (如果是UNSIGNED,为0~18446744073709551615)的数 |
BOOLEAN(或BOOL) | 布尔标志,或者为0或者为1,主要用于开/关(on/off)标志 |
DECIMAL(或DEC) | 精度可变的浮点值 |
DOUBLE | 双精度浮点值 |
FLOAT | 单精度浮点值 |
INT(或INTEGER) | 整数值,支持2147483648~2147483647(如果是UNSIGNED,为0~4294967295)的数 |
MEDIUMINT | 整数值,支持8388608~8388607(如果是UNSIGNED,为0~ 16777215)的数 |
REAL | 4字节的浮点值 |
SMALLINT | 整数值,支持32768~32767(如果是UNSIGNED,为0~ 65535)的数 |
TINYINT | 整数值,支持128~127(如果为UNSIGNED,为0~255)的数 |
不使用引号 :与串不一样,数值不应该括在引号内。
存储货币数据类型: MySQL中没有专门存储货币的数据类型,一般情况下使用DECIMAL(8, 2)
日期和时间数据类型
MySQL使用专门的数据类型来存储日期和时间值
数据类型 | 说明 |
---|---|
DATE | 表示1000-01-01~9999-12-31的日期,格式为YYYY-MM-DD |
DATETIME | DATE和TIME的组合 |
TIMESTAMP | 功能和DATETIME相同(但范围较小) |
TIME | 格式为HH:MM:SS |
YEAR | 用2位数字表示,范围是70(1970年)~69(2069年),用4位数字表示,范围是1901年~2155年 |
二进制数据类型
二进制数据类型可存储任何数据(甚至包括二进制信息),如图像、多媒体、字处理文档等
数据类型 | 说明 |
---|---|
BLOB | Blob最大长度为64 KB |
MEDIUMBLOB | Blob最大长度为16 MB |
LONGBLOB | Blob最大长度为4 GB |
TINYBLOB | Blob最大长度为255字节 |