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<select list> INTO<new_tablename > 语法可以从一个或多个数据表中检索数据并将结果数据列输出到一个新的数据表。当执行 SELECT...INTO 陈述式时,新数据表会自动产生并按照选取清单中的数据行定义。新数据表中的每一个数据行与来源数据行有相同的数据型别,并且和选取清单中的数据行名称相同。使用者必须在目标数据库中有 CREAT TABLE 权限,才能执行 SELECT...INTO。在 第30章 中将介绍如何设定权限。
您可以使用 SELECT...INTO 将结果存入暂存性或永久性的资料表中。对于一个本域暂存数据表(只有目前的联机或使用者能看见),必须在数据表明前加上 # 号。对于一个全域暂存数据表(对任何使用者都能看见),必须在数据表前加上两个 # 号(##)。当所有正在使用同一个暂存数据表的使用者都与 SQL 服务器断线时,该暂存数据表将会自动被删除。要选择存入永久性数据表,您无需为数据表名称加前置字,但是目标数据库的 Select Into/Bulk Copy 选项必须选取。要为 pubs 数据库选取这个选项,可以执行以下 SQL 查询:
sp_dboption pubs, "select into/bulkcopy", true
GO
您也可以用 SQL Server Enterprise Manager 来选择这个选项,如下所示:
1. 在 Enterprise Manager 窗口中点选 pubs 数据库并且按右钮,出现快捷菜单后选择 内容 ,可显示 pubs 数据库的属性,如图14-1(这个画面曾在 第九章 出现过,当时我们建立了一个数据库并指定其档案成长选项)。
2. 按一下 选项 卷标页,如图14-2,并在 模型 的下拉式选单中选择 大量登入 。其它选项保持原先的设定。按一下 确定 。
下面的查询使用 SELECT...INTO 建立一个名为 emp_info 的永久数据表,其中包含所有员工的姓名,以及它们的职务说明(在 pubs 数据库内):
SELECT employee.fname, employee.lname, jobs.job_desc
INTO emp_info
FROM employee, jobs
WHERE employee.job_id = jobs.job_id
GO
图14-1 数据库属性窗口的「一般」卷标页
图14-2 数据库属性窗口的「选项」卷标页
emp_info数据表包含了三个数据行名称-fname、lname以及 job_desc,其数据型别与原数据表(employee与 jobs数据表)的定义的数据型别相同。若要使新数据表成为一个本域暂存数据表,必须在数据表名称前加上 # 符号,如 #emp_info;若要成为全域暂存数据表,则使用 ## 符号,如 ##emp_info。
WHERE 子句及搜寻条件
WHERE 子句依据搜寻条件来限定查询所传回的数据列。本节中我们将说月许多可用在搜寻条件的操作。
________________________________________
说明
搜寻条件不只可用在 SELECT 陈述式中的 WHERE 子句,也可用于 UPDATE 以及 DELETE 陈述式。(UPDATE 与 DELETE 陈述式将在 20章 讨论)
________________________________________
首先让我们先复习一些术语。搜寻条件可以透过结合逻辑运算子 AND、OR 和NOT 而包含无限多个述词。 述词 (predicate)是指传回 TRUE、FALSE 和 UNKNOWN 值的表达式。一个 表达式 (expression)可以是数据行名称、常数、纯量函数(传回一个值的函数)、变量、纯量子查询(传回一个数据行的子查询)或是透过运算子结合这些元素所形成的总体。本节中,述词或表达式均以 表达式 这个术语来表达。
比较运算子
表达式中可使用的等式与不等式运算子数据列于表14-1。
表14-1 比较运算子
运算子 检验的条件
= 检验两个表达式是否相等
<> 检验两个表达式是否不等
!= 检验两个表达式是否不等(同<>)
> 检验前一个表达式是否大于后一个表达式
>= 检验前一个表达式是否大于或等于后一个表达式
!> 检验前一个表达式是否不大于后一个表达式
< 检验前一个表达式是否小于后一个表达式
<= 检验前一个表达式是否小于或等于后一个表达式
!< 检验前一个表达式是否不小于后一个表达式
一个简单的 WHERE 子句可能利用等于运算子(=)来比较两个表达式。例如,下面的 SELECT 陈述式可用来检验每数据列 lname 数据行中数据型别为 char 的值,如果为 Latimer,则传回 TRUE(传回 TRUE 的资料列将被包含在结果集)。
SELECT *
FROM employee
WHERE lname = "Latimer"
GO
本例的查询将传回一数据列。Latimer必须加上引号,因为它是字符串。
________________________________________
说明
SQL Server 预设接受单引号(')与双引号("),例如 'Latimer'或 "Latimer"。本书的范例只使用双引号来避免混淆。您可以使用 SET QUOTED_IDENTIFIER 选项来允许保留关键词当作对象名称,并只使用单引号。将该选项设定为 ON(OFF为默认值)。
________________________________________
下面的查询使用不等于运算子(<>),数据行为 integer 数据型别的 job_id。
SELECT job_desc
FROM jobs
WHERE job_id <> 1
GO
这个查询将传回在jobs 数据表中 job_id 不等于1的数据列中的说明文字。本例中有13资料列被传回。如果某数据列中有 NULL 值,它不等于1也不等于其它值,故此数据列也将被传回。
逻辑运算子
逻辑运算子 AND 和 OR 测试两个表达式,并根据结果传回一个 TRUE、FALSE 或 UNKNOWN 的布尔值。NOT 逻辑运算子会传回与表达式相反的结果。图14-3中数据列出了 AND、OR 与 NOT 运算子传回的每种可能值。AND 与 OR 表格中,左边的数据行是第一个表达式的值,最上面的数据列是第二个表达式的值,两者交叉的表格中即是结果的布尔值。NOT 表格则更直接一些。UNKNOWN 是表达式中有 NULL 值所传回的结果。
图14-3 AND、OR 与 NOT 的运算结果
下面的查询是在 WHERE 子句中对两个表达式运用 AND 逻辑运算子例子:
SELECT job_desc, min_lvl,max_lvl
FROM jobs
WHERE min_lvl >= 100 AND
max_lvl <= 225
GO
如图14-3,要让 AND 运算传回 TRUE,必须两个条件均为 TRUE。在这个查询中,有 4 数据列被传回。
下一个查询中,一个 OR 运算将检测出版商是否位于华盛顿 D.C.或位于麻州。若两个检验中只要有一个值为 TRUE,则此数据列将被传回。
SELECT p.pub_name, p.state, t.title
FROM publishers p, titles t
WHERE p.state = "DC" OR
p.state = "MA" AND
t.pub_id = p.pub_id
GO
此查询传回 23 数据列。
NOT 运算单纯的传回其后布尔表达式的相反值。例如,要查询所有的作者版税不少于 20% 的书籍名称,您可以下列方法来运用 NOT 运算子:
SELECT t.title, r.royalty
FROM titles t, roysched r
WHERE t.title_id = r.title_id AND NOT
r.royalty < 20
GO
其它关键词
除了以上这些运算子,还有多种 T-SQL 的关键词可用于搜寻条件。本节将解释最常用的一些关键词,也会给予范例说明。
LIKE LIKE 关键词在搜寻条件中指定模式比对。 模式比对 (Pattern matching)是在一个比对表达式与搜寻条件中指定的模式之间进行的比对检测,语法如下:
<match_EXPRESSION> LIKE <pattern>
若比对表达式符合模式,则传回 TRUE 的布尔值。反之则传回 FALSE。对比运算是必须是字符串数据型别。如果不是的话,SQL Server 会尽可能将其自动转换为字符串数据型别。
模式是真正的字符串表达式。 字符串表达式 定义为一个由字符或万用字符组成的字符串。 万用字符 是在字符串表达式中代表特殊意义的字符。表14-2数据列出了模式中可用的万用字符:
表14-2 T-SQL的万用字符
万用字符 说明
% 百分比符号;与零个或若干个字符比对
_ 底线;与任何一个单一字符比对
[ ] 范围万用字符,与指定范围或集合中的单一字符比对,例如 [m-p] 或 [mnop] 表示m、n、o、p中的任一字符。
[^] 不在范围内万用字符;与不包括在该范围或集合中的任一个字符比对,例如 [^m-p] 或 [^mnop] 表示除了m、n、o、p以外的其它任一字符。
要进一步了解 LIKE 关键词与万用字符,请看面几个例子。若要在 authors资料表中寻找以 S 为开头的作者姓氏,您可以使用以下含有 % 万用字符的查询:
SELECT au_lname
FROM authors
WHERE au_lname LIKE "S%"
GO
结果将为:
au_lname
-----
Smith
Straight
Stringer
这个查询中,S% 表示传回以 S 开头,后接任何字符的姓氏。
________________________________________
说明
本范例假设您使用的是预设的按字典顺序排序,且不区分大小写。若指定其它的排序方法,您的结果可能不同,但使用 LIKE 关键词的原理是相同的。
________________________________________
要搜寻 ID 以 724 开头的作者信息,若已知每个 ID 的格式就像社会安全号码(3个数字-2个数字-4个数字),您可使用底线(_)万用字符,如下所示:
SELECT *
FROM authors
WHERE au_id LIKE "724-_ _-_ _ _ _"
GO
结果集将包含两资料列,au_id 值为 724-08-9931 与 724-80-9391。
现在让我们看一个使用 [ ] 万用字符的例子。要搜寻开头为 A 到 M 之间的任一字母的作者姓氏,您可以组合 [ ] 及 % 万用字符:
SELECT au_lname
FROM authors
WHERE au_lname LIKE "[A-M]%"
GO
结果集将包含有以 A-M 开头的名字的 14 数据列。(如果使用区分大小写的排序,则得到 13 数据列。 )
如果用 [^] 万用字符取代 [ ] 万用字符执行类似的查询,将得到除了 A 到 M以外字母为开头的姓氏,如下所示:
SELECT au_lname
FROM authors
WHERE au_lname LIKE "[^A-M]%"
GO
此查询传回 9 数据列。
如果使用区分大小写的排序,但要得到符合条件但不区分大小写的所有名称,可以使用一个检验第一个字母为小写或大写的查询,如下所示:
SELECT au_lname
FROM authors
WHERE au_lname LIKE "[A-M]%" OR
au_lname LIKE "[a-m]%"
GO
结果集包含名称 del Castillo,如果仅用区分大小写的查询方式,则得不到这个结果。
LIKE 关键词也可放在 NOT 运算子之后。NOT LIKE 传回不符合搜寻条件的资料列。例如,要找出不以 The 开头的书名,您可以使用 NOT LIKE,如下列查询所示:
SELECT title
FROM titles
WHERE title NOT LIKE "The %"
GO
此查询将传回 15 数据列。
您可以很有创意的使用 LIKE 关键词。不过要小心的测试您的查询以便确定传回的数据是您真正需要的部分。如果遗漏了原本应该包含的 NOT 或 ^ 字符,得到的结果集会刚好与您期望的值相反。遗漏了原本必须的 % 字符也会得到错误的结果,您还必须注意前后空格的使用。
ESCAPE ESCAPE 关键词使您能为万用字符本身进行模式比对,例如 ^、%、[ 以及 _。在 ESCAPE 关键词之后接着您指定要避开的字符,代表字符串表达式中的这些字符必须按字面意义逐字比对。例如,在 titles数据表中要找出 title 数据行中有下底线的所有资料列,您可以使用下列查询:
SELECT title
FROM titles
WHERE title LIKE "%e_%" ESCAPE "e"
GO
此查询将没有数据列被传回,因为数据库中并没有任何一本书名含有下底线。
BETWEEN BETWEEN 关键词通常与 AND 一起使用来为搜寻条件指定包括的范围。语法如下:
<test_expression> BETWEEN <begin_ expression> AND <end_ expression>
当 test_ expression 表达式大于或等于 begin_ expression 表达式并且小于或等于 end_ expression表达式 时,搜寻条件的结果为 TRUE 布尔值;否则结果为 FALSE。
下面的查询使用 BETWEEN 关键词来找出价格在 5 到 25 美元之间的书名:
SELECT price, title
FROM titles
WHERE price BETWEEN 5.00 AND 25.00
GO
此查询将传回 14 数据列。
您也可以将 NOT 与 BETWEEN 一起使用,找出不在指定范围内的数据列。例如,寻找价格不在 20 到 30 美元的书名(亦即价格小于 20 美元或大于 30 美元),您可以使用下列查询:
SELECT price, title
FROM titles
WHERE price NOT BETWEEN 20.00 AND 30.00
GO
当您使用 BETWEEN 关键词,test_expression 表达式必须与begin_expression 表达式及 end_expression 表达式有相同的数据型别。
在刚刚的范例中,price数据行的数据型别为 money,因此 begin_ expression 表达式与 end_ expression 表达式必须是一个可以被与之比较或被 隐含转换 (implicit conversion)为 money 数据型别的数字。您不可在 test_ expression 表达式使用价格却在 begin_ expression 表达式与 end_ expression 表达式使用字符串或 char 数据型别;否则 SQL Server 将传回一个错误讯息。
________________________________________
说明
如果隐含转换是可行的话,SQL Server 会在需要时自动的转换数据型别。隐含转换能自动的将数据型别转换为另一个能兼容的数据型别。转换后便可进行比较。例如,把一个 smallint 数据型别的数据行与一个 int 数据型别的数据行作比较时,SQL Server 会在比较前将 smallint 隐含转换为 int 数据型别。如果没有提供隐含转换,您可以使用 CAST 或 CONVERT 函数来明确的指定转换数据行。关于 SQL Server 中哪些数据型别可隐含转换、哪些数据型别需外显转换,可于 SQL Server《在线丛书》索引「CAST」,并在 找到的主题 对话框中选择 CAST与CONVERT 。
________________________________________
最后一个与 BETWEEN 关键词相关的例子,是在搜寻条件中使用字符串。要找出从 Bennet 到 McBadden 之间按字母顺序排数据列的作者姓氏,您可以使用下列查询:
SELECT au_lname
FROM authors
WHERE au_lname BETWEEN "Bennet" AND "McBadden"
GO
BETWEEN 的范围会包含起始值和结束值,因此存在于数据表中的 Bennet 与 McBadden 也会包括在查询结果之内。
IS NULL IS NULL 关键词用来在搜寻条件中选择在指定数据行中有 NULL 值的数据列。例如,在 titles数据表寻找 notes数据行没有数据(亦即 notes值为 NULL)的书名,您可以使用下列查询:
SELECT title, notes
FROM titles
WHERE notes IS NULL
GO
结果将为:
title notes
------------------------------------ ------
The Psychology of Computer Cooking NULL
如您所见,notes 资料行的 NULL 值在结果集中显示为 NULL。NULL 并不是资料行中的实际值-它仅指出资料行中存在 NULL 值。
要找到 notes数据行有数据的书名 (notes 值不为 null 的书名 ),使用 IS NOT NULL,如下所示:
SELECT title, notes
FROM titles
WHERE notes IS NOT NULL
GO
结果集显示有 17 数据列其 notes数据行内至少有一个字符,所以其 notes数据行中没有 null 值。
IN IN 关键词在搜寻条件中用来判断给定的测试表达式是否符合子查询或值清单中的任一值。如果找到一个相符的情况, TRUE 将被传回。
NOT IN 传回 IN 的相反结果,因此,如果测试表达式在子查询数值清单中没有找到,TRUE 将被传回。语法如下:
<test_expression> IN (<subquery>)
或
<test_expression> IN (<list of values>)
子查询 (subquery)是一个结果集中只传回一个资料行的 SELECT 陈述式。子查询必须被包含在小括号内。 值清单 也是如此,这些清单被包含在小括号内,并以逗号分开。从子查询或值清单所得的数据行,其数据型别必须与 test_expression 表达式相同。必要时 SQL Server 会执行隐含转换。
您可以使用 IN 关键词和值清单找到3个特定工作说明的 job_id 数字,查询如下:
SELECT job_id
FROM jobs
WHERE job_desc IN ("Operations Manager",
"Marketing Manager",
"Designer" )
GO
此查询的值清单为:("Operations Manager"、"Marketing Manager"、"Designer" ),为传回 job ID 的条件,必须是数据列中 job_desc 数据行中的值为前述三个之一。比起使用两个 OR 运算子来完成查询,IN 关键词可以让您的查询易读易懂,如下所示:
SELECT job_id
FROM jobs
WHERE job_desc = "Operations Manager" OR
job_desc = "Marketing Manager" OR
job_desc = "Designer"
GO
下面的查询在一个陈述式中使用 IN 关键词两次-一次针对子查询,另一次针对子查询中的值清单:
SELECT fname, lname --Outer query
FROM employee
WHERE job_id IN ( SELECT job_id --Inner query, or subquery
FROM jobs
WHERE job_desc IN ("Operations Manager",
"Marketing Manager",
"Designer"))
GO
子查询的结果集将先被找到-本例中,它是一个 job_id 值的集合。此子查询产生的 job_id 值的集合并不会传回到屏幕上,外层的查询将把它当作表达式一般运用在自己的 IN 搜寻条件。最后的结果集将包含所有职业头衔为 Operations Manager、Marketing Manager 或 Designer 的员工姓名。结果集如下:
fname lname
-------------------- ----------------------
Pedro Afonso
Lesley Brown
Palle Ibsen
Karin Josephs
Maria Larsson
Elizabeth Lincoln
Patricia McKenna
Roland Mendel
Helvetius Nagy
Miguel Paolino
Daniel Tonini
(影响11个数据列)
IN 也可与 NOT 运算子一起使用。例如,要传回所有不位在加州、德州或伊利诺伊州的出版商姓名,请执行以下查询:
SELECT pub_name
FROM publishers
WHERE state NOT IN ("CA",
"TX",
"IL")
GO
查询将传回 state 数据行值不为值清单所数据列三州之一的五个资料列。如果您的数据库选项 ANSI nulls 设定为 ON ,结果将只传回三数据列。这是因为原始结果集中的五数据列里有两数据列的 state值为 NULL,而当 ANSI nulls 设定为 ON 时 NULL 是不会被选取。
要判断 pubs 数据库中您的 ANSI nulls 设定为何,可以执行以下的系统预存程序:
sp_dboption "pubs", "ANSI nulls"
GO
如果 ANSI nulls 设定为 OFF ,可使用以下陈述式将其改为 ON :
sp_dboption "pubs", "ANSI nulls", TRUE
GO
要将 ON 改为 OFF ,以 FALSE 代替 TRUE 。
EXISTS EXISTS 关键词用来检测其后的子查询中的数据数据列的存在性。语法如下:
EXISTS (<subquery>)
如果有任何数据列满足此一子查询,TRUE 将被传回。
要选择曾经出版过书籍的作者姓名,您可使用以下查询:
SELECT au_fname, au_lname
FROM authors
WHERE EXISTS (SELECT au_id
FROM titleauthor
WHERE titleauthor.au_id = authors.au_id )
GO
存在于 authors资料表,但没有出版任何书(数据列于 titleauthor 数据表)的作者姓名将不会被选择。如果在子查询中没有任何数据列被选择,交给外层查询使用的结果集为空。(零数据列被选择)
CONTAINS 与 FREETEXT CONTAINS 与 FREETEXT 关键词用来在基于字符的数据型别的数据行中进行全文检索。它们比 LIKE 关键词提供更大的弹性。例如,CONTAINS 关键词让您可以搜寻与指定字符或词组不完全相符但类似的文字(是一种 模糊 (fuzzy)比对)。FREETEXT 允许您搜寻与指定字符串部份或全部相符(或模糊比对)的若干文字。这些文字不需要完全符合指定的搜寻字符串,也不需要与字符串中的文字有相同的顺序。这两个关键词有多种用途并对全文检索提供了不少选项,不过这些主题已超过本章的讨论范围。
________________________________________
相关信息
关于 CONTAINS 与 FREETEXT 关键词的相关信息,可于 SQL Server《在线丛书》索引「CONTAINS」与「FREETEXT」。
________________________________________
GROUP BY 子句
GROUP BY 子句用于 WHERE 子句之后,指示结果集中的数据列将按照指定的群组数据行来群组化。如果 SELECT 子句使用到汇总函数,则每个群组都会计算一个总计值并显示于输出结果。( 汇总函数 (aggregate function)执行计算并传回值;这些函数将在本章稍后 〈汇总函数〉 一节里有详细说明。)
________________________________________
说明
选取清单中的每个数据行-除了汇总函数使用的数据行-必须在 GROUP BY 子句中指定为群组数据行;否则 SQL Server 将传回一个错误讯息。如果不遵循此一规则(指定的 GROUP BY 数据行必须群组选取清单中的每个数据行),输出结果将无法以合理的方式显示。
________________________________________
当 SELECT 子句含有汇总函数时,GROUP BY 最为有用。让我们看一下用GROUP BY 子句寻找每种书籍销售总量的 SELECT 陈述式:
SELECT title_id, SUM (qty )
FROM sales
GROUP BY title_id
GO
结果集如下:
title_id
-------- ------------
BU1032 15
BU1111 25
BU2075 35
BU7832 15
MC2222 10
MC3021 40
PC1035 30
PC8888 50
PS1372 20
PS2091 108
PS2106 25
PS3333 15
PS7777 25
TC3218 40
TC4203 20
TC7777 20
(影响16个数据列)
此查询没有 WHERE 子句-您并不需要。结果集显示了一个 title_id 数据行以及一个没有标题的摘要数据行。对每个不同的 title_id,各个书籍的总销售量显示在摘要数据行。例如,title_id 为 BU1032的书籍在 sales数据表出现了两次-一次在 qty数据行显示销售了 5 本, 第二次在另一个订单中销售了 10 本。SUM 汇总函数总和这两次的销售得出 15 本的总量,并显示于摘要数据行。要为摘要数据行加上一个标题,可使用 AS 关键词:
SELECT title_id, SUM(qty) AS "Total Sales"
FROM sales
GROUP BY title_id
GO
现在结果集中的摘要资料行标题为 Total Sales:
title_id Total Sales
-------- ------------
BU1032 15
BU1111 25
BU2075 35
BU7832 15
MC2222 10
MC3021 40
PC1035 30
PC8888 50
PS1372 20
PS2091 108
PS2106 25
PS3333 15
PS7777 25
TC3218 40
TC4203 20
TC7777 20
(影响16个数据列)
您可以在 GROUP BY 子句中包含一个以上的数据行让群组巢状化。 巢状群组 (nesting groups)意味着结果集将以每个群组数据行被指定的顺序来进行群组化的工作。例如,要找出已经依类型及出版商群组化的书籍的平均价格,可执行下列查询:
SELECT type, pub_id, AVG(price ) AS "Average Price"
FROM titles
GROUP BY type, pub_id
GO
结果集如下:
type pub_id Average Price
------------ ------ ------------------------
business 0736 2.99
psychology 0736 11.48
UNDECIDED 0877 NULL
mod_cook 0877 11.49
psychology 0877 21.59
trad_cook 0877 15.96
business 1389 17.31
popular_comp 1389 21.48
(影响8个数据列)
注意心理学和商业类型的书籍出现了不只一次,因为它们按照不同的出版商 ID分组。UNDECIDED 类型的 NULL 值表示数据表中没有此种类型的价格,因此也没有计算平均值。
GROUP BY 子句提供了一个选择性的关键词 ALL,用以指定结果集中将包含所有群组,即使它们可能不符合搜寻条件。没有数据列符合搜寻条件的群组将会在摘要数据行出现 NULL 以便于辨别。例如,要显示版税为 12% 的书籍的平均价格(以及其它条件不符的书籍,其摘要数据行为 NULL),并将这些书籍先以类型再以出版商 ID 来群组化,执行下列查询:
SELECT type, pub_id, AVG(price) AS "Average Price"
FROM titles
WHERE royalty = 12
GROUP BY ALL type, pub_id
GO
结果集如下:
type pub_id Average Price
------------ ------ ------------------------
business 0736 NULL
psychology 0736 10.95
UNDECIDED 0877 NULL
mod_cook 0877 19.99
psychology 0877 NULL
trad_cook 0877 NULL
business 1389 NULL
popular_comp 1389 NULL
(影响8个数据列)
所有类型的书籍都在输出中显示,版税不为 12% 的书籍则出现了 NULL 值。
如果我们不使用关键词 ALL,结果集将只包含版税为 12% 的书籍类型:
type pub_id Average Price
------------ ------ ------------------------
psychology 0736 10.95
mod_cook 0877 19.99
(影响2个数据列)
GROUP BY 子句常与 HAVING 子句一起使用,我们将在下面讨论。
HAVING 子句
HAVING 用来为一个群组或一个汇总函数指定搜寻条件。HAVING 通常使用在GROUP BY 子句之后,主要用途在于对一个已经被群组化的结果指定要检测的搜寻条件。如果搜寻条件是使用在群组化之前,则 WHERE 子句要比 HAVING 更有效率。HAVING 技巧减少了必须群组化的数据列数。如果没有 GROUP BY 子句,HAVING 只能与选取清单的汇总函数一起使用。在这种情况下,HAVING 子句与 WHERE 子句的作用相同。如果 HAVING 不是用在这些情况下,SQL Server 将传回错误信息。
HAVING 子句的语法如下:
HAVING <search_condition>
这里的 search_condition 与在本章前面 〈WHERE子句和搜寻条件〉 部分所讲的搜寻条件具有相同的意义。HAVING 子句和 WHERE 子句不同在于 HAVING 子句可以在搜寻条件中包含汇总函数,而 WHERE 子句不行。
________________________________________
说明
您可以在 SELECT 和 HAVING 子句中使用汇总函数,但在 WHERE 子句不能。
________________________________________
下面的查询用 HAVING 子句选择平均价格超过 15 美元的出版商的书籍种类:
SELECT type, pub_id, AVG (price ) AS "Average Price"
FROM titles
GROUP BY type,pub_id
HAVING AVG(price ) > 15.00
GO
结果集如下:
type pub_id Average Price
------------ ------ ------------------------
psychology 0877 21.59
trad_cook 0877 15.96
business 1389 17.31
popular_comp 1389 21.48
(影响4个数据列)
您也可以将逻辑运算子与 HAVING 子句一起使用。加上 AND 运算子的查询如下:
SELECT type, pub_id, AVG (price ) AS "Average Price"
FROM titles
GROUP BY type, pub_id
HAVING AVG(price) >= 15.00 AND AVG(price ) <= 20.00
GO
结果集如下:
type pub_id Average Price
------------ ------ ------------------------
trad_cook 0877 15.96
business 1389 17.31
(影响2个数据列)
您也可以用 BETWEEN 子句代替 AND,得到的结果是相同的:
SELECT type, pub_id, AVG(price) AS "Average Price"
FROM titles
GROUP BY type, pub_id
HAVING AVG (price ) BETWEEN 15.00 AND 20.00
GO
若您在使用 HAVING 时不用 GROUP BY 子句,则在选取清单与 HAVING 子句里必须包含一个汇总函数。例如要选取 mod_cook 类型的总金额大于 20 美元的各种书籍,可执行以下查询:
SELECT SUM(price)
FROM titles
WHERE type = "mod_cook"
HAVING SUM(price) > 20
GO
如果将表达式 SUM(price) > 20 置于 WHERE 子句中,SQL Server 将会传回错误信息(在 WHERE 子句中不允许使用汇总函数)。
________________________________________
说明
记住,使用 HAVING 子句是为检测 GROUP BY 子句所得到的结果集而加上一个搜寻条件,或用来检测一个汇总函数。其它的情况应该用 WHERE 子句指定搜寻条件,那样将更有效率而且避免 SQL Server 发生错误。
________________________________________
ORDER BY 子句
ORDER BY 子句用来指定结果集里数据列的排序方式。您可以用 ASC 或DESC 选项来指定是升幂(从小到大)或是降序(从大到小)。当没有选定时,预设为升幂。您可以在 ORDER BY 子句中指定很多数据行,结果将按第一个数据行来排序,如果第一个数据行有重复值,就按第二个数据行排序,依此类推。在后面的内容中可以看到,如果让 ORDER BY 与 GROUP BY 共同使用,这种排序将更有意义。首先让我们看一个在 ORDER BY 子句使用一个数据行,按作者姓氏来升幂排序的例子:
SELECT au_lname, au_fname
FROM authors
ORDER BY au_lname ASC
GO
结果集将按姓的字母顺序排列。记住若在安装 SQL Server 时设定 排序顺序区分大小写 ,将会对例如 del Castillo 这样的姓氏产生影响。
如果要排序的结果数据行不只一个,只要加上这些数据行的名称,并在 ORDER BY 子句中用逗号区隔即可。下面的查询选择 job_id 和员工的姓名然后将它们先按姓氏再按名字排序:
SELECT job_id, lname, fname
FROM employee
ORDER BY job_ifd, lname, fname
GO
结果集如下:
job_id lname fname
------ ------------------------------ -------------
2 Cramer Philip
3 Devon Ann
4 Chang Francisco
5 Henriot Paul
5 Hernadez Carlos
5 Labrune Janine
5 Lebihan Laurence
5 Muller Rita
5 Ottlieb Sven
5 Pontes Maria
6 Ashworth Victoria
6 Karttunen Matti
6 Roel Diego
6 Roulet Annette
7 Brown Lesley
7 Ibsen Palle
7 Larsson Maria
7 Nagy Helvetius
... ...
13 Accorti Paolo
13 O'Rourke Timothy
13 Schmitt Carine
14 Afonso Pedro
14 Josephs Karin
14 Lincoln Elizabeth
(影响43个数据列)
在这个查询的结果集中看不出依照名称排序,这是因为在同一个 job_id 中没有同样的两个姓氏。
现在让我们看一下如何将 ORDER BY 字句和 GROUP BY 子句及汇总函数一起使用:
SELECT type, pub_id, AVG(price) AS "Average Price"
FROM titles
GROUP BY type, pub_id
ORDER BY type
GO
结果集如下:
type pub_id Average Price
------------ ------ ------------------------
UNDECIDED 0877 NULL
business 0736 2.99
business 1389 17.31
mod_cook 0877 11.49
popular_comp 1389 21.48
psychology 0736 11.48
psychology 0877 21.59
trad_cook 0877 15.96
(影响8个数据列)
查询结果将按书籍类型的字母顺序(升幂)来排列。并请注意在这个查询中,type和 pub_id 必须包括在 GROUP BY 子句中,因为它们不是汇总函数的一部分。如果在 GROUP BY 子句中没有包含 pub_id 数据行,SQL Server 将显示与图14-4类似的错误讯息。
在 ORDER BY 子句中不能使用汇总函数或子查询,但如果在 SELECT 子句中给汇总函数一个别名,就可以在 ORDER BY 子句中使用,如下所示:
SELECT type, pub_id, AVG(price) AS "Average Price"
FROM titles
GROUP BY type, pub_id
ORDER BY "Average Price"
GO
结果集如下:
type pub_id Average Price
------------ ------ ------------------------
UNDECIDED 0877 NULL
business 0736 2.99
psychology 0736 11.48
mod_cook 0877 11.49
psychology 0877 21.59
trad_cook 0877 15.96
business 1389 17.31
popular_comp 1389 21.48
(影响8个数据列)
图14-4 在GROUP BY 子句中不包含 pub_id 时显示的错误讯息
现在,结果是按平均价格排序。NULL 值被认为最低,因此它排在最上面。
________________________________________
说明
ORDER BY 子句检索的实际结果将依 SQL Server 设定的排序顺序而定。
________________________________________
UNION 运算子
UNION 被认为是一个运算子,而不是一个子句。它用来将两个以上的查询结果合并到一个结果集中。使用 UNION 时必须遵循以下两个规则:
• 所有的查询有相同的数据行数目。
• 每个查询所对应的数据行,数据型别必须能彼此兼容。
在 SELECT 子句中用 UNION 联合的数据行将以下列方式相互对应:第一个SELECT 子句的第一数据行与其后的 SELECT 子句的第一数据行相对应,第二数据行与其后 SELECT 中的第二数据行相对应,以此类推。如此,由 UNION 联合的每个 SELECT 子句有相同的数据行数,且一一对应。
另外,对应的数据行必须要有兼容的数据型别,即两个对应的数据行要不是有相同的数据型别,就是 SQL Server 能隐含转换其中一个。下面的例子用 UNION 联合两个 SELECT 陈述式的结果集,该陈述式用于在 publisher 和 stores 数据表中检索 city和 state数据行:
SELECT city, state
FROM publishers
UNION SELECT city, state
FROM stores
GO
结果集如下:
city state
-------------------- ------
Fremont CA
Los Gatos CA
Portland OR
Remulade WA
Seattle WA
Tustin CA
Chicago IL
Dallas TX
Munchen NULL
Boston MA
New York NY
Paris NULL
Berkeley CA
Washington DC
(影响14个数据列)
________________________________________
说明
此查询的输出顺序可能和您使用的 SQL Server 排序顺序而有不同。
________________________________________
在 publisher 和 stores 数据表中,city 和 state 两数据行有相同的数据型别(char);这样就不需要转换数据型别。UNION 结果集的资料行标题取自第一个 SELECT 陈述式。如果要为标题建立别名,请将其放在第一个 SELECT 陈述式中,如下所示:
SELECT city AS "All Cities", state AS "All States"
FROM publishers
UNION SELECT city, state
FROM stores
GO
结果集如下:
All Cities All States
-------------------- ------------
Fremont CA
Los Gatos CA
Portland OR
Remulade WA
Seattle WA
Tustin CA
Chicago IL
Dallas TX
Munchen NULL
Boston MA
New York NY
Paris NULL
Berkeley CA
Washington DC
(影响14个数据列)
您并不需要在 UNION 的全部两个 SELECT 子句中使用相同的数据行。例如,可以选择 stores 数据表中的 city 和 state 数据行,选择 publishers 数据表中的 city 和 country 数据行,如下所示:
SELECT city, country
FROM publishers
UNION SELECT city, state
FROM stores
GO
结果集如下:
city country
-------------------- ---------
Fremont CA
Los Gatos CA
Portland OR
Remulade WA
Seattle WA
Tustin CA
New York USA
Paris France
Boston USA
Munchen Germany
Washington USA
Chicago USA
Berkeley USA
Dallas USA
(影响14个数据列)
注意在这个结果集中,前六列来自对 stores 数据表的查询,而后八列则是对publishers 数据表的查询。state 数据行的数据型别为 char;country数据行的数据型别为 varchar。因为数据型别必须兼容,SQL Server 将执行隐式数据型别转换,使两数据行都变为 varchar 型别。数据行标题为city和country,它们是由第一个 SELECT 子句中的数据行提供的。但在结果集中,country 数据行标题若为 State or Country 会更好一些。
有一个可与 UNION 共同使用的选择性关键词:ALL。如果指定了 ALL,所有相同的数据列都将会显示在结果集中,换句话说,所有的资料列都被包含。如果没有指定 ALL,默认值是在结果集中去除重复的列。
您仅能在最后一个 UNION 的陈述式中使用 ORDER BY,而不能在每个SELECT 陈述式中使用,这个限制保证了最终的结果集将对所有的结果只排序一次。另一方面,只能在个别的陈述式中使用 GROUP BY 和 HAVING,它们只影响各自的结果集但不能影响最终的结果集。下面的 UNION 联合了两个各含有一个 GROUP BY 子句的 SELECT 陈述式所传回的结果。
SELECT type, COUNT(title) AS "Number of Titles"
FROM titles
GROUP BY type
UNION SELECT pub_name, COUNT(titles.title)
FROM publishers, titles
WHERE publishers.pub_id = titles.pub_id
GROUP BY pub_name
GO
结果集如下:
type Number of Titles
---------------------------------------- ----------------
Algodata Infosystems 6
Binnet & Hardley 7
New Moon Books 5
psychology 5
mod_cook 2
trad_cook 3
popular_comp 3
UNDECIDED 1
business 4
(影响9个数据列)
结果集显示每一个出版商实际出版书的项目数(前三列),并显示每一种类别书籍的项目数。注意在每个各自的查询中使用 GROUP BY。
UNION 也可以使用于多于两个的 SELECT 陈述式。当您建立 UNION 时,注意确认过这些查询的所有数据行及数据型别正确相符。
________________________________________
相关信息
请参阅在 SQL Server《在线丛书》中与所有可用的 T-SQL 关键词和参数的相关文件。
________________________________________
使用 T-SQL 函数
现在您已经对 SELECT 陈述式的基本子句相当熟悉了( 第20章 中将介绍更多),让我们看一下可以用在 SELECT 子句中的 T-SQL 函数,这样您就能在建立查询时有更大的弹性。这些函数有以下几类:组态、数据指针(cursor)、日期和时间、安全性、中断数据、系统、系统统计、文字与影像、数学、数据列集(rowset)、字符串及汇总函数。这些函数可以执行计算、转换或其它的一些操作,或传回一定的信息。您能够使用的函数有很多,但在本节里我们只详细讨论常用的汇总函数。
________________________________________
相关信息
若要得到以上这些种类及未列出种类的函数的信息,您可以在在线丛书的索引签页中搜寻「函数」。
________________________________________
汇总函数
如前所述,汇总函数针对一群值进行计算并传回一个单一值。它们可以在选取清单中指定,但大部分情况下是用于陈述式中包含 GROUP BY 子句的时候。前述的一些查询范例使用了汇总函数 AVG 和 COUNT。资料表14-3列出了可用的汇总函数。
表14-3 汇总函数
函数 说明
AVG 传回表达式中除了NULL值以外的值的平均数。
COUNT 传回表达式中的项目数(与列数相同)。
COUNT_BIG 类似 COUNT 函数。其间差异在于传回 bigint 数据型别的计数值,而非 int资料型别。
GROUPING 传回一个额外的特殊数据行。仅在 GROUP BY 子句中包含 CUBE或 ROLLUP 运算子时使用。
MAX 传回表达式中的最大值。
MIN 传回表达式中的最小值。
STDEV 传回表达式中所有值的统计标准差。此函数假定计算中使用的值为母体的一个抽样样本。
STDEVP 传回表达式所有值的母体标准差。此函数假定计算中使用的值为统计母体。
SUM 传回表达式中所有值的和。
VAR 传回表达式所有值的统计变异数。此函数假定计算中使用的值为母体的一个抽样样本。
VARP 传回表达式中所有值的统计母体变异数。此函数假定计算中使用的值为统计母体。
COUT 函数有一个特殊的用处:计算数据表中所有数据列的数目。在 COUNT后用(*)就可达成,如下所示:
SELECT COUNT(*)
FROM publishers
GO
结果集如下:
--------
8
结果集显示 publishers 数据表现在含有8个资料列。
AVG、COUNT、MAX、MIN 和 SUM 函数都可以与选择性关键词 ALL 和DISTINCT 一起使用。对于每一个函数,ALL 表示这个函数必须运用于这个表达式中的所有值,DISTINCT 则指出重复值将被忽略,相同的数值将只被计算一次。如果选项没有指定,预设为 ALL。
下面的例子在选取清单中一起使用 MAX 和 MIN 函数,以寻找最贵和最便宜的书之间的价格差:
SELECT MAX(price) - MIN(price) AS "Price Difference"
FROM titles
GO
结果集如下:
Price Difference
------------------------
19.96
这个范例中,利用 SUM 来得到各个商店中所订购的项目总数:
SELECT stores.stor_name, SUM(sales.qty) AS "Total Items Ordered"
FROM sales, stores
WHERE sales.stor_id = stores.stor_id
GROUP BY stor_name
GO
结果集如下:
stor_name Total Items Ordered
---------------------------------------- -------------------
Barnum's 125
Bookbeat 80
Doc-U-Mat: Quality Laundry and Books 130
Eric the Read Books 8
Fricative Bookshop 60
News & Brews 90
(影响6个数据列)
________________________________________
说明
记住,SQL Server 提供了许多种函数。如果要执行一个特殊的作业,先检视一下 SQL Server《在线丛书》有没有提供现成的内建函数。
________________________________________
SELECT 的其它用法
SELECT 陈述式主要用来执行查询,从数据库中检索数据。这些查询可以很简单,也可以很复杂。SELECT 也可以用来设定变量的值或呼叫函数。
您可以用 SELECT 陈述式在交易或预存程序中设定一个本机变量的值,尽管更好的方式是用 SET 陈述式。本机变量必须用 @ 作为其第一个字符。例如使用SET给定本机变量 @count 值为0,所用的陈述式如下:
SET @count = 0
GO
您可以用 SELECT 将一个本域变量给定为一个查询所传回的值。例如把一个本域变量 @price 指定为从 price数据行中得到的最高价格,使用以下陈述式:
SELECT @price = MAX(price)
FROM items
GO
当在 SELECT 陈述式中设定一个本域变量时,最好的情况是查询只传回一数据列。如果有多个数据列被传回时,本域变量将被设定为最后一列传回的值。
您也可以用 SELECT 呼叫函数,例如系统提供的函数 GETDATE。下面的陈述式呼叫 GATDATE 搜寻当前的日期和时间:
SELECT GETDATE()
GO
GETDATE 并不包含任何参数,但括号还是必须的。
本章总结
在这一章中,我们讨论了 SELECT 陈述式、各种可以在 SELECT 陈述式中使用的子句及其使用方法,以及搜寻条件和函数。您已经学习了逻辑运算子、联结、别名、模式比对、结果群组和排序以及更多的内容。我们已经概括了不少信息,但关于 SELECT 陈述式还有更多您可利用的地方。在 第20章 中,我们将学习可以在 SELECT 陈述式及其它资料操作陈述式 INSERT、UPDATE 和 DELETE 中使用的进阶选项。在 第15章 中,我们将学习如何修改数据库数据表。