SELECT子句用于指定需要在查询返回的结果集中包含的属性(列)。SELECT列表中的表达式可以直接基于正在查询的表的各个列,也可以在此基础上做进一步的处理。例如,在前面的代码中的SELECT列表就包含以下表达式:empid、YEAR(orderdate)、COUNT(*)。如果表达式直接引用了某个列(如empid),那么目标列的名称默认与原始列的名称一样。也可以选择为目标列分配自定义的名称,为此要使用AS子句(例如,empid AS employee_id)。当在表达式中进行了一定的处理(例如,YEAR(orderdate)),或者没有基于原始表的列(如调用CURRENT_TIMESTAMP函数),这时如果不为表达式起一个别名的话,在查询的结果集中就不能拥有列名。在某些情况下,T-SQL允许查询返回没有名称的结果集列,但关系模型不允许这样。所以强烈推荐为诸如YEAR(orderdate)之类的表达式起类似orderyear这样的别名,以便所有的结果列都能拥有名称。就此而言,可以认为查询返回的结果表是符合关系模型的。
除了AS子句,T-SQL还支持另外两种为表达式定义别名的格式。分别是<别名>=<表达式>(别名 等号 表达式)和<表达式> <别名>(表达式 空格 别名)。前者的一个例子是orderyear=YEAR(orderdate),后者的一个例子是YEAR(orderdate) orderyear。后者直接在表达式后面跟一个空格和别名,建议避免使用这种定义别名的方式。
有一点很有趣,如果你不小心忘记在SELECT列表的两个列名之间指定一个逗号,代码也不会失败,相反,SQL Server会认为第二个名称是第一个列名的别名。例如,假设你想写一条查询语句,选择Sales.Orders表的orderid和orderdate列,结果不小心,忘记在两个列名之间加一个逗号,如下所示:
2 FROM Sales.Orders
这一查询语句在语法上是有效的,意思是说你想将orderid列的别名定义为orderdate。得到的输出只有一列包含订单ID的列,其别名为orderdate。要查出这样的bug可能很难,所以最好在写代码时小心谨慎一些。
加上SELECT处理阶段后,到目前为止,我们已经处理了以下查询的各个子句:
2 FROM Sales.Orders
3 WHERE Custid = 71
4 GROUP BY empid, YEAR (orderdate)
5 HAVING COUNT ( * ) > 1 ;
最终由SELECT子句生成查询的结果表。记住,SELECT子句是在FROM、WHERE、GROUP BY,以及HAVING子句后处理的。这意味着对于SELECT子句之前处理的那些子句,在SELECT子句中为表达式分配的别名并不存在。对查询子句正确的逻辑处理顺序不熟悉的程序员,他们经常犯的一个非常典型的错误是在SELECT子句之前处理的子句中引用表达式的别名。以下就是在WHERE子句中使用这种无效引用的一个例子:
2 FROM Sales.Orders
3 WHERE orderyear > 2006 ;
从表面来看,这一查询似乎没什么问题,但如果考虑到列的别名是在SELECT阶段(在WHERE阶段之后的一个处理阶段)才创建的,就会明白在WHERE子句中对别名orderyear的引用是无效的。事实上,运行这一查询,SQL Server将生成以下报错信息:
列名 ' orderyear ' 无效。
解决这一问题的一种方法就是在WHERE子句和SELECT子句中重复使用表达式YEAR(orderdate):
2 FROM Sales.Orders
3 WHERE YEAR (orderdate) > 2006 ;
有趣的是SQL Server能够标识在查询中重复使用的同一表达式(YEAR(orderdate)),所以,只需要计算一次表达式。
在关系模型中,所有操作和关系都基于关系代数和关系(集合)中的结果。在SQL中,情况略有不同,因SELECT查询并不保证返回一个真正的集合(即,由唯一行组成的无序集合)。首先,SQL不要求表必须符合集合的条件。SQL表可以没有键,行也不一定具有唯一性,在这些情况下,表都不是集合,而是多集(multiset)或包(bag)。但即使正在查询的表具有主键、也符合集合的条件,针对这个表的SELECT查询仍然可能返回包含重复行的结果。在描述SELECT查询的输出时,经常会使用结果集(result set)这个术语,不过,结果集并不一定非得严格满足数学意义上的集合条件。例如,即使Orders表是一个集合(因为通过键实施了唯一性约束),对Orders表的查询也可以返回重复的行。为了确保SELECT语句执行结果中行的唯一性,SQL提供的方法就是使用DISTINCT子句来删除重复的行。
SQL支持在SELECT列表中用星号(*)来选择查询表中的所有列,这样就不必显示地将它们全部都列出来,如下所示:
2 FROM Sales.Shippers;
不过,除了很少数的例外,在绝大数情况下,这样使用星号是一种糟糕的编程习惯,我们应该尽可能显示地列出列名。不管分配列名的表达式是在试图引用它的表达式的左边还是右边,在SELECT子句内部也仍然不能够引用同一SELECT子句中创建的别名列。例如,以下查询是无效的:
2 FROM Sales.Orders;
解决这一问题的一种方法就是重复表达式:
2 FROM Sales.Orders;