创建计算字段
计算字段
存储在数据库表中的数据不一定是应用程序需要的数据,我们需要直接从数据库中检索出转换、计算或格式化过的数据,而不是检索出数据,然后再在客户端应用程序中重新格式化
提示:客户端与服务器的格式
在SQL语句内可完成的许多转换和格式化工作都可以直接在客户端应用程序内完成。但一般来说,在数据库服务器上完成这些操作比在客户端中完成要快得多
拼接字段
假如要生成一个供应商报表,需要在格式化的名称vend_name中 列出供应商的位置,则需要将vend_name字段和vend_country字段拼接组合起来
在MySQL中用法如下:
select concat(Rtrim(vend_name), ' (', Rtrim(vend_country), ')')
from vendors
order by vend_name;
结果如下图:
说明:Rtrim()函数去掉字符串右边的空格、Ltrim()函数去掉字符串左边的空格、trim()函数去掉字符串左右两边的空格(大多数DBMS都支持这几个函数)
字符拼接在不同的DBMS中实现不尽相同:SQL server使用 +
号,DB2、Oracle、postgreSQL和SQLite使用 ||
,MySQL和MariaDB使用Concat()函数
使用别名
从图7-1可以看到,select语句可以很好地拼接地址字段。但是,这个新计算列实际上没有名字,它只是一个值。如果仅在SQL查询工具中查看一下结果,这样没有什么不好。但是,一个未命名的列不能用于客户端应用中,因为客户端没有办法引用它。
这里我们可以使用列别名。别名(alias)是一个字段或值的替换名,用 as 关键字赋予。在MySQL代码中添加列别名:
select concat(Rtrim(vend_name), ' (', Rtrim(vend_country), ')')
as vend_title
from vendors
order by vend_name;
结果图如下:
在很多DBMS中,as 关键字是可选的,不过最好使用它,这被视为一条最佳实践。
别名最常见的使用是将多个单词的列名重命名为一个单词的名字,而不是多个单词组合的字符串(尽管多单词的名字可读性高,但这会给客户端应用带来各种问题)
提示:如何测试计算
select语句为测试、检验函数和计算提供了很好的方法。虽然select通常用于从表中检索数据,但是省略了from子句后就是简单的访问和处理表达式,例如select 3*2
将返回6,select trim(' abc ')
将返回abc,select curdate()
将返回当前日期和时间。
我们可以根据需要使用select语句进行检验
使用函数处理数据
SQL函数
与大多数其他计算机语言一样,SQL也可以用函数来处理数据。函数一般是在数据上执行的,为数据的转换和处理提供了方便。
不过使用SQL函数存在一些问题:不同于DBMS等同地支持SQL语句(如select),每一个DBMS都有其特定的函数。事实上,只有少数几个函数被所有主要的DBMS等同地支持。虽然所有类型的函数一般都可以在每个DBMS中使用,但各个函数的名称和用法可能及其不同
所以,与SQL语句不同,SQL函数不是可移植的。这意味着为特定SQL实现编写的代码在其他实现中可能不能用。
为了代码的可移植,许多SQL程序员不赞成使用特定于实现的功能。虽然这样做很有好处,但有时候并不利于应用程序的性能。如果不使用这些函数,编写某些应用程序代码会很难。必须利用其他方法来实现DBMS可以非常有效完成的工作
提示:是否应该使用函数?
现在,你面临是否应该使用函数的选择。决定权在你,使用或是不使用也没有对错之分。如果你决定使用函数,应该确保做好代码注释,以便以后你自己(或其他人)能够确切地知道这些SQL代码的含义
soundex()函数:返回字符串的soundex值,是一个将任何文本串转换为描述其语音表示的字母数字模式的算法。soundex考虑了类似的发音字符和音节,使得能对字符串进行发音比较而不是字母比较。虽然soundex不是SQL概念,但多数DBMS都提供对soundex的支持。
其使用如下:
customers表中有一个顾客 kids Place,其联系名为Michelle Green。但是如果这是错误的输入,此联系名实际上应该是Michael Green 那该怎么办?显然按正确的联系名搜索不会返回数据,我们可以使用soundex()函数进行搜索,它匹配所有发音类似于Michael Green的联系名:
select cust_name, cust_contact
from customers
where soundex(cust_contact) = soundex('Michael Green');
结果如下图:
汇总数据
聚集数据
AVG()函数
通过对表中行数计数并计算其列值之和,求得该列的平均值
注意:AVG()函数只能用来确定特定数值列的平均值,而且列名必须作为函数参数给出。为了获取多个列的平均值,必须使用多个AVG()函数。*****************
说明:AVG()函数忽略值为NULL的行。
COUNT()函数
用来确定表中行的数目或符合特定条件的行的数目。
有两种使用方式:
- 使用
count(*)
对表中行的数目进行计数,不管表列中包含的是空值(NULL)还是非空值
select count(*)
as num_cust
from Customers;
结果如图:
- 使用
count(column)
对特定列中具有值的行进行计数,忽略NULL值
select count(cust_email)
as num_cust
from Customers;
结果如图:
SUM()函数
SUM()用来返回指定列值的和(总计)
也可以用来合计计算值。
select sum(item_price*quantity) as total_price
from orderitems
where order_num=20005;
结果如图:
如上例所示,利用标准的算术操作符,所有聚集函数都可用来执行多个列上的计算
聚集不同值
聚集函数可以如下使用:
- 对所有行执行计算,指定 all 参数或不指定参数(因为all是默认行为)
- 只包含不同的值,指定 distinct 参数
select avg(distinct prod_price) as avg_dist_price
from products
where vend_id='DLL01';
结果如图:
在使用了 distinct 之后,结果中平均价格比较高。因为有多个物品具有相同的较低价格。排除它们提升了平均价格。
注意:DISTINCT不能用于COUNT(*)
如果指定列名,则DISTINCT只能用于COUNT()。DISTINCT不能用于COUNT(*)。类似地,DISTINCT必须使用列名,不能用于计算或表达式。
分组数据
创建分组
分组是使用select语句的 group by 子句建立的
select vend_id, count(*) as num_prods
from products
group by vend_id;
结果如图:
上面的select语句指定了两个列:vend_id包含产品供应商的ID,num_prods为计算字段(用count(*)函数建立)。 group by 子句指示DBMS按vend_id排序并分组数据。这就会对每个vend_id而不是整个表计算num_prods一次。从输出中可以看到,供应商BRS01有3个产品,DLL01有4个产品,FNG01有2个产品。
过滤分组
SQL允许过滤分组,规定包括哪些分组,排除哪些分组。例如,想要列出至少有两个订单的所有顾客。为此,必须基于完整的分组而不是个别的行进行过滤。
我们已经知道了where子句的作用。但是,在这个例子中where不能完成任务,因为where过滤指定的是行而不是分组。事实上,where没有分组的概念。
那么,不使用where使用什么呢?SQL为此提供了另一个子句:having 子句。
having非常类似于where。事实上,目前为止所学过的所有类型的where子句都可以用having来替代。唯一的差别就是,where过滤行,而having过滤分组。
提示:having支持所有的where操作符。它们的句法是相同的,只是关键字有差别。
过滤分组:
select cust_id, count(*) as orders
from orders
group by cust_id
having count(*)>=2;
结果如图:
最后一行的having子句,它过滤count(*)>=2(两个或以上订单)的那些分组。
说明:having和where的差别
这里有另一种理解方法,where在数据分组前进行过滤,having在数据分组后进行过滤。这是一个重要的区别,where排除的行不包括在分组中。这可能会改变计算值,从而影响having子句中基于这些值过滤掉的分组。
考虑需要在一条语句中同时使用where和having子句的情况:返回过去12个月内具有两个以上订单的顾客。为此,可以增加一条where子句,过滤出过去12个月内下过的订单,然后再增加having子句过滤出具有两个以上订单的分组。
为了更好理解,考虑下面的例子:它列出具有两个以上产品且其价格大于等于4的供应商:
select vend_id, count(*) as num_prods
from products
where prod_price>=4
group by vend_id
having count(*)>=2;
结果如图:
where子句过滤所有prod_price至少为4的行,然后按vend_id分组数据,having子句过滤计数为2或以上的分组。如果没有where子句,就会多检索出一行(供应商DLL01,销售4个产品,价格都在4以下):
select vend_id, count(*) as num_prods
from products
group by vend_id
having count(*)>=2;
结果如图:
使用 having 时应该结合 group by 子句,而 where 子句用于标准的行级过滤。
你以某种方式分组数据(获得特定的分组聚集值),并不表示你需要以相同的方式排序输出。应该提供明确的 order by 子句,即使效果等同于 group by 子句。
一般在使用 group by 子句时,应该也给出 order by 子句。这是保证数据正确排序的唯一方法。千万不要仅依赖group by排序数据。