17xie > SQL Server 2005高级程序设计 > 第2章 T-SQL基础
背景:                 
[本书目录] [图书首页] [本书讨论区]  
链接地址:http://www.17xie.com/read-105349.html    注册17xie 一起来写书 实现您的出书梦想!

章依然还是回顾。这里,我们将快速浏览最基本的T-SQL语句。像所有本书前面的几个章节一样,通常假定你已经掌握了不少这方面的内容——因此,这里只是作一个复习,填补上学习中的缺漏。

T-SQL是SQL Server特有的一种SQL方言。在2005版中,对T-SQL作了全面的修订,加入了很多新的编程构造。其中一个改变是,T-SQL变成了CLR(公共语言运行库)兼容语言——简言之,它现在已经是一种.NET语言了。虽然,对于SQL Server 2005而言,可以使用任何.NET语言访问数据库,但最终的数据访问总是要用到一些SQL,T-SQL仍然是SQL Server中编程处理的核心语言。不过,就本章涉及的内容而言,几乎没什么改变——在最基本的语句上没做什么修改。

本章中将学到如下的T-SQL语句:

l    SELECT;

l    INSERT;

l    UPDATE;

l    DELETE。

这4种语句是T-SQL的支柱。虽然在后面将学到很多其他的语句,但这4种语句构成了T-SQL的数据操作语言(DML)的基础。通常,在发出命令中,操作(即读取和修改)数据的命令远远多于其他类型的命令(如授予用户权限或创建表),所以,确实没有比这里的语句更基础的了。

另外,SQL提供了很多运算符和关键字来帮助细化查询。本章中将回顾其中最常用的一些,例如JOIN。

虽然T-SQL是SQL Server所特有的,但常用的语句却并非如此。T-SQL是初级兼容ANSI SQL-92标准的,也即它遵从一个非常广阔的标准的某个级别。这对于开发者意味着,本书中学习的很多SQL可以直接转移到其他基于SQL的数据库服务器,如Sybase(很久以前,与SQL Server共享同样的代码库)、Oracle、DB2和MySQL。然而,要明白,所有的RDBMS都超越ANSI标准做了不同程度的延展和性能增强。在适当的时候,我会指出ANSI方式与非ANSI方式做事情的差异。某些情况下,会面临性能与到其他RDBMS的可移植性之间的抉择。然而,大多数时候,ANSI方式与其他任何选择一样高效快捷。而在这样的情形下,应该作何选择是显而易见的,这就是——保持ANSI兼容。

2.1  基本的SELECT语句

SELECT语句以及语句中使用的结构构成了在SQL Server中执行的大部分命令的基础。下面来看SELECT语句的基本语法规则:

SELECT <列的列表>

[FROM <一个或多个源表>[[AS]<表别名>]

[[{FULL|INNER|{LEFT|RIGHT} OUTER|CROSS}] JOIN <另一个表>

[ON <联结条件>] [<其他JOIN子句>...]]]

[WHERE <约束条件>]

[GROUP BY <SELECT>列列表中的列名或列表达式]

[HAVING <基于GROUP BY结果的约束条件>]

[ORDER BY <列的列表>]

[[FOR XML {RAW|AUTO|EXPLICIT|PATH [(XML元素)]}[, XMLDATA][,ELEMENTS][,BINARY

Base 64]]

[OPTION (<查询提示>,[, ...n])]

[;]

2.1.1  SELECT语句和FROM子句

语句中的“动词”(这里是SELECT)是整个语句中告诉SQL Server要做什么的部分。SELECT表明我们只是要读取信息,而非修改它。

FROM子句指定要从中获取数据的一个或多个表的表名。有了这些,就足以生成一个基本的SELECT语句。启动SQL Server Management Studio,再次看看上一章中执行过的SELECT语句:

来看看在这个语句中都请求了什么。我们请求SELECT信息(也可以想成是要求显示信息)。*与它在所有其他地方的作用一样,即充当一个通配符。SELECT *,就是说要从表中选取所有的列。接下来的FROM表明,想要输出的项已经说完了,现在要指明的是信息源是什么——这里是INFORMATION_ SCHEMA.TABLE。

INFORMATION_SCHEMA是一种特殊的访问途径,用来显示关于系统的数据库及其内容的元数据。INFORMATION_SCHEMA有个单独的部分可以在一个句点后指定,如INFORMATION_SCHEMA. SCHEMATA或INFORMATION_SCHEMA.VIEWS。给出了这些访问系统的元数据的特殊途径,就不必通过“系统表”来访问了。大多数情况下,INFORMATION_SCHEMA是符合ANSI标准的(尽管有时它在SQL Server中的实现有其自身的一些改变)。此外,SQL Server中还有一组“sys”函数,用于提供INFORMATION_SCHEMA所能给予的信息的超集。虽然这些“sys”函数远比INFORMATION_SCHEMA强健,但它们不符合ANSI标准。

现在来看看更具体一点的信息。假设想要得到所有客户的姓氏的列表:

这里,USE AdventureWorks命令是把“当前”数据库更改为指定数据库的指令。把该命令放在这里是为了确保处于正确的数据库中,即已拥有指定的模式.表组合的数据库。

查询执行后的结果类似下面这样:

注意,为了简短,未显示中间的行——在结果集中应当有19972行。由于我们需要的只是客户的名字,因此就只选取了所需要的东西。

这一查询非常简单,但随着查询变得越来越长,可能想要给表指定“别名”——可以在表名的后面放置想使用的别名。

在上面的语句中,为表赋予了别名,但却没有用别名做任何事情。在实际应用中,会在查询中要引用表的地方使用别名。

在查询的上面这一版本中,已经为表Person.Contact指定了别名c。回到选择列表,这里使用别名明确地说明需要从表Person.Contact中获取姓氏。对于一个如此简单的查询,这样做似乎意义不大,但是,当讲述JOIN时,会看到别名能使代码更加具有可读性。事实上,在某些高级的查询结构中,别名是必需的。

很多编写SQL代码的人为了让查询变得简短,习惯于在选择标准中用一个*来选取所有的列。这是又一个应当抵制的习惯。当输入*时,节省了一些输入想要的列名所需的时间,但这也意味着将获取的数据比真正需要的数据更多。此外,SQL Server必须找出总共有多少列以及每一列具体是什么。这样做会降低多少应用程序的性能以及网络的性能,其答案将让人大吃一惊。简言之,良好的规则是只选所需——既不多选,也不少选。

2.1.2  JOIN子句

在我之前的书里,单独用了一章来说明该子句,因此,这里也会花点时间仔细讲述。

规范化(normalized)的数据库就是经过这种处理的数据库:为了消除重复数据、节省空间、提高性能以及增强数据完整性,把数据从较大的表中取出放入到许多较小的表中。规范化是关系型数据库必不可少的要素,然而,这也意味着需要从这里或那里、从四面八方获取数据。JOIN(联结)正是用来定义如何从多个表中选取数据并组合成一个结果集。JOIN子句有多种形式,这些形式改变着要进行联结的表之间相互作用的方式。另外,有一个较老的语法来执行JOIN——这将在本章的最后谈及(在接触它之前需要了解更多一些的概念)。

1.JOIN基础

在规范化的环境中进行操作时,常常会陷入这样的境地:我们想要获取的所有信息并不都在一个表中。另一种情形是,我们想要返回的信息都在一个表中,但是,要在其上设置条件的信息却在另一个表中。以上这些情况都要用到JOIN。

究竟JOIN是怎样把来自两个表的信息放入一个结果集中的呢?关于如何把数据放置在一起,这取决于它是怎样被告知的——因而有4种不同类型的JOIN。所有JOIN的共同点是:通过记录的联结列,把一条记录同一条或多条其他记录进行匹配,从而产生出是这些记录的超集的记录。

例如,从名为Films的表中提取一条记录:

接下来,从名为Actors的表中提取记录:

通过JOIN,就能从两条来自完全独立的表的记录生成一条记录:

事实上,这里从总共3条记录(第一个表中一条记录,第二个表中两条记录)中创建出了两条记录。我们多次使用表Films中的一条记录——每次都用它来关联相应的演员(因为是通过FilmID来“联结”的)。

由于这里所使用的例子的数据集太少,以致无论使用哪种类型的JOIN,都会得到相同的结果集。下面来具体看看各种不同的JOIN类型。

2.INNER JOIN

INNER JOIN无疑是最常用的JOIN类型。虽然都是基于一个或多个公共字段来把记录匹配到一起,但INNER JOIN只返回进行联结的字段上匹配的记录。在前面的例子中,所有的记录都至少在结果集中出现了一次,然而,在现实世界里,这种情形较为少见。

现在我们对表做些修改,然后,看看使用INNER JOIN会得到什么结果。下面是Films表:

这是Actors表:

使用INNER JOIN后,结果集将会是这样:

注意Bogey被排除到了结果集之外。这是因为他在Films表中没有匹配的记录。如果两个表中都没有匹配,将不返回记录。

实际上,INNER JOIN是在做排除——即是说,如果任一行在两个表中不匹配,则注定将从最终的结果集中排除掉。

理论就讲到这里,下面讨论代码。

首先,来看看语法的简化版本:

SELECT <列的列表>

FROM <第一个表> [<别名>]

<联结类型> <第二个表> [<别名>]

  [ON <联结条件>][;]

这是ANSI语法,在非SQL Server数据库系统上使用这种语法会远远好于使用前面提到过的旧式的语法(直到今天许多开发者仍然在用这种语法——相关的讨论将在后面进行)。

启动Management Studio,在AdventureWorks上使用下面的代码尝试INNER JOIN:

该查询的结果集太大,就不在这里给出了,你将获得略少于300行的记录。关于查询的结果,请注意如下的事情:

l    不仅能对两个表进行联结,实际上,还可以把一个表与其自身相联结——允许在同一个表中对不同的行进行比较(这里,把一个表看作经理表,另一个表看作所有雇员的表)。要实现这一目的,必须使用表的别名(此处分别选择e和m作为表的别名)。

l    将从两个对表的引用中返回所有的列。如果引用的是不同的表,则将看到来自两个表的列名。而看到两组列名时,没法区分出哪个列名来自哪个引用(同样的列名将出现两次,但通常值是不同的)。

现在直接进入要找寻的结果,然后完全展开JOIN逻辑:

这实在有点让人晕眩——把几乎所有与INNER JOIN有关的概念都汇集到了一起,所以,请紧随我,通过查看单独的部分来了解这里发生了什么。

这是第一个联结,这里把一个表与它自身进行联结。当为了某种原因把一个表联结回它自身时,称为自引用(self-referencing)。由于该表有两个实例,因此必须使用表的别名,以便在查询中进行引用时能够知道引用的是哪一个表。

联结到Contact表来获取雇员的名字。在该数据库的设计中,设计人员把联系信息分离出来放入单独的一个表中,因为雇员也可能是客户。不过,要注意,需要联结Contact表两次——一次联结得到雇员的姓名而另一次取得经理的姓名。

注意到选择列表中有几处改变。首先,选择列表削减为只剩感兴趣的列(哪一些雇员向哪一位经理报告)。由于每个表都有不止一个实例,因此不仅对表的名字使用别名,而且对输出的列属于哪个实例也使用别名。

还要说明的一件事是:INNER JOIN是默认的联结类型。因此,关键字INNER是可选的。实际上,大多数SQL开发者为了让查询精简,省去了该关键字。这样一来,我们的查询看起来将会是:

上面的查询也是正确的,并将生成同样的结果。

3.OUTER JOIN

这种类型的JOIN更像是一种例外,而非一种惯常的方法。这样说绝对不是因为它们没有用处,而是由于:

l    人们通常需要的是INNER JOIN提供的这种独占性;

l    许多SQL编写者学习了INNER JOIN之后就没有再深入了解其他的联结类型了——他们完全不清楚OUTER联结类型;

l    常常有其他的方法来实现这里要完成的事情;

l    人们经常忘记这种选项。

尽管INNER JOIN几乎是唯一的联结,然而,OUTER以及本章后面将谈及的FULL JOIN其实也是包含在内的。OUTER JOIN能够使一些看似困难的问题简单化,人们不知道如何利用它们实在是一大损失。同时,它们通常还能用来替代其他的选择产生同样的结果,并提高性能。

联结中有侧部的概念——左和右。第一个指定的表被认为是在左侧,第二个指定的表被认为是在右侧。在INNER JOIN中没有讲述左侧和右侧,因为两侧都是同等对待的。然而,在OUTER JOIN中,理解左侧和右侧非常重要。虽然它们看起来似乎很简单(因为它们的确很简单),但是,许多与OUTER JOIN有关的查询错误都是源于没有考虑好左和右所致。

回顾最早的雇员/经理查询的变体:

我们不会看到数量极多的结果,注意一下取得的行的数目——我得到了289行(在AdventureWorks的默认安装之下)。

现在,稍微改动一下查询:

上面的查询表明,无论左侧(LEFT)表中的行是否与右侧表相匹配,都要返回左表中的所有行。更具体地说,要返回所有雇员记录,以及有匹配行的经理记录。

看看现在的结果,会发现比刚才多获得了一行(对于我的数据库现在是290行)。浏览结果,寻找一下“新增的”行。

看起来ID为109的雇员似乎不向任何人负责(或许此人是CEO吧)。要特别注意,当右侧表中的列没有数据时,SQL Server是如何给出这些列的——对于这些列,使用了NULL值。稍后将看到如何在联结的一端寻找NULL值,以发现联结的一端没有匹配数据的行。

若将联结更改为RIGHT JOIN,那么这里得到的结果集将与INNER JOIN相同,因为右侧(RIGHT)经理表中不存在不属于左侧雇员表的记录。尽管如此,RIGHT联结的概念类似,即想要包含右侧表中的所有行,以及左侧表中有匹配记录的行。

4.FULL JOIN

设想一下把LEFT和RIGHT联结结合到一起的情况。使用FULL联结,将告诉SQL Server要包含位于联结两侧的表中的所有行。AdventureWorks中没有真正合适的例子来说明FULL JOIN,不过,既然已经了解了外部联结,理解这一概念相当容易,因此这里就随便做一个简单的演示:

别太担心上面我们还没有讨论到的一些语句,除了你很可能已经具备基本的知识外(若需要温习基础知识,看看《SQL Server 2005基础教程》一书吧),眼下,这里也只是构建一个可以使用FULL JOIN的例子罢了——一些其他的语句,将在本章后面以及后续章节中详细讲述。

接下来运行FULL JOIN查询,并查看得到的结果:

审视查询结果将看到:联结在一起的两个表中匹配的数据,只在左侧表中存在的数据(对于右侧表中的列,使用NULL),以及只在右侧表中存在的数据(对于左侧表中的列,使用NULL)。

5.CROSS JOIN

最后要讲述的联结是CROSS JOIN,实际上,这是一种很奇特的联结类型。CROSS JOIN与其他的JOIN的不同之处在于:它没有ON操作符,并且它将JOIN一侧的表中每一条记录与另一侧的表中所有的记录联结起来。简言之,得到的结果是位于JOIN两侧的表中所有记录的笛卡尔积。其语法与所有其他JOIN的语法一样,除了这里使用的关键字是CROSS(而不是INNER、OUTER或FULL),并且这里没有ON操作符。

那么,假设要把所有的电影同所有的演员组合起来:

返回的结果是,电影表中的所有记录与演员表中每一个演员的组合:

现在,提出了一个问题,“究竟为什么想要得到这样的结果呢?”这是个很好的问题。要回答它有一定的难度,因为这是随环境而变化的。迄今为止,我只在两种情形下看见过CROSS JOIN的使用:

l    样本数据——CROSS JOIN擅长于把小的数据集放置在一起,以各种可能的方式混合两个数据集,从而得到相当大的数据集来处理。

l    科学数据——我相信这也与样本有关,不过,这里要利用笛卡尔积做很多科学计算。CROSS JOIN是为某些统计类型“准备”数据的一种方式。虽然我不预备装作理解有关它的统计,但我知道它是这样得来的。

最后要说——尽管这种联结用得非常非常少,不过,一旦需要,要记得使用!

2.1.3  WHERE子句

WHERE子句用于设置要返回的数据应满足的条件。迄今为止,返回的数据都是无限制的信息,即除非被联结的类型筛选掉,否则将返回表中所有的行。现在将呆在单个表的场景中——本节的最后会再次引入JOIN。在构建列表框和组合框这类事物时,以及在试图提供范围清单(domain listing)的场景时,无限制的查询非常有用。

文本框:  
图  3-1

不要把这里的范围(domain)与Windows的域(domain)混为一谈。范围清单是一种完整的选择列表。例如,如果要某人提供关于美国某个州的信息,可以为他们提供一个列表,将选择的范围限制在50个州之内。

 

同样,通过无限制的联结,能够获得关联数据的完整清单。

然而,现在要寻找的是更特殊的信息。看看能否从一个查询返回ProductID为356的产品的名称、产品编号和ReorderPoint。

现在拆开来讲,一部分一部分地构建查询。首先,找出要返回的信息,于是涉及SELECT语句。前面的陈述说明想要找到的信息是产品名称、产品编号以及ReorderPoint,因此,必须知道这些信息的列名是什么。同时,还需要了解从哪个或哪些表中能够得到这些列。

此次先给出要从中检索信息的表Production. Product(本章后面将讲述如何在不了解的情况下找出什么表是可用的)。Production.Product表中有一些列。要快速列出列的选项,可以在Management Studio中的对象资源管理器里,查看表Production.Product的树。单击数据库AdventureWorks下的表成员结点,在Management Studio中显示出表。然后,展开Production. Product和列结点。如图3-1所示,将看到所有的列以及列的数据类型和是否允许空值。另外,在本章的稍后,将给出找到这些信息的一些其他方法。

这里没有名为产品名称(product name)的列,但是,有一个列可能是我们要找寻的列,其列名是:Name(有创意吧?)。另外两个列也很容易识别,其列名只是没有了两个单词(要获取的信息)之间的空格。

这样,Products表将是要从中(FROM)获取信息的地方,而Name、ProductNumber和ReorderPoint列是要获取的信息所在的具体列:

不过,该查询仍然不能给出想要寻找的结果——它返回了太多的信息。运行这一查询将看到,它返回了表中所有的列,而非只返回要寻找的那一列。

如果表中只有几条记录,则仅需要快速浏览一下,那么,这样的查询或许也还不错。但是,如果有100 000或者1 000 000条记录呢?我们需要有一个条件语句,将结果限制在产品标识符为356的产品上。此时就要用到WHERE子句。WHERE子句紧随FROM子句之后,定义要返回的记录必须满足的条件。此处需要ProductID等于356,因此,查询将是这样:

在AdventureWorks数据库上运行该查询,得到的结果如下:

这一次准确获得了要查询的结果——既不多,也不少。此外,该查询执行得比前面的查询快很多。

接下来讲讲所有能在WHERE子句中使用的操作符。

运  算  符

用法示例

作    用

=、>、<、

>=、<=、<>、

!=、!>、!<

<列名> = <另一个列名>

<列名> = 'Bob'

标准的比较运算符——与其在几乎所有其他编程语言中的作用一样,注意以下几点:

(1) 如何判断“大于”、“小于”和“等于”,取决于所选择的排序顺序。例如,当选择的是不区分大小写的排序规则时,"ROMEY" = "romey";在区分大小写的情形下,"ROMEY" < > "romey"

(2) !=和<>都是“不相等”的意思。!<和!>分别表示“不小于”和“不大于”

AND、OR、NOT

<列1> = <列2> AND <列3> = <列4>

<列1> != "MyLiteral" OR <列2> = "MyOtherLiteral"

标准的布尔逻辑运算符。可以使用这些运算符把多个条件组合到一个WHERE子句中。最先评估NOT,然后是AND,然后是OR。如果要改变求值的顺序,可以使用括号。注意,不支持XOR

BETWEEN

<列1> BETWEEN 1 AND 5

如果第一个值在包含第二个值和第三个值的范围之内,则比较的结果为TRUE。其功能等同于A>=B AND A<=C。指定的值可以是列名、变量或文字

LIKE

<列1> LIKE "ROM%"

这里,%和_是通配符。%表示可以用零个或多个任意字符来替代%字符。_表示可以用单个任意字符来替代_字符

把字符包含在[]符号中表示,[]符号中包含的任何单个字符都可以([a-c]表示a、b或c都可行。[ab]表示a或b都行)

^与NOT运算符的作用一样——表明下一个字符将被排除掉

(续)

运  算  符

用法示例

作    用

IN

<列1> IN (数字列表)

<列1> IN ("A","b","345")

如果IN关键字左边的值与IN关键字后面给出的列表中的任何值相匹配,则返回TRUE。这常常在子查询中使用,相关内容将在第6章中讲述

ALL,ANY,SOME

<列|表达式> (比较操作符) <ANY|SOME> (子查询)

如果子查询检索的所有值或任何值(取决于选择的是ALL、ANY或SOME)都满足比较运算符(如<、>、=、>=)条件,则返回TRUE。ALL表明值必须与结果集中的所有值匹配。ANY与SOME在功能上等价,当表达式与结果集中的任何值匹配时返回TRUE

EXISTS

EXISTS (子查询)

如果子查询至少返回了一行,则返回TRUE。同样,将在第6章中进一步探讨

接下来,正如前面说过的,将WHERE子句与JOIN一起使用。其实这里的使用与已经了解过的WHERE使用没什么不同——只要加在语句的后面就可以了。如果列名重复,可以使用表名或者表的别名指定列来自哪个表(如果列名在所有的表中只出现过一次,则可以直接使用列名而无需限定表名——大多数时候这种方式都是可行的,不过,在WHERE子句的约束中,我倾向于以表名作为列名的前缀,这样能够是数据的来源更加清晰)。

现在,回到用来显示所有雇员及其经理的复杂JOIN语句。这里要找出由经理Jo Brown管理的所有雇员的列表。由于我们知道查出的是Jo Brown管理的雇员的列表,那么就不需要在结果集中包含她的名字,因此,将选择列表编辑为如下形式:

此处混入了几个概念。首先,选择列表中没有使用WHERE子句约束中出现的列——实际上,那个表除了作为数据筛选的数据源而外,别无他用。

下面是得到的查询结果:

可以看到,结果集从先前的289行减少到了12行。

2.1.4  ORDER BY

迄今为止,大多数运行过的查询都是以某种类似字母顺序或数字顺序的方式给出。这是偶然的吗?答案是肯定的,这可能会让人有些吃惊。如果没有说明想要以何种排列方式返回查询结果,则会以SQL Server决定的方式来给出。这通常取决于SQL Server认为哪一种汇集数据的方式开销最小。因此,返回的结果经常是基于表中数据的物理顺序或SQL Server用来找寻数据所使用的某个索引。

可以把ORDER BY子句想成是某种“sort by”。能够通过该子句定义数据返回的顺序。在ORDER BY子句中可以使用任意列的组合,只要列是FROM子句里的表中的列(或派生列)。

来看这样一个查询:

查询产生的结果如下:

查询的结果集是以ProductID的顺序给出的。为什么会是这样的顺序呢?因为SQL Server认为最好是以索引来查看数据,该索引是按ProductID来分类数据的。这种方式恰好使查询的开销(在CPU和I/O上)最小。当表变得非常大时,再运行同样的查询,SQL Server可能会选择完全不同的执行计划,这样一来,就会以不同的顺序对结果进行排序。如果要强制使用某种排序方法,则可以把查询修改为:

注意,在上面的查询中,WHERE子句不是必需的。根据要完成的任务,可以使用也可以不使用WHERE子句——只是要记住,如果查询中需要WHERE子句,则WHERE子句应放在ORDER BY子句的前面。

遗憾的是,上面的查询没有给出不一样结果,我们看不出究竟发生了什么。下面对查询进行修改,以不同的方式排序数据——按ProductNumber来排序:

现在,得到的结果大不一样了。虽然结果集中的数据没有不同,但排列的方式发生了很大的改变:

SQL Server依然是选择开销最小的方式来生成查询结果,但是,由于查询的性质有了变化,因此实际得到的结果集也有所不同。

值得注意的是,也可以按照数字字段进行排序。

不要被上面的查询结果所蒙蔽了。在前面给出的数据子集中,由于数据看起来恰好是按照ReorderPoint的降序排列的,这容易让人觉得根据ReorderPoint字段对数据进行了分类——其实那纯属巧合,并且,如果实际查看返回的数据(本书为了讲述上的简洁,只在这里显示了部分的数据),会发现ReorderPoint是未分类的。

接下来,对要获取的列稍加修改,改为额外返回Weight列:

查询结果如下:

在这一查询中要注意几点。首先,查询中使用了前面讲过的许多事情。把WHERE子句条件同ORDER BY组合了起来。其次,在ORDER BY子句中加入了新的东西——DESC关键字。该关键字告知SQL Server这里的ORDER BY按降序排序,而非默认的升序。(如果要明确指定排序顺序为升序,可使用ASC。)

使用ORDER BY还可以做更多的事情,眼下,先基于多个列来进行分类。要指定多个列,只需在第一个列名后添加一个逗号,然后跟以下一个要用来分类的列名即可。

例如,假设想要获得在2001年7月8日到7月9日之间下的所有订单。不过,此处还有更多一点的要求,这里要先按日期顺序排序订单,然后,基于CudtomerID进一步进行排序。为了多增加一些内容,再加入一点小要求:我们希望在结果集中先显示最近的订单(按日期的降序排列)。

查询类似下面这样:

这一次得到的数据按两种方式来排序:

对于CustomerID列,由于我们没有指明降序,因此将按(默认的)升序来排序,但是,可以看到7月9日的订单排在7月8日订单的前面——降序排序。

虽然,我们经常基于返回列中的某一个来对结果进行排序,但值得注意的是,ORDER BY子句可以基于查询中使用到的任何表中的任何列来进行排序,无论该列是否包含在SELECT列表中。

2.1.5  使用GROUP BY子句聚集数据

讨论ORDER BY时,我们打破了本章前面的SELECT语句的阅读顺序。下面复习一下整个语句的结构:

SELECT <列的列表>

[FROM <一个或多个源表>]

[WHERE <约束条件>]

[GROUP BY <SELECT列列表中的列名或列表达式>]

[HAVING <基于GROUP BY结果的约束条件>]

[ORDER BY <列的列表>]

[[FOR XML] [RAW, AUTO, EXPLICIT][, XMLDATA][, ELEMENTS][, BINARY base 64]]

[OPTION (<查询提示>, [, ...n])]

既然ORDER BY最后出现,为什么要在GROUP BY之前讲述它呢?这样做的原因有如下两个:

l    ORDER BY的使用比GROUP BY更经常,因此,希望你能更熟悉ORDER BY;

l    我想要确保你明白,只要按照SQL Server希望的顺序(在语法定义中规定)给出,FROM子句后的所有子句都可以混搭。

GROUP BY子句用于聚集信息。来看一个没有用到GROUP BY的简单的查询。假设我们想知道,在一组给定的订单上订购了多少份产品。

该查询产生的结果集如下:

即使只想要查询3个订单,也将会看到每一个单独的订单详情的行。我们要么通过加法机来获得结果,要么使用GROUP BY子句结合聚集来解决——这里使用的是SUM()。

执行上面的查询将得到要找寻的结果:

正如所希望的,SUM函数会返回总和——但这是什么的总和呢?如果不提供GROUP BY子句,总和将是所有行在指定列中的值的总和。不过,这里我们给出了GROUP BY子句,因此,SUM函数产生的总和是每一组的总和。

我们也可以基于多个列进行分组。要实现这一目的,与ORDER BY子句中所做的类似,只需要添加一个逗号,接着再添加下一个列名即可。

注意,一旦在查询中使用了GROUP BY,SELECT列表中的每一列必须要么包含在GROUP BY列表中,要么包含在聚集中。这意味着什么?下面就来找寻答案。

聚集

当考虑到聚集经常与GROUP BY子句一起使用时,对于聚集(aggregate)是在一组数据上进行处理的函数,你多半不会觉得惊诧。例如,在前面的一个查询中,获得了Quantity列的总和。对于GROUP BY子句中定义的每一组(这里是OrderID),在选定的列上进行计算并返回总和。虽然有大量的聚集可用,但这里只讲述最常用的聚集。

虽然,聚集在与GROUP BY子句一起使用时,展示了它们的能力,但聚集并不局限于分组的查询——如果在不包含GROUP BY的查询中使用聚集,则将基于整个结果集(所有满足WHERE子句的行)进行聚集。这里隐藏的问题是,当不与GROUP BY一起使用时,一些聚集只能与其他聚集一起位于SELECT列表中——即是说,如果没有GROUP BY,聚集不能与列名搭配出现在SELECT列表中。例如,除非有GROUP BY子句,否则,AVG只能与SUM搭配使用,而不能与特定的列名一起使用。

  ● AVG

AVG用于计算平均值。毋庸多言。

  ● MIN/MAX

这两个聚集函数的作用不言自明。获取每一组中选定的列的最小数字和最大数字。这也没什么特别的。

  ● COUNT(表达式|*)

COUNT(*)用于获取一个查询中的行数。我们从最常用的查询类型入手进行讲解:

此处返回的记录集与前面所熟悉记录集有些许不同:

让我们来看看不同之处。首先,由于返回的所有列就是函数调用的结果,因此这里没有默认的列名——如果希望这里显示列名,那么需要提供一个别名。其次,注意到,实际上几乎没有返回什么。那么,返回的记录集代表着什么?它表示,在FROM子句的表中,满足查询中的WHERE条件的行数。

要记住这种查询。这是一种基础的查询,可以通过该查询来验证表中满足WHERE条件的确切的行数。

只是为了好玩,试着运行下面这个不带WHERE子句的查询:

如果没有在Employee表中做过数据插入或删除,那么将得到类似下面的记录集:

该数字代表什么意思呢?这是Employee表的总行数。该查询是另一个需要记住的查询,以备将来之需。

接下来该讨论与表达式一起使用的情形了——通常是列名。首先,基于一个新的表,按老方法运行COUNT。

这是一个较小的表,因此得到的总记录数较少:

现在,修改查询以某个计算特定列的数量:

此时得到的结果与前面的结果有所不同:

为什么会不同呢?答案很明显——因为这里没有值,同样,并非所有行的EmployeeID列都有值。简言之,当COUNT不是以COUNT(*)的形式来使用时,会忽略NULL值。

实际上,除COUNT(*)之外,任何聚集函数都会忽略NULL。考虑一下这种情况——它将对查询的结果产生重大的影响。在执行聚集时,很多用户希望数字字段的NULL值能被当作0值,但NULL不等于0值,同样,也不应该被用作0值。如果在有NULL值的列上进行AVG或其他聚集函数运算,NULL值将不会参与聚集,除非在函数中(例如,使用COALESCE()或ISNULL())把NULL值处理成非NULL值。这些问题将在第6章中做进一步探讨,当编写T-SQL代码和设计数据库时,要小心这一点。

为什么这会影响到数据库的设计呢?是这样的:当考虑查询在数据库上可能运行的方式以及希望如何进行聚集时,这关系到在一个字段中是否允许NULL值。

现在,了解了如何操作组,下面接着讲述一个很多人都有困难的概念。当然,阅读完下一节后,你会认为这不过是小菜一碟。

2.1.6  使用HAVING子句在组上放置条件

仅当查询中也有GROUP BY子句时使用HAVING子句。WHERE子句在行成为组的一部分之前,应用到每一个行上,而HAVING子句应用到组的聚集值上。

为了说明HAVING子句,我们在最后一个GROUP BY子句的例子中使用的查询上添加HAVING子句,快速构建一个查询,运行该查询:

记得原始的查询返回了三个行,而现在添加了HAVING子句后,减少为了两个行(筛选掉了OrderQty总和为5的行):

2.1.7  使用FOR XML子句进行XML输出

回到2000年,当SQL Server的上一个版本面世时,XML刚刚出现,但很快就证明它是表示和交换数据的一种关键方法。作为那个版本SQL Server(SQL Server 2000)的一部分,微软增加了以XML格式(而非关系型结果集)返回结果的能力。这非常有用,特别是在Web环境和跨平台的环境中。

此后,微软重写了输出XML的方式,但其基础和价值依然保持不变。由于XML是一个独立的讨论,眼下先回避该子句的细节,留到第16章再对XML特别讲述。现在,先学习基础知识更为合适。

2.1.8  使用OPTION子句指定提示

OPTION子句是一种覆盖SQL Server认为的最佳查询执行计划的方法。通常,SQL Server的确知道什么对查询最好,因而使用OPTION子句更经常是带来危害而非帮助。不过,为以防万一,知道OPTION子句的存在也是必要的。

这也是一个要留待以后再来讲述的主题。当在本书后面(第12章)讨论锁定时,将单独讲述查询提示,此外,只有知晓了提示会带来什么影响时,才具备理解OPTION子句的基础——正因为如此,我们推迟对它的讨论。

2.1.9  DISTINCT

这里还有一个重要的概念需要阐述,然后,我们就可以从SELECT语句进入到操作语句。该概念与重复数据有关。

DISTINCT是用来消除重复数据的。如果值是相同的,则该值只出现一次(并且,如果与COUNT()一起使用,则只计算一次)。它要么应用于SELECT列表的开始处(如果希望结果集中没有重复的行),要么出现在COUNT()语句的内部(如果与COUNT()一起使用的话)。

为了示范,我们将运行4个快速查询,由查询本身来说明。

首先,从SalesOrderDetail表中选取SalesOrderID从43 685到43 687的所有行。

得到的结果如下:

接着,在查询中加入DISTINCT:

此时,查询结果与前面大不相同:

现在改为计算返回项的数量。回到第一个查询:

返回的数量为9:

但是,若使用DISTINCT:

将返回3:

注意,DISTINCT能够与任何聚集函数一起使用,但是,它在很多聚集函数中的使用是否有实际的意义,这一点很值得怀疑。例如,为什么要只求得DISTINCT行的平均值呢?


字数:15852    最后更新:7个月以前 [04-23 15:27]happyskynet 修改
本页编辑者:happyskynet  
[前一页]:1.5 小结  [后一页]:2.2 用INSERT语句添加…
[在本页中加入书签] [收藏本书] [推荐本书]
  17xie论坛 > 本书讨论区 > 本页评论   (共0条)
发表评论

用户名称 匿名发表
评论内容
验证码

关于我们 | 版权声明 | 免责声明 | 诚聘英才 | 联系我们 | 合作伙伴 | 友情链接 | 广告合作 | 提交意见
Copyright © 2007 17xie.com 互联网协同写书平台 京ICP备08002671号