日期计算
MySQL 有用来计算日期的函数,比如计算年龄或提取日期部分。
要想确定每个宠物有多大,可以使用函数TIMESTAMPDIFF()
计算当前日期的年和出生日期之间的差也可以按照直接使用语句(YEAR(CURDATE())-YEAR(birth))
计算,其中函数CURDATE()
是计算当前的日期。如果当前日期的日历年比出生日期早,则减去一年。以下代码是查询每个宠物的出生日期、当前日期和年龄(以年作为计算单位),其中关键字age
是年龄这个计算结果的标签。
mysql> SELECT name, birth, CURDATE(),
-> TIMESTAMPDIFF(YEAR,birth,CURDATE()) AS age
-> FROM pet;
# 比较这两个查询语句的结果
mysql> SELECT name, birth, CURDATE(),
-> (YEAR(CURDATE())-YEAR(birth))
-> - (RIGHT(CURDATE(),5)<RIGHT(birth,5)) AS age
-> FROM pet;
此处,YEAR()
提取日期的年部分,RIGHT()
提取日期最右面5个字符的MM-DD
(月份和日期)部分。MM-DD
值的表达式部分的值一般为1
或0
,如果CURDATE()
的年比birth
的年早,则年份应减去1
。整个表达式看起来有些难懂,使用age
来使输出的列标记更有意义。
尽管查询可行,但是人类的欲望是永无止尽的,如果以某个顺序排列行,那么会使得浏览结果变得更加轻松。添加ORDER BY name
子句则能够实现按照名字进行排序输出。
mysql> SELECT name, birth, CURDATE(),
-> (YEAR(CURDATE())-YEAR(birth))
-> - (RIGHT(CURDATE(),5)<RIGHT(birth,5))
-> AS age
-> FROM pet ORDER BY name;
为了按age
而非name
排序输出,只要再使用一个ORDER BY子句:
mysql> SELECT name, birth, CURDATE(),
-> (YEAR(CURDATE())-YEAR(birth))
-> - (RIGHT(CURDATE(),5)<RIGHT(birth,5))
-> AS age
-> FROM pet ORDER BY age;
可以使用一个类似的查询来确定已经死亡动物的死亡年龄。你通过检查death
值是否为NULL
来确定是哪些动物已经死亡,然后对于那些非NULL
值的动物,需要计算出death
和birth
值之间的差来知道他们在这个世界上所存在的时间:
mysql> SELECT name, birth, death,
-> (YEAR(death)-YEAR(birth)) - (RIGHT(death,5)<RIGHT(birth,5))
-> AS age
-> FROM pet WHERE death IS NOT NULL ORDER BY age;
查询使用death IS NOT NULL
而非death != NULL
,因为NULL是特殊的值,不能使用普通比较符来比较。
如果你想要知道哪个动物下个月过生日怎么办?对于这类计算,年和天是无关的,你只需要提取birth
列的月份部分。MySQL提供几个日期方面的提取函数,例如YEAR()
、MONTH()
和DAYOFMONTH()
。在这里MONTH()
是我们需要的函数。为了观察它的实现原理,可以运行以下简单的查询显示birth
和MONTH(birth)
的值:
mysql> SELECT name, birth, MONTH(birth) FROM pet;
找出下个月生日的动物也很简单。假定当前月是4月,那么月值是4
,你需要找在5月出生的动物,方法是:
mysql> SELECT name, birth FROM pet WHERE MONTH(birth) = 5;
如果当前月份是12月,就有点复杂了。你不能只把1
加到月份数(12
)上并寻找在13月出生的动物,因为没有这样的月份。相反,你应寻找在1月出生的动物。
你甚至可以编写查询,不管当前月份是什么它都能执行。因此不必在查询中使用一个特定的月份,DATE_ADD()
允许在一个给定的日期上加上时间间隔。如果在NOW()
值上加上一个月,然后用MONTH()
提取月份,产生生日所在月份:
mysql> SELECT name, birth FROM pet
-> WHERE MONTH(birth) = MONTH(DATE_ADD(CURDATE(),INTERVAL 1 MONTH));
完成该任务的另一个方法是加1得出当前月份的下一个月(在使用取模函数MOD()
后,如果月份当前值是12
,则“返回”到值0
):
mysql> SELECT name, birth FROM pet
-> WHERE MONTH(birth) = MOD(MONTH(CURDATE()), 12) + 1;
注意,MONTH
返回在1
和12
之间的一个数字,且MOD(something,12)
返回在0和11之间的一个数字,因此必须在MOD()
后加1,否则我们将从11月(11
)跳到1月(1
)。
2.2.6 NULL
值操作
NULL
值可能令人感到奇怪因此你需要习惯它。概念上,NULL
意味着“没有值”或“未知值”,并且它被看作使与众不同的值。为了测试NULL
,你不能使用算术比较操作符例如=
、<
或!=
。为了了解它,试试下列查询:
mysql> SELECT 1 = NULL, 1 <> NULL, 1 < NULL, 1 > NULL;
很显然你不能通过这些得到有意义的结果,因为任何使用算数比较操作符对NULL
进行比较的结果都是NULL
。因此使用IS NULL
和IS NOT NULL
操作符:
mysql> SELECT 1 IS NULL, 1 IS NOT NULL;
请注意在MySQL中,0
或NULL
意味着假而其它值意味着真。布尔运算的默认真值是1
。
为了查询出哪个动物不再是活着的,使用death IS NOT NULL
而不使用death != NULL
的原因。
在GROUP BY
中,两个NULL
值被视为相同等价的。
执行ORDER BY
语句排序时,如果运行ORDER BY ... ASC
,则NULL
值出现在最前面,若运行ORDER BY ... DESC
,则NULL
值出现在最后面。
NULL
操作的常见错误是认为不能在定义为NOT NULL
的列内插入0
或空字符串,但事实并非如此。这样的结果是在NULL
表示"没有数值"的时候恰恰是有数值0
存在的。因此使用IS [NOT] NULL
则可以很容易地进行区分,如下所示:
mysql> SELECT 0 IS NULL, 0 IS NOT NULL, ‘‘ IS NULL, ‘‘ IS NOT NULL;
因此完全可以在定义为NOT NULL
的列内插入0
或空字符串,因为它们实际是NOT NULL
。
2.2.7 模式匹配
MySQL提供标准的SQL模式匹配,以及一种基于类Unix
里的程序如vi
、grep
和sed
里的扩展正则表达式模式匹配的格式。 SQL模式匹配允许你使用“_”匹配任何单个字符,而“%”匹配任意数目字符(包括零字符)。在 MySQL中,SQL的模式默认是忽略大小写的。下面给出一些例子。注意使用SQL模式时,不能使用=
或!=
;而应使用LIKE
或NOT LIKE
比较操作符。 要想找出以“b”开头的名字的动物信息:
mysql> SELECT * FROM pet WHERE name LIKE ‘b%‘;
要想找出以“fy”结尾的名字:
mysql> SELECT * FROM pet WHERE name LIKE ‘%fy‘;
要想找出包含“w”的名字:
mysql> SELECT * FROM pet WHERE name LIKE ‘%w%‘;
要想找出正好包含5个字符的名字,使用“_”模式字符:
mysql> SELECT * FROM pet WHERE name LIKE ‘_____‘;
由MySQL提供的模式匹配的其它类型是使用扩展正则表达式。当你对这类模式进行匹配测试时,使用REGEXP
和NOT REGEXP
操作符(或RLIKE
和NOT RLIKE
,它们是同义词)。
扩展正则表达式的一些字符是:
- ‘.’匹配任何单个的字符。
- 字符类“[...]”匹配在方括号内的任何字符。例如,“[abc]”匹配“a”、“b”或“c”。为了命名字符的范围,使用一个“-”。“[a-z]”匹配任何字母,而“[0-9]”匹配任何数字。
- “ ”匹配零个或多个在它前面的字符。例如,“x”匹配任何数量的“x”字符,“[0-9]”匹配任何数量的数字,而“.”匹配任何数量的任何字符。
如果REGEXP
模式与被测试值的任何地方匹配,模式就匹配(这不同于LIKE
模式匹配,只有与整个值匹配,模式才匹配)。 为了定位一个模式以便它必须匹配被测试值的开始或结尾,在模式开始处使用“^”或在模式的结尾用“$”。 为了说明扩展正则表达式如何工作,下面使用REGEXP
重写上面所示的LIKE查询:
为了找出以“b”开头的名字,使用“^”匹配名字的开始:
mysql> SELECT * FROM pet WHERE name REGEXP ‘^b‘;
如果你想强制使REGEXP
比较区分大小写,使用BINARY
关键字使其中一个字符串变为二进制字符串。该查询只匹配名称首字母的小写‘b’。
mysql> SELECT * FROM pet WHERE name REGEXP BINARY ‘^b‘;
为了找出以“fy”结尾的名字,使用“$”匹配名字的结尾:
mysql> SELECT * FROM pet WHERE name REGEXP ‘fy$‘;
为了找出包含一个“w”的名字,使用以下查询:
mysql> SELECT * FROM pet WHERE name REGEXP ‘w‘;
既然如果一个正则表达式出现在值的任何地方,他就会被模式匹配,就不必在先前的查询中在模式的两侧放置一个通配符以使得它匹配整个值,就像你使用了一个SQL模式那样。
为了找出包含正好5个字符的名字,使用“^”和“$”匹配名字的开始和结尾,和5个“.”实例在两者之间:
mysql> SELECT * FROM pet WHERE name REGEXP ‘^.....$‘;
你也可以使用“{n}”重复n次操作符,重写前面的查询:
mysql> SELECT * FROM pet WHERE name REGEXP ‘^.{5}$‘;
2.2.8 计算行数
数据库经常用于回答这个问题,“查询出某个类型的数据在表中出现的频数是多少?”
例如,你可能想要知道你有多少宠物,或每位主人有多少宠物,或你可能想要对你的动物进行各种类型的普查。
计算你拥有动物的总数目与“在pet表中有多少行?”
是同样的问题,因为每个宠物都对应一条记录。COUNT(*)
函数计算行数,所以计算动物数目的查询应为:
mysql> SELECT COUNT(*) FROM pet;
在前面的章节中,你检索了拥有宠物的人的名字。如果你想要知道每个主人有多少宠物,你也可以使用COUNT(*)
函数:
mysql> SELECT owner, COUNT(*) FROM pet GROUP BY owner;
注意,使用GROUP BY
对每个owner
的所有记录分组,没有它,你会得到错误消息:
mysql> SELECT owner, COUNT(*) FROM pet;
ERROR 1140 (42000): Mixing of GROUP columns (MIN(),MAX(),COUNT(),...)
with no GROUP columns is illegal if there is no GROUP BY clause
COUNT(*)
和GROUP BY
以各种形式分类你的数据。下列例子显示出以不同方式进行动物普查操作。
查看每种动物的数量:
mysql> SELECT species, COUNT(*) FROM pet GROUP BY species;
查看每种性别的动物数量:
mysql> SELECT sex, COUNT(*) FROM pet GROUP BY sex;
按种类和性别组合分类的动物数量:
mysql> SELECT species, sex, COUNT(*) FROM pet GROUP BY species, sex;
若使用COUNT(*)
,你不必检索整个表。例如, 当只对狗和猫进行查询时,应为:
mysql> SELECT species, sex, COUNT(*) FROM pet
-> WHERE species = ‘dog‘ OR species = ‘cat‘
-> GROUP BY species, sex;
或,如果你仅需要知道已知性别的按性别分组的动物数目:
mysql> SELECT species, sex, COUNT(*) FROM pet
-> WHERE sex IS NOT NULL
-> GROUP BY species, sex;
2.2.9 使用1个以上的表
pet
表追踪你拥有的宠物。如果你想要记录其它相关信息,例如在他们看兽医的情况或后代出生的情况,那么你需要另外的表。这张表应该拥有些什么呢?它需要:
- 需要包含宠物名字以便你知道每个发生的事件属于哪个动物。
- 需要一个日期以便你知道事件是什么时候发生的。
- 需要一个描述事件的字段。
- 如果你想要对事件进行分类,则需要一个事件类型字段。
综上所述,event
表的CREATE TABLE语句应为:
mysql> CREATE TABLE event (name VARCHAR(20), date DATE,
-> type VARCHAR(15), remark VARCHAR(255));
类似于于pet
表,最简单的方法是创建一个用定位符分隔的文本文件来加载载初始记录:
采用如下方式加载记录:
mysql> LOAD DATA LOCAL INFILE ‘/home/shiyanlou/Desktop/event.txt‘ INTO TABLE event;
由于你已经在pet
表上的查询中学到了一定的知识,你应该能执行对event
表中记录的检索;原理是一样的。但是有没有event
表本身不能回答你可能问的问题呢?
当宠物们生了了一窝小动物时,假定你想要找出这时候每只宠物的年龄。我们前面看到了如何通过两个日期计算年龄。event
表中有母亲的生产日期,但是为了计算母亲的年龄,你需要她的出生日期,存储在pet
表中。说明查询需要两个表:
mysql> SELECT pet.name,
-> (YEAR(date)-YEAR(birth)) - (RIGHT(date,5)<RIGHT(birth,5)) AS age,
-> remark
-> FROM pet, event
-> WHERE pet.name = event.name AND event.type = ‘litter‘;
关于该查询要注意以下几件事:
FROM
子句连接两个表,因为查询需要从两个表中提取信息。- 当从多个表组合(联结)信息时,你需要指定其中一个表中的列明以期匹配其它表的列名。这很简单,因为它们都有一个
name
列,查询可以通过使用WHERE
子句基于name
值来匹配两个表中的记录。 - 因为
name
列都存在两个表中,因此当引用该列时,一定要指定是哪个表,把表名附在列名前即可以实现。 如果你想要将一个表的记录与该表的其它记录进行比较,可以将该表联结到自身。例如,为了在你的宠物之中选择繁殖中的配偶,你可以用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‘;
在这个查询中,我们为表名指定别名p1
和p2
以便能引用它们的列并且使得每一个列的引用更直观。
2.3 获得数据库和表的信息
如果你忘记数据库或表的名字,或给定的表的结构是什么(例如,它的列叫什么),怎么办?MySQL提供一些语句解决这个问题。 你已经知道SHOW DATABASES
可以列出由服务器管理的所有数据库。为了找出当前选择了哪个数据库,使用DATABASE()
函数:
mysql> SELECT DATABASE();
如果你还没选择任何数据库,结果是NULL
。
为了找出当前的数据库包含什么表(例如,当你不能确定一个表的名字),使用这个命令:
mysql> SHOW TABLES;
如果你想要知道一个表的结构,可以使用DESCRIBE
命令;它显示表中每个列的信息:
mysql> DESCRIBE pet;
Field
显示列名字,Type
是列的数据类型,Null
表示列是否能包含NULL
值,key
显示列是否被索引而Default
指定列的默认值。
如果表有索引,SHOW INDEX FROM tbl_name
生成有关索引的信息。