9.1 计算字段
存储在数据库表中的数据一般不是应用程序所需要的格式。比如:
- 如果想要在一个字段中既显示公司名,又显示公式的地址,但这两个信息一般包含在不同的表列中。
- 城市、州和邮政编码存储在不同的列中,但邮件标签打印程序却需要把它们作为一个恰当格式的字段检索出来。
- 列数据是大小写混合的,但报表程序需要把所有数据按大写表示出来。
在上面举的例子中,存储在表中的数据都不是应用程序所需要的。我们需要直接数据库中检索出转换、计算或格式化过的数据;而不是检索出数据,然后在客户机应用程序或报告程序中重新格式化。
所以就需要计算字段。计算字段并不实际存储于数据库表中,而是运行时在SELECT语句内创建的。
这里的字段(field)基本上跟列(column)的意思相同,经常互换使用,不过数据库列一般称为列,而术语字段通常用在计算字段的连接上。
可在SQL语句内完成的许多转换和格式化工作都可以直接在客户机应用程序内完成。但是一般来说,在数据库服务器上完成这些操作比在客户机中完成要快得多,因为DBMS是设计来快速地完成这种处理的。
9.1.1 计算字段的使用
举一个创建由两列组成的标题的简单例子。
vendors表包含供应商名和位置信息。假如要生成一个供应商报表,需要在供应商的名字中按照name(location)这样的格式列出供应商的位置。
此报表需要单个值,而表中数据存储在两个列vend_name和vend_country中。此外,需要用括号将vend_country括起来,这些东西都没有明确存储在数据库表中。来看看如何用SELECT来编写这样的格式。
拼接(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) |
| Jet Set(England) |
| LT Supplies(USA) |
+-------------------------------------------+
4 rows in set (0.06 sec)
解释:
- CONCAT()拼接串),即把多个串连接起来形成一个较长的串。CONCAT()需要一个或多个指定的串,各个串之间用逗号分隔。(关于更多函数的使用后面会讲)
- 上面的SELECT语句连接以下4个元素:
- 存储在vend_name列中的名字;
- 包括一个空格和一个左圆括号的串;
- 存储在vend_country列中的国家;
- 包括一个右圆括号的串。
9.1.2 使用别名
你拿上面那条语句去执行,会发现新计算出的列名,列名好长而且列名的意义不能明确看出。实际上它是没有名称,它只是一个值。如果仅在SQL查询工具中查看一下结果,这样没什么不好。但是,一个未命名的列不能用于客户机应用中,因为客户机没有办法引用它。这时候SQL就引出别名。
别名(alias)是一个字段或值的替换名。别名用AS关键字赋予。
使用别名修改上面的SQL语句
SELECT CONCAT(vend_name, '(', vend_country, ')')
AS vend_title
FROM vendors
ORDER BY vend_name;
AS也可省略,只要在需要重命名的后面空一格。
SELECT CONCAT(vend_name, '(', vend_country, ')')
vend_title
FROM vendors
ORDER BY vend_name;
输出:
+------------------+
| vend_title |
+------------------+
| ACME(USA) |
| Anvils R Us(USA) |
| Jet Set(England) |
| LT Supplies(USA) |
+------------------+
4 rows in set (0.06 sec)
这样任何客户机都可以按别名引用这个列,看起来是一个实际的表列一样。
别名不止用于计算字段中,还可以在实际的表列名包含不符合规定的字符(含空格)时重新命名它,在原来的名字含混或容易误解时扩充它,等。但记住,并不是真正会去重命名表中的实际列名。
别名有时候也称为导出列(derived column),不管称为什么,它们所代表的都是相同的东西。
9.1.3 执行算术计算
计算字段的另一常见用途是读检索出的数据进行算术计算。比如,一个订单表order中含有物品价格price和物品数量quantity,需要求物品总价格。那么只需要 物品价格乘以物品数据即可。
SELECT order_id, order_price*order_quantiry
AS expanded_price
FROM order;
MySQL算术操作符:+、-、*、/。
SELECT可用通过使用计算字段来测试。
SELECT 2*3;
小结:介绍了计算字段以及如何创建计算字段。此外还学了如何创建和使用别名,以便应用程序能引用计算字段。
9.2 函数
SQL支持利用函数来处理数据。
可移植性(portable): 能运行在多个系统上的代码。
多数SQL语句是可移植的,在SQL实现之间有差异时,这些差异通常不那么难处理。而函数的可移植性却不强。几乎每种主要的DBMS的实现都支持其他实现不支持的函数,而且有时差异还很大。
为了代码的可移植性,很多SQL程序员不赞成使用特殊实现的功能。虽然这样有好处,但是不使用这些函数,编写某些应用程序代码会很难。
如果决定使用函数,应该保证做好代码注释,以便以后你(或其他人)能确切地知道所编写的SQL代码的含义。
9.2.1 使用函数
大多数SQL实现支持以下类型的函数
- 用于处理文本串(如删除或填充值,转换值为大写或小写)的文本函数
- 用于在数值上进行算术操作(如返回绝对值,进行代数运算)的数值函数。
- 用于处理日期和时间值并从这些值中提取特定成分(例如,返回两个日期之差,检查日期有效性等)的日期和时间函数。
- 返回DBMS正使用的特殊信息(如返回用户登录信息,检查版本细节)的系统函数
9.2.2 文本处理函数
之前已经介绍TRIM():去空格的函数。类似的函数如下:
参考:菜鸟教程
函数 | 说明 | 演示 |
---|---|---|
LEFT(s,n) | 返回字符串 s 的前 n 个字符 | 返回字符串 runoob 中的前两个字符: SELECT LEFT('runoob',2) -- ru |
RIGHT(s,n) | 返回字符串 s 的后 n 个字符 | 返回字符串 runoob 的后两个字符:SELECT RIGHT('runoob',2) -- ob |
LOWER(s) | 将串转换为小写 | 把字符串 Runoob 全转小写: SELECT LOWER('Runoob') -- runoob |
UPPER(s) | 将串转换为大写 | 返回字符串 runoob 全转小写:SELECT UPPER('Runoob') -- RUNOOB |
TRIM(s) | 去掉串的左右空格 | 去掉字符串 空格runoob空格 的空格:SELECT TRIM(' runoob ') -- runoob |
LTRIM(s) | 去掉串左边的空格 | 去掉字符串 空格runoob空格 的左空格:SELECT LTRIM(' runoob ') -- runoob空格 |
RTRIM(s) | 去掉串右边的空格 | 去掉字符串 空格runoob空格 的右空格:SELECT RTRIM(' runoob ') -- 空格runoob |
SOUNDEX() | 返回串的SOUNDEX值 | 看下面的解释 |
SUBSTRING(s, start, length) | 从字符串 s 的 start 位置截取长度为 length 的子字符串 | 从字符串 RUNOOB 中的第 2 个位置截取 3个 字符:SELECT SUBSTRING("RUNOOB", 2, 3) AS ExtractString; -- UNO |
LOCATE(s1,s) | 从字符串 s 中获取 s1 的开始位置 | 获取 b 在字符串 abc 中的位置:SELECT LOCATE('st','myteststring'); -- 5 返回字符串 abc 中 b 的位置:SELECT LOCATE('b', 'abc') -- 2 |
LENGTH() | 返回串的长度 | 返回字符串 runoob 的长度:SELECT LENGTH('runoob') -- 6 |
上面的SOUNDEX需要进一步解释:SOUNDEX是一个而将任何文本串转换为描述其语音表示的字母数字模式的算法。SOUNDEX考虑了类似的发音字符和音节,使得能对串进行发音比较而不是字母比较。虽然SOUNDEX不是SQL概念,但MySQL(就像很大DBMS一样)都提供对SOUNDEX的支持。
比如,创建一张表名为customers,表中包含顾客(cust_name)和联系名(cust_contact)。
CREATE TABLE customers(
cust_name varchar(20) PRIMARY KEY,
cust_contact varchar(20)
);
现在假设有一个顾客Coyote Inc. , 其联系名为 Y.Lee。但如果这是输入错误的结果,此联系名实际应该是Y.Lie,怎么办?显然,按正确的联系名搜索不会返回数据,如下:
SELECT cust_name, cust_contact
FROM customers
WHERE cust_contact = 'Y.Lie';
输出:
Empty set
现在试下使用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 |
+-------------+--------------+
1 row in set (0.05 sec)
在这个例子中,WHERE子句使用SOUNDEX()函数来转换cust_contact列值和搜索串为它们的SOUNDEX值。因为Y.Lee和Y.lie发音相似,所以它们的SOUNDEX值匹配,因此WHERE子句正确地过滤了所需的数据。
9.2.3 日期和时间处理函数
日期和时间采用相应的数据类型和特殊的格式存储,以便快速和有效地排序或过滤,并节省物理存储空间。
一般,应用程序不使用来存储日期和时间的格式,因此日期和时间函数总是被用来读取、统计和处理这些值。所以,日期和时间函数在MySQL语言中很重要。
表格转载:菜鸟教程
函数 | 说明 | 演示 |
---|---|---|
ADDDATE(d,n) | 计算起始日期 d 加上 n 天的日期 | SELECT ADDDATE("2020-01-15", 3); 或 SELECT ADDDATE("2020-01-15", INTERVAL 3 DAY); ->(2020-01-18) |
ADDTIME(t,n) | 时间 t 加上 n 秒的时间 | SELECT ADDTIME('2011-11-11 11:11:11', 5); ->2011-11-11 11:11:16 (秒) |
CURDATE() | 返回当前日期 | SELECT CURDATE(); ->2020-01-15 |
CURTIME() | 返回当前时间 | SELECT CURTIME(); ->16:41:01 |
DATE() | 从日期或日期时间表达式中提取日期值 | SELECT DATE("2020-01-15"); ->2020-01-15 |
DATEDIFF(d1,d2) | 计算日期 d1->d2 之间相隔的天数 | SELECT DATEDIFF("2020-01-15","2020-01-4"); ->11(前面的日期减去后面的日期) |
DATE_ADD(d,INTERVAL expr type) | 计算起始日期 d 加上一个时间段后的日期 | SELECT ADDDATE('2011-11-11 11:11:11',1); -> 2011-11-12 11:11:11 (默认是天) SELECT ADDDATE('2011-11-11 11:11:11', INTERVAL 5 MINUTE); -> 2011-11-11 11:16:11 (TYPE的取值与上面那个列出来的函数类似) |
DATE_FORMAT(d,f) | 按表达式 f的要求显示日期 d | SELECT DATE_FORMAT('2011-11-11 11:11:11','%Y-%m-%d %r') -> 2011-11-11 11:11:11 AM |
DAY(d) | 返回日期值 d 的日期部分 | SELECT DAY("2020-01-15"); -> 15 |
DAYOFWEEK(d) | 日期 d 今天是星期几,1 星期日,2 星期一,以此类推 | SELECT DAYOFWEEK("2020-01-15"); -> 4 |
HOUR(t) | 返回 t 中的小时值 | SELECT HOUR("2020-01-15 17:21"); -> 17 |
MINUTE(t) | 返回 t 中的分钟值 | SELECT MINUTE("2020-01-15 17:21"); -> 21 |
MONTH(d) | 返回日期d中的月份值,1 到 12 | SELECT MONTH("2020-01-15 17:21"); -> 1 |
NOW() | 返回当前日期和时间 | SELECT NOW(); -> 2020-01-15 17:24:18 |
MICROSECOND(date) | 返回日期参数所对应的微秒数 | SELECT MICROSECOND("2017-06-20 09:34:00.000023"); -> -> 23 |
TIME(expression) | 提取传入表达式的时间部分 | SELECT TIME("19:30:10"); -> 19:30:10 |
YEAR(t) | 返回t日期中的年份 | SELECT YEAR("2020-01-15 17:21"); -> 2020 |
更多参考:菜鸟教程
需要注意的是MySQL使用的日期格式。无论是什么时候指定一个日期,或是插入或更新等,日期必须为格式yyyy-mm-dd。所以,2020年1月15号,给出的是2020-01-15.虽然其他的日期格式可能也行,但这是首选的日期格式,因为它排除了多义性(如,04/05/06是2006年5月4号还是2006年4月5号或...)。
应该总是使用4位数字的年份。MySQL虽然支持2位数字的年份,比如处理00-69位2000-2069。虽然它们可能是打算要的年份,但使用完整的4位数字年份更可靠。
在数据库表中检索时间日期,比如:
SELECT cust_id, order_num
FROM orders
WHERE order_date = '2005-09-01';
这样写的 order_date = '2005-09-01' 可靠吗?如果order_date的数据类型是datetime,这种类型存储日期及时间值,那么在例表中的值全都具有时间值00:00:00,但实际上很可能并不总是这样。如果用当前日期和时间存储订单日期(因此我们得知道订单日期和下订单当前的时间),怎么办??比如,存储的order_date值为2005-09-01 11:30:05,则WHERE order_date = '2005-09-01'就失败。
解决的方式:让MySQL仅将给出的日期与列中的日期部分进行比较,而不是将给出的日期与整个列值进行比较。所以得使用DATE()函数。DATE(order_date)表示MySQL仅提取列的日期部分,所以修改如下:
SELECT cust_id, order_num
FROM orders
WHERE DATE(order_date) = '2005-09-01';
所以,对于日期的数据要特别注意,如果要的是日期,使用DATE(),如果要的是天,使用DAY(),如果要的是月,使用MONTH()等。最好明确要的是什么格式的日期,即使知道相应的列只包含日期也应该加上函数。
还有一种日期比较需要说明。如果要检索2005年9月下的所有订单,怎么办??简单的相等测试肯定不行,因为它也要匹配月份中的天数。提供以下的解决方法:
SELECT cust_id, order_num
FROM orders
WHERE DATE(order_date) BETWEEN '2005-09-01' AND '2005-09-30';
其中,BETWEEN操作符用来把2005-09-01和2005-09-30定义为一个要匹配的日期范围。
还有另一种:
SELECT cust_id, order_num
FROM orders
WHERE YEAR(order_date) = 2005 AND MONTH(order_date) = 9;
解释:YEAR()是一个从日期(或日期时间)中返回年份的函数。类似,MONTH从日期中返回月份。因此WHERE YEAR(order_date) = 2005 AND MONTH(order_date) = 9检索出order_date为2005年9月的所有行。
9.2.3 数值处理函数
数值处理函数仅处理数值数据。一般主要用于代数、三角或几何运算。
在主要DBMS的函数中,数值函数是最统一最一致的函数。
函数 | 说明 |
---|---|
ABS(t) | 返回数t的绝对值 |
COS(t) | 返回角度为t的余弦 |
EXP(t) | 返回数t的指数值 |
MOD(a, b) | 返回除操作(a/b)的余数 = (a%b) |
PI() | 返回圆周率 |
RAND() | 返回一个随机数 |
SIN(t) | 返回角度为t的正弦 |
SQRT(t) | 返回数t的平方根 |
TAN(t) | 返回角度为t的正切 |
小结:介绍了如何使用SQL的数据处理函数,主要注意日期函数的使用。这些函数不需要死记硬背,忘了就拿出来看。当然,最好简单的函数就记一记,比如:ABS()、SQRT()、YEAR()、HOUR()、DAY()等这些。