4.1 集合函数
到现在为止,你只学习了如何根据特定的条件从表中取出一条或多条记录。但是,假如你想对一个表中的记录进行数据统计。例如,如果你想统计存储在表中的一次民意测验的投票结果。或者你想知道一个访问者在你的站点上平均花费了多少时间。要对表中的任何类型的数据进行统计,都需要使用集合函数。你可以统计记录数目,平均值,最小值,最大值,或者求和。当你使用一个集合函数时,它只返回一个数,该数值代表这几个统计值之一。
这些函数的最大特点就是经常和GROUP BY语句配合使用,需要注意的是集合函数不能和非分组的列混合使用。
4.1.1 行列计数
l 计算查询语句返回的记录行数
直接计算函数COUNT(*)的值,例如,计算pet表中猫的只数:
mysql>select count(*) from pet where species=’cat’;
+----------+
| count(*) |
+----------+
| 2 |
+----------+
4.1.2统计字段值的数目
例如,计算pet表中species列的数目:
mysql> select count(species) from pet;
+----------------+
| count(species) |
+----------------+
| 9 |
+----------------+
如果相同的种类出现了不止一次,该种类将会被计算多次。如果你想知道种类为某个特定值的宠物有多少个,你可以使用WHERE子句,如下例所示:
mysql> SELECT COUNT(species) FROM pet WHERE species=\'cat\' ;
注意这条语句的结果:
+----------------+
| COUNT(species) |
+----------------+
| 2 |
+----------------+
这个例子返回种类为\'cat\'的作者的数目。如果这个名字在表pet中出现了两次,则次函数的返回值是2。 而且它和上面提到过的语句的结果是一致的:
select count(*) from pet where species=’cat’
实际上,这两条语句是等价的。
假如你想知道有多少不同种类的的宠物数目。你可以通过使用关键字DISTINCT来得到该数目。如下例所示:
mysql> SELECT COUNT(DISTINCT species) FROM pet;
+-------------------------+
| COUNT(DISTINCT species) |
+-------------------------+
| 5 |
+-------------------------+
如果种类\'cat\'出现了不止一次,它将只被计算一次。关键字DISTINCT 决定了只有互不相同的值才被计算。
通常,当你使用COUNT()时,字段中的空值将被忽略。
另外,COUNT()函数通常和GROUP BY子句配合使用,例如可以这样返回每种宠物的数目:
mysql> SELECT species,count(*) FROM pet GROUP BY species;
+---------+----------+
| species | count(*) |
+---------+----------+
| bird | 2 |
| cat | 2 |
| dog | 3 |
| hamster | 1 |
| snake | 1 |
+---------+----------+
4.1.3 计算字段的平均值
需要计算这些值的平均值。使用函数AVG(),你可以返回一个字段中所有值的平均值。
假如你对你的站点进行一次较为复杂的民意调查。访问者可以在1到10之间投票,表示他们喜欢你站点的程度。你把投票结果保存在名为vote的INT型字段中。要计算你的用户投票的平均值,你需要使用函数AVG():
SELECT AVG(vote) FROM opinion
这个SELECT语句的返回值代表用户对你站点的平均喜欢程度。函数AVG()只能对数值型字段使用。这个函数在计算平均值时也忽略空值。
再给出一个实际例子,例如我们要计算pet表中每种动物年龄的平均值,那么使用AVG()函数和GROUP BY子句:
mysql> SELECT species,AVG(CURDATE()-birth) FROM pet GROUP BY species;
返回的结果为:
+---------+----------------------+
| species | AVG(CURDATE()-birth) |
+---------+----------------------+
| bird | 34160 |
| cat | 74959.5 |
| dog | 112829.66666667 |
| hamster | 19890 |
| snake | 49791 |
+---------+----------------------+
4.1.4 计算字段值的和
假设你的站点被用来出售某种商品,已经运行了两个月,是该计算赚了多少钱的时候了。假设有一个名为orders的表用来记录所有访问者的定购信息。要计算所有定购量的总和,你可以使用函数SUM():
SELECT SUM(purchase_amount) FROM orders
函数SUM()的返回值代表字段purchase_amount中所有值的总和。字段purchase_amount的数据类型也许是decimal类型,但你也可以对其它数值型字段使用函数SUM()。
用一个不太恰当的例子说明,我们计算pet表中同种宠物的年龄的总和:
mysql> SELECT species,SUM(CURDATE()-birth) FROM pet GROUP BY species;
你可以查看结果,与前一个例子对照:
+---------+----------------------+
| species | SUM(CURDATE()-birth) |
+---------+----------------------+
| bird | 68320 |
| cat | 149919 |
| dog | 338489 |
| hamster | 19890 |
| snake | 49791 |
+---------+----------------------+
4.1.5 计算字段值的极值
求字段的极值,涉及两个函数MAX()和MIN()。
例如,还是pet表,你想知道最早的动物出生日期,由于日期最早就是最小,所以可以使用MIN()函数:
mysql> SELECT MIN(birth) FROM pet;
+------------+
| MIN(birth) |
+------------+
| 1989-05-13 |
+------------+
但是,你只知道了日期,还是无法知道是哪只宠物,你可能想到这样做:
SELECT name,MIN(birth) FROM pet;
但是,这是一个错误的SQL语句,因为集合函数不能和非分组的列混合使用,这里name列是没有分组的。所以,你无法同时得到name列的值和birth的极值。
MIN()函数同样可以与GROUP BY子句配合使用,例如,找出每种宠物中最早的出生日期:
mysql> SELECT species,MIN(birth) FROM pet GROUP BY species;
下面是令人满意的结果:
+---------+------------+
| species | MIN(birth) |
+---------+------------+
| bird | 1997-12-09 |
| cat | 1993-02-04 |
| dog | 1989-05-13 |
| hamster | 1999-03-30 |
| snake | 1996-04-29 |
+---------+------------+
另一方面,如果你想知道最近的出生日期,就是日期的最大值,你可以使用MAX()函数,如下例所示:
mysql> SELECT species,MAX(birth) FROM pet GROUP BY species;
+---------+------------+
| species | MAX(birth) |
+---------+------------+
| bird | 1998-09-11 |
| cat | 1994-03-17 |
| dog | 1990-08-31 |
| hamster | 1999-03-30 |
| snake | 1996-04-29 |
+---------+------------+
4.1.6 总结
在本节中,介绍了一些典型的集合函数的用法,包括计数、均值、极值和总和,这些都是SQL语言中非常常用的函数。
这些函数之所以称之为集合函数,是因为它们应用在多条记录中,所以集合函数最常见的用法就是与GROUP BY子句配合使用,最重要的是集合函数不能同未分组的列混合使用。
4.2 操作日期和时间
日期和时间函数对建立一个站点是非常有用的。站点的主人往往对一个表中的数据何时被更新感兴趣。通过日期和时间函数,你可以在秒级跟踪一个表的改变。
日期和时间类型是DATETIME、DATE、TIMESTAMP、TIME和YEAR。这些的每一个都有合法值的一个范围,而“零”当你指定确实不合法的值时被使用。注意,MySQL允许你存储某个“不严格地”合法的日期值,例如1999-11-31,原因我们认为它是应用程序的责任来处理日期检查,而不是SQL服务器。为了使日期检查更“快”,MySQL仅检查月份在0-12的范围,天在0-31的范围。上述范围这样被定义是因为MySQL允许你在一个DATE或DATETIME列中存储日期,这里的天或月是零。这对存储你不知道准确的日期的一个生日的应用程序来说是极其有用的,在这种情况下,你简单地存储日期象1999-00-00或1999-01-00。(当然你不能期望从函数如DATE_SUB()或DATE_ADD()得到类似以这些日期的正确值)。
4.2.1 返回当前日期和时间
通过函数GETDATE(),你可以获得当前的日期和时间。例如,
l CURDATE() 返回当前日期
CURRENT_DATE
以\'YYYY-MM-DD\'或YYYYMMDD格式返回今天日期值,取决于函数是在一个字符串还是数字上下文被使用。
mysql> select CURDATE();
+------------+
| CURDATE() |
+------------+
| 2001-02-20 |
+------------+
mysql> select CURDATE() + 0;
+-------------+
| CURDATE()+0 |
+-------------+
| 20010220 |
+-------------+
l CURTIME() 返回当前时间
以\'HH:MM:SS\'或HHMMSS格式返回当前时间值,取决于函数是在一个字符串还是在数字的上下文被使用。
mysql> select CURTIME();
+-----------+
| CURTIME() |
+-----------+
| 10:42:38 |
+-----------+
mysql> select CURTIME() + 0;
+-------------+
| CURTIME()+0 |
+-------------+
| 104525 |
+-------------+
l NOW() 返回当前时期和时间
NOW()以YYYY-MM-DD HH:MM:SS的格式或者YYYYMMDDHHMMSS的格式返回日期和时间值,取决于上下文。
mysql>select now();
+---------------------+
| now() |
+---------------------+
| 2001-02-20 10:45:57 |
+---------------------+
mysql>select now()+0;
+----------------+
| now()+0 |
+----------------+
| 20010220105635 |
+----------------+
这些得到当前日期和时间的函数,对于日期和时间的计算很方便,尤其是计算一个时间到现在的时间差。例如,在pet表中,我们以天为单位计算宠物的年龄:
mysql> SELECT name,CURDATE()-birth FROM pet;
+----------+-----------------+
| name | CURDATE()-birth |
+----------+-----------------+
| Fluffy | 80016 |
| Claws | 69903 |
| Buffy | 119707 |
| Chirpy | 29309 |
| Fang | 109393 |
| Bowser | 109389 |
| Whistler | 39011 |
| Slim | 49791 |
| Puffball | 19890 |
+----------+-----------------+
4.2.2 自动记录数据的改变时间
TIMESTAMP列类型提供一种类型,TIMESTAMP值可以从1970的某时的开始一直到2037年,精度为一秒,其值作为数字显示。你可以使用它自动地用当前的日期和时间标记INSERT或UPDATE的操作。如果你有多个TIMESTAMP列,只有第一个自动更新。
自动更新第一个TIMESTAMP列在下列任何条件下发生:
l 列没有明确地在一个INSERT或LOAD DATA INFILE语句中指定。
l 列没有明确地在一个UPDATE语句中指定且一些另外的列改变值。(注意一个UPDATE设置一个列为它已经有的值,这将不引起TIMESTAMP列被更新,因为如果你设置一个列为它当前的值,MySQL为了效率而忽略更改。)
l 你明确地设定TIMESTAMP列为NULL.
除第一个以外的TIMESTAMP列也可以设置到当前的日期和时间,只要将列设为NULL,或NOW()。
例如,创建如下的表:
mysql> CREATE TABLE student
-> (
-> id int,
-> name char(16),
-> english tinyint,
-> chinese tinyint,
-> history tinyint,
-> time timestamp
-> );
向表中插入记录,可以查看效果:
mysql> INSERT student(id,name,englisht,Chinese,history) VALUES(11,”Tom”,66,93,67);
查看记录的存储情况:
mysql> SELECT * FROM student;
+------+---------+---------+---------+---------+----------------+
| id | name | english | chinese | history | time |
+------+---------+---------+---------+---------+----------------+
| 11 | Tom | 66 | 93 | 67 | 20010220123335 |
+------+---------+---------+---------+---------+----------------+
你可以看到time列纪录下了数据录入时的时间值。如果你更新改记录,在查看操作的结果:
mysql> UPDATE student SET english=76 WHERE id=11;
mysql> SELECT * FROM student;
+------+------+---------+---------+---------+----------------+
| id | name | english | chinese | history | time |
+------+------+---------+---------+---------+----------------+
| 11 | Tom | 76 | 93 | 67 | 20010220125736 |
+------+------+---------+---------+---------+----------------+
可以清楚的看到,time列的时间被自动更改为修改记录的时间。
有时候你希望不更改任何值,也能打到修改TIMESTAMP列的值,这时只要设置该列的值为NULL,MySQL就可以自动更新TIMESTAMP列的值:
mysql> UPDATE student SET time=NULL WHERE id=11;
mysql> select * from student where id=11;
+------+------+---------+---------+---------+----------------+
| id | name | english | chinese | history | time |
+------+------+---------+---------+---------+----------------+
| 11 | Tom | 76 | 93 | 67 | 20010220130517 |
+------+------+---------+---------+---------+----------------+
通过明确地设置希望的值,你可以设置任何TIMESTAMP列为不同于当前日期和时间的值,即使对第一个TIMESTAMP列也是这样。例如,如果,当你创建一个行时,你想要一个TIMESTAMP被设置到当前的日期和时间,但在以后无论何时行被更新时都不改变,你可以使用这样使用:
l 让MySQL在行被创建时设置列,这将初始化它为当前的日期和时间。
l 当你执行随后的对该行中其他列的更改时,明确设定TIMESTAMP列为它的当前值。
例如,当你在修改列时,可以把原有的值付给TIMESTAMP列:
mysql> UPDATE student SET english=66,time=time WHERE id=11;
mysql> select * from student where id=11;
+------+------+---------+---------+---------+----------------+
| id | name | english | chinese | history | time |
+------+------+---------+---------+---------+----------------+
| 11 | Tom | 66 | 93 | 67 | 20010220130517 |
+------+------+---------+---------+---------+----------------+
另一方面,你可能发现,当你想要实现上面这个效果时,很容易用一个你用NOW()初始化的DATETIME列然后不再改变它,这样也许直接些。 但是,TIMESTAMP列的以后好处是存储要求比较小,节省空间。TIMESTAMP的存储需求是4字节,而DATETIME列的存储需求是8字节。
4.2.3 返回日期和时间范围
当你分析表中的数据时,你也许希望取出某个特定时间的数据。我们用下面一个表来模仿一个web站点的记录。
mysql> CREATE TABLE weblog
-> (
-> data float,
-> entrydate datetime
-> );
然后随机的增加几个数据:
mysql> INSERT weblog VALUES(rand(),now());
rand()函数返回一个随机的浮点值,now()函数返回当前时间。多执行上面语句几次,得到一个作为测试的表。
最为测试你还可以增加一个值:
mysql> INSERT weblog VALUES(rand(),”2001-02-08”);
这条语句,插入一个entry为”2001-02-08 00:00:00”的值(假定现在为2001年2月8日),你可以查看这个表的值:
mysql> select * from weblog;
+-----------+---------------------+
| data | entrydate |
+-----------+---------------------+
| 0.973723 | 2001-02-08 00:00:00 |
| 0.437768 | 2001-02-08 13:57:06 |
| 0.327279 | 2001-02-08 13:57:09 |
| 0.0931809 | 2001-02-08 13:58:29 |
| 0.198805 | 2001-02-08 13:57:54 |
+-----------+---------------------+
你也许对特定的某一天中――比如说2001年2月18日――访问者在你站点上的活动感兴趣。要取出这种类型的数据,你也许会试图使用这样的SELECT语句:
mysql> SELECT * FROM weblog WHERE entrydate="2001-02-08"
不要这样做。这个SELECT语句不会返回正确的记录――它将只返回值为2000-02-08 00:00:00的记录,换句话说,只返回当天零点零时的记录。上面语句的结果为:
+----------+---------------------+
| data | entrydate |
+----------+---------------------+
| 0.973723 | 2001-02-08 00:00:00 |
+----------+---------------------+
要返回正确的记录,你需要适用日期和时间范围。有不止一种途径可以做到这一
点。
1、使用关系运算符和逻辑运算符来限制时间范围
例如,下面的这个SELECT 语句将能返回正确的记录:
mysql> SELECT * FROM weblog
-> WHERE entrydate>="2001-02-08" AND entrydate<"2001-02-09" ;
这个语句可以完成任务,因为它选取的是表中的日期和时间大于等于2001-02-08 00:00:00并小于2001-02-09 00:00:00的记录。换句话说,它将正确地返回2000年2月8日这一天输入的每一条记录。 其结果为:
+-----------+---------------------+
| data | entrydate |
+-----------+---------------------+
| 0.973723 | 2001-02-08 00:00:00 |
| 0.437768 | 2001-02-08 13:57:06 |
| 0.327279 | 2001-02-08 13:57:09 |
| 0.0931809 | 2001-02-08 13:58:29 |
| 0.198805 | 2001-02-08 13:57:54 |
+-----------+---------------------+
2、另一种方法是,你可以使用LIKE来返回正确的记录。通过在日期表达式中包含通配符“%”,你可以匹配一个特定日期的所有时间。
这里有一个例子:
mysql> SELECT * FROM weblog WHERE entrydate LIKE \'2001-02-08%\' ;
这个语句可以匹配正确的记录。因为通配符“%”代表了任何时间。
+-----------+---------------------+
| data | entrydate |
+-----------+---------------------+
| 0.973723 | 2001-02-08 00:00:00 |
| 0.437768 | 2001-02-08 13:57:06 |
| 0.327279 | 2001-02-08 13:57:09 |
| 0.0931809 | 2001-02-08 13:58:29 |
| 0.198805 | 2001-02-08 13:57:54 |
+-----------+---------------------+
3、上面两种方法的异同
由于使用关系运算符进行的是比较过程,时转换成内部的存储格式后进行的,因此,因此时间的书写可以不是那么严格要求。
例如,下面几种写法是等价的:
mysql> SELECT * FROM weblog WHERE entrydate>="2001-02-08";
mysql> SELECT * FROM weblog WHERE entrydate>="2001-2-8";
mysql> SELECT * FROM weblog WHERE entrydate>="2001*02*08";
mysql> SELECT * FROM weblog WHERE entrydate>="20010208";
SELECT * FROM weblog WHERE entrydate>="2001/2/8";
而使用LIKE运算符和模式匹配,是通过比较串值进行的,因此必须使用标准的时间书写格式,YYYY-MM-DD HH-MM-SS。
4.2.5 比较日期和时间
已知两个日期,比较它们的前后,可以直接求出它们的差和零值比较,也可以利用已知的时间函数:
TO_DAYS(date)
给出一个日期date,返回一个天数(从0年的天数),date可以是一个数字,也可以是一个串值,当然更可以是包含日期的时间类型。
mysql> select TO_DAYS(960501);
+-----------------+
| TO_DAYS(960501) |
+-----------------+
| 729145 |
+-----------------+
mysql> select TO_DAYS(\'1997-07-01\');
+-----------------------+
| TO_DAYS(\'1997-07-01\') |
+-----------------------+
| 729571 |
+-----------------------+
例如:返回2个时间相差的天数(21世纪已经过去了多少天)
mysql> select to_days(now())-to_days(\'20010101\');
+---------------------------------------------------+
| to_days(now()-00000012000000)-to_days(\'20010101\') |
+---------------------------------------------------+
| 38 |
+---------------------------------------------------+
4.3 字符串模式匹配
MySQL提供标准的SQL模式匹配,以及一种基于象Unix实用程序如vi、grep和sed的扩展正则表达式模式匹配的格式。
4.3.1 标准的SQL模式匹配
SQL的模式匹配允许你使用“_”匹配任何单个字符,而“%”匹配任意数目字符(包括零个字符)。在 MySQL中,SQL的模式缺省是忽略大小写的。下面显示一些例子。注意在你使用SQL模式时,你不能使用=或!=;而使用LIKE或NOT LIKE比较操作符。
例如,在表pet中,为了找出以“b”开头的名字:
mysql> SELECT * FROM pet WHERE name LIKE "b%";
+--------+--------+---------+------+------------+------------+
| name | owner | species | sex | birth | death |
+--------+--------+---------+------+------------+------------+
| Buffy | Harold | dog | f | 1989-05-13 | NULL |
| Bowser | Diane | dog | m | 1989-08-31 | 1995-07-29 |
+--------+--------+---------+------+------------+------------+
为了找出以“fy”结尾的名字:
mysql> SELECT * FROM pet WHERE name LIKE "%fy";
+--------+--------+---------+------+------------+-------+
| name | owner | species | sex | birth | death |
+--------+--------+---------+------+------------+-------+
| Fluffy | Harold | cat | f | 1993-02-04 | NULL |
| Buffy | Harold | dog | f | 1989-05-13 | NULL |
+--------+--------+---------+------+------------+-------+
为了找出包含一个“w”的名字:
mysql> SELECT * FROM pet WHERE name LIKE "%w%";
+----------+-------+---------+------+------------+------------+
| name | owner | species | sex | birth | death |
+----------+-------+---------+------+------------+------------+
| Claws | Gwen | cat | m | 1994-03-17 | NULL |
| Bowser | Diane | dog | m | 1989-08-31 | 1995-07-29 |
| Whistler | Gwen | bird | NULL | 1997-12-09 | NULL |
+----------+-------+---------+------+------------+------------+
为了找出包含正好5个字符的名字,使用“_”模式字符:
mysql> SELECT * FROM pet WHERE name LIKE "_____";
+-------+--------+---------+------+------------+-------+
| name | owner | species | sex | birth | death |
+-------+--------+---------+------+------------+-------+
| Claws | Gwen | cat | m | 1994-03-17 | NULL |
| Buffy | Harold | dog | f | 1989-05-13 | NULL |
+-------+--------+---------+------+------------+-------+
4.3.2 扩展正则表达式模式匹配
由MySQL提供的模式匹配的其他类型是使用扩展正则表达式。当你对这类模式进行匹配测试时,使用REGEXP和NOT REGEXP操作符(或RLIKE和NOT RLIKE,它们是同义词)。
扩展正则表达式的一些字符是:
“.”匹配任何单个的字符。
一个字符类“[...]”匹配在方括号内的任何字符。例如,“[abc]”匹配“a”、“b”或“c”。为了命名字符的一个范围,使用一个“-”。“[a-z]”匹配任何小写字母,而“[0-9]”匹配任何数字。
“ * ”匹配零个或多个在它前面的东西。例如,“x*”匹配任何数量的“x”字符,“[0-9]*”匹配的任何数量的数字,而“.*”匹配任何数量的任何东西。
正则表达式是区分大小写的,但是如果你希望,你能使用一个字符类匹配两种写法。例如,“[aA]”匹配小写或大写的“a”而“[a-zA-Z]”匹配两种写法的任何字母。
如果它出现在被测试值的任何地方,模式就匹配(只要他们匹配整个值,SQL模式匹配)。
为了定位一个模式以便它必须匹配被测试值的开始或结尾,在模式开始处使用“^”或在模式的结尾用“$”。
为了说明扩展正则表达式如何工作,上面所示的LIKE查询在下面使用REGEXP重写:
为了找出以“b”开头的名字,使用“^”匹配名字的开始并且“[bB]”匹配小写或大写的“b”:
mysql> SELECT * FROM pet WHERE name REGEXP "^[bB]";
+--------+--------+---------+------+------------+------------+
| name | owner | species | sex | birth | death |
+--------+--------+---------+------+------------+------------+
| Buffy | Harold | dog | f | 1989-05-13 | NULL |
| Bowser | Diane | dog | m | 1989-08-31 | 1995-07-29 |
+--------+--------+---------+------+------------+------------+
为了找出以“fy”结尾的名字,使用“$”匹配名字的结尾:
mysql> SELECT * FROM pet WHERE name REGEXP "fy$";
+--------+--------+---------+------+------------+-------+
| name | owner | species | sex | birth | death |
+--------+--------+---------+------+------------+-------+
| Fluffy | Harold | cat | f | 1993-02-04 | NULL |
| Buffy | Harold | dog | f | 1989-05-13 | NULL |
+--------+--------+---------+------+------------+-------+
为了找出包含一个“w”的名字,使用“[wW]”匹配小写或大写的“w”:
mysql> SELECT * FROM pet WHERE name REGEXP "[wW]";
+----------+-------+---------+------+------------+------------+
| name | owner | species | sex | birth | death |
+----------+-------+---------+------+------------+------------+
| Claws | Gwen | cat | m | 1994-03-17 | NULL |
| Bowser | Diane | dog | m | 1989-08-31 | 1995-07-29 |
| Whistler | Gwen | bird | NULL | 1997-12-09 | NULL |
+----------+-------+---------+------+------------+------------+
既然如果一个正规表达式出现在值的任何地方,其模式匹配了,就不必再先前的查询中在模式的两方面放置一个通配符以使得它匹配整个值,就像如果你使用了一个SQL模式那样。
为了找出包含正好5个字符的名字,使用“^”和“$”匹配名字的开始和结尾,和5个“.”实例在两者之间:
mysql> SELECT * FROM pet WHERE name REGEXP "^.....$";
+-------+--------+---------+------+------------+-------+
| name | owner | species | sex | birth | death |
+-------+--------+---------+------+------------+-------+
| Claws | Gwen | cat | m | 1994-03-17 | NULL |
| Buffy | Harold | dog | f | 1989-05-13 | NULL |
+-------+--------+---------+------+------------+-------+
你也可以使用“{n}”“重复n次”操作符重写先前的查询:
mysql> SELECT * FROM pet WHERE name REGEXP "^.{5}$";
+-------+--------+---------+------+------------+-------+
| name | owner | species | sex | birth | death |
+-------+--------+---------+------+------------+-------+
| Claws | Gwen | cat | m | 1994-03-17 | NULL |
| Buffy | Harold | dog | f | 1989-05-13 | NULL |
+-------+--------+---------+------+------------+-------+
4.3.3 总结
4.3节介绍了有关字符串模式匹配的有关知识。标准的SQL模式匹配是SQL语言的标准,可以被其它关系数据库系统接受。扩展正规表达式模式匹配是根据Unix系统的标准开发了,一般只可使用在MySQL上,但是其功能要比标准的SQL模式匹配更强。
4.4 深入select的查询功能
本节将讲述SELECT语句的一些高级功能。
4.4.1 列和表的别名
4.4.1.1列的别名
精选输出的列可以用列名、列别名或列位置在ORDER BY和GROUP BY子句引用,列位置从1开始。
例如,我们从pet表中检索出宠物和种类,直接引用列名:
mysql> select name,species from pet ORDER BY name, species;
其输出为:
+----------+---------+
| name | species |
+----------+---------+
| Bowser | dog |
| Buffy | dog |
| Chirpy | bird |
| Claws | cat |
| Fang | dog |
| Fluffy | cat |
| Puffball | hamster |
| Slim | snake |
| Whistler | bird |
+----------+---------+
在子句中使用列的位置:
mysql> SELECT name,species FROM pet ORDER BY 1,2;
这条语句的输出与上面并无不同。
最后,你还可以为列命名:
mysql> SELECT name AS n,species AS s FROM pet ORDER BY n,s;
注意返回的结果:
+----------+---------+
| n | s |
+----------+---------+
| Bowser | dog |
| Buffy | dog |
| Chirpy | bird |
| Claws | cat |
| Fang | dog |
| Fluffy | cat |
| Puffball | hamster |
| Slim | snake |
| Whistler | bird |
+----------+---------+
返回的记录顺序并无不同。但是列的名字有了改变,这一点在使用CREATE TABLE…SELECT语句创建表时是有意义的。
例如,我们想从pet表生成包括其中name,owner字段的表,但是想把name和owner字段的名字重新命名为animal和child,一个很笨的方法就是创建表再录入数据,如果使用别名,则仅仅一条SQL语句就可以解决问题,非常简单,我们要使用的语句使CREATE TABLE:
mysql> CREATE TABLE pet1
-> SELECT name AS animal,owner AS child
-> FROM pet;
然后,检索生成的表,看看是否打到目的:
mysql> SELECT * FROM pet1;
+----------+--------+
| animal | child |
+----------+--------+
| Fluffy | Harold |
| Claws | Gwen |
| Buffy | Harold |
| Chirpy | Gwen |
| Fang | Benny |
| Bowser | Diane |
| Whistler | Gwen |
| Slim | Benny |
| Puffball | Diane |
+----------+--------+
4.4.1.2 在子句中使用列的别名
你可以在GROUP BY、ORDER BY或在HAVING部分中使用别名引用列。别名也可以用来为列取一个更好点的名字:
mysql> SELECT species,COUNT(*) AS total FROM pet
-> GROUP BY species HAVING total>1;
+---------+-------+
| species | total |
+---------+-------+
| bird | 2 |
| cat | 2 |
| dog | 3 |
+---------+-------+
注意,你的 ANSI SQL 不允许你在一个WHERE子句中引用一个别名。这是因为在WHERE代码被执行时,列值还可能没有终结。例如下列查询是不合法:
SELECT id,COUNT(*) AS total FROM pet WHERE total > 1 GROUP BY species
会有下面的错误:
ERROR 1054: Unknown column \'total\' in \'where clause\'
WHERE语句被执行以确定哪些行应该包括GROUP BY部分中,而HAVING用来决定应该只用结果集合中的哪些行。
4.4.1.3表的别名
别名不仅可以应用于列,也可以引用于表名,具体方法类似于列的别名,这里不再重复。
列的别名经常用于表自身的连接中。你不必有2个不同的表来执行一个联结。如果你想要将一个表的记录与同一个表的其他记录进行比较,联结一个表到自身有时是有用的。例如,为了在你的宠物之中繁殖配偶,你可以用pet联结自身来进行相似种类的雄雌配对:
mysql> SELECT p1.name, p1.sex, p2.name, p2.sex, p1.species
-> FROM pet AS p1, pet AS p2
-> WHERE p1.species = p2.species AND p1.sex = "f" AND p2.sex = "m";
+--------+------+--------+------+---------+
| name | sex | name | sex | species |
+--------+------+--------+------+---------+
| Fluffy | f | Claws | m | cat |
| Buffy | f | Fang | m | dog |
| Buffy | f | Bowser | m | dog |
+--------+------+--------+------+---------+
在这个查询中,我们为表名指定别名以便能引用列并且使得每一个列引用关联于哪个表实例更直观。
4.4.2 取出互不相同的记录
有时候你可能希望取出的数据互不重复,因为重复的数据可能对你没有意义。
解决的办法是使用DISTINCT关键字,使用这个关键字保证结果集中不包括重复的记录,也就是说,你取出的记录中,没有重复的行。
例如,我们取出pet表中Benny所拥有的宠物的记录:
mysql> SELECT name,owner,species,sex FROM pet WHERE owner="Benny";
+------+-------+---------+------+
| name | owner | species | sex |
+------+-------+---------+------+
| Fang | Benny | dog | m |
| Slim | Benny | snake | m |
+------+-------+---------+------+
注意上面的结果,因为我们要使用它。
假定我们指定DISTINCT关键字,并返回列name,species,sex列:
mysql> SELECT DISTINCT name,species,sex FROM pet WHERE owner="Benny";
+------+---------+------+
| name | species | sex |
+------+---------+------+
| Fang | dog | m |
| Slim | snake | m |
+------+---------+------+
你可以看到有两条结果,这是因为返回的结果集中的行不同,如果我们做以下更改,只返回owner,sex列,你可以观察变化:
mysql> SELECT DISTINCT owner,sex FROM pet WHERE owner="Benny";
+-------+------+
| owner | sex |
+-------+------+
| Benny | m |
+-------+------+
DISTINCT关键字的存在,使查询只返回不同的记录行。
如果一个表中,有完全相同的行,你可以使用DISTINCT,以去除冗余的输出:
SELECT DISTINCT * FROM tbl_name
4.4.3 NULL值的问题
NULL值可能很奇怪直到你习惯于它。概念上,NULL意味着“没有值”或“未知值”,且它被看作有点与众不同的值。为了测试NULL,你不能使用算术比较运算符例如=、<或!=。为了说明它,试试下列查询:
mysql> SELECT 1 = NULL, 1 != NULL, 1 < NULL, 1 > NULL;
+----------+-----------+----------+----------+
| 1 = NULL | 1 != NULL | 1 < NULL | 1 > NULL |
+----------+-----------+----------+----------+
| NULL | NULL | NULL | NULL |
+----------+-----------+----------+----------+
很清楚你从这些比较中得到毫无意义的结果。相反使用IS NULL和IS NOT NULL操作符:
mysql> SELECT 1 IS NULL, 1 IS NOT NULL;
+-----------+---------------+
| 1 IS NULL | 1 IS NOT NULL |
+-----------+---------------+
| 0 | 1 |
+-----------+---------------+
在MySQL中,0意味着假而1意味着真。
NULL这样特殊的处理是为什么,在前面的章节中,为了决定哪个动物不再是活着的,使用death IS NOT NULL而不是death != NULL是必要的:
mysql> SELECT * FROM pet WHERE death IS NOT NULL;
+--------+-------+---------+------+------------+------------+
| name | owner | species | sex | birth | death |
+--------+-------+---------+------+------------+------------+
| Bowser | Diane | dog | m | 1990-08-31 | 1995-07-29 |
+--------+-------+---------+------+------------+------------+
NULL值的概念是造成SQL的新手的混淆的普遍原因,他们经常认为NULL是和一个空字符串\'\'的一样的东西。不是这样的!例如,下列语句是完全不同的:
mysql> INSERT INTO my_table (phone) VALUES (NULL);
mysql> INSERT INTO my_table (phone) VALUES ("");
两个语句把值插入到phone列,但是第一个插入一个NULL值而第二个插入一个空字符串。第一个的含义可以认为是“电话号码不知道”,而第二个则可意味着“她没有电话”。
在SQL中,NULL值在于任何其他值甚至NULL值比较时总是假的(FALSE)。包含NULL的一个表达式总是产生一个NULL值,除非在包含在表达式中的运算符和函数的文档中指出。在下列例子,所有的列返回NULL:
mysql> SELECT NULL,1+NULL,CONCAT(\'Invisible\',NULL);
+------+--------+--------------------------+
| NULL | 1+NULL | CONCAT(\'Invisible\',NULL) |
+------+--------+--------------------------+
| NULL | NULL | NULL |
+------+--------+--------------------------+
如果你想要寻找值是NULL的列,你不能使用=NULL测试。下列语句不返回任何行,因为对任何表达式,expr = NULL是假的:
mysql> SELECT * FROM my_table WHERE phone = NULL;
要想寻找NULL值,你必须使用IS NULL测试。下例显示如何找出NULL电话号码和空的电话号码:
mysql> SELECT * FROM my_table WHERE phone IS NULL;
mysql> SELECT * FROM my_table WHERE phone = "";
在MySQL中,就像很多其他的SQL服务器一样,你不能索引可以有NULL值的列。你必须声明这样的列为NOT NULL,而且,你不能插入NULL到索引的列中。
当使用ORDER BY时,首先呈现NULL值。如果你用DESC以降序排序,NULL值最后显示。当使用GROUP BY时,所有的NULL值被认为是相等的。
为了有助于NULL的处理,你能使用IS NULL和IS NOT NULL运算符和IFNULL()函数。
对某些列类型,NULL值被特殊地处理。如果你将NULL插入表的第一个TIMESTAMP列,则插入当前的日期和时间。如果你将NULL插入一个AUTO_INCREMENT列,则插入顺序中的下一个数字。
4.4.4 大小写敏感性
1、数据库和表名
在MySQL中,数据库和表对应于在那些目录下的目录和文件,因而,内在的操作系统的敏感性决定数据库和表命名的大小写敏感性。这意味着数据库和表名在Unix上是区分大小写的,而在Win32上忽略大小写。
注意:在Win32上,尽管数据库和表名是忽略大小写的,你不应该在同一个查询中使用不同的大小写来引用一个给定的数据库和表。下列查询将不工作,因为它作为my_table和作为MY_TABLE引用一个表:
mysql> SELECT * FROM my_table WHERE MY_TABLE.col=1;
2、列名
列名在所有情况下都是忽略大小写的。
3、表的别名
表的别名是区分大小写的。下列查询将不工作,: 因为它用a和A引用别名:
mysql> SELECT col_name FROM tbl_name AS a
WHERE a.col_name = 1 OR A.col_name = 2;
4、列的别名
列的别名是忽略大小写的。
5、字符串比较和模式匹配
缺省地,MySQL搜索是大小写不敏感的(尽管有一些字符集从来不是忽略大小写的,例如捷克语)。这意味着,如果你用col_name LIKE \'a%\'搜寻,你将得到所有以A或a开始的列值。如果你想要使这个搜索大小写敏感,使用象INDEX(col_name, "A")=0检查一个前缀。或如果列值必须确切是"A",使用STRCMP(col_name, "A") = 0。
简单的比较操作(>=、>、= 、< 、<=、排序和聚合)是基于每个字符的“排序值”。有同样排序值的字符(象E,e)被视为相同的字符!
LIKE比较在每个字符的大写值上进行(“E”=”e”)。
如果你想要一个列总是被当作大小写敏感的方式,声明它为BINARY。
例如:
mysql> SELECT "E"="e","E"=BINARY "e";
+---------+----------------+
| "E"="e" | "E"=BINARY "e" |
+---------+----------------+
| 1 | 0 |
+---------+----------------+
4.4.5 检索语句与多个表的连接
SELECT语句不仅可以从单个表中检索数据,也可以通过连接多个表来检索数据。这里将介绍全连接和左连接的作用。
我们创建两个表作为例子。
mysql> CREATE TABLE first
-> (
-> id TINYINT,
-> first_name CHAR(10)
-> );
录入如下数据:
+------+-----------+
| id | first_name|
+------+-----------+
| 1 | Tom |
| 2 | Marry |
| 3 | Jarry |
+------+-----------+
mysql> CREATE TABLE last
-> (
-> id TINYINT,
-> last_name CHAR(10)
-> );
录入数据
+------+-----------+
| id | last_name |
+------+-----------+
| 2 | Stone |
| 3 | White |
| 4 | Donald |
+------+-----------+
4.4.5.1 全连接
全连接:在检索时指定多个表,将每个表用都好分隔,这样每个表的数据行都和其他表的每行交叉产生所有可能的组合,这样就是一个全连接。所有可能的组和数即每个表的行数之和。
那么观察下面的检索的结果:
mysql> SELECT * FROM first,last;
+------+------------+------+-----------+
| id | first_name | id | last_name |
+------+------------+------+-----------+
| 1 | Tom | 2 | Stone |
| 2 | Marry | 2 | Stone |
| 3 | Jarry | 2 | Stone |
| 1 | Tom | 3 | White |
| 2 | Marry | 3 | White |
| 3 | Jarry | 3 | White |
| 1 | Tom | 4 | Donald |
| 2 | Marry | 4 | Donald |
| 3 | Jarry | 4 | Donald |
+------+------------+------+-----------+
你可以看到输出的结果集中共有3×3=9 行,这就是全连接的结果。
你也可以这样使用SQL语句:
mysql> SELCT first.*,last.* FROM first,last;
输出结果与上面的例子相同,并无二致。记录集的输出的排序是以FROM子句后的表的顺序进行,即先排列位置靠前的表,即使你改变记录集中列的显示顺序,例如下面的例子:
mysql> SELECT last.*,first.* FROM first,last;
+------+-----------+------+------------+
| id | last_name | id | first_name |
+------+-----------+------+------------+
| 2 | Stone | 1 | Tom |
| 2 | Stone | 2 | Marry |
| 2 | Stone | 3 | Jarry |
| 3 | White | 1 | Tom |
| 3 | White | 2 | Marry |
| 3 | White | 3 | Jarry |
| 4 | Donald | 1 | Tom |
| 4 | Donald | 2 | Marry |
| 4 | Donald | 3 | Jarry |
+------+-----------+------+------------+
上面的例子是两个非常小的表的例子,如果是几个非常大的表的全连接,例如,两个行数分别为1000的表,这样的连接可以产生非常大的结果集合1000×1000=100万行。而实际上你并不需要这么多行的结果,通常你需要使用一个WHERE从句来限制返回的记录集的行数:
mysql> SELECT * FROM first,last WHERE first.id= last.id;
+------+------------+------+-----------+
| id | first_name | id | last_name |
+------+------------+------+-----------+
| 2 | Marry | 2 | Stone |
| 3 | Jarry | 3 | White |
+------+------------+------+-----------+
4.4.5.2 左连接
左连接:全连接给出FROM子句中所有表都有匹配的行。对于左连接,不仅匹配类似前面的行记录,而且还显示左边的表有而右边的表中无匹配的行。对于这样的行,从右边表选择的列均被显示为NULL。这样,每一匹配的行都从左边的表被选出,而如果右边表有一个匹配的行,则被选中,如果不匹配,行仍然被选中,不过,其中右边相应的列在结果集中均设为NULL。即,LEFT JOIN强制包含左边表的每一行,而不管右边表是否匹配。
语法:SELECT FROM table_reference LEFT JOIN table_reference ON conditional_expr
其中table_reference为连接的表,ON子句后接类似WHERE子句的条件。
下面我们详细讲述左连接的使用:
l 首先,返回一个全连接的结果集:
mysql> SELECT * FROM first,last;
+------+------------+------+-----------+
| id | first_name | id | last_name |
+------+------------+------+-----------+
| 1 | Tom | 2 | Stone |
| 2 | Marry | 2 | Stone |
| 3 | Jarry | 2 | Stone |
| 1 | Tom | 3 | White |
| 2 | Marry | 3 | White |
| 3 | Jarry | 3 | White |
| 1 | Tom | 4 | Donald |
| 2 | Marry | 4 | Donald |
| 3 | Jarry | 4 | Donald |
+------+------------+------+-----------+
注意上面的结果,下面的例子要与这个例子对照。
l 我们在给出一个限制条件的查询:
mysql> SELECT * FROM first,last WHERE first.id=last.id;
+------+------------+------+-----------+
| id | first_name | id | last_name |
+------+------------+------+-----------+
| 2 | Marry | 2 | Stone |
| 3 | Jarry | 3 | White |
+------+------------+------+-----------+
这个结果类似于是从上一个全连接中选择出first.id>last.id 的行。
现在我们给出一个真正的左连接的例子,你可以仔细观察它的结果,要了解检索的记录顺序:
mysql> SELECT * FROM first LEFT JOIN last ON first.id=last.id;
+------+------------+------+-----------+
| id | first_name | id | last_name |
+------+------------+------+-----------+
| 1 | Tom | NULL | NULL |
| 2 | Marry | 2 | Stone |
| 3 | Jarry | 3 | White |
+------+------------+------+-----------+
上面的结果,即用左边表的每一行与右边表匹配,如果匹配,则选择到结果集中,如果没有匹配,则结果集中,右边表相应的列置为NULL。
l 为了进一步理解这一点,我们给出一个有点奇怪的例子:
mysql> SELECT * FROM first LEFT JOIN last ON first.id=1;
+------+------------+------+-----------+
| id | first_name | id | last_name |
+------+------------+------+-----------+
| 1 | Tom | 2 | Stone |
| 1 | Tom | 3 | White |
| 1 | Tom | 4 | Donald |
| 2 | Marry | NULL | NULL |
| 3 | Jarry | NULL | NULL |
+------+------------+------+-----------+
因为,在结果的最后两行有似乎你不希望的结果。记住,如果只有ON子句的条件,那么左边表的每一行都会返回,只是如果没有匹配的右边表(虽然本例没有约束右边表的列),则记录集中显示为NULL。
前面只是帮助你理解左连接,下面LEFT JOIN的一些有用的技巧。LEFT JOIN最常见的是与WHERE子句共同使用。
使用IS NULL或者IS NOT NULL操作符可以筛选NULL或者非NULL值的列,这是最常见的技巧。
例如,选出first.id=last.id的组合,并且剔除其中没有右表的匹配记录:
mysql> SELECT * FROM first LEFT JOIN last ON first.id=last.id
-> WHERE last.id IS NOT NULL;
+------+------------+------+-----------+
| id | first_name | id | last_name |
+------+------------+------+-----------+
| 2 | Marry | 2 | Stone |
| 3 | Jarry | 3 | White |
+------+------------+------+-----------+
你可以看到这样做的例子结果与语句
SELECT * FROM first,last WHERE first.id=last.id
的输出是相同的。
又如,检索id值只在左边表出现,而不再右边表出现的记录:
mysql> SELECT first.* FROM first LEFT JOIN last ON first.id=last.id
-> WHERE last.id IS NULL;
+------+------------+
| id | first_name |
+------+------------+
| 1 | Tom |
+------+------------+
这个语句是不能用功能相同的带WHERE子句的全连接代替的。
注意:全连接和左连接的结果集排列顺序是不同的,例如:
mysql> SELECT * FROM first,last WHERE first.id!=last.id;
+------+------------+------+-----------+
| id | first_name | id | last_name |
+------+------------+------+-----------+
| 1 | Tom | 2 | Stone |
| 3 | Jarry | 2 | Stone |
| 1 | Tom | 3 | White |
| 2 | Marry | 3 | White |
| 1 | Tom | 4 | Donald |
| 2 | Marry | 4 | Donald |
| 3 | Jarry | 4 | Donald |
+------+------------+------+-----------+
mysql> SELECT * FROM first LEFT JOIN last ON first.id!=last.id;
+------+------------+------+-----------+
| id | first_name | id | last_name |
+------+------------+------+-----------+
| 1 | Tom | 2 | Stone |
| 1 | Tom | 3 | White |
| 1 | Tom | 4 | Donald |
| 2 | Marry | 3 | White |
| 2 | Marry | 4 | Donald |
| 3 | Jarry | 2 | Stone |
| 3 | Jarry | 4 | Donald |
+------+------------+------+-----------+
4.4.6 总结
本节的内容非常繁杂,各小节之间可能没有什么联系,但是本节所述的都是检索数据时很常用的技巧,主要的一些内容如下:
1、 为表和列使用别名
2、 注意NULL值在查询中的使用
3、 注意表名、列名、别名和字符串的大小写问题
4、 如何避免取出重复的记录
4.5 索引属性
索引是加速表内容访问的主要手段,特别对涉及多个表的连接的查询更是如此。这是第 8 章数据库优化中的一个重要内容,第4章讨论了为什么需要索引,索引如何工作以及怎样利用它们来优化查询。本节中,我们将介绍索引的特点,以及创建和删除索引的语法。
4.5.1 索引的特点
所有的MySQL列类型能被索引。在相关的列上的使用索引是改进SELECT操作性能的最好方法。
l 一个表最多可有16个索引。最大索引长度是256个字节,尽管这可以在编译MySQL时被改变。
l 对于CHAR和VARCHAR列,你可以索引列的前缀。这更快并且比索引整个列需要较少的磁盘空间。对于BLOB和TEXT列,你必须索引列的前缀,你不能索引列的全部。
l MySQL能在多个列上创建索引。一个索引可以由最多15个列组成。(在CHAR和VARCHAR列上,你也可以使用列的前缀作为一个索引的部分)。
虽然随着 MySQL 的进一步开发创建索引的约束将会越来越少,但现在还是存在一些约束的。下面的表根据索引的特性,给出了 ISAM 表和 MyISAM 表之间的差别:
表2-1 通道信息特征字对照表
|
索引的特点 |
ISAM 表 |
MyISAM 表 |
|
NULL 值 |
不允许 |
允许 |
|
BLOB 和 TEXT 列 |
不能索引 |
只能索引列的前缀 |
|
每个表中的索引数 |
16 |
32 |
|
每个索引中的列数 |
16 |
16 |
|
最大索引行尺寸 |
256 字节 |
500 字节 |
从此表中可以看到,对于 ISAM 表来说,其索引列必须定义为 NOT NULL,并且不能对 BLOB 和 TEXT 列进行索引。MyISAM 表类型去掉了这些限制,而且减缓了其他的一些限制。两种表类型的索引特性的差异表明,根据所使用的 MySQL 版本的不同,有可能对某些列不能进行索引。例如,如果使用3.23 版以前的版本,则不能对包含 NULL 值的列进行索引。
索引有如下的几种情况:
l INDEX索引:通常意义的索引,某些情况下KEY是它的一个同义词。索引的列可以包括重复的值。
l UNIQUE索引:唯一索引,保证了列不包含重复的值,对于多列唯一索引,它保证值的组合不重复。
l PRIMARY KEY索引:也UNIQUE索引非常类似。事实上,PRIMARY KEY索引仅是一个具有PRIMARY名称的UNIQUE索引。这表示一个表只能包含一个PRIMARY KEY。
4.5.2 用Alter Table语句创建与删除索引
为了给现有的表增加一个索引,可使用 ALTER TABLE 或 CREATE INDEX 语句。ALTER TABLE 最常用,因为可用它来创建普通索引、UNIQUE 索引或 PRIMARY KEY 索引,如:
ALTER TABLE tbl_name ADD INDEX index_name (column_list)
ALTER TABLE tbl_name ADD UNIQUE index_name (column_list)
ALTER TABLE tbl_name ADD PRIMARY KEY index_name (column_list)
其中 tbl_name 是要增加索引的表名,而 column_list 指出对哪些列进行索引。一个(col1,col2,...)形式的列表创造一个多列索引。索引值有给定列的值串联而成。如果索引由不止一列组成,各列名之间用逗号分隔。索引名 index_name 是可选的,因此可以不写它,MySQL 将根据第一个索引列赋给它一个名称。ALTER TABLE 允许在单个语句中指定多个表的更改,因此可以在同时创建多个索引。
同样,也可以用ALTER TABLE语句产出列的索引:
ALTER TABLE tbl_name DROP INDEX index_name
ALTER TABLE tbl_name DROP PRIMARY KEY
注意上面第一条语句可以用来删除各种类型的索引,而第三条语句只在删除 PRIMARY KEY 索引时使用;在此情形中,不需要索引名,因为一个表只可能具有一个这样的索引。如果没有明确地创建作为 PRIMARY KEY 的索引,但该表具有一个或多个 UNIQUE 索引,则 MySQL 将删除这些 UNIQUE 索引中的第一个。
如果从表中删除了列,则索引可能会受到影响。如果所删除的列为索引的组成部分,则该列也会从索引中删除。如果组成索引的所有列都被删除,则整个索引将被删除。
例如,对于上面所使用的student为例,你可能想为之创建这样的索引,以加速表的检索速度:
mysql> ALTER TABLE student
-> ADD PRIMARY KEY(id),
-> ADD INDEX mark(english,Chinese,history);
这个例子,既包括PRIMARY索引,也包括多列索引。记住,使用PRIMARY索引的列,必须是一个具有NOT NULL属性的列,如果你愿意产看创建的索引的情况,可以使用SHOW INDEX语句:
mysql> SHOW INDEX FROM student;
其结果为:
+---------+------------+----------+--------------+-------------+-
| Table | Non_unique | Key_name | Seq_in_index | Column_name |
+---------+------------+----------+--------------+-------------+-
| student | 0 | PRIMARY | 1 | id |
| student | 1 | mark | 1 | english |
| student | 1 | mark | 2 | chinese |
| student | 1 | mark | 3 | history |
+---------+------------+----------+--------------+-------------+-
由于列数太多,上表并没有包括所有的输出,读者可以试着自己查看。
再使用ALTER TABLE语句删除索引,删除索引需要知道索引的名字,你可以通过SHOW INDEX语句得到:
mysql> ALTER TABLE student DROP PRIMARY KEY,
-> DROP INDEX mark;
再产看表中的索引,其语句和输出为:
mysql> SHOW INDEX FROM student;
Empty set (0.01 sec)
4.5.3 用CREATE\DROP INDEX创建索引
还可以用CREATE INDEX语句来创建索引.CREATE INDEX 是在 MySQL 3.23版中引入的,但如果使用3.23 版以前的版本,可利用 ALTER TABLE 语句创建索引(MySQL 通常在内部将 CREATE INDEX 映射到 ALTER TABLE)。该语句创建索引的语法如下:
CREATE UNIQUE INDEX index_name ON tbl_name (column_list)
CREATE INDEX index_name ON tbl_name (column_list)
tbl_name、index_name 和 column_list 具有与 ALTER TABLE 语句中相同的含义。这里索引名不可选。很明显,CREATE INDEX 可对表增加普通索引或 UNIQUE 索引,不能用 CREATE INDEX 语句创建 PRIMARY KEY 索引。
可利用 DROP INDEX语句来删除索引。类似于 CREATE INDEX 语句,DROP INDEX 通常在内部作为一条 ALTER TABLE 语句处理,并且DROP INDEX是在 MySQL 3.22 中引入的。
删除索引语句的语法如下:
DROP INDEX index_name ON tbl_name
还是上一节的例子,由于CREATE INDEX不能创建PRIMARY索引,所以这里我们值创建一个多列索引:
mysql> CREATE INDEX mark ON student(english,chinese,history);
同样的检查student表,可知:
mysql> SHOW INDEX FROM student;
+---------+------------+----------+--------------+-------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name |
+---------+------------+----------+--------------+-------------+
| student | 1 | mark | 1 | english |
| student | 1 | mark | 2 | chinese |
| student | 1 | mark | 3 | history |
+---------+------------+----------+--------------+-------------+
然后使用下面的语句删除索引:
mysql> DROP INDEX mark ON student;
4.5.4 在创建表时指定索引
要想在发布 CREATE TABLE 语句时为新表创建索引,所使用的语法类似于 ALTER TABLE 语句的语法,但是应该在您定义表列的语句部分指定索引创建子句,如下所示:
CREATE TABLE tbl_name
(
…
INDEX index_name (column_list),
KEY index_name (column_list),
UNIQUE index_name (column_list),
PRIMARY KEY index_name (column_list),
…
)
与ALTER TABLE 一样,索引名对于 INDEX 和 UNIQUE 都是可选的,如果未给出,MySQL 将为其选一个。另外,这里KEY时INDEX的一个别名,具有相同的意义。
有一种特殊情形:可在列定义之后增加 PRIMARY KEY 创建一个单列的PRIMARY KEY 索引,如下所示:
CREATE TABLE tbl_name
(
i INT NOT NULL PRIMARY KEY
)
该语句等价于以下的语句:
CREATE TABLE tbl_name
(
i INT NOT NULL,
PRIMARY KEY (i)
)
前面所有表创建样例都对索引列指定了 NOT NULL。如果是 ISAM 表,这是必须的,因为不能对可能包含 NULL 值的列进行索引。如果是 MyISAM 表,索引列可以为 NULL,只要该索引不是 PRIMARY KEY 索引即可。
在CREATE TBALE语句中可以某个串列的前缀进行索引(列值的最左边 n 个字符)。
如果对某个串列的前缀进行索引,应用 column_list 说明符表示该列的语法为 col_name(n) 而不用col_name。例如,下面第一条语句创建了一个具有两个 CHAR 列的表和一个由这两列组成的索引。第二条语句类似,但只对每个列的前缀进行索引:
CREATE TABLE tbl_name
(
name CHAR(30),
address CHAR(60),
INDEX (name,address)
)
CREATE TABLE tbl_name
(
name CHAR(30),
address CHAR(60),
INDEX (name(10),address(20))
)
你可以检查所创建表的索引:
mysql> show index from tbl_name;
+----------+------------+----------+--------------+-------------+-
| Table | Non_unique | Key_name | Seq_in_index | Column_name |
+----------+------------+----------+--------------+-------------+-
| tbl_name | 1 | name | 1 | name |
| tbl_name | 1 | name | 2 | address |
+----------+------------+----------+--------------+-------------+-
在某些情况下,可能会发现必须对列的前缀进行索引。例如,索引行的长度有一个最大上限,因此,如果索引列的长度超过了这个上限,那么就可能需要利用前缀进行索引。在 MyISAM 表索引中,对 BLOB 或 TEXT 列也需要前缀索引。
对一个列的前缀进行索引限制了以后对该列的更改;不能在不删除该索引并使用较短前缀的情况下,将该列缩短为一个长度小于索引所用前缀的长度的列。
4.5.5 总结
本节对索引的类型,已经如何创建索引做了介绍,其中涉及三个比较重要的SQL语句——ALTER TABLE、CREATE/DROP INDEX和CREATE TABLE,注意它们的用法。
索引最重要的功能是,通过使用索引加速表的检索,有关这方面的知识,将在第十章数据库优化中介绍。
思考题
1、 建立一个如下所述的表:
data:FLOAT列,使用随机函数填充数据
birth:DATETIME列,填充当前时间。
然后,请录入几条数据。最后计算data列的平均值、总和、极值,并且按照data列降序排序检索值。
2、 分别使用标准SQL模式和扩展正规表达式模式匹配,匹配上面创建的表,假设你创建表的当前日期为2001-01-01,用模式匹配检索出birth列包含该日期的值。(实际上,上面的表中记录都是同一日期录入的,因此实际将返回全部记录。)
3、 为前几章使用的数据表创建索引:
student:为id段创建一个PRIMARY索引,为english、chinese和history创建一个多列索引。
pet:为name和owner段创建一个多类索引。
4、删除为pet表创建的索引。