SQL语言中的Select命令用于从数据库中查询并返回数据行。如果以中文表述,其语法结构如下:
select [谓词] 列表达式 [as 别名] [from 表名] [where 条件] [group by 分组] [order by 排序]
其中,以中括号包起来的部分是可选的。现在,我们就来逐个关键字的解析它,以帮助大家快速掌握这一数据查询利器。
必需关键字select
由上面的结构可以看出,整个语句中,只有select关键字和列表达式是必须的,其它全部都是可选的。例如,以下是一个最简单的select语句:
select null
执行后将生成一个内容为空的查询结果。如果将这个语句改为:
select '我是用来测试的'
则结果如图所示。
由于这个语句仅仅使用了一个字符串表达式“我是用来测试的”,且没有指定列名,因此,返回的查询结果中就由数据库随机分配了一个列名。很显然,随机的列名不方便后期的数据处理,如要给其加上固定的列名,可以在表达式后面使用as标志来进行重命名。例如:
select '我是用来测试的' as A1
执行结果为:
这里指定的列名也被称为别名。当在别名中使用一些特殊字符时,要注意使用中括号括起来。例如,下面的语句:
select '我是用来测试的' as 占比%
由于列名中使用了特殊字符%,如果不用中括号的话,将提示错误。正确的写法为:
select '我是用来测试的' as [占比%]
from关键字
此关键字用于指定来源表,也就是从哪里返回查询数据。
例如,从“订单”表中返回数据,代码如下:
select '我是用来测试的' as A1 from 订单
执行后发现,查询结果变成了864行,但内容都是一样的。如图所示。
这是因为,当没有指定来源表时,由于仅设置了一个字符串表达式的列,因此返回的结果只有一行,该行的内容就是这个字符串表达式的值;当指定了来源表时,其返回的行数由来源表决定。由于订单表存在864条记录,因此这里就变成了864行;由于列值是用一个固定的字符串表示的,因此每行的值也都一样。
这里的列表达式既可以是字符串,也可以是具体的列名,或者是不同列之间拼接而成的其它表达式。多个列表达式之间用半角逗号隔开。再如下面的select语句:
这里的第1列是A1列,它是固定的字符串;第2列是B1列,用的是22+33的算术表达式,因此该列返回值都是55;第3列使用的是列名表达式“产品id”,这是在来源表中本身就存在的列,因此返回值取决于来源表;第4列同样使用的列名表达式“客户id”,只不过给它起了一个别名;第5列是通过多个列名组成的表达式,由于没有使用别名,因此返回的列名是随机的Expr1004。
尽管订单表中的列名使用的是大写,如:产品ID、客户ID,但在SQL语句中使用这些列时却可以用小写。这就是说,SQL语句中所用到的列名是不区分大小写的。
如要获取来源表中的全部字段列,可以直接简写成这样:
select * from 订单
如要在全部列的基础上再增加列,可以这样:
select *,'我是字符串' as A1, 单价*折扣*数量 as 金额 from 订单
这样得到的查询结果中,就会在“订单”表原有全部列的基础上又增加了A1列和金额列。
表达式谓词top和distinct
谓词是可选的,用于指定返回列内容的方式。默认为All,表示返回所有记录。例如,上面的select语句也可以写成这样:
select all *,'我是字符串' as A1, 单价*折扣*数量 as 金额 from 订单
除此之外,最常用的是top和distinct。
top用于指定返回的行数。例如,以下语句仅返回前面的3行数据:
select top 3 产品ID, 客户ID, 单价, 折扣, 数量, 单价*折扣*数量 as 金额 from 订单
这里的行数也可改用percent,用于指定返回总行数的百分比。例如:
select top 10 percent 产品ID, 客户ID, 单价*折扣*数量 as 金额 from 订单
返回的总行数为全部记录中的10%,也就是87条(864*10%=86.4,最终返回87条)。请注意,这里的87条仍然是从头部(top,也就是第一条)开始算起的。
另外一个谓词是distinct,它用于返回指定列的不重复值,在select语句中只能出现一次。例如:
select distinct 产品ID from 订单
select distinct 产品ID, 客户ID from 订单
上述第一条语句的返回记录为5条,而第二条语句的返回记录为25条。其中,第二条语句不能这样写:
select distinct 客户ID, distinct 产品ID from 订单
当需要对多列进行重复值排除时,可使用如下方式:
select distinct 客户ID + 产品ID as 客户产品 from 订单
查询条件where
这个非常好理解,用于限定查询的数据范围。例如,仅获取2000年度的全部数据记录、前5条记录及产品ID的不重复值:
select *,单价*折扣*数量 as 金额 from 订单 where year(日期)=2000
select top 5 *,单价*折扣*数量 as 金额 from 订单 where year(日期)=2000
select distinct 产品ID from 订单 where year(日期)=2000
再如,获取2000年度且客户ID为C01的全部记录(由于不区分大小写,用c01也是同样的效果):
select *,单价*折扣*数量 as 金额 from 订单 where year(日期)=2000 and 客户id='C01'
需要注意的是,这里如果用金额作为判断条件(比如金额大于200),以下写法是错误的:
select *,单价*折扣*数量 as 金额 from 订单 where 金额>200
这是因为,金额列在来源表中并不存在,需要通过表达式动态生成。因此,应改为:
select *,单价*折扣*数量 as 金额 from 订单 where 单价*折扣*数量>200
分组依据group by
该关键字用于指定如何进行分组合并。分组时,可以使用聚合函数,也可以不使用聚合函数;可以使用一列分组,也可以使用多列分组。当使用多列时,多列之间用半角逗号隔开。
聚合函数最常用是以下5个:Sum、Avg、Max、Min和Count。● 未使用聚合函数的分组
当没有使用聚合函数进行分组时,其效果相当于同类项合并,也就是类似于distinct的效果。例如,下面的两条语句,执行效果是完全一样的:
select distinct 产品ID,客户ID from 订单
select 产品ID,客户ID from 订单 group by 产品ID,客户ID
● 使用聚合函数的分组
凡是select中没有出现在group by子句里的所有列表达式,都必须使用聚合函数。换言之,凡是出现在select子句中且没有使用聚合函数的列表达式,都必须包含在group by中。例如,下面语句中的数量列既没有使用聚合函数,也没有用在分组中,因此,该语句在执行时将出错:
select 产品ID,客户ID,数量 from 订单 group by 产品ID,客户ID
正确的写法应该是:
select 产品ID,客户ID,sum(数量) from 订单 group by 产品ID,客户ID
再如,对表达式列进行统计计算:
select 产品ID,客户ID,sum(数量) as 总量,sum(单价*折扣*数量) as 总额 from 订单 group by 产品ID,客户ID
按产品ID列进行分组,统计每组中的记录数:
select 产品ID,count(*) as 记录条数 from 订单 group by 产品ID
这个count聚合函数中的*也可以换成某个指定的列名。例如:
select 产品ID,count(客户ID) as 记录条数 from 订单 group by 产品ID
请注意,这样得到的统计结果和count(*)得到的结果可能并非完全一致:count(*)仅统计数据记录的行数,不会读取列中的任何数据,因此执行效率非常高;而count(客户ID)在统计时会判断该列中的值,一旦出现空值就会剔除该行。也就是说,count(客户ID)统计的记录数是不会包括指定列内容为空的记录行的。
● 分组依据可以是具体的列,也可以是表达式
例如,订单表中并没有单独的年份列,我们可以使用表达式来实现按年分组:
select 客户id,year(日期) as 年,sum(数量) as 总量 from 订单 group by 客户id,year(日期)
● 分组依据和最终返回的列并不是完全对应的
仍以上一行语句为例,尽管该语句按“客户ID”和“year(日期)”分组,但它们并不一定要必须返回。例如,将其改成这样也是可以的:
select 客户id,sum(数量) as 总量 from 订单 group by 客户id,year(日期)
返回的总量数据仍然不变,记录数也不变,只是没有了年份列,数据查看起来比较费劲而已。
再比如,这里的分组用的是“year(日期)”,我们还可以将输出的年份列内容改成这样的表达式:
select 客户id,cstr(year(日期))+'年' as 年份,sum(数量) as 总量 from 订单 group by 客户id,year(日期)
由于“year(日期)”获取的数据是整数,如果要在它后面加上其它字符,就必须先将其转为字符串形式,这里的CStr就是Access中用于将其它类型数据转为字符的函数,SQLServer使用的则是convert。
生成的查询表如图所示。
● 聚合函数可以用在没有指定分组的select语句中
对于这样的语句,所有的列表达式都必须使用聚合函数,否则会出现错误。例如:
select max(数量) as 最大数量值, sum(单价*折扣*数量) as 总额 from 订单
这样得到的结果就只有一条统计行,因为没有指定分组列。如下写法就是错误的:
select 产品id, max(数量) as 最大数量值, sum(单价*折扣*数量) as 总额 from 订单
● where和group by有着严格的顺序要求,且聚合函数不能用于where中
当一个select语句中既有where又有group by时,where必须写在前面,group by写在后面,这种顺序是有严格要求的,不能搞错!而当需要使用聚合函数作为查询条件时,必须使用having代替where。例如,下面的SQL语句预览窗口:
这是对产品ID为P05的所有记录按照“客户ID”所进行的分组统计。如果希望仅返回总量大于5000的记录,怎么办?可以这样在where中设置吗?
select 客户id,sum(数量) as 总量 from 订单 where 产品ID='P05' and sum(数量)>5000 group by 客户id
结果出现执行错误。这是因为,where中不能使用聚合函数!如要使用聚合函数作为数据查询条件,必须将其以having跟在group by的后面。例如:
select 客户id,sum(数量) as 总量 from 订单 where 产品ID='P05' group by 客户id having sum(数量)>5000
● 分组统计的同时生成小计行
对于SQLServer数据源,在分组依据的后面加上运算符rollup或cube时,还可返回小计行。
例如,按“产品ID”和“客户ID”对订单表中的数量进行汇总统计,三种方式的统计结果如下图:
其中,第一个语句仅仅只是使用group by进行分组,生成的结果中没有任何额外的小计行;第二个语句在分组后面加上了with rollup,生成的结果就会自动按最高级的分组排序,同时加上小计和最后一行的总计;第三个语句在分组后面加上了with cube,则生成的结果在rollup的基础上更进一步,额外又添加了二级分组的小计值。
如果设置的分组标志只有1项,rollup和cube的效果相同,都是在最后增加一个总计行。
需要注意的是,在使用rollup或cube时,不能使用带有distinct的统计函数。例如,统计不同“产品ID”中的不重复客户数量,以下语句是正常的:
select 产品id,count(distinct 客户id) as 客户数量 from 订单 group by 产品id
如果在上述语句的后面加上运算符rollup或cube,执行时将出错。
排序依据order by
这个就比较简单了,只需在指定的排序列名称后面加上排序类型即可。升序为ASC,这也是默认的类型;降序为DESC。指定的排序列可以有多个,多个列之间用半角逗号隔开。
例如,在上面的语句中加上“order by 客户id”或者“order by 客户id asc”都可以按“客户id”列升序排序。排序字段还可以直接使用聚合函数或其它表达式,如下图:
排序中所用到的表达式未必一定要出现在select子句中。如上图,即使将select中的总量列去掉依然可以正常执行。再如,下面的语句尽管在select子句中没有数量列,但仍可得到需要的查询结果:
select 产品id,客户id,单价,折扣 from 订单 order by 数量
甚至还可以这样:
select 产品id,客户id from 订单 order by 单价*折扣*数量
除此之外,还要注意where、group by和order by三者的顺序:当它们同时出现在SQL语句中时,where必须排在第一位,group by其次,order by放在最后。而且,当在select子句中用到top谓词时,最好是和order by一起配合使用,这是因为:只有对排序以后的数据取前N行的值时才有意义。
而当top和order by组合使用时,又会出现一种特殊的情况:如果指定范围内的最后一条记录有多个相同值,那么这些值对应的记录也会被返回,也就是说,最后返回的记录数可能会大于指定的数量。如下图,尽管指定的数量是4个,但最终的返回记录数却是5。原因就在于,用于排序的列“产品ID”的最后一行有相同的两个值:
如果在上述语句中不使用order by,则返回的记录数正常是4条。