《MS SQL Server 2000管理员手册》系列——14. 使用 T-SQL 检索数据

14. 使用 T-SQL 检索数据
SELECT 陈述式
使用 T-SQL 函数
SELECT 的其它用法
本章总结
在本章中,您将学习如何使用 Transcat-SQL(T-SQL)的 SELECT 陈述式来检索资料,本章同时涵盖许多在 SELECT 陈述式中使用的选择性子句、搜寻条件和函数。这些元素在您提出查询时有助于您找到真正需要的数据。
SELECT 陈述式
 
尽管 SELECT 陈述式主要用于检索特定的数据,它同时也可以用来分配值给本域变量或呼叫函数,这部分内容会在本章最后的 〈SELECT 的其它用途〉 一节介绍。SELECT 陈述式可以很简单,也可以很复杂-当然能不复杂最好。当您还要再继续检索结果时,尽量使您的 SELECT 陈述式简单化。例如,如果您只需要一个数据表中的两个数据行的数据,那么只须把这两个数据行包括在 SELECT 陈述式中,以减少传回的数据量。
当您决定哪些数据要从哪些数据表回传后,您可以加入其它任何有必要的选项。这些选项包括:使用索引时,指定 WHERE 子句中要包括哪些数据行、指定传回的数据是否需要排序、指定是否只需要回传不同的数据。关于查询最佳化的相关信息,请参阅 第35章 。
让我们从检视 SELECT 陈述式中的不同选项和每个子句的检索范例讲起。本章范例中使用的数据库是 pubs 和 Northwind,这两个数据库在您安装 Microsoft SQL Server 2000 时即已自动建立。您可以使用 SQL Server Enterprise Manager 来检视 pubs 和 Northwind 数据库的数据表,以熟悉这两个数据库。
SELECT 陈述式的语法由几个选择性的子句组成。在大多数的情况下,一个SELECT 陈述式至少包括一条 SELECT 子句和 FROM 子句。这两条子句分别判断哪一个数据行或哪几个数据行的数据需要检索,以及从哪个数据表中检索数据。例如,对于 pubs 数据库的 authors 数据表,一条简单的 SELECT 陈述式如下:
SELECT  au_fname, au_lname
FROM    authors
如果您使用 OSQL 命令数据列 ( 第13章 中介绍的 ),不要忘记使用 GO 命令执行陈述式。要使用 OSQL,SELECT 陈述式的完整的 T-SQL 语法如下:
USE     pubs
SELECT  au_fname, au_lname
FROM    authors
GO
________________________________________
说明
由于关键词并不分大小写,您可以随意使用,但最好尽量保持一致,以便您的语法便于阅读。因此,本书中关键词均采用大写字母。
________________________________________
当您互动地使用 SELECT 陈述式时 (例如使用 OSQL 或 SQL Server 查询 Analyzer ),结果会显示在各数据行中,同时每一个数据行都以标题指明。 (有关 T-SQL 简介、OSQL 和查询 Analyzer 的介绍,请参考 第13章 )
SELECT 子句
 
SELECT 子句由选择性的自变量和您所要求的选取清单组成。 选取清单 (select list)是一个包含 表达式 (expression)或数据行的清单,用以在 SELECT 子句中指明哪些数据行需要回传。下面介绍选择性的自变量和选取清单。
自变量
 
SELECT 子句使用下列两个自变量控制传回的数据数据列:
•   DISTINCT 只传回唯一的资料列。如果选择的清单包括数个数据行,当至少有一数据行的数据值不同时,数据列会被视为是唯一的。如果有两数据列数据相同,它们必定在每一数据行中的值都相同。
 
•   TOP n [PERCENT] 传回结果集合的前 n 数据列。如果指定了PERCENT,那么将只传回前 n 个百分比的资料列数。当使用 PERCENT 时,n 必须是介于 0 到 100 之间的数。如果查询中包括一条 ORDER BY 子句 (ORDER BY 子句将在本章后面 〈ORDER BY子句〉 一节中细述),数据列会先按顺序排数据列,然后从排好的结果中传回前 n 资料列或前百分之 n 资料列。
 
下面的 T-SQL 语法显示了 SELECT 子句范例,它执行了三次,每次使用不同的自变量。第一次查询时使用 DISTINCT 自变量,第二次使用 TOP 50 PERCENT 自变量,第三次使用 TOP 5 自变量。
SELECT  DISTINCT au_fname, au_lname
FROM    authors
GO

SELECT  TOP 50 PERCENT au_fname, au_lname
FROM    authors
GO

SELECT  TOP 5 au_fname, au_lname
FROM    authors
GO
第一次查询传回 23 数据列,每一数据列均为唯一的。第二次传回 12 数据列 (大约为 50%,无条件进入 ),第三次传回 5 资料列。
选取清单
 
如上所述,选取清单是关于表达式或数据行的清单,用于在 SELECT 子句中指定哪些数据行的数据需要回传。表达式可以是数据行名称、函数或常数的清单。选取清单可以包括好几个表达式或数据行名称,彼此之间用逗号隔开。上面的例子使用了以下的选取清单:
au_fname, au_lname
 星号或万用符号 您可以使用星号 (* )或万用符号在选取清单中指定需要传回FROM 子句中指定数据表和检视表的所有数据和数据行。例如,传回 sales 数据表的所有数据行的数据列,可以使用下列查询:
SELECT  *
FROM    sales
GO
本章后面 〈交叉联结〉 一节将讲述当 SELECT * 陈述式的 FROM 子句资料列出了不只一个资料表的情形。
 IDENTITYCOL和ROWGUIDCOL 要从数据表中的识别数据行(Identity Column)中检索数值,可以简单地在选取清单中使用 IDENTITYCOL 表达式。下面是查询 Northwind 数据库的范例,其中 Employess 数据表中定义了识别数据行:
USE     Northwind
GO
SELECT  IDENTITYCOL
FROM    Employees
GO
结果集将如下面所示:
EmployeeID
----------
3
4
8
1
2
6
7
5
9
(影响9个数据列)
请您注意结果集中的数据行标题和数据库中具有 IDENTITY 属性的数据行的名称符合,在本例中是 EmployeeID。
同样的,您可以在选取清单中使用 ROWGUIDCOL 表达式检索资料列的通用唯一识别码(GUID)数据行,即具有 ROWGUIDCOL 属性的数据行。必须是 uniqueidentifier 数据型别的数据行才有 ROWGUIDCOL 属性。
 数据行别名 使用数据行别名可以使结果集中显示的数据行标题换成您希望的样子。使用别名可让输出数据行中的数据意义更清楚,可指派标题给函数中使用的数据行,亦可在 ORDER BY 子句中引用这些别名。
当在多个不同的资料表中拥有同名的数据行,为了易于分辨起见,您可能需要在输出的数据行标题中包含数据表的名称。举一个使用别名的例子。让我们看看 pubs 数据库中的 employee 数据表中的lname数据行。如果您执行以下的查询:
USE     pubs
GO
SELECT  lname
FROM    employee
GO
将会得到如下的结果:
lname
--------
Cruz
Roulet
Devon
...
O'Rourke
Ashworth
Latimer

(影响43个数据列)
要使显示结果中的数据行标题 lname 换成 「 Employee Last Name」,以强调lname的实质意义,可利用 AS 关键词:
SELECT  lname AS "Employee Last Name"
FROM    employee
GO
此命令显示的结果为:
Employee Last Name
------------------
Cruz
Roulet
Devon
...
O'Rourke
Ashworth
Latimer

(影响43个数据列)
您也可以在选取清单中使用其它类型的表达式和数据行别名,并作为 ORDER BY 子句的引用资料行。假设在选取清单中有一个函数被呼叫,您可以使用 AS 关键词来指定一个描述此函数输出的数据行别名。如果函数不用数据行别名,则根本就没有资料行标题。例如,下面的陈述式为 MAX 函数的输出指定了标题 Maximum Job ID:
SELECT  MAX (job_id ) AS "Maximum Job ID"
FROM    employee
GO
此别名上有引号,是因为它由多个字组成,而且之中有空格。如果别名中不包含空格,就可以不用引号。
您可以引用在 SELECT 子句中指定的数据行别名,作为 ORDER BY 子句的自变量。当选取清单中包含一个运算结果需要排序的函数时,这个技巧会十分有用。例如,以下的命令将检索每一间书店的销售量,并且将输出结果依量的大小排序。在选取清单中指派的别名将被应用在 ORDER BY 子句。
SELECT   SUM (qty ) AS Quantity_of_Books, stor_id
FROM     sales
GROUP BY stor_id
ORDER BY Quantity_of_Books
GO
本例中,别名不含空格,因此我们不需为别名加上引号。
如果我们并未替查询的 SUM (qty )数据行指定别名,下面的例子将输出同样的结果,但 sum 数据行则没有标题:
SELECT   SUM (qty ), stor_id FROM sales
GROUP BY stor_id
ORDER BY SUM (qty )
GO
记住,数据行别名可让您分派标题给输出数据行,但是仅止于名称的改变;无论它们为何都不会影响查询的结果。
FROM 子句
 
FROM 子句可内含数据表的名称和用来选择数据的检视表。每个 SELECT 子句都会要求使用 FROM 子句,除非是选取清单内不含数据行名称,而且只有常数、变量、数学表达式。您已经看到一些使用 FROM 子句的例子,不过 FROM 子句也可以内含衍生的数据表、联结(Join)和别名。
衍生数据表
 
 衍生数据表 (derived table)是在 FROM 子句中内含另一个 SELECT 子句的结果集。被内含的 SELECT 子句的结果集被视为一个数据表,在该数据表以外的 SELECT 子句可以选择其数据。下面的查询使用一个衍生数据表寻找商店名称,此商店提供至少一种可能的折扣方式。
USE     pubs
GO
SELECT  s.stor_name
FROM    stores AS s,(SELECT stor_id, COUNT (DISTINCT discounttype )
                        AS d_count
                        FROM discounts
                        GROUP BY stor_id ) AS d
WHERE   s.stor_id = d.stor_id AND
        d.d_count >= 1
GO
如果执行这个命令,会看到某一数据列被选取,表示数据库中仅有一家商店─Bookbeat-提供至少一种折扣。
注意此查询使用速记法来代表数据表名称(s代表stores资料表;d代表discounts资料表)。这种速记法称作 数据表别名 (Table Aliases),本章后面的 〈资料表别名〉 部分将会讨论这点。
________________________________________
说明
在 WHERE 子句中不能有衍生数据表。WHERE 子句中的 SELECT 陈述式是当成搜寻条件。本章后面的 〈WHERE子句及搜寻条件〉 部分将详细介绍这点。
________________________________________
联结资料表
 
 联结数据表 (join table)是两个更多数据表联结操作后产生的结果集。资料表之间可以执行的联结方式有:内部联结、完全外部联结、左外部联结、右外部联结及交叉联结等。下面简述了每一种联结。
 内部联结 内部联结(inner join)为预设的联结形式;它指定只有符合 ON 条件数据表中的数据列才能被包含在结果集中,而不符合的资料列都被排除在外。要指定一个联结,可以使用 JOIN 关键词。使用 ON 关键词是用来定义联结基本的搜寻条件。下面的查询联结了 stores 和 discounts 两个数据表,以显示哪些商店提供了折扣以及折扣的种类(预设为内部联结,表示只有符合 ON 搜寻条件的资料列会被传回)。
SELECT  s.stor_id, d.discounttype
FROM    stores s JOIN discounts d
ON      s.stor_id = d.stor_id
GO
结果集如下:
stor_id  discounttype
-------  -------------------
8042     Customer Discount
您可以看到,只有一家商店提供折扣,而且只有一种折扣。在传回的唯一数据数据列里,它从 stores 数据表中得到的 stor_id 与从 discounts 数据表中得到的 sotr_id 相符合,所以此 stor_id 及它的 discounttype 被传回。
 完全外部联结 完全外部联结(full outer join)指定了不论是符合的数据列(满足 ON 搜寻条件的资料列)或不符合的资料列(不满足 ON 条件的资料列)都被包含在结果集中。对于不符合的数据列,NULL 将显示在不符合的数据行中。在本例中,NULL 即表示商店不提供任何折扣,这样它在 stores 数据表中有 sotr_id 值,但在 discounts 资料表中则没有值。NULL 也代表在 discounts 数据表中此种形式的折扣任何商店都不提供。下面的查询用了与前面内部联结相同的查询,但这次我们要指定完全外部联结:
SELECT  s.stor_id, d.discounttype
FROM    stores s FULL OUTER JOIN discounts d
ON      s.stor_id = d.stor_id
GO
结果集如下:
stor_id  discounttype
-------  ------------------
NULL     Initial Customer
NULL     Volume Discount
6380     NULL
7066     NULL
7067     NULL
7131     NULL
7896     NULL
8042     Customer Discount
结果集显示只有一数据列完全符合。其它数据列则会在某一栏中具有 NULL 值。
 左外部联结 左外部联结(left outer join)传回符合的数据列及指定于 JOIN 关键词左边之数据表的所有数据列。使用与上述相同的查询,在此我们指定左外部联结,如下所示:
SELECT  s.stor_id, d.discounttype
FROM    stores s LEFT OUTER JOIN discounts d
ON      s.stor_id = d.stor_id
GO
结果集如下:
stor_id discounttype
------- ----------------------------------------
6380    NULL
7066    NULL
7067    NULL
7131    NULL
7896    NULL
8042    Customer Discount
结果集中包含符合 ON 条件的一数据列和 stores 数据表中的其它数据列,这些数据列在 discounts 数据表中并没有与之符合的 stor_id(这些资料列的 discounttype 栏为 NULL)。
 右外部联结 右外部联结(right outer join)与左外部联结相反,它传回符合的数据列及指定于 JOIN 关键词右边的数据表中所有的数据列。下面使用同一个查询说明右外部联结:
SELECT  s.stor_id, d.discounttype
FROM    stores s RIGHT OUTER JOIN discounts d
ON      s.stor_id = d.stor_id
GO
结果集如下:
stor_id  discounttype
-------  -------------------
NULL     Initial Customer
NULL     Volume Discount
8042     Customer Discount
结果集显示了符合 ON 条件的数据列和在 discounts 数据表中的其它数据列,这些数据列在 stores 数据表中没有与之符合的 stor_id(其 stor_id 栏为 NULL)。
 交叉联结 交叉联结(cross join)是没有指定 WHERE 子句时,两个数据表的交叉产物。如果存在 WHERE 子句,则交叉联结就与内部联结类似。当没有 WHERE 子句时,所有的栏和数据列将从这两个数据表中以下列方式传回:第一个数据表的每一数据列与第二个数据表的每一数据列相符,所以结果集的数目为第一个数据表的数据列数乘以第二个数据表的数据列数。
为了理解交叉联结,下面将举几个新的例子。先让我们看一个没有 WHERE 子句的交叉联结的情况,然后举三个包含 WHERE 子句的交叉联结的例子。下面的查询是一个简单的范例。执行这三个查询,并且注意每个查询输出的数据列数。
SELECT  *
FROM    stores
GO

SELECT  *
FROM    sales
GO

SELECT  *
FROM    stores CROSS JOIN sales
GO
________________________________________
说明
如果 FROM 子句中包含两个数据表,其作用相当于指定了一个 CROSS JOIN,例如:
SELECT  *
FROM    stores, sales
GO
________________________________________
要避免出现大量杂乱的信息(如果远超过了我们需要的部分),便需要加一个WHERE 子句来缩小查询的范围,如下所述:
SELECT  *
FROM    sales CROSS JOIN stores
WHERE   sales.stor_id = stores.stor_id
GO
该陈述式只传回符合 WHERE 子句中搜寻条件的资料列,将结果缩减到只有 21资料列。WHERE 子句使交叉联结与内部联结的作用相同(即只有满足搜寻条件的数据列被传回)。该查询将传回 sales 数据表中的数据数据列与 stores 数据表中有相同 stor_id 的数据列,这些数据表将集合在一起。不符合的数据列不会被传回。
为了进一步缩减结果集,可以在星号(*)前面加上数据表名称,指定将选择该数据表的所有数据列和数据行,如下面的例子所示。您也可以透过在数据表名称和数据行名称之间插入一点(.)来选定数据表的某一数据行。
SELECT  sales.*, stores.city
FROM    sales CROSS JOIN stores
WHERE   sales.stor_id = stores.stor_id
GO
这个查询传回 sales 数据表中的所有数据行,并附加上 stores 数据表数据列中有同样 stor_id 的 city 资料行。实际上,结果集包含 sales 数据表中符合 stor_id 的数据列以及所附加的商店所在的城市。
以下是不含星号(*)的同一个查询,sales 数据表中仅有 stor_id 数据行被选取。
SELECT  sales.stor_id, stores.city
FROM    sales CROSS JOIN stores
WHERE   sales.stor_id = stores.stor_id
GO
资料表别名
 
我们已经看过了几个使用数据表别名的例子。AS 关键词是选择性的(FROM tablename AS alias 的结果与 FROM tablename alias 是相同的)。让我们再看一下 〈右外部联结〉 部分的查询,这个查询用到了别名:
SELECT  s.stor_id, d.discounttype
FROM    stores s RIGHT OUTER JOIN discounts d
ON      s.stor_id = d.stor_id
GO
这两个数据表中都含有 stor_id 数据行。要区分在查询中所引用的是哪一个数据表中的 stor_id 数据行,必须提供数据表名称或在数据表别名后插入一点(.)再加上数据行名称。在本例中,s 代表 stores 资料表,d代表 discounts 资料表。在指定某一栏时,s或d加在资料行名称前以表示此资料行属于哪一个资料表。包含 AS 关键词的同一个查询如下:
SELECT  s.stor_id, d.discounttype
FROM    stores AS s RIGHT OUTER JOIN discounts AS d
ON      s.stor_id = d.stor_id
GO
INTO 子句
 
现在进入 SELECT 陈述式的第一个真正的选择性子句:INTO 子句。利用 SELECT