《MySQL必知必会》的学习笔记和总结。
书籍链接
数据库(database):保存有组织的数据的容器(通常是一个文
件或一组文件)。
确切地说,数据库软件应称为DBMS(数据库管理系统)。数据库是通过DBMS创建和操纵的容器。数据库可以是保存在硬设备上的文件,但也可以不是。
表(table):某种特定类型数据的结构化清单。
表名的唯一性取决于多个因素,如数据库名和表名等的结合。这表示,虽然在相同数据库中不能两次使用相同的表名,但在不同的数据库中却可以使用相同的表名。
表具有一些特性,这些特性定义了数据在表中如何存储,描述表的这些特性就是所谓的模式,模式可以用来描述数据库中特定的表以及整个数据库(和其中表的关系)。
模式(schema):关于数据库和表的布局及特性的信息。
列(column):表中的一个字段。所有表都是由一个或多个列组成的。
数据类型(datatype):所容许的数据的类型。每个表列都有相应的数据类型,它限制(或容许)该列中存储的数据。
行(row):表中的一个记录。
行(row)有时也称为数据库记录(record)。
主键(primary key):一列(或一组列),其值能够唯一区分表中每个行。
应该尽量应保证他们创建的每个表具有一个主键,以便于以后的数据操纵和管理。
表中的任何列都可以作为主键,只要它满足以下条件:
除了以上的条件,使用主键几个比较好的习惯:
当然还有一种重要的键,即外键,后面会介绍到。
SQL是一种专门用来与数据库通信的语言。设计SQL的目的是很好地完成一项任务,即提供一种从数据库中读写数据的简单有效的方法。
SQL有如下的优点:
值得注意的是,不要认为所有的SQL语法是完全可移植的。
MySQL是一种DBMS(数据库管理系统),即它是一种数据库软件。
TODO: 待更新。
# 创建数据库
create database learnDB;
# 使用数据库
use learnDB
show databases;
返回可用数据库的一个列表。包含在这个列表中的可能是MySQL内部使用的数据库。
show tables;
返回当前选择的数据库内可用表的列表。
show columns from table;
要求给出一个表名,它对每个字段返回一行,行中包含字段名、数据类型、是否允许NULL、键信息、默认值以及其他信息。
describe table;
它是show columns from table;的一种快捷形式。
自动增量:某些表列需要唯一值。在每个行添加到表中时,MySQL可以自动地为每个行分配下一个可用编号,不用在添加一行时手动分配唯一值(这样做必须记住最后一次使用的值)。这个功能就是所谓的自动增量。
其他show语句:
show status; 用于显示广泛的服务器状态信息。show create database; 和 show create table; 分别用来显示创建特定数据库或表。show grants; 用来显示授予用户(所有用户或者特定用户)的安全权限。show errors; 和 show warnings; 用来显示服务器错误或警告信息。为了使用SELECT检索表数据,必须至少给出两条信息——想选择什么,以及从什么地方选择。
select prod_name
from products;
从products表中检索一个名为prod_name的列。
select prod_id, prod_name, prod_price
from products;
从products表中检索一个名为prod_id、prod_name、prod_price的列。
select *
from products;
使用通配符有一个大优点。由于不明确指定列名(因为星号检索每个列),所以能检索出名字未知的列。
select distinct vend_id
from products;
只返回不同(唯一)的vend_id行,如果使用DISTINCT关键字,它必须直接放在列名的前面。
注意:不能部分使用DISTINCT。DISTINCT关键字应用于所有列而不仅是前置它的列。如果给出 SELECT DISTINCT vend_id, prod_price,除非指定的两个列都不同,否则所有行都将被检索出来。
select distinct vend_id, prod_price
from products
limit 5;
LIMIT 5指示MySQL返回不多于5行。
select prod_name
from products
limit 4 offset 3;
意为从行3开始取4行,等效于 select prod_name from products limit 3, 4;
select products.prod_name
from products
limit 4 offset 3;
其实,检索出的数据并不是以纯粹的随机顺序显示的。如果不明确控制的话,不能(也不应该)依赖该排序顺序。关系数据库设计理论认为,如果不明确规定排序顺序,则不应该假定检索出的数据的顺序有意义。
子句(clause):SQL语句由子句构成,有些子句是必需的,而有的是可选的。一个子句通常由一个关键字和所提供的数据组成。
select prod_name
from products
order by prod_name;
指示MySQL对prod_name列以字母顺序排序数据。
select prod_id, prod_price, prod_name
from products
order by prod_price, prod_name;
检索3个列,并按其中两个列对结果进行排序——首先按价格,然后再按名称排序。
数据排序默认为升序排序,为了进行降序排序,必须指定DESC关键字。
select prod_id, prod_price, prod_name
from products
order by prod_price desc;
按价格以降序排序产品。
注意:DESC关键字只应用到直接位于其前面的列名。如果想在多个列上进行降序排序,必须对每个列指定DESC关键字。
注意:在给出ORDER BY子句时,应该保证它位于FROM子句之后。如果使用LIMIT,它必须位于ORDER BY之后。使用子句的次序不对将产生错误消息。
只检索所需数据需要指定搜索条件(search criteria),搜索条件也称为过滤条件(filter condition)。WHERE子句在表名(FROM子句)之后给出。
select prod_id, prod_price, prod_name
from products
where prod_price = 2.50;
从products表中检索两个列,但不返回所有行,只返回prod_price值为2.50的行,
注意:在同时使用ORDER BY和WHERE子句时,应该让ORDER BY位于WHERE之后,否则将会产生错误。
MySQL支持的所有条件操作符:
=<>!=<<=>>=BETWEENselect prod_id, prod_price, prod_name
from products
where prod_price between 5 and 10;
它检索价格在5美元和10美元之间的所有产品。BETWEEN匹配范围中所有的值,包括指定的开始值和结束值。
NULL 无值(no value):它与字段包含0、空字符串或仅仅包含空格不同。
SELECT语句有一个特殊的WHERE子句,可用来检查具有NULL值的列。这个WHERE子句就是IS NULL子句。
select cust_id
from customers
where cust_email is null;
注意:在通过过滤选择出不具有特定值的行时,你可能希望返回具有NULL值的行。但是,不行。因为未知具有特殊的含义,数据库不知道它们是否匹配,所以在匹配过滤
或不匹配过滤时不返回它们。因此,在过滤数据时,一定要验证返回数据中确实给出了被过滤列具有NULL的行。
为了进行更强的过滤控制,MySQL允许给出多个WHERE子句。这些子句可以两种方式使用:以AND子句的方式或OR子句的方式使用。
操作符(operator):用来联结或改变WHERE子句中的子句的关键字。也称为逻辑操作符(logical operator)。
AND:用在WHERE子句中的关键字,用来指示检索满足所有给定条件的行。
select vend_id, prod_price
from products
where vend_id = 1003 and prod_price <= 10;
此SQL语句检索由供应商1003制造且价格小于等于10美元的所有产品的名称和价格。
OR:WHERE子句中使用的关键字,用来表示检索匹配任一给定条件的行。
select vend_id, prod_price
from products where vend_id = 1002 or vend_id = 1003;
此SQL语句检索由任一个指定供应商制造的所有产品的产品名和价格。
注意:任何时候使用具有AND和OR操作符的WHERE子句,都应该使用圆括号明确地分组操作符。不要过分依赖默认计算次序,即使它确实是你想要的东西也是如此。使用圆括号没有什么坏处,它能消除歧义。
IN操作符用来指定条件范围,范围中的每个条件都可以进行匹配。IN取合法值的由逗号分隔的清单,全都括在圆括号中。
IN:WHERE子句中用来指定要匹配值的清单的关键字,功能与OR相当。
select prod_name, prod_price
from products
where vend_id in (1002, 1003) order by prod_name;
检索供应商1002和1003制造的所有产品。
为什么要使用IN操作符?它有如下优点:
WHERE子句中的NOT操作符有且只有一个功能,那就是否定它之后所跟的任何条件。
NOT:WHERE子句中用来否定后跟条件的关键字。
select prod_name, prod_price
from products
where vend_id not in (1002, 1003) order by prod_name;
检索供应商1002和1003之外制造的所有产品。
MySQL支持使用NOT对IN、BETWEEN和EXISTS子句取反,这与多数其他DBMS允许使用NOT对各种条件取反有很大的差别。
通配符(wildcard):用来匹配值的一部分的特殊字符。它本身实际是SQL的WHERE子句中有特殊含义的字符
搜索模式(search pattern):由字面值、通配符或两者组合构成的搜索条件。
为在搜索子句中使用通配符,必须使用LIKE操作符。LIKE指示MySQL,后跟的搜索模式利用通配符匹配而不是直接相等匹配进行比较。
操作符何时不是操作符?答案是在它作为谓词(predicate)时。从技术上说,LIKE是谓词而不是操作符。
在搜索串中,%表示任何字符出现任意次数。
select prod_id, prod_name
from products
where prod_name like 'jet%';
将检索任意以jet起头的prod_name。
注意:%代表搜索模式中给定位置的0个、1个或多个字符。
尾空格可能会干扰通配符匹配。解决这个问题的一个简单的办法是在搜索模式最后附加一个%。一个更好的办法是使用函数去掉首尾空格。
虽然似乎%通配符可以匹配任何东西,但有一个例外,即NULL。即使是WHERE prod_name LIKE '%'也不能匹配用值NULL作为产品名的行。
下划线只匹配单个字符而不是多个字符。
select prod_id, prod_name
from products
where prod_name like '_ ton anvil';
通配符搜索的处理一般要比前面讨论的其他搜索所花时间更长。
一些使用通配符要记住的技巧:
正则表达式是用来匹配文本的特殊的串(字符集合)。所有种类的程序设计语言、文本编辑器、操作系统等都支持正则表达式。
MySQL仅支持多数正则表达式实现的一个很小的子集。
select prod_name
from products
where prod_name regexp '1000' order by prod_name;
检索列prod_name包含文本1000的所有行。
select prod_name
from products
where prod_name regexp '.000' order by prod_name;
.是正则表达式语言中一个特殊的字符。它表示匹配任意一个字符,因此,1000和2000等都匹配且返回。
LIKE和REGEXP的一个重要差别:
select prod_name
from products
where prod_name like '1000'
order by prod_name;
select prod_name
from products
where prod_name regexp '1000' order by prod_name;
执行上述两条语句,会发现第一条语句不返回数据,而第二条语句返回一行。为什么?
因为LIKE匹配整个列。如果被匹配的文本在列值中出现,LIKE将不会找到它,相应的行也不被返回(除非使用通配符)。而REGEXP在列值内进行匹配,如果被匹配的文本在列值中出现,REGEXP将会找到它,相应的行将被返回。
要让REGEXP用来匹配整个列值(从而起与LIKE相同的作用),使用^和$定位符(anchor)即可。
MySQL中的正则表达式匹配(自版本3.23.4后)不区分大小写,即大写和小写都匹配)。为区分大小写,可使用BINARY关键字。
select prod_name
from products
where prod_name regexp '1000|2000' order by prod_name;
|为正则表达式的OR操作符。它表示匹配其中之一,因此1000和2000都匹配并返回。
使用|从功能上类似于在SELECT语句中使用OR语句,多个OR条件可并入单个正则表达式。
select prod_name
from products
where prod_name regexp '[123] Ton' order by prod_name;
[123]定义一组字符,它的意思是匹配1或2或3,因此,1 ton和2 ton都匹配且返回(没有3 ton)。
select prod_name
from products
where prod_name regexp '[1-5] Ton' order by prod_name;
[1-5]定义了一个范围,这个表达式意思是匹配1到5,因此返回3个匹配行。
为了匹配特殊字符,必须用\为前导。\-表示查找-,\.表示查找.。
为更方便工作,可以使用预定义的字符集,称为字符类。
[:alnum:][a-zA-Z0-9])[:alpha:][a-zA-Z])[:blank:][\t])[:cntrl:][: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])匹配多个实例可以用正则表达式重复元字符来完成。
正则表达式重复元字符:
*+{1,})?{0, 1}){n}{n,}{n,m}select prod_name
from products
where prod_name regexp '\([0-9] sticks?\)'
order by prod_name;
# 输出
+----------------+
| prod_name |
+----------------+
| TNT (1 stick) |
| TNT (5 sticks) |
+----------------+
select prod_name
from products
where prod_name regexp '[[:digit:]]{4}'
order by prod_name;
# 输出:匹配连在一起的任意4位数字。
+--------------+
| prod_name |
+--------------+
| JetPack 1000 |
| JetPack 2000 |
+--------------+
为了匹配特定位置的文本,需要使用定位符。
^$[[:<:]][[:>:]]select prod_name
from products
where prod_name regexp '^[0-9\.]'
order by prod_name;
# 输出
+--------------+
| prod_name |
+--------------+
| .5 ton anvil |
| 1 ton anvil |
| 2 ton anvil |
+--------------+
^的双重用途:
[和]定义),用它来否定该集合。LIKE和REGEXP的不同在于,LIKE匹配整个串而REGEXP匹配子串。利用定位符,通过用^开始每个表达式,用$结束每个表达式,可以使REGEXP的作用与LIKE一样。
在有的情况下,存储在表中的数据都不是应用程序所需要的。我们需要直接从数据库中检索出转换、计算或格式化过的数据;而不是检索出数据,然后再在客户机应用程序或报告程序中重新格式化。
计算字段并不实际存在于数据库表中。计算字段是运行时在SELECT语句内创建的。
字段(field):基本上与列(column)的意思相同,经常互换使用,不过数据库列一般称为列,而术语字段通常用在计算字段的连接上。
拼接(concatenate):将值联结到一起构成单个值。
在MySQL的SELECT语句中,可使用Concat()函数来拼接两个列。
注意:多数DBMS使用+或||来实现拼接,MySQL则使用Concat()函数来实现。当把SQL语句转换成MySQL语句时一定要把这个区别铭记在心。
select concat(vend_name, '(', vend_country, ')')
from vendors
order by vend_name;
# 输出
+-------------------------------------------+
| concat(vend_name, '(', vend_country, ')') |
+-------------------------------------------+
| ACME(USA) |
| Anvils R Us(USA) |
| Furball Inc.(USA) |
| Jet Set(England) |
| Jouets Et Ours(France) |
| LT Supplies(USA) |
+-------------------------------------------+
别名(alias)是一个字段或值的替换名。别名用AS关键字赋予。
select concat(vend_name, '(', vend_country, ')') as vend_title
from vendors
order by vend_name;
# 输出
+------------------------+
| vend_title |
+------------------------+
| ACME(USA) |
| Anvils R Us(USA) |
| Furball Inc.(USA) |
| Jet Set(England) |
| Jouets Et Ours(France) |
| LT Supplies(USA) |
+------------------------+
计算字段的另一常见用途是对检索出的数据进行算术计算。
# 检索订单号20005中的所有物品:
select prod_id, quantity, item_price
from orderitems
where order_num = 20005;
# 输出
+---------+----------+------------+
| prod_id | quantity | item_price |
+---------+----------+------------+
| ANV01 | 10 | 5.99 |
| ANV02 | 3 | 9.99 |
| TNT2 | 5 | 10.00 |
| FB | 1 | 10.00 |
+---------+----------+------------+
# 下汇总物品的价格(单价乘以订购数量)
select prod_id, quantity, item_price, quantity*item_price as expanded_price
from orderitems
where order_num = 20005;
# 输出
+---------+----------+------------+----------------+
| prod_id | quantity | item_price | expanded_price |
+---------+----------+------------+----------------+
| ANV01 | 10 | 5.99 | 59.90 |
| ANV02 | 3 | 9.99 | 29.97 |
| TNT2 | 5 | 10.00 | 50.00 |
| FB | 1 | 10.00 | 10.00 |
+---------+----------+------------+----------------+
SQL支持利用函数来处理数据,函数一般是在数据上执行的,它给数据的转换和处理提供了方便。
由于函数的可移植性不是那么强,如果决定使用函数,应该确保做好代码注释,以便以后能确切知道其含义。
大多数SQL实现支持以下类型的函数:
select vend_name, upper(vend_name) as vend_name_upcase
from vendors
order by vend_name;
# 输出
+----------------+------------------+
| vend_name | vend_name_upcase |
+----------------+------------------+
| ACME | ACME |
| Anvils R Us | ANVILS R US |
| Furball Inc. | FURBALL INC. |
| Jet Set | JET SET |
| Jouets Et Ours | JOUETS ET OURS |
| LT Supplies | LT SUPPLIES |
+----------------+------------------+
Upper()将文本转换为大写,因此本例子中每个供应商都列出两次,第一次为vendors表中存储的值,第二次作为列vend_name_upcase转换为大写。
常用的文本处理函数:
left()length()locate()lower()upper()ltrim()rtrim()soundex()soundex值substring()soundex是一个将任何文本串转换为描述其语音表示的字母数字模式的算法。
# customers表中有一个顾客Coyote Inc.,其联系名为Y.Lee。
# 但如果这是输入错误,此联系名实际应该是Y.Lie,怎么办?
select cust_name, cust_contact
from customers
where cust_contact = 'Y. Lie';
# 输出:显然,按正确的联系名搜索不会返回数据
# 使用Soundex()函数进行搜索,它匹配所有发音类似于Y.Lie的联系名
select cust_name, cust_contact
from customers
where soundex(cust_contact) = soundex('Y. Lie');
# 输出
+-------------+--------------+
| cust_name | cust_contact |
+-------------+--------------+
| Coyote Inc. | Y Lee |
+-------------+--------------+
日期和时间采用相应的数据类型和特殊的格式存储,以便能快速和有效地排序或过滤,并且节省物理存储空间。
常用的日期和时间处理函数:
AddDate()AddTime()CurDate()CurTime()Date()DateDiff()Date_Add()Date_Format()Day()DayOfWeek()Hour()Minute()Month()Now()Second()Time()Year()值得注意的是,。无论你什么时候指定一个日期,不管是插入或更新表值还是用WHERE子句进行过滤,日期必须为格式yyyy-mm-dd。
select cust_id, order_num, order_date
from orders
where order_date = '2005-09-01';
# 输出
+---------+-----------+---------------------+
| cust_id | order_num | order_date |
+---------+-----------+---------------------+
| 10001 | 20005 | 2005-09-01 00:00:00 |
+---------+-----------+---------------------+
使用WHERE order_date = '2005-09-01'可靠吗?order_date的数据类型为datetime。这种类型存储日期及时间值。样例表中的值全都具有时间值00:00:00,但实际中很可能并不总是这样。
解决办法是指示MySQL仅将给出的日期与列中的日期部分进行比较,而不是将给出的日期与整个列值进行比较。
select cust_id, order_num, order_date
from orders
where date(order_date) = '2005-09-01';
# 范围比较
select cust_id, order_num, order_date
from orders
where year(order_date) = 2005 and month(order_date) = 9;
# 输出:2005年9月的所有数据
+---------+-----------+---------------------+
| cust_id | order_num | order_date |
+---------+-----------+---------------------+
| 10001 | 20005 | 2005-09-01 00:00:00 |
| 10003 | 20006 | 2005-09-12 00:00:00 |
| 10004 | 20007 | 2005-09-30 00:00:00 |
+---------+-----------+---------------------+
数值处理函数仅处理数值数据。这些函数一般主要用于代数、三角或几何运算。
常用的数值处理函数:
Abs()Cos()Exp()Mod()Pi()Rand()Sin()Sqrt()Tan()经常需要汇总数据而不用把它们实际检索出来,为此MySQL提供了专门的函数。使用这些函数,MySQL查询可用于检索数据,以便分析和报表生成。
这种类型的检索例子有以下几种:
聚集函数(aggergate function):运行在行组上,计算和返回单个值的函数。
SQL聚集函数:
AVG()COUNT()MAX()MIN()SUM()AVG()通过对表中行数计数并计算特定列值之和,求得该列的平均值。AVG()可用来返回所有列的平均值,也可以用来返回特定列或行的平均值。
# 返回products表中所有产品的平均价格
select avg(prod_price) as avg_price
from products;
# 输出
+-----------+
| avg_price |
+-----------+
| 16.133571 |
+-----------+
# 返回products表中1003产品的平均价格
select avg(prod_price) as avg_price
from products
where vend_id = 1003;
# 输出
+-----------+
| avg_price |
+-----------+
| 13.212857 |
+-----------+
注意:AVG()只能用来确定特定数值列的平均值,而且列名必须作为函数参数给出。为了获得多个列的平均值,必须使用多个AVG()函数。AVG()函数忽略列值为NULL的行。
COUNT()函数进行计数。可利用COUNT()确定表中行的数目或符合特定条件的行的数目。
COUNT()函数有两种使用方式:
# 返回customers表中客户的总数
select count(*) as num_cust
from customers;
# 输出
+----------+
| num_cust |
+----------+
| 5 |
+----------+
select count(cust_email) as num_cust
from customers;
# 输出: NULL被忽略
+----------+
| num_cust |
+----------+
| 3 |
+----------+
MAX()、MIN()分别返回指定列中的最大值和最小值,它们都要求指定列名。
# 返回products表中最贵的物品的价格
select max(prod_price) as max_price
from products;
# 输出
+-----------+
| max_price |
+-----------+
| 55.00 |
+-----------+
# 返回products表中最便宜的物品的价格
select min(prod_price) as min_price
from products;
# 输出
+-----------+
| min_price |
+-----------+
| 2.50 |
+-----------+
SUM()用来返回指定列值的和。
# 返回所有quantity值之和
select sum(quantity) as items_ordered
from orderitems
where order_num = 20005;
# 输出
+---------------+
| items_ordered |
+---------------+
| 19 |
+---------------+
# 返回所有item_price*quantity值之和
select sum(item_price*quantity) as total_price
from orderitems
where order_num = 20005;
# 输出
+-------------+
| total_price |
+-------------+
| 149.87 |
+-------------+
select avg(distinct prod_price) as avg_price
from products
where vend_id = 1003;
# 输出
+-----------+
| avg_price |
+-----------+
| 15.998000 |
+-----------+
但实际上SELECT语句可根据需要包含多个聚集函数。
select count(*) as num_items, min(prod_price) as price_min, max(prod_price) as price_max, avg(prod_price) as price_avg
from products;
# 输出
+-----------+-----------+-----------+-----------+
| num_items | price_min | price_max | price_avg |
+-----------+-----------+-----------+-----------+
| 14 | 2.50 | 55.00 | 16.133571 |
+-----------+-----------+-----------+-----------+
分组允许把数据分为多个逻辑组,以便能对每个组进行聚集计算。
# 一个例子来理解分组
select vend_id, count(*) as num_prods
from products
group by vend_id;
# 输出
+---------+-----------+
| vend_id | num_prods |
+---------+-----------+
| 1001 | 3 |
| 1002 | 2 |
| 1003 | 7 |
| 1005 | 2 |
+---------+-----------+
GROUP BY子句指示MySQL按vend_id排序并分组数据。这导致对每个vend_id而不是整个表计算num_prods一次。
在具体使用GROUP BY子句前,需要知道一些重要的规定:
使用WITH ROLLUP关键字,可以得到每个分组以及每个分组汇总级别(针对每个分组)的值。
select vend_id, count(*) as num_prods
from products
group by vend_id with rollup;
# 输出
+---------+-----------+
| vend_id | num_prods |
+---------+-----------+
| 1001 | 3 |
| 1002 | 2 |
| 1003 | 7 |
| 1005 | 2 |
| NULL | 14 |
+---------+-----------+
HAVING非常类似于WHERE。事实上,目前为止所学过的所有类型的WHERE子句都可以用HAVING来替代。唯一的差别是WHERE过滤行,而HAVING过滤分组。
有关WHERE的所有这些技术和选项都适用于HAVING。它们的句法是相同的,只是关键字有差别。WHERE在数据分组前进行过滤,HAVING在数据分组后进行过滤。 这是一个重要的区别,WHERE排除的行不包括在分组中。这可能会改变计算值,从而影响HAVING子句中基于这些值过滤掉的分组。
# 返回有三个以上的
select vend_id, count(*) as num_prods
from products
group by vend_id
having count(*) >= 3;
# 输出
+---------+-----------+
| vend_id | num_prods |
+---------+-----------+
| 1001 | 3 |
| 1003 | 7 |
+---------+-----------+
# 列出具有2个(含)以上、价格为10(含)以上的产品的供应商
select vend_id, count(*) as num_prods
from products
where prod_price >= 10
group by vend_id
having count(*) >= 2;
# 输出
+---------+-----------+
| vend_id | num_prods |
+---------+-----------+
| 1003 | 4 |
| 1005 | 2 |
+---------+-----------+
GROUP BY与ORDER BY的差别:
一般在使用GROUP BY子句时,应该也给出ORDER BY子句。这是保证数据正确排序的唯一方法。千万不要仅依赖GROUP BY排序数据。
# 检索总计订单价格大于等于50的订单的订单号和总计订单价格
select order_num, sum(quantity*item_price) as ordertotal
from orderitems
group by order_num
having sum(quantity*item_price) >= 50;
# 输出
+-----------+------------+
| order_num | ordertotal |
+-----------+------------+
| 20005 | 149.87 |
| 20006 | 55.00 |
| 20007 | 1000.00 |
| 20008 | 125.00 |
+-----------+------------+
# 为按总计订单价格排序输出,需要添加ORDER BY子句
select order_num, sum(quantity*item_price) as ordertotal
from orderitems
group by order_num
having sum(quantity*item_price) >= 50
order by ordertotal;
# 输出
+-----------+------------+
| order_num | ordertotal |
+-----------+------------+
| 20006 | 55.00 |
| 20008 | 125.00 |
| 20005 | 149.87 |
| 20007 | 1000.00 |
+-----------+------------+
SELECT子句及其顺序:
SELECTFROMWHEREGROUP BYHAVINGORDER BYLIMIT查询(query):任何SQL语句都是查询。但此术语一般指SELECT语句。
SQL还允许创建子查询(subquery),即嵌套在其他查询中的查询。
假如需要列出订购物品TNT2的所有客户,应该怎样检索?
# 第一步:检索包含物品TNT2的所有订单的编号。
select order_num
from orderitems
where prod_id = 'TNT2';
# 输出
+-----------+
| order_num |
+-----------+
| 20005 |
| 20007 |
+-----------+
# 第二步:检索具有前一步骤列出的订单编号的所有客户的ID。
select cust_id
from orders
where order_num in (20005, 20007);
# 输出
+---------+
| cust_id |
+---------+
| 10001 |
| 10004 |
+---------+
# 第三步:检索前一步骤返回的所有客户ID的客户信息。
select cust_name, cust_contact
from customers
where cust_id in (10001, 10004);
# 输出
+----------------+--------------+
| cust_name | cust_contact |
+----------------+--------------+
| Coyote Inc. | Y Lee |
| Yosemite Place | Y Sam |
+----------------+--------------+
# 将上面的三步进行合并
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'));
# 输出
+----------------+--------------+
| cust_name | cust_contact |
+----------------+--------------+
| Coyote Inc. | Y Lee |
| Yosemite Place | Y Sam |
+----------------+--------------+
对于能嵌套的子查询的数目没有限制,不过在实际使用时由于性能的限制,不能嵌套太多的子查询。使用子查询并不总是执行这种类型的数据检索的最有效的方法。
使用子查询的另一方法是创建计算字段。
# 如需要显示customers表中每个客户的订单总数。
# 第一步:从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;
# 输出
+----------------+------------+--------+
| cust_name | cust_state | orders |
+----------------+------------+--------+
| Coyote Inc. | MI | 2 |
| E Fudd | IL | 1 |
| Mouse House | OH | 0 |
| Wascals | IN | 1 |
| Yosemite Place | AZ | 1 |
+----------------+------------+--------+
相关子查询(correlated subquery):涉及外部查询的子查询。
SQL最强大的功能之一就是能在数据检索查询的执行中联结(join)表。
关系表的设计就是要保证把信息分解成多个表,一类数据一个表。各表通过某些常用的值(即关系设计中的关系(relational))互相关联。
外键(foreign key):外键为某个表中的一列,它包含另一个表的主键值,定义了两个表之间的关系。
关系数据可以有效地存储和方便地处理。因此,关系数据库的可伸缩性远比非关系数据库要好。
可伸缩性(scale):能够适应不断增加的工作量而不失败。设计良好的数据库或应用程序称之为可伸缩性好(scale well)。
如果数据存储在多个表中,怎样用单条SELECT语句检索出数据?
答案是使用联结。简单地说,联结是一种机制,用来在一条SELECT语句中关联表,因此称之为联结。使用特殊的语法,可以联结多个表返回一组输出,联结在运行时关联表中正确的行。
维护引用完整性,它是通过在表的定义中指定主键和外键来实现的。这个后面会细讲!
# 创建联结
select vend_name, prod_name, prod_price
from vendors, products
where vendors.vend_id = products.vend_id
order by vend_name, prod_name;
# 输出
+-------------+----------------+------------+
| vend_name | prod_name | prod_price |
+-------------+----------------+------------+
| ACME | Bird seed | 10.00 |
| ACME | Carrots | 2.50 |
| ACME | Detonator | 13.00 |
| ACME | Safe | 50.00 |
| ACME | Sling | 4.49 |
| ACME | TNT (1 stick) | 2.50 |
| ACME | TNT (5 sticks) | 10.00 |
| Anvils R Us | .5 ton anvil | 5.99 |
| Anvils R Us | 1 ton anvil | 9.99 |
| Anvils R Us | 2 ton anvil | 14.99 |
| Jet Set | JetPack 1000 | 35.00 |
| Jet Set | JetPack 2000 | 55.00 |
| LT Supplies | Fuses | 3.42 |
| LT Supplies | Oil can | 8.99 |
+-------------+----------------+------------+
注意:在引用的列可能出现二义性时,必须使用完全限定列名(用一个点分隔的表名和列名)。如果引用一个没有用表名限制的具有二义性的列名,MySQL将返回错误。
在一条SELECT语句中联结几个表时,相应的关系是在运行中构造的。在数据库表的定义中不存在能指示MySQL如何对表进行联结的东西。你必须自己做这件事情。
没有WHERE子句,第一个表中的每个行将与第二个表中的每个行配对,而不管它们逻辑上是否可以配在一起。
笛卡儿积(cartesian product):由没有联结条件的表关系返回的结果为笛卡儿积。检索出的行的数目将是第一个表中的行数乘以第二个表中的行数。
注意:应该保证所有联结都有WHERE子句,否则MySQL将返回比想要的数据多得多的数据。同理,应该保证WHERE子句的正确性。不正确的过滤条件将导致MySQL返回不正确的数据。
目前为止所用的联结称为等值联结(equijoin),它基于两个表之间的相等测试。这种联结也称为内部联结。
# 同WHERE子句联结
select vend_name, prod_name, prod_price
from vendors inner join products on vendors.vend_id = products.vend_id
order by vend_name, prod_name;
# 输出
| vend_name | prod_name | prod_price |
+-------------+----------------+------------+
| ACME | Bird seed | 10.00 |
| ACME | Carrots | 2.50 |
| ACME | Detonator | 13.00 |
| ACME | Safe | 50.00 |
| ACME | Sling | 4.49 |
| ACME | TNT (1 stick) | 2.50 |
| ACME | TNT (5 sticks) | 10.00 |
| Anvils R Us | .5 ton anvil | 5.99 |
| Anvils R Us | 1 ton anvil | 9.99 |
| Anvils R Us | 2 ton anvil | 14.99 |
| Jet Set | JetPack 1000 | 35.00 |
| Jet Set | JetPack 2000 | 55.00 |
| LT Supplies | Fuses | 3.42 |
| LT Supplies | Oil can | 8.99 |
+-------------+----------------+------------+
ANSI SQL规范首选INNER JOIN语法。此外,尽管使用WHERE子句定义联结的确比较简单,但是使用明确的联结语法能够确保不会忘记联结条件,有时候这样做也能影响
性能。
# 显示编号为20005的订单中的物品
select prod_name, vend_name, prod_price, quantity
from orderitems, products, vendors
where products.vend_id = vendors.vend_id and orderitems.prod_id = products.prod_id and order_num = 20005;
# 输出
+----------------+-------------+------------+----------+
| prod_name | vend_name | prod_price | quantity |
+----------------+-------------+------------+----------+
| .5 ton anvil | Anvils R Us | 5.99 | 10 |
| 1 ton anvil | Anvils R Us | 9.99 | 3 |
| TNT (5 sticks) | ACME | 10.00 | 5 |
| Bird seed | ACME | 10.00 | 1 |
+----------------+-------------+------------+----------+
值得一提的是,MySQL在运行时关联指定的每个表以处理联结。这种处理可能是非常耗费资源的,因此应该仔细,不要联结不必要的表。联结的表越多,性能下降越厉害。
# 重写子查询例子:返回订购产品TNT2的客户列表
select cust_name, cust_contact
from customers
where cust_id in (select cust_id
from orders
where order_num in (select order_num
from orderitems
where prod_id = 'TNT2'));
# 等价于
select cust_name, cust_contact
from customers, orders, orderitems
where customers.cust_id = orders.cust_id and orderitems.order_num = orders.order_num and orderitems.prod_id = 'TNT2';
# 输出
+----------------+--------------+
| cust_name | cust_contact |
+----------------+--------------+
| Coyote Inc. | Y Lee |
| Yosemite Place | Y Sam |
+----------------+--------------+
别名除了用于列名和计算字段外,SQL还允许给表名起别名。这样做有两个主要理由:
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 oi.prod_id = 'TNT2';
# 输出
+----------------+--------------+
| cust_name | cust_contact |
+----------------+--------------+
| Coyote Inc. | Y Lee |
| Yosemite Place | Y Sam |
+----------------+--------------+
# 求首先找到生产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, p1.prod_name
from products as p1, products as p2
where p1.vend_id = p2.vend_id and p2.prod_id = 'DTNTR';
# 输出
+---------+----------------+
| DTNTR | Detonator |
| FB | Bird seed |
| FC | Carrots |
| SAFE | Safe |
| SLING | Sling |
| TNT1 | TNT (1 stick) |
| TNT2 | TNT (5 sticks) |
+---------+----------------+
自联结通常作为外部语句用来替代从相同表中检索数据时使用的子查询语句。虽然最终的结果是相同的,但有时候处理联结远比处理子查询快得多。
自然联结是这样一种联结,其中你只能选择那些唯一的列。这一般是通过对表使用通配符(SELECT *),对所有其他表的列使用明确的子集来完成的。
事实上,迄今为止我们建立的每个内部联结都是自然联结,很可能我们永远都不会用到不是自然联结的内部联结。
联结包含了那些在相关表中没有关联行的行。这种类型的联结称为外部联结。
# 检索所有客户及其订单
select customers.cust_id, orders.order_num
from customers inner join orders on customers.cust_id = orders.cust_id;
# 输出
+---------+-----------+
| cust_id | order_num |
+---------+-----------+
| 10001 | 20005 |
| 10001 | 20009 |
| 10003 | 20006 |
| 10004 | 20007 |
| 10005 | 20008 |
+---------+-----------+
# 为了检索所有客户,包括那些没有订单的客户,使用外部联结
select customers.cust_id, orders.order_num
from customers left outer join orders on customers.cust_id = orders.cust_id;
# 输出
+---------+-----------+
| cust_id | order_num |
+---------+-----------+
| 10001 | 20005 |
| 10001 | 20009 |
| 10002 | NULL |
| 10003 | 20006 |
| 10004 | 20007 |
| 10005 | 20008 |
+---------+-----------+
与内部联结关联两个表中的行不同的是,外部联结还包括没有关联行的行。在使用OUTER JOIN语法时,必须使用RIGHT或LEFT关键字指定包括其所有行的表(RIGHT指出的是OUTER JOIN右边的表,而LEFT指出的是OUTER JOIN左边的表)。
注意:MySQL不支持简化字符=和=的使用,这两种操作符在其他DBMS中是很流行的。
存在两种基本的外部联结形式:左外部联结和右外部联结。它们之间的唯一差别是所关联的表的顺序不同。换句话说,左外部联结可通过颠倒FROM或WHERE子句中表的顺序转换为右外部联结。因此,两种类型的外部联结可互换使用,而究竟使用哪一种纯粹是根据方便而定。
# 要检索所有客户及每个客户所下的订单数
select customers.cust_name,
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;
# 输出
+----------------+---------+---------+
| cust_name | cust_id | num_ord |
+----------------+---------+---------+
| Coyote Inc. | 10001 | 2 |
| Wascals | 10003 | 1 |
| Yosemite Place | 10004 | 1 |
| E Fudd | 10005 | 1 |
+----------------+---------+---------+
# 包含那些没有任何下订单的客户
select customers.cust_name,
customers.cust_id,
count(orders.order_num) as num_ord
from customers left outer join orders on customers.cust_id = orders.cust_id
group by customers.cust_id;
# 输出
+----------------+---------+---------+
| cust_name | cust_id | num_ord |
+----------------+---------+---------+
| Coyote Inc. | 10001 | 2 |
| Mouse House | 10002 | 0 |
| Wascals | 10003 | 1 |
| Yosemite Place | 10004 | 1 |
| E Fudd | 10005 | 1 |
+----------------+---------+---------+
关于联结及其使用的某些要点:
多数SQL查询都只包含从一个或多个表中返回数据的单条SELECT语句。MySQL也允许执行多个查询(多条SELECT语句),并将结果作为单个查询结果集返回。这些组合查询通常称为并(union)或复合查询(compound query)。
需要使用组合查询的情况:
任何具有多个WHERE子句的SELECT语句都可以作为一个组合查询给出。
可用UNION操作符来组合数条SQL查询。利用UNION,可给出多条SELECT语句,将它们的结果组合成单个结果集。
# 检索价格不高于5的所有物品
select vend_id, prod_id, prod_price
from products
where prod_price <= 5;
# 输出
+---------+---------+------------+
| vend_id | prod_id | prod_price |
+---------+---------+------------+
| 1003 | FC | 2.50 |
| 1002 | FU1 | 3.42 |
| 1003 | SLING | 4.49 |
| 1003 | TNT1 | 2.50 |
+---------+---------+------------+
# 找出供应商1001和1002生产的所有物品
select vend_id, prod_id, prod_price
from products
where vend_id in (1001, 1002);
# 输出
+---------+---------+------------+
| vend_id | prod_id | prod_price |
+---------+---------+------------+
| 1001 | ANV01 | 5.99 |
| 1001 | ANV02 | 9.99 |
| 1001 | ANV03 | 14.99 |
| 1002 | FU1 | 3.42 |
| 1002 | OL1 | 8.99 |
+---------+---------+------------+
# 组合这两条语句
select vend_id, prod_id, prod_price
from products
where prod_price <= 5
union
select vend_id, prod_id, prod_price
from products
where vend_id in (1001, 1002);
# 输出
+---------+---------+------------+
| vend_id | prod_id | prod_price |
+---------+---------+------------+
| 1003 | FC | 2.50 |
| 1002 | FU1 | 3.42 |
| 1003 | SLING | 4.49 |
| 1003 | TNT1 | 2.50 |
| 1001 | ANV01 | 5.99 |
| 1001 | ANV02 | 9.99 |
| 1001 | ANV03 | 14.99 |
| 1002 | OL1 | 8.99 |
+---------+---------+------------+
使用组合查询则需要注意:
UNION从查询结果集中自动去除了重复的行(换句话说,它的行为与单条SELECT语句中使用多个WHERE子句条件一样)。这是UNION的默认行为,但是如果需要,可以改变它。事实上,如果想返回所有匹配行,可使用UNION ALL而不是UNION。
select vend_id, prod_id, prod_price
from products
where prod_price <= 5
union all
select vend_id, prod_id, prod_price
from products
where vend_id in (1001, 1002);
# 输出
+---------+---------+------------+
| vend_id | prod_id | prod_price |
+---------+---------+------------+
| 1003 | FC | 2.50 |
| 1002 | FU1 | 3.42 |
| 1003 | SLING | 4.49 |
| 1003 | TNT1 | 2.50 |
| 1001 | ANV01 | 5.99 |
| 1001 | ANV02 | 9.99 |
| 1001 | ANV03 | 14.99 |
| 1002 | FU1 | 3.42 |
| 1002 | OL1 | 8.99 |
+---------+---------+------------+
UNION几乎总是完成与多个WHERE条件相同的工作。UNION ALL为UNION的一种形式,它完成WHERE子句完成不了的工作。如果确实需要每个条件的匹配行全部出现(包括重复行),则必须使用UNION ALL而不是WHERE。
SELECT语句的输出用ORDER BY子句排序。在用UNION组合查询时,只能使用一条ORDER BY子句,它必须出现在最后一条SELECT语句之后。
select vend_id, prod_id, prod_price
from products
where prod_price <= 5
union all
select vend_id, prod_id, prod_price
from products
where vend_id in (1001, 1002)
order by vend_id, prod_price;
# 输出
+---------+---------+------------+
| vend_id | prod_id | prod_price |
+---------+---------+------------+
| 1001 | ANV01 | 5.99 |
| 1001 | ANV02 | 9.99 |
| 1001 | ANV03 | 14.99 |
| 1002 | FU1 | 3.42 |
| 1002 | FU1 | 3.42 |
| 1002 | OL1 | 8.99 |
| 1003 | TNT1 | 2.50 |
| 1003 | FC | 2.50 |
| 1003 | SLING | 4.49 |
+---------+---------+------------+
MySQL支持几种基本的数据库引擎。但是并非所有的引擎都支持全文本搜索。两个最常使用的引擎为MyISAM和InnoDB,前者支持全文本搜索,而后者不支持。
我们知道利用通配操作符和正则表达式等搜索机制非常有用,但是它们存在几个重要的限制:
所有这些限制以及更多的限制都可以用全文本搜索来解决。在使用全文本搜索时,MySQL不需要分别查看每个行,不需要分别分析和处理每个词。MySQL创建指定列中各词的一个索引,搜索可以针对这些词进行。
为了进行全文本搜索,必须索引被搜索的列,而且要随着数据的改变不断地重新索引。
一般在创建表时启用全文本搜索。CREATE TABLE语句接受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;
在定义之后,MySQL自动维护该索引。在增加、更新或删除行时,索引随之自动更新。
不要在导入数据时使用FULLTEXT:更新索引要花时间,虽然不是很多,但毕竟要花时间。如果正在导入数据到一个新表,此时不应该启用FULLTEXT索引。应该首先导入所有数据,然后再修改表,定义FULLTEXT。这样有助于更快地导入数据(而且使索引数据的总时间小于在导入每行时分别进行索引所需的总时间)。
在索引之后,使用两个函数Match()和Against()执行全文本搜索,其中Match()指定被搜索的列,Against()指定要使用的搜索表达式。
# 检索单个列note_text
select note_text
from productnotes
where match(note_text) against('rabbit');
# 输出
+-----------------------------------------------------------------------------------------------------------------------+
| note_text |
+-----------------------------------------------------------------------------------------------------------------------+
| Customer complaint: rabbit has been able to detect trap, food apparently less effective now. |
| Quantity varies, sold by the sack load.
All guaranteed to be bright and orange, and suitable for use as rabbit bait. |
+-----------------------------------------------------------------------------------------------------------------------+
此SELECT语句检索单个列note_text。由于WHERE子句,一个全文本搜索被执行。Match(note_text)指示MySQL针对指定的列进行搜索,Against('rabbit')指定词rabbit作为搜索文本。由于有两行包含词rabbit,这两个行被返回。
传递给 Match() 的值必须与FULLTEXT()定义中的相同。如果指定多个列,则必须列出它们(而且次序正确)。
除非使用BINARY方式,否则全文本搜索不区分大小写。
查询扩展用来设法放宽所返回的全文本搜索结果的范围。
在使用查询扩展时,MySQL对数据和索引进行两遍扫描来完成搜索:
利用查询扩展,能找出可能相关的结果,即使它们并不精确包含所查找的词。
# 简单的全文本搜索,没有查询扩展
select note_text
from productnotes
where match(note_text) against('anvils');
# 输出
+----------------------------------------------------------------------------------------------------------------------------------------------------------+
| note_text |
+----------------------------------------------------------------------------------------------------------------------------------------------------------+
| Multiple customer returns, anvils failing to drop fast enough or falling backwards on purchaser. Recommend that customer considers using heavier anvils. |
+----------------------------------------------------------------------------------------------------------------------------------------------------------+
# 使用查询扩展
select note_text
from productnotes
where match(note_text) against('anvils' with query expansion);
# 输出
+----------------------------------------------------------------------------------------------------------------------------------------------------------+
| note_text |
+----------------------------------------------------------------------------------------------------------------------------------------------------------+
| Multiple customer returns, anvils failing to drop fast enough or falling backwards on purchaser. Recommend that customer considers using heavier anvils. |
| Customer complaint:
Sticks not individually wrapped, too easy to mistakenly detonate all at once.
Recommend individual wrapping. |
| Customer complaint:
Not heavy enough to generate flying stars around head of victim. If being purchased for dropping, recommend ANV02 or ANV03 instead. |
| Please note that no returns will be accepted if safe opened using explosives. |
| Customer complaint: rabbit has been able to detect trap, food apparently less effective now. |
| Customer complaint:
Circular hole in safe floor can apparently be easily cut with handsaw. |
| Matches not included, recommend purchase of matches or detonator (item DTNTR). |
+----------------------------------------------------------------------------------------------------------------------------------------------------------+
第一行包含词anvils,因此等级最高。第二行与anvils无关,但因为它包含第一行中的两个词(customer和recommend),所以也被检索出来。第3行也包含这两个相同的词,但它们在文本中的位置更靠后且分开得更远,因此也包含这一行,但等级为第三。第三行确实也没有涉及anvils(按它们的产品名)。
MySQL支持全文本搜索的另外一种形式,称为布尔方式(boolean mode)。
以布尔方式,可以提供关于如下内容的细节:
注意:布尔方式不同于迄今为止使用的全文本搜索语法的地方在于,即使没有定义
FULLTEXT索引,也可以使用它。但这是一种非常缓慢的操作(其性能将随着数据量的增加而降低)。
select note_text
from productnotes
where match(note_text) against('heavy' in boolean mode);
# 输出+----------------------------------------------------------------------------------------------------------------------------------------------------------+
| note_text |
+----------------------------------------------------------------------------------------------------------------------------------------------------------+
| Item is extremely heavy. Designed for dropping, not recommended for use with slings, ropes, pulleys, or tightropes. |
| Customer complaint: Not heavy enough to generate flying stars around head of victim. If being purchased for dropping, recommend ANV02 or ANV03 instead. |
+----------------------------------------------------------------------------------------------------------------------------------------------------------+
# 了匹配包含heavy但不包含任意以rope开始的词的行
select note_text
from productnotes
where match(note_text) against('heavy -rope*' in boolean mode);
# 输出
+----------------------------------------------------------------------------------------------------------------------------------------------------------+
| note_text |
+----------------------------------------------------------------------------------------------------------------------------------------------------------+
| Customer complaint:Not heavy enough to generate flying stars around head of victim. If being purchased for dropping, recommend ANV02 or ANV03 instead. |
+----------------------------------------------------------------------------------------------------------------------------------------------------------+
这一次仍然匹配词heavy,但-rope明确地指示MySQL排除包含rope(任何以rope开始的词,包括ropes)的行。
两个全文本搜索布尔操作符-和*,-排除一个词,而*是截断操作符(可想象为用于词尾的一个通配符)。
全文本布尔操作符:
+-><()~*""# 搜索匹配包含词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);
关于全文本搜索的某些重要的说明:
顾名思义,INSERT是用来插入(或添加)行到数据库表的。插入可以用几种方式使用:
插入及系统安全:可针对每个表或每个用户,利用MySQL的安全机制禁止使用INSERT语句。
把数据插入表中的最简单的方法是使用基本的INSERT语法,它要求指定表名和被插入到新行中的值。
insert into customers
value(
null,
'parzulpan',
'Haidian',
'Beijing',
'BJ',
'01000',
'CN',
null,
null
);
虽然这种语法很简单,但并不安全,应该尽量避免使用。上面的SQL语句高度依赖于表中列的定义次序,并且还依赖于其次序容易获得的信息。
# 更安全(不过更烦琐)的方法
insert into customers(
cust_name,
cust_address,
cust_city,
cust_state,
cust_zip,
cust_country,
cust_contact,
cust_email
)
value(
'parzulpanA',
'Haidian',
'Beijing',
'BJ',
'01000',
'CN',
null,
null
);
一般不要使用没有明确给出列的列表的INSERT语句。使用列的列表能使SQL代码继续发挥作用,即使表结构发生了变化。
注意:不管使用哪种INSERT语法,都必须给出VALUES的正确数目。如果不提供列名,则必须给每个表列提供一个值。如果提供列名,则必须对每个列出的列给出一个值。如果不这样,将产生一条错误消息,相应的行插入不成功。
如果表的定义允许,则可以在INSERT操作中省略某些列。省略的列必须满足以下某个条件:
提高整体性能:数据库经常被多个客户访问,对处理什么请求以及用什么次序处理进行管理是MySQL的任务。INSERT操作可能很耗时(特别是有很多索引需要更新时),而且它可能降低等待处理的SELECT语句的性能。如果数据检索是最重要的(通常是这样),则你可以通过在INSERT和INTO之间添加关键字LOW_PRIORITY,指示MySQL
降低INSERT语句的优先级。
insert low_priority into customers(
cust_name,
cust_address,
cust_city,
cust_state,
cust_zip,
cust_country,
cust_contact,
cust_email
)
value(
'parzulpanB',
'Haidian',
'Beijing',
'BJ',
'01000',
'CN',
null,
null
);
可以使用多条INSERT语句,甚至一次提交它们,每条语句用一个分号结束。
insert into customers(
cust_name,
cust_address,
cust_city,
cust_state,
cust_zip,
cust_country,
cust_contact,
cust_email
)
value(
'parzulpanC',
'Haidian',
'Beijing',
'BJ',
'01000',
'CN',
null,
null
);
insert into customers(
cust_name,
cust_address,
cust_city,
cust_state,
cust_zip,
cust_country,
cust_contact,
cust_email
)
value(
'parzulpanD',
'Haidian',
'Beijing',
'BJ',
'01000',
'CN',
null,
null
);
实际上,MySQL用单条INSERT语句处理多个插入比使用多条INSERT语句快。
insert into customers(
cust_name,
cust_address,
cust_city,
cust_state,
cust_zip,
cust_country,
cust_contact,
cust_email
)
value(
'parzulpanE',
'Haidian',
'Beijing',
'BJ',
'01000',
'CN',
null,
null
),
(
'parzulpanF',
'Haidian',
'Beijing',
'BJ',
'01000',
'CN',
null,
null
);
可以将一条SELECT语句的结果插入表中。这就是所谓的INSERT SELECT,顾名思义,它是由一条INSERT语句和一条SELECT语句组成的。
insert into customers(
cust_id,
cust_name,
cust_address,
cust_city,
cust_state,
cust_zip,
cust_country,
cust_contact,
cust_email
)
select cust_id,
cust_name,
cust_address,
cust_city,
cust_state,
cust_zip,
cust_country,
cust_contact,
cust_email
from custonew;
# 使用INSERT SELECT从custnew中将所有数据导入customers。
# SELECT语句从custnew检索出要插入的值,而不是列出它们。
为了更新(修改)表中的数据,可使用UPDATE语句。可采用两种方式使用UPDATE:
注意:在使用UPDATE时一定要注意细心。因为稍不注意,就会更新表中所有行。
基本的UPDATE语句由3部分组成,分别是:
# 更新id为10006的电子邮件地址
update customers
set cust_email = 'xx@gmail.com'
where cust_id = 10006;
# 更新多个列
update customers
set cust_email = 'xx@gmail.com',
cust_name = 'PARZULPAN'
where cust_id = 10006;
UPDATE语句中可以使用子查询,使得能用SELECT语句检索出的数据更新列数据。
注意:如果用UPDATE语句更新多行,并且在更新这些行中的一行或多行时出一个现错误,则整个UPDATE操作被取消(错误发生前更新的所有行被恢复到它们原来的值)。
为了即使是发生错误,也继续进行更新,可使用IGNORE关键字。
update ignore customers
set cust_email = 'yy@gmail.com',
cust_name = 'PARZULPANE',
cust_state = 'ASAF'
where cust_id = 10006;
为了从一个表中删除(去掉)数据,使用DELETE语句。可以两种方式使用DELETE:
注意:在使用DELETE时一定要注意细心。因为稍不注意,就会错误地删除表中所有行。
# 删除id为10006的行
delete from customers
where cust_id = 10006;
注意:如果想从表中删除所有行,不要使用DELETE。可使用TRUNCATE TABLE语句,它完成相同的工作,但速度更快(TRUNCATE实际是删除原来的表并重新创建一个表,而不是逐行删除表中的数据)。
使用UPDATE或DELETE时所遵循的习惯:
MySQL不仅用于表数据操纵,而且还可以用来执行数据库和表的所有操作,包括表本身的创建和处理。
两种创建表的方法:
为利用CREATE TABLE创建表,必须给出下列信息
#
create table if not exists customers_p
(
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;
如果你仅想在一个表不存在时创建它,应该在表名前给出IF NOT EXISTS。这样做不检查已有表的模式是否与你打算创建的表模式相匹配。它只是查看表名是否存在,并且仅在表名不存在时创建它。
NULL值就是没有值或缺值。允许NULL值的列也允许在插入行时不给出该列的值。不允许NULL值的列不接受该列没有值的行,换句话说,在插入或更新行时,该列必须有值。
注意:不要把NULL值与空串相混淆。NULL值是没有值,它不是空串。如果指定''(两个单引号,其间没有字符),这在NOT NULL列中是允许的。空串是一个有效的值,它不是无值。NULL值用关键字NULL而不是空串指定。
主键值必须唯一,即表中的每个行必须具有唯一的主键值。如果主键使用单个列,则它的值必须唯一。如果使用多个列,则这些列的组合值必须唯一。
主键可以在创建表时定义,或者在创建表之后定义。主键为其值唯一标识表中每个行的列。主键中只能使用不允许NULL值的列。允许NULL值的列不能作为唯一标识。
AUTO_INCREMENT告诉MySQL,本列每当增加一行时自动增量。每次执行一个INSERT操作时,MySQL自动对该列增量(从而才有这个关键字AUTO_INCREMENT),给该列赋予下一个可用的值。
每个表只允许一个AUTO_INCREMENT列,而且它必须被索引(如,通过使它成为主键)。
在使用AUTO_INCREMENT列时获得这个值呢?可使用last_insert_id()函数获得这个值。
select last_insert_id() from customers;
如果在插入行时没有给出值,MySQL允许指定此时使用的默认值。默认值用CREATE TABLE语句的列定义中的DEFAULT关键字指定。
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不允许使用函数作为默认值,它只支持常量。
一个建议是,使用默认值而不是NULL列,特别是对用于计算或数据分组的列更是如此。
几个需要知道的引擎:
值得一提的是,混用引擎类型有一个大缺陷。外键(用于强制实施引用完整性)不能跨引擎,即使用一个引擎的表不能引用具有使用不同引擎的表的外键。
为更新表定义,可使用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 orderitems
add constraint fk_orderitems_orders
foreign key (order_num) references orders (order_num);
复杂的表结构更改一般需要手动删除过程,它涉及以下步骤:
注意:使用ALTER TABLE要极为小心,应该在进行改动前做一个完整的备份(模式和数据的备份)。数据库表的更改不能撤销,如果增加了不需要的列,可能不能删除它们。类似地,如果删除了不应该删除的列,可能会丢失该列中的所有数据。
删除表(删除整个表而不是其内容)非常简单,使用DROP TABLE语句。
drop table customers_p_p;
使用RENAME TABLE语句可以重命名一个表。
rename table customers_p to customers_rename;
视图是虚拟的表。与包含数据的表不一样,视图只包含使用时动态检索数据的查询。
视图的常见应用:
在视图创建之后,可以用与表基本相同的方式利用它们。可以对视图执行SELECT操作,过滤和排序数据,将视图联结到其他视图或表,甚至能添加和更新数据(不过添加和更新数据存在某些限制)。
重要的是知道视图仅仅是用来查看存储在别处的数据的一种设施。视图本身不包含数据
正是因为视图不包含数据,所以每次使用视图时,都必须处理查询执行时所需的任一个检索。如果你用多个联结和过滤创建了复杂的视图或者嵌套了视图,可能会发现性能下降得很厉害。因此,在部署使用了大量视图的应用前,应该进行测试。
关于视图创建和使用的一些最常见的规则和限制:
视图的创建:
SHOW CREATE VIEW viewname; 来查看创建视图的语句。DROP VIEW viewname;。先用DROP再用CREATE,也可以直接用CREATE OR REPLACE VIEW。如果要更新的视图不存在,则第2条更新语句会创建一个视图;如果要更新的视图存在,则第2条更新语句会替换原有视图。视图的最常见的应用之一是隐藏(简化)复杂的SQL,这通常都会涉及联结。
# 创建一个名为productcustomers的视图,
# 它联结三个表,以返回已订购了任意产品的所有客户的列表。
create view productcustomers as
select cust_name, cust_contact, prod_id
from customers, orders, orderitems
where customers.cust_id = orders.cust_id
and orderitems.order_num = orders.order_num;
# 检索订购了产品TNT2的客户
select cust_name, cust_contact
from productcustomers
where prod_id = 'TNT2';
# 输出
+----------------+--------------+
| cust_name | cust_contact |
+----------------+--------------+
| Coyote Inc. | Y Lee |
| Yosemite Place | Y Sam |
+----------------+--------------+
创建不受特定数据限制的视图是一种好办法。例如,上面创建的视图返回生产所有产品的客户而不仅仅是生产TNT2的客户。扩展视图的范围不仅使得它能被重用,而且甚至更有用。这样做不需要创建和维护多个类似视图。
视图的另一常见用途是重新格式化检索出的数据。
# 创建一个名为vendorlocations的视图,
# 在单个组合计算列中返回供应商名和位置
create view vendorlocations as
select concat(rtrim(vend_name), '(', rtrim(vend_country), ')') as vend_title
from vendors
order by vend_name;
# 检索出以创建所有供应商名和位置的数据
select *
from vendorlocations;
# 输出
+------------------------+
| vend_title |
+------------------------+
| ACME(USA) |
| Anvils R Us(USA) |
| Furball Inc.(USA) |
| Jet Set(England) |
| Jouets Et Ours(France) |
| LT Supplies(USA) |
+------------------------+
# 过滤没有电子邮件地址的客户
create view customereamillist as
select cust_id, cust_name, cust_email
from customers
where cust_email is not null;
select *
from customereamillist;
# 输出
+---------+----------------+---------------------+
| cust_id | cust_name | cust_email |
+---------+----------------+---------------------+
| 10001 | Coyote Inc. | ylee@coyote.com |
| 10003 | Wascals | rabbit@wascally.com |
| 10004 | Yosemite Place | sam@yosemite.com |
+---------+----------------+---------------------+
如果从视图检索数据时使用了一条WHERE子句,则两组子句(一组在视图中,另一组是传递给视图的)将自动组合。
# 检索某个特定订单中的物品,计算每种物品的总价格
create view orderitemsexpanded as
select order_num,
prod_id,
quantity,
item_price,
quantity*item_price as expanded_price
from orderitems;
select *
from orderitemsexpanded
where order_num = 20005;
# 输出
+-----------+---------+----------+------------+----------------+
| order_num | prod_id | quantity | item_price | expanded_price |
+-----------+---------+----------+------------+----------------+
| 20005 | ANV01 | 10 | 5.99 | 59.90 |
| 20005 | ANV02 | 3 | 9.99 | 29.97 |
| 20005 | TNT2 | 5 | 10.00 | 50.00 |
| 20005 | FB | 1 | 10.00 | 10.00 |
+-----------+---------+----------+------------+----------------+
正确使用视图可极大地简化复杂的数据处理。
通常,视图是可更新的(即,可以对它们使用INSERT、UPDATE和DELETE)。更新一个视图将更新其基表(因为视图本身没有数据)。如果你对视图增加或删除行,实际上是对其基表增加或删除行。
但是,并非所有视图都是可更新的。基本上可以说,如果MySQL不能正确地确定被更新的基数据,则不允许更新(包括插入和删除)。这实际上意味着,如果视图定义中有以下操作,则不能进行视图的更新:
所以,一般来说,应该将视图用于检索(SELECT语句)而不用于更新(INSERT、UPDATE和DELETE)。
存储过程简单来说,就是为以后的使用而保存的一条或多条MySQL语句的集合。可将其视为批文件,虽然它们的作用不仅限于批处理。
一些主要的理由:
总的来说,使用存储过程有三个好处,即即简单、安全、高性能。
存储过程的一些缺点:
MySQL称存储过程的执行为调用,因此MySQL执行存储过程的语句为CALL。CALL接受存储过程的名字以及需要传递给它的任意参数。
# 执行名为productpricing的存储过程,
# 它计算并返回产品的最低、最高和平均价格。
call productpricing(@pricelow,
@pricehigh,
@priceavg);
# 一个返回产品平均价格的存储过程。
create procedure productpricing()
begin
select avg(prod_price) as priceavg
from products;
end;
值得注意的是,如果使用的是mysql命令行实用程序,默认的MySQL语句分隔符为;。mysql命令行实用程序也使用;作为语句分隔符。如果命令行实用程序要解释存储过程自身内的;字符,则它们最终不会成为存储过程的成分,这会使存储过程中的SQL出现句法错误。
解决办法是临时更改命令行实用程序的语句分隔符。
DELIMITER //
create procedure productpricing()
begin
select avg(prod_price) as priceavg
from products;
end //
DELIMITER;
# 使用存储过程
call productpricing();
# 输出
+-----------+
| priceavg |
+-----------+
| 16.133571 |
+-----------+
drop procedure if exists productpricing;
一般,存储过程并不显示结果,而是把结果返回给你指定的变量。
变量(variable):内存中一个特定的位置,用来临时存储数据。
DELIMITER //
create procedure productpricing(
out p1 decimal(8,2),
out p2 decimal(8,2),
out p3 decimal(8,2)
)
begin
select min(prod_price)
into p1
from products;
select max(prod_price)
into p2
from products;
select avg(prod_price)
into p3
from products;
end //
DELIMITER;
关键字OUT指出相应的参数用来从存储过程传出一个值(返回给调用者)。
MySQL支持IN(传递给存储过程)、OUT(从存储过程传出,如这里所用)和INOUT(对存储过程传入和传出)类型的参数。
# 调用并显示检索
call productpricing(@pricelow, @pricehigh, @priceavg);
select @pricelow, @pricehigh, @priceavg; //
# 输出
+-----------+------------+-----------+
| @pricelow | @pricehigh | @priceavg |
+-----------+------------+-----------+
| 2.50 | 55.00 | 16.13 |
+-----------+------------+-----------+
# ordertotal接受订单号并返回该订单的合计
delimiter //
create procedure ordertotal(
in onumber int,
out ototal decimal(8,3)
)
begin
select sum(item_price*quantity)
from orderitems
where order_num = onumber
into ototal;
end //
# 调用
call ordertotal(20005, @total);
select @total; //
# 输出
+---------+
| @total |
+---------+
| 149.870 |
+---------+
# 调用
call ordertotal(20009, @total);
select @total; //
# 输出
+--------+
| @total |
+--------+
| 38.470 |
+--------+
考虑这个场景。你需要获得与以前一样的订单合计,但需要对合计增加营业税,不过只针对某些顾客(或许是你所在市中那些顾客)。那么,你需要做下面几件事情:
delimiter //
create procedure ordertotalA(
in onumber int,
in taxable boolean,
out ototal decimal(8,2)
) comment 'obatain order total, optionally 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 //
# 调用
call ordertotalA(20005, 0, @ototal);
select @ototal; //
# 输出
+---------+
| @ototal |
+---------+
| 149.87 |
+---------+
# 调用
call ordertotalA(20005, 1, @total);
select @total; //
# 输出
+--------+
| @total |
+--------+
| 158.86 |
+--------+
为显示用来创建一个存储过程的CREATE语句,使用SHOW CREATE PROCEDURE语句:
show create procedure ordertotalA; //
SHOW PROCEDURE STATUS列出所有存储过程。为限制其输出,可使用LIKE指定一个过滤模式。
show procedure status like 'ordertotalA'; //
有时,需要在检索出来的行中前进或后退一行或多行。这就是使用游标的原因。
游标(cursor)是一个存储在MySQL服务器上的数据库查询,它不是一条SELECT语句,而是被该语句检索出来的结果集。在存储了游标之后,应用程序可以根据需要滚动或浏览其中的数据。
游标主要用于交互式应用,其中用户需要滚动屏幕上的数据,并对数据进行浏览或做出更改。
值得注意的是,不像多数DBMS,MySQL游标只能用于存储过程(和函数)。
使用游标涉及几个明确的步骤:
游标用DECLARE语句创建。DECLARE命名游标,并定义相应的SELECT语句,根据需要带WHERE和其他子句。
# 定义名为ordernumbers的游标,使用可以检索所有订单的SELECT语句
delimiter //
create procedure processorders()
begin
declare ordernumbers cursor
for
select order_num from orders;
# 打开
open ordernumbers;
# 关闭
close ordernumbers;
end //
存储过程处理完成后,游标就消失(因为它局限于存储过程)。
# 打开
open ordernumbers;
# 关闭
close ordernumbers;
在一个游标被打开后,可以使用FETCH语句分别访问它的每一行。FETCH指定检索什么数据(所需的列),检索出来的数据存储在什么地方。它还向前移动游标中的内部行指针,使下一条FETCH语句检索下一行(不重复读取同一行)。
# 从游标中检索单个行(第一行)
create procedure processordersB()
begin
declare o int;
declare ordernumbers cursor
for
select order_num from orders;
open ordernumbers;
fetch ordernumbers into o;
close ordernumbers;
end;
# 调用
call processordersB();
# 循环检索数据,从第一行到最后一行
create procedure processordersC()
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 ordernumbers;
repeat
fetch ordernumbers into o;
until done end repeat;
close ordernumbers;
end;
# 调用
call processordersC();
# 创建和填充另一个表
create procedure processordersD()
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 ordernumbers;
repeat
fetch ordernumbers into o;
call ordertotal(o, t);
insert into ordertotals(order_num, total)
values(o, t);
until done end repeat;
close ordernumbers;
end;
# 调用
call processordersD();
select *
from ordertotals;
触发器 是MySQL响应以下任意语句而自动执行的一条MySQL语句(或位于BEGIN和END语句之间的一组语句):
DELETEINSERTUPDATE其他MySQL语句不支持触发器。
在创建触发器时,需要给出4条信息:
值得一提的是,在MySQL 5中,触发器名必须在每个表中唯一,但不是在每个数据库中唯一。这表示同一数据库中的两个表可具有相同名字的触发器。这在其他每个数据库触发器名必须唯一的DBMS中是不允许的,而且以后的MySQL版本很可能会使命名规则更为严格。因此,现在最好是在数据库范围内使用唯一的触发器名。
触发器用CREATE TRIGGER语句创建。
# 使用INSERT语句添加一行或多行到products中,
# 将看到对每个成功的插入,显示Product added消息。
create trigger newproduct after insert on products
for each row select 'Product added';
# 使用
insert into products(
prod_id,
vend_id,
prod_name,
prod_price,
prod_desc
) value(
'TNT3',
1003,
'TNT (5 sticks)',
10.00,
'TNT, red, pack of 10 sticks'
);
# 输出
只有表才支持触发器,视图不支持(临时表也不支持)。
触发器按每个表每个事件每次地定义,每个表每个事件每次只允许一个触发器。因此,每个表最多支持6个触发器(每条INSERT、UPDATE和DELETE的之前和之后)。
drop trigger newproduct;
INSERT触发器在INSERT语句执行之前或之后执行。需要知道以下几点:
create trigger neworder after insert on orders
for each row select NEW.order_num;
# 使用
insert into orders(order_date, cust_id)
value(Now(), 10001);
DELETE触发器在DELETE语句执行之前或之后执行。需要知道以下两点:
# 使用OLD保存将要被删除的行到一个存档表中
create table archive_orders
(
order_num int NOT NULL AUTO_INCREMENT,
order_date datetime NOT NULL ,
cust_id int NOT NULL ,
PRIMARY KEY (order_num)
) ENGINE=InnoDB;
create trigger deleteorder before delete on orders
for each row
insert into archive_orders(order_num, order_date, cust_id)
value(OLD.order_num, OLD.order_date, OLD.cust_id);
delete from orders where order_num = 20010;
select * from archive_orders;
# 输出
+-----------+---------------------+---------+
| order_num | order_date | cust_id |
+-----------+---------------------+---------+
| 20010 | 2020-09-01 15:00:16 | 10001 |
+-----------+---------------------+---------+
使用BEFORE DELETE触发器的优点(相对于AFTER DELETE触发器来说)为,如果由于某种原因,订单不能存档,DELETE本身将被放弃。
UPDATE触发器在UPDATE语句执行之前或之后执行。需要知道以下几点:
# 保证国家缩写总是大写
create trigger updatevendor before update on vendors
for each row set NEW.vend_state = Upper(NEW.vend_state);
一些使用触发器时需要记住的重点:
MySQL支持几种基本的数据库引擎。但是并非所有引擎都支持明确的事务处理管理。MyISAM和InnoDB是两种最常使用的引擎。前者不支持明确的事务处理管理,而后者支持。
事务处理(transaction processing)可以用来维护数据库的完整性,它保证成批的MySQL操作要么完全执行,要么完全不执行。
事务处理是一种机制,用来管理必须成批执行的MySQL操作,以保证数据库不包含不完
整的操作结果。利用事务处理,可以保证一组操作不会中途停止,它们或者作为整体执行,或者完全不执行(除非明确指示)。如果没有错误发生,整组语句提交给(写到)数据库表。如果发生错误,则进行回退(撤销)以恢复数据库到某个已知且安全的状态。
事务处理需要知道的几个术语:
管理事务处理的关键在于将SQL语句组分解为逻辑块,并明确规定数据何时应该回退,何时不应该回退。
# 来标识事务的开始
start transaction
MySQL的ROLLBACK命令用来回退(撤销)MySQL语句。
select * from ordertotals;
start transaction;
delete from ordertotals;
select * from ordertotals;
rollback;
select * from ordertotals;
显然,ROLLBACK只能在一个事务处理内使用(在执行一条STARTTRANSACTION命令之后)。
哪些语句可以回退?
事务处理用来管理INSERT、UPDATE和DELETE语句。你不能回退SELECT语句(这样做也没有什么意义)。你不能回退CREATE或DROP操作。事务处理块中可以使用这两条语句,但如果你执行回退,它们不会被撤销。
一般的MySQL语句都是直接针对数据库表执行和编写的。这就是所谓的隐含提交(implicit commit),即提交(写或保存)操作是自动进行的。
但是,在事务处理块中,提交不会隐含地进行。为进行明确的提交,使用COMMIT语句。
start transaction;
delete from orderitems where order_num = 20010;
delete from orders where order_num = 20010;
commit;
如果第一条DELETE起作用,但第二条失败,则DELETE不会提交(实际上,它是被自动撤销的)。
值得一提的是,当COMMIT或ROLLBACK语句执行后,事务会自动关闭(将来的更改会隐含提交)。
简单的ROLLBACK和COMMIT语句就可以写入或撤销整个事务处理。但是,只是对简单的事务处理才能这样做,更复杂的事务处理可能需要部分提交或回退。
为了支持回退部分事务处理,必须能在事务处理块中合适的位置放置占位符。这样,如果需要回退,可以回退到某个占位符。
这些占位符称为保留点。为了创建占位符,可如下使用SAVEPOINT语句。
# 每个保留点都取标识它的唯一名字,以便在回退时,MySQL知道要回退到何处
savepoint delete1;
# 回退到本例给出的保留点
rollback to delete1;
可以在MySQL代码中设置任意多的保留点,越多越好。为什么呢?因为保留点越多,你就越能按自己的意愿灵活地进行回退。
保留点在事务处理完成(执行一条ROLLBACK或COMMIT)后自动释放。自MySQL 5以来,也可以用RELEASE SAVEPOINT明确地释放保留点。
默认的MySQL行为是自动提交所有更改。换句话说,任何时候你执行一条MySQL语句,该语句实际上都是针对表执行的,而且所做的更改立即生效。
# 指示MySQL不自动提交更改
set autocommit = 0;
autocommit标志是针对每个连接而不是服务器的。
几个重要术语:
在MySQL的正常数据库活动(SELECT、INSERT等)中,不需要操心太多的东西。使用何种字符集和校对的决定在服务器、数据库和表级进行。
MySQL支持众多的字符集。
# 查看所支持的字符集完整列表
show character set;
# 输出
+----------+-----------------------------+---------------------+--------+
| Charset | Description | Default collation | Maxlen |
+----------+-----------------------------+---------------------+--------+
| big5 | Big5 Traditional Chinese | big5_chinese_ci | 2 |
| dec8 | DEC West European | dec8_swedish_ci | 1 |
| cp850 | DOS West European | cp850_general_ci | 1 |
| hp8 | HP West European | hp8_english_ci | 1 |
| koi8r | KOI8-R Relcom Russian | koi8r_general_ci | 1 |
| latin1 | cp1252 West European | latin1_swedish_ci | 1 |
| latin2 | ISO 8859-2 Central European | latin2_general_ci | 1 |
| swe7 | 7bit Swedish | swe7_swedish_ci | 1 |
| ascii | US ASCII | ascii_general_ci | 1 |
| ujis | EUC-JP Japanese | ujis_japanese_ci | 3 |
| sjis | Shift-JIS Japanese | sjis_japanese_ci | 2 |
| hebrew | ISO 8859-8 Hebrew | hebrew_general_ci | 1 |
| tis620 | TIS620 Thai | tis620_thai_ci | 1 |
| euckr | EUC-KR Korean | euckr_korean_ci | 2 |
| koi8u | KOI8-U Ukrainian | koi8u_general_ci | 1 |
| gb2312 | GB2312 Simplified Chinese | gb2312_chinese_ci | 2 |
| greek | ISO 8859-7 Greek | greek_general_ci | 1 |
| cp1250 | Windows Central European | cp1250_general_ci | 1 |
| gbk | GBK Simplified Chinese | gbk_chinese_ci | 2 |
| latin5 | ISO 8859-9 Turkish | latin5_turkish_ci | 1 |
| armscii8 | ARMSCII-8 Armenian | armscii8_general_ci | 1 |
| utf8 | UTF-8 Unicode | utf8_general_ci | 3 |
| ucs2 | UCS-2 Unicode | ucs2_general_ci | 2 |
| cp866 | DOS Russian | cp866_general_ci | 1 |
| keybcs2 | DOS Kamenicky Czech-Slovak | keybcs2_general_ci | 1 |
| macce | Mac Central European | macce_general_ci | 1 |
| macroman | Mac West European | macroman_general_ci | 1 |
| cp852 | DOS Central European | cp852_general_ci | 1 |
| latin7 | ISO 8859-13 Baltic | latin7_general_ci | 1 |
| utf8mb4 | UTF-8 Unicode | utf8mb4_general_ci | 4 |
| cp1251 | Windows Cyrillic | cp1251_general_ci | 1 |
| utf16 | UTF-16 Unicode | utf16_general_ci | 4 |
| utf16le | UTF-16LE Unicode | utf16le_general_ci | 4 |
| cp1256 | Windows Arabic | cp1256_general_ci | 1 |
| cp1257 | Windows Baltic | cp1257_general_ci | 1 |
| utf32 | UTF-32 Unicode | utf32_general_ci | 4 |
| binary | Binary pseudo charset | binary | 1 |
| geostd8 | GEOSTD8 Georgian | geostd8_general_ci | 1 |
| cp932 | SJIS for Windows Japanese | cp932_japanese_ci | 2 |
| eucjpms | UJIS for Windows Japanese | eucjpms_japanese_ci | 3 |
+----------+-----------------------------+---------------------+--------+
# 查看所支持校对的完整列表
show collation;
# 输出
为了给表指定字符集和校对,可使用带子句的CREATE TABLE。
create table mytable
(
column1 int,
column2 varchar(10)
) default character set hebrew collate hebrew_gengeral_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_gengeral_ci;
值得注意的是,如果绝对需要,串可以在字符集之间进行转换。为此,使用Cast()或Convert()函数。
MySQL服务器的安全基础是:用户应该对他们需要的数据具有适当的访问权,既不能多也不能少。换句话说,用户不能对过多的数据具有过多的访问权。
注意:应该严肃对待root登录的使用。仅在绝对需要时使用它(或许在你不能登录其他管理账号时使用)。不应该在日常的MySQL操作中使用root。
MySQL用户账号和信息存储在名为mysql的MySQL数据库中。
# 获得所有用户账号列表
use mysql;
select user from user;
# 输出
+---------------+
| user |
+---------------+
| parzulpan |
| mysql.session |
| mysql.sys |
| root |
+---------------+
为了创建一个新用户账号,使用CREATE USER语句。
create user parzulpanT identified by 'xxxxxxx';
IDENTIFIED BY指定的口令为纯文本,MySQL将在保存到user表之前对其进行加密。为了作为散列值指定口令,使用IDENTIFIED BY PASSWORD。
重新命名一个用户账号,使用RENAME USER语句。
rename user parzulpanT to parzulpanRename;
删除一个用户账号(以及相关的权限),使用DROP USER语句。
drop user parzulpanRename;
在创建用户账号后,必须接着分配访问权限。新创建的用户账号没有访问权限。它们能登录MySQL,但不能看到数据,不能执行任何数据库操作。
为看到赋予用户账号的权限,使用SHOW GRANTS FOR。
show grants for parzulpanT;
# 输出
+-------------------------------------------------------------------------+
| Grants for parzulpanT@% |
+-------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO `parzulpanT`@`%` IDENTIFIED BY PASSWORD '*21AD' |
+-------------------------------------------------------------------------+
显示用户parzulpanT有一个权限USAGE ON .。USAGE表示根本没有权限。
为设置权限,使用GRANT语句。GRANT要求你至少给出以下信息:
# 允许用户在learnDB.*(learnDB数据库的所有表)上使用SELECT。
grant select on learnDB.* to parzulpanT;
# 输出
+--------------------------------------------------+
| Grants for parzulpanT@% |
+--------------------------------------------------+
| GRANT SELECT ON `learnDB`.* TO `parzulpanT`@`%` |
+--------------------------------------------------
GRANT的反操作为REVOKE,用它来撤销特定的权限。
revoke select on learnDB.* from parzulpanT;
GRANT和REVOKE可在几个层次上控制访问权限:
可以授予或撤销的每个权限:
更改用户口令,可使用SET PASSWORD语句。
# 新口令必须传递到Password()函数进行加密。
set password for parzulpanT = password('xxxxaaaa');
# 设置自己的口令
# 在不指定用户名时,SET PASSWORD更新当前登录用户的口令。
set password = password('ghdauighaw');
像所有数据一样,MySQL的数据也必须经常备份。由于MySQL数据库是基于磁盘的文件,普通的备份系统和例程就能备份MySQL的数据。但是,由于这些文件总是处于打开和使用状态,普通的文件副本备份不一定总是有效。
几种解决方案:
mysqldump转储所有数据库内容到某个外部文件。在进行常规备份前这个实用程序应该正常运行,以便能正确地备份转储文件。mysqlhotcopy从一个数据库复制所有数据(并非所有数据库引擎都支持这个实用程序)。BACKUP TABLE或SELECT INTO OUTFILE转储所有数据到某个外部文件。这两条语句都接受将要创建的系统文件名,此系统文件必须不存在,否则会出错。数据可以用RESTORETABLE来复原。为了保证所有数据被写到磁盘(包括索引数据),可能需要在进行备份前使用FLUSH TABLES语句。
应该知道的一些语句:
ANALYZE TABLE,用来检查表键是否正确。analyze table orders;
# 输出
+----------------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+----------------+---------+----------+----------+
| learnDB.orders | analyze | status | OK |
+----------------+---------+----------+----------+
CHECK TABLE用来针对许多问题对表进行检查。在MyISAM表上还对索引进行检查。CHECK TABLE支持一系列的用于MyISAM表的方式。CHANGED检查自最后一次检查以来改动过的表。EXTENDED执行最彻底的检查,FAST只检查未正常关闭的表,MEDIUM检查所有被删除的链接并进行键检验,QUICK只进行快速扫描。check table orders, orderitems;
+--------------------+-------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+--------------------+-------+----------+----------+
| learnDB.orders | check | status | OK |
| learnDB.orderitems | check | status | OK |
+--------------------+-------+----------+----------+
REPAIR TABLE来修复相应的表。这条语句不应该经常使用,如果需要经常使用,可能会有更大的问题要解决。在排除系统启动问题时,首先应该尽量用手动启动服务器。MySQL服务器自身通过在命令行上执行mysqld启动。
几个重要的mysqld命令行选项:
--help 显示帮助——一个选项列表;--safe-mode 装载减去某些最佳配置的服务器;--verbose 显示全文本消息(为获得更详细的帮助消息与--help联合使用);--version 显示版本信息然后退出。MySQL维护管理员依赖的一系列日志文件。
主要的日志文件有以下几种:
提供进行性能优化探讨和分析的一个出发点:
SHOW VARIABLES;和SHOW STATUS;。)SHOW PROCESSLIST显示所有活动进程(以及它们的线程ID和执行时间)。你还可以用KILL命令终结某个特定的进程(使用这个命令需要作为管理员登录)。SELECT *(除非你真正需要每个列)。