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;
