第4章 基本的查询

所谓查询就是让数据库服务器根据客户端的要求搜寻出用户所需要的信息资料,并按用户规定的格式进行整理后返回给客户端。查询语句SELECT在任何一种SQL语言中,都是使用频率最高的语句。可以说SELECT语句是SQL语言的灵魂。SELECT语句具有强大的查询功能,有的用户甚至只需要熟练掌握SELECT语句的一部分,就可以轻松地利用数据库来完成自己的工作。本章将主要介绍一下SELECT语句的基本结构,以及一些简单的查询操作。

4.1 SELECT语句的结构与执行

SELECT语句由一系列灵活的子句组成,这些子句共同确定检索哪些数据。用户使用SELECT语句除可以查看普通数据库中的表格和视图的信息外,还可以查看SQL Server的系统信息。在介绍SELECT语句的使用之前,有必要对SELECT语句的基本语法结构及执行过程作一下介绍。

4.1.1 SELECT语句的语法结构

虽然SELECT语句的完整语法较复杂,但其主要子句可归纳如下:

        SELECT select_list
        [ INTO new_table]
        FROM table_source
        [ WHERE search_condition]
        [ GROUP BY group_by_expression]
        [ HAVING search_condition]
        [ ORDER BY order_expression [ ASC|DESC ] ]

必需的子句只有SELECT子句和FROM子句,其他的子句都是可选的。各子句具体含义如下:

· SELECT子句:指定由查询返回的列。

· INTO子句:将检索结果存储到新表或视图中。

· FROM子句:用于指定引用的列所在的表或视图。如果对象不止一个,那么它们之间必须用逗号分开。

· WHERE子句:指定用于限制返回的行的搜索条件。如果SELECT语句没有WHERE子句,DBMS假设目标表中的所有行都满足搜索条件。

· GROUP BY子句:指定用来放置输出行的组,并且如果SELECT子句<select list>中包含聚合函数,则计算每组的汇总值。

· HAVING子句:指定组或聚合的搜索条件。HAVING通常与GROUP BY子句一起使用。如果不使用GROUP BY子句,HAVING的行为与WHERE子句一样。

· ORDER BY子句:指定结果集的排序。ASC关键字表示升序排列结果,DESC关键字表示降序排列结果。如果没有指定任何一个关键字,那么ASC就是默认的关键字。如果没有ORDER BY子句,DBMS将根据输入表中的数据的存放位置来显示数据。

4.1.2 SELECT各子句的顺序及功能

SELECT语句中的子句顺序非常重要。可以省略可选子句,但这些子句在使用时必须按适当的顺序出现。它们在SELECT查询语句中的排列顺序及主要作用如表4.1所示。

表4.1 SELECT查询语句各子句的顺序及作用

如果在同一个SELECT查询语句中,用到了表4.1中所示的一些查询子句,则各查询子句的排列就依照它们的顺序序号由低到高的顺序。

4.1.3 SELECT语句各子句的执行

为了让DBMS显示表中的值,最简单的就是执行带有FROM子句的SELECT语句即可。而在实践中,几乎所有的SELECT语句都包括强制输出数据满足某种标准的WHERE子句;另外,许多SELECT语句涉及从多个表中选择列的问题。

当执行SELECT语句时,DBMS的执行步骤可表示如下:

首先执行FROM子句,组装来自不同数据源的数据,即根据FROM子句中的一个或多个表创建工作表。如果在FROM子句中有两个或多个表,DBMS将执行CROSS JOIN运算对表进行交叉连接,作为工作表。

如果有WHERE子句,实现基于指定的条件对记录行进行筛选,即DBMS将WHERE子句列出的搜索条件作用于第步中生成的工作表。DBMS将保留那些满足搜索条件的行,删除那些不满足搜索条件的行。

如果有GROUP BY子句,它将把数据划分为多个分组。DBMS将第步生成的结果表中的行分成多个组,每个组中所有行的group_by_expression字段具有相同的值。接着,DBMS将每组减少到单行,而后将其添加到新的结果表中,用以代替第步的工作表。

★ 注意 ★

DBMS将NULL值看作是相等的,而且把所有NULL值都放入其自己的组中。

如果有HAVING子句,它将筛选分组。DBMS将HAVING子句列出的搜索条件作用于第步生成的“组合”表中的每一行。DBMS将保留那些满足搜索条件的行,删除那些不满足搜索条件的行。

将SELECT子句作用于结果表。删除结果表中不包含在select_list中的列。如果SELECT子句包含DISTINCT关键字,DBMS将从结果中删除重复的行。

如果有ORDER BY子句,则按指定的排序规则对结果进行排序。

对于交互式的SELECT语句,在屏幕上显示结果,对于嵌入式SQL,使用游标将结果传递给宿主程序中。

以上就是SELECT语句的基本执行过程。对于初学者来讲,可能很难理解。之所以在这里就提出,是为了让读者对SELECT语句有一个整体的了解。下面就开始从简单的查询开始,详细讲解SELECT查询语句的具体使用。

4.2 最简单的查询

在SELECT语句中,只使用FROM子句即可实现最简单的列的查询。有时甚至可以不使用FROM子句,只用SELECT子句实现无数据源检索。

4.2.1 向实例表中添加数据

为了便于通过实例讲解SELECT查询语句的使用,本章所有查询实例均针对前面创建的BookInfo表,且向该表添加一些数据。

在SQL Server 2005中,向表中添加数据可以使用Management Studio提供的图形界面,也可以采用Transact-SQL提供的INSERT INTO语句。这里向BookInfo表添加的数据如表4.2所示。

表4.2 BookInfo表中的数据

使用Transact-SQL的INSERT INTO语句向BookInfo表中添加数据的代码如下:

        USE Library
        INSERT INTO BookInfo
        (bookid,bookname,publish,pubdate,price,author,store,reader)
        VALUES(4001,' HTML教程','青年出版社','2004-9-1', 35.00,'王宾,李宏',1,9702)
        INSERT INTO BookInfo
        (bookid,bookname,publish,pubdate,price,author,store,reader)
        VALUES(4002,'Photoshop设计基础','大众出版社','2005-2-1',46.50,'刘小静',1,9707))
        INSERT INTO BookInfo
        (bookid,bookname,publish,pubdate,price,author,store,reader)
        VALUES(4003,'Photoshop设计基础','大众出版社','2005-2-1',46.50,'刘小静',1,9705)
        INSERT INTO BookInfo(bookid,bookname,publish,pubdate,price,author,store,reader)
        VALUES(4004,'Photoshop设计基础','大众出版社','2005-2-1',46.50,'刘小静',0,null)
        INSERT INTO BookInfo(bookid,bookname,publish,pubdate,price,author,store,reader)
        VALUES(4006,'数字图形处理','科学出版社','2002-8-1',56.40,'王刚,赵明',0,null)
        ……

4.2.2 查询表中列的数据——FROM子句

在数据库中,一般情况下,每个表都包含若干列信息。用户在查询表中的记录时,大多数情况下只是关心表的一列或者几列的信息。这时,只需要使用SELECT语句的常规使用方式即可:

        SELECT 列名1[,列名2,…列名n]
        FROM  表名

SELECT关键词指明了要查询的表的列的名称,FROM关键词指明了要查询的列所在表的名字。

实例1 查询单列数据

本实例将查询BookInfo表中的bookname列的数据,代码如下:

        USE Library                                --指定表所在的数据库
        SELECT bookname
        FROM BookInfo

运行该代码,得到的查询结果如图4.1所示。

对比表4.2,查询结果包含了bookname列的所有数据,即包括了重复的数据,且查询结果集中数据的顺序与其在表中的存储位置一致。

★ 说明 ★

在SQL语言中,SQL关键词对大小写不敏感,所以对SELECT关键词来说,SELECT、select或者Select都是一样的。在SQL Server中,其字段名称对大小写也不敏感。

当然,也可以一次查询多列的数据。多列查询需要在SELECT关键词后指定要查询的列,但是各列之间必须用逗号分开。注意,在列出的最后一列的名字后面不能加逗号,否则会造成语法错误。

图4.1 查询bookname列的数据

实例2 查询多列数据

本实例将查询BookInfo表中的bookname、bookid、price和pubdate列的数据,代码如下:

        USE Library
        SELECT bookname,bookid,price,pubdate   --指定要查询的列
        FROM BookInfo

运行该代码,得到的查询结果如图4.2所示。

图4.2 查询多列数据的结果

4.2.3 去除结果的重复信息——DISTINCT

前面介绍的最基本的查询方式会返回从表格中搜索到的所有行的数据,而不管这些数据是否重复,这常常不是用户所希望看到的。使用DISTINCT关键字就能够从返回的结果数据集合中删除重复的行,使返回的结果更简洁。

在SELECT子句中,通过指明DISTINCT关键字去除列中的重复信息。语法如下:

        SELECT   DISTINCT column
        FROM                   table_name_name

DISTINCT关键字去除的是SELECT子句查询的列的重复信息。如果SELECT子句查询的列为多列,那么只有这些列的信息同时重复的记录才被去除。

实例3 查询BookInfo表中所有的图书名称(去除重复值)

本实例将查询BookInfo表中存在的所有图书的名称,即查询bookname列的数据,且去除重复的记录,代码如下:

        USE Library
        SELECT DISTINCT bookname               --指定DISTINCT关键字
        FROM BookInfo

运行该代码,得到的查询结果如图4.3所示。

图4.3 去除重复信息的bookname列的数据

对比实例1的图4.1所示的查询结果,结果集将重复的记录删除了,同时对结果集中的数据按照升序的方式进行了排序。实际上,DBMS的操作过程是先对查询结果排序,而后查找并从结果中除去重复的值。

正因为如此,DISTINCT的使用是要付出代价的。因为要去掉重复值,必须对结果集中的记录进行排序,使得相同的记录聚集在一起,只有按这种方法对记录进行分组,才能去掉重复值,而这一工作甚至比查询本身还费时间。

★ 注意 ★

在使用DISTINCT关键字后,如果表中有多个为NULL的数据,服务器会把这些数据视为相等。

实例4 DISTINCT对NULL值的处理

本实例将查询BookInfo表中所有读者的编号,即查询reader列的数据,且去除重复的记录,代码如下:

        USE Library
        SELECT DISTINCT reader
        FROM BookInfo

运行该代码,得到的查询结果如图4.4所示。

图4.4 去除重复值的reader列的数据

可见,所有的NULL值被视为相等的,且排序时在最前面。

4.2.4 查询所有列——“*”

除了能够进行单列查询和多列查询以外,使用SELECT语句还可以查询表中的所有列,这是通过星号(*)通配符实现的。语法如下:

          SELECT *
          FROM 表名

使用“*”通配符,查询结果将列出表中所有列的元素,而不必指明各列的列名,这在用户不清楚表中各列的列名时非常有用。服务器会按用户创建表格时声明列的顺序来显示所有的列。

实例5 查询BookInfo表中所有列的记录

本实例将查询BookInfo表中所有列的记录,代码如下:

        USE Library
        SELECT *                      --使用通配符
        FROM BookInfo

运行该代码,得到的查询结果如图4.5所示。

图4.5 BookInfo表中所有列的记录

使用“*”通配符时要慎重,在不需要查询所有列时,尽量采用前面介绍的单列查询或多列查询,以免占用过多的资源。

4.2.5 进行无数据源检索

使用SELECT语句还可以进行无数据源检索。所谓无数据源检索就是使用SELECT语句来检索不在表中的数据。此时,只需要简单的SELECT子句,即不带有FROM子句。例如,可以使用SELECT语句检索常量、全局变量或已经赋值的变量。无数据源检索实质上就是在客户机屏幕上显示出变量或常量的值。

下面通过几个简单的实例简单说明其用法。

实例6 使用SELECT语句查看常量

使用SELECT语句可以查看常量,即在客户机屏幕上显示常量的值。实例代码如下:

        USE Library
        SELECT  '使用SELECT语句查看常量的值'
        SELECT  'SQL Server 2005'

运行该代码,得到的结果如图4.6所示。

图4.6 查看常量的值

在SQL Server中,还经常使用SELECT语句来查看全局变量,以获取服务器的相关信息。

实例7 使用SELECT语句查看全局变量

本实例将使用SELECT语句查询本地SQL Server服务器的版本信息和使用的语言。实例代码如下:

        USE Library
        SELECT  @@version,@@language

运行该代码,得到的结果如图4.7所示。

图4.7 查看SQL Server服务器的版本信息和使用的语言

4.2.6 返回查询的部分数据——TOP

SQL Server 2005提供了TOP关键字,让用户指定返回前面一定数量的数据。当查询到的数据量非常庞大时,如有100万行,但没有必要对所有数据进行浏览时,使用TOP关键字查询可以大大减少查询花费的时间。

在SELECT语句中,TOP关键字的使用语法如下:

          SELECT [TOP n | TOP n PERCENT] 列名1[,列名2,…列名n]
          FROM  表名

其中,TOP n表示返回最前面的n行,n表示返回的行数;TOP n PERCENT表示返回前面的n%行。

★ 说明 ★

在SQL Server 2005中,TOP表达式可用在SELECT、INSERT、UPDATE和DELETE语句中。

实例8 查询BookInfo表的前6行记录

本实例将使用TOP关键字查询BookInfo表中前6行的记录,代码如下:

        USE Library
        SELECT TOP 6 *                         --前6行所有列
        FROM BookInfo

运行该代码,得到的查询结果如图4.8所示。

图4.8 查询BookInfo表中前6行记录

也可以通过TOP关键字指定返回查询结果集的百分数。

实例9 查询BookInfo表的bookname、bookid、price的前60%条记录

本实例将使用TOP关键字查询BookInfo表中的bookname、bookid、price列的前60%条记录,代码如下:

        USE Library
        SELECT TOP 60 PERCENT bookname,bookid,price  --前60%行
        FROM BookInfo

运行该代码,得到的查询结果如图4.9所示。

图4.9 查询BookInfo表的bookname、bookid、price的前60%条记录

4.3 带有搜索条件的查询

一个数据表中存放着大量相关的记录数据。实际使用时,往往只需要其中满足要求的部分记录,这时就需要用到WHERE条件子句。

4.3.1 简单的选择查询——WHERE

WHERE子句允许指定查询条件,使得SELECT语句的结果表中只包含那些满足查询条件的记录。语法格式如下:

        SELECT  列名1[,列名2,…列名n]
        FROM    表名
        WHERE   条件

使用WHERE子句可以限制查询的范围,提高查询效率。在使用时,WHERE子句必须紧跟在FROM子句后面。WHERE子句中的条件表达式包括算术表达式和逻辑表达式两种,SQL Server对WHERE子句中的查询条件的数目没有限制。

实例10 查询BookInfo表中publish列科学出版社的记录

本实例将使用WHERE子句,查询BookInfo表中publish列为科学出版社的所有记录,代码如下:

        USE Library
        SELECT *
        FROM BookInfo
        WHERE publish='科学出版社'

运行该代码,得到的查询结果如图4.10所示。

当处理带WHERE子句的SELECT语句时,DBMS对输入表的每一行应用搜索条件,从而进行筛选。具体来讲,就是用行中列的值代替WHERE子句中的列名。在本例中,DBMS依次取出publish列中的记录,将其与“科学出版社”比较,满足条件则保留,否则就排除在结果表外。

图4.10 查询BookInfo表中publish列为科学出版社的所有记录

在该例中,WHERE子句中使用了“=”运算符,它要求两边的数值类型必须相同。publish列为char型,所以后面的“科学出版社”必须写在单引号内,表明它是一个字符串。如果是数值,如是int型,直接写值即可。

4.3.2 使用比较表达式

在WHERE子句中,可以使用比较表达式作为搜索条件。其一般表达形式如下所示。

        表达式    算术操作符  表达式

其中的表达式可以为常量、变量和列表达式的任意有效组合。

WHERE子句中允许使用的比较操作符包括:=(等于)、<(小于)、>(大于)、<>(不等于)、!>(不大于)、!<(不小于)、>=(大于等于)、<=(小于等于)、!=(不等于)。

算术表达式的使用比较简单。下面就通过具体的实例讲一下比较操作符的用法。

实例11 查询BookInfo表中价格不超过50元的图书记录

本实例将查询BookInfo表中价格少于50元的图书记录,即price列的值不大于50。代码如下:

        USE Library
        SELECT *
        FROM BookInfo
        WHERE price!>50

运行该代码,得到的查询结果如图4.11所示。

图4.11 查询BookInfo表中价格不超过50元的图书记录

实例中“!>”操作符也可以用“<=”替换,二者是等价的。

另外,除了可以对数值类型的字段使用比较操作符外,对于字符串类型的字段也可以使用比较操作符。而对于如“>”、“>=”、“<”等比较操作符,它是根据字符的排列顺序比较的。

字符串的排序是根据其首字母的顺序进行的,如果首字符相同,则比较其下一个字符,依此类推。而对于汉字的排序,是根据其汉语拼音的第一个字母的顺序进行的,如果第一个字母相同,则比较第二个字母,依此类推,且汉字的顺序要高于字母的顺序。

实例12 使用比较运算符,比较字符串

本实例将查询BookInfo表中,bookname字段中图书名称字符串小于等于“多媒体基础”的所有记录,代码如下:

        USE Library
        SELECT *
        FROM BookInfo
        WHERE bookname<='多媒体基础'

运行该代码,得到的查询结果如图4.12所示。

图4.12 查询BookInfo表中图书名称字符串小于等于“多媒体基础”的所有记录

从图4.12所示的结果不难理解比较操作符对字符串的操作结果。

另外,在比较运算符中,不等于运算符“<>”和“!=”也经常使用,二者实现的功能完全相同。

实例13 不等于运算符“<>”的使用

本实例将查询BookInfo表中,除了“大众出版社”外,其他出版社的图书记录。代码如下:

        USE Library
        SELECT *
        FROM BookInfo
        WHERE publish<>'大众出版社'

运行该代码,得到的查询结果如图4.13所示。

图4.13 查询BookInfo表中“大众出版社”外的其他出版社的图书记录

4.3.3 使用基本的逻辑表达式——NOT、AND、OR

在WHERE子句中,可以使用多个搜索条件选择记录(行),即通过逻辑运算符(NOT、AND或OR)将多个单独的搜索条件结合在一个WHERE子句中,形成一个复合的搜索条件。当对复合搜索条件求值时,DBMS对每个单独的搜索条件求值,然后执行布尔运算来决定整个WHERE子句的值是TRUE还是FALSE。只有那些满足整个WHERE子句的值是TRUE的记录才出现在结果表中。

1.AND运算符

AND运算符表示逻辑“与”的关系。当使用AND运算符组合两个逻辑表达式时,只有当两个表达式均为TRUE时返回TRUE。其基本使用语法如下:

        boolean_expression AND boolean_expression

AND运算符的真值表如表4.3所示。

表4.3 AND运算符的真值表

在Transact-SQL中,逻辑表达式共有3种可能的结果值,分别是TRUE、FALSE和UNKOWN。UNKOWN是由值为NULL的数据参与逻辑运算得出的结果,即只要有NULL值参与逻辑与(AND)运算,其结果均为UNKOWN。

★ 说明 ★

当语句中使用多个逻辑运算符时,将首先计算AND运算符。当然,也可以通过使用括号改变求值顺序。

实例14 使用AND运算符进行多条件查询

本实例将查询BookInfo表中,已经被借出(store值为1)的科学出版社的图书记录。代码如下:

        USE Library
        SELECT *
        FROM BookInfo
        WHERE store=1
        AND publish='科学出版社'

运行该代码,得到的查询结果如图4.14所示。

在一个WHERE子句中,也可以同时使用多个AND运算符连接多个查询条件。这时,只有满足所有的查询条件的记录,才被包括在结果表中。

图4.14 查询BookInfo表中已经被借出的科学出版社的图书记录

2.OR运算符

OR运算符实现逻辑“或”的运算关系。当使用OR运算符组合两个逻辑表达式时,只要其中一个表达式的条件为TRUE,结果便返回TRUE。其基本语法如下:

        boolean_expression OR boolean_expression

OR运算符的真值表如表4.4所示。

表4.4 OR运算符的真值表

★ 注意 ★

当UNKNOWN (NULL)与TRUE进行OR运算时,结果为TRUE而不是UNKNOWN (NULL)。

OR运算符的优先级低于AND运算符,即在AND运算符之后才对OR运算符求值。不过,使用括号可以更改求值的顺序。

实例15 使用OR运算符进行多条件查询

本实例将查询BookInfo表中,大众出版社或者价格高于50元的图书记录。代码如下:

        USE Library
        SELECT *
        FROM BookInfo
        WHERE publish='大众出版社'
        OR price>50

运行该代码,得到的查询结果如图4.15所示。

图4.15 查询BookInfo表中大众出版社或者价格高于50元的图书记录

3.NOT运算符

NOT运算符实现逻辑“非”的运算关系,用于对搜索条件的逻辑值求反。其基本语法如下:

        NOT boolean_expression

NOT运算符的真值表如表4.5所示。

表4.5 NOT运算符的真值表

NOT运算符允许根据在列中找不到某些值来选择行。

实例16 使用NOT运算符进行多条件查询

本实例将查询BookInfo表中,非大众出版社的图书记录。代码如下:

        USE Library
        SELECT *
        FROM BookInfo
        WHERE NOT publish='大众出版社'

运行该代码,得到的查询结果如图4.16所示。

图4.16 查询BookInfo表中非大众出版社的图书记录

NOT运算符对紧跟其后的条件取反,如在本实例中,“NOT publish='大众出版社'”语句实际上就等价于“publish!='大众出版社'”或者“publish<>'大众出版社'”语句。

在实际应用中,使用NOT运算符时,经常忽视其对NULL值的处理问题。要时刻牢记一点,NOT NULL的结果仍为NULL。如下面的实例:

实例17 NOT运算符对NULL值的处理

本实例在BookInfo表中,查询图书读者的读者编号不大于9704(包括没有读者)的所有图书记录,如采用下面的代码:

        USE Library
        SELECT *
        FROM BookInfo
        WHERE NOT reader>9704

运行该代码,得到的查询结果如图4.17所示。

图4.17 查询结果

从图4.17所示的查询结果可见,读者编号(reader)为NULL的值并没有出现在结果表中。因为当reader为NULL时,“reader >9704”的执行结果也为NULL,而NOT NULL的运算结果仍然为NULL,也就不满足查询条件。

★ 说明 ★

在WHERE子句中,有关空值(NULL)的判定与操作参见4.3.4节的介绍。

4.基本逻辑运算符的组合使用

在WHERE子句中,各种逻辑运算符可以组合使用,即AND、OR、NOT运算符可以同时使用。与使用算术运算符进行运算一样,使用逻辑运算符也存在运算的优先级的问题。这3种逻辑运算符中,NOT运算符的优先级最高,而后是AND,最后是OR。其中AND和OR运算符同时使用时,要特别注意。即当出现下面的运算关系时:

        condition1
        OR       condition2
        AND condition3

其运算实际上是等价于:

        condition1
        OR      (condition2 AND condition3)

下面通过一个实例来说明运算符的优先级顺序。

实例18 逻辑运算符的优先级

本实例查询BookInfo表中,所有大众出版社和科学出版社并且价格不高于50元的图书记录。如果采用下面的代码:

        USE Library
        SELECT *
        FROM BookInfo
        WHERE publish='大众出版社'
        OR publish='科学出版社'
        AND NOT price>50

运行该代码,得到的查询结果如图4.18所示。

图4.18 查询结果

显然并没有得到预期的结果。下面就根据逻辑运算符的优先级顺序分析一下WHERE子句中代码的执行过程。

· 首先DBMS执行NOT运算,即,结果集中的记录需要满足价格不高于50元。

· 而后DBMS执行AND运算,即,结果集中的记录需要满足科学出版社且价格不高于50元。

· 最后DBMS执行OR运算,即,结果集中的记录需要满足大众出版社或者满足科学出版社且价格不高于50元。

显然就与预期的结果不相符。如果要得到正确的结果,就需要使用括号改变运算的顺序,如下面的代码:

        USE Library
        SELECT *
        FROM BookInfo
        WHERE (publish='大众出版社'OR publish='科学出版社')
        AND NOT price>50

运行该代码,即得到了正确的查询结果,如图4.19所示。

图4.19 正确的查询结果

★ 说明 ★

当OR运算符和AND运算符同时运用时,建议用户不要考虑其默认的优先级顺序,而是采用括号( )来实现需要的执行顺序,这样可以增强程序的可读性。

4.3.4 空值(NULL)的判断——IS [NOT] NULL

通过前面的学习可知,除非在创建时指定了NOT NULL约束,数据库表中某些列的值可以为NULL。什么是NULL?顾名思义,NULL就是空,在数据库中,含有空值的表的列的长度为零。

具体来讲,NULL与0、空字符串、空格都不同,等价于没有任何值,是未知数。然而,NULL却是一个数据值,而且它属于一个域。例如一个字符串字段,其中的NULL只能是一个字符串。尽管它的内容没有定义,或者未知,但它是字符串,这一点无可置疑。

对于NULL,一般的运算,比如加、减、乘、除,都会返回NULL。永远不会有什么数据等于NULL。当然,1不等于NULL,2也一样。注意,NULL也不等于NULL,说一个NULL等于NULL是错误的。

在SQL中,表达式“X=NULL”,结果应当是UNKOWN。而表达式“X IS NULL”,就得看情况,如果X是NULL,就返回TRUE;X不为NULL,则返回FALSE。

因此,在WHERE子句中,判断一个值是否为NULL,只能采用IS [NOT] NULL判别式。下面通过具体实例说明IS [NOT] NULL判别式的用法。

实例19 使用IS [NOT] NULL判别式处理NULL值

本实例将实现实例17提出的查询功能,即在BookInfo表中,查询图书读者的读者编号不大于9704(包括没有读者,为NULL)的所有图书记录,代码如下:

        USE Library
        SELECT *
        FROM BookInfo
        WHERE NOT reader>9704
        OR reader IS NULL

运行该代码,得到的查询结果如图4.20所示。

图4.20 查询结果

可见,相比实例17,此时才得到了正确的结果。

★ 说明 ★

SAL IS NULL不可以写成SAL=NULL;除了IS [NOT] NULL之外,空值不满足任何查找条件;如果NULL参与算术运算,则该算术表达式的值为NULL;如果NULL参与比较运算,则结果为UNKNOWN;如果NULL参与聚集运算,则除count(*)之外其他聚集函数都忽略NULL。

4.3.5 限定数据范围——BETWEEN

在WHERE子句中,使用BETWEEN关键字可以更方便地限制查询数据的范围。当然,还可以使用NOT BETWEEN关键字查询限定数据范围之外的记录。

语法格式可表示如下:

        表达式[NOT] BETWEEN 表达式1 AND 表达式2

比如,选择范围在10~100之间的数,采用BETWEEN运算符可以表示为BETWEEN 10 AND 100。

★ 注意 ★

使用BETWEEN限制查询数据范围时同时包括了边界值,而使用NOT BETWEEN进行查询时没有包括边界值。

实例20 使用BETWEEN限定数据范围查询

本实例实现在BookInfo表中,查询图书价格在35~60之间的所有图书记录。代码如下:

        USE Library
        SELECT *
        FROM BookInfo
        WHERE price BETWEEN 35 AND 60

运行该代码,得到的查询结果如图4.21所示。

图4.21 查询BookInfo表中图书价格在35~60之间的记录

从结果可见,使用BETWEEN关键字查询包含了边界值。

实际上,使用BETWEEN表达式进行查询的效果完全可以用含有“>=”和“<=”的逻辑表达式来代替,使用NOT BETWEEN进行查询的效果完全可以用含有“>”和“<”的逻辑表达式来代替。

对于实例20,若采用含有“>=”和“<=”的逻辑表达式来代替BETWEEN表达式,代码如下:

        USE Library
        SELECT *
        FROM BookInfo
        WHERE price >=35
        AND price <=60

执行该代码,会得到同样的查询结果。

使用BETWEEN运算符也可以实现时间的比较,下面就给出一个具体的应用实例。

实例21 使用BETWEEN运算符限定时间范围查询

本实例实现在BookInfo表中,查询出版日期在2004/5/1和2006/1/1之间的所有图书记录。代码如下:

        USE Library
        SELECT *
        FROM BookInfo
        WHERE pubdate BETWEEN '2004/5/1' AND '2006/1/1'

运行该代码,得到的查询结果如图4.22所示。

图4.22 查询BookInfo表中出版日期在2004/5/1和2006/1/1之间的记录

4.3.6 限制检索数据的范围——IN

同BETWEEN关键字一样,IN的引入也是为了更方便地限制检索数据的范围,灵活使用IN关键字,可以用简洁的语句实现结构复杂的查询。

语法格式可表示如下:

        表达式 [NOT]  IN  (表达式1 , 表达式2 [,…表达式n])

所有的条件在IN运算符后面罗列,并以括号( )包括起来,条件中间用逗号分开。当要判断的表达式处于括号中列出的一系列值之中时,IN运算符求值为TRUE。

实例22 使用IN限定检索数据的范围

本实例实现在BookInfo表中,查询9702、9705和9707读者所借阅的所有图书记录。代码如下:

        USE Library
        SELECT *
        FROM BookInfo
        WHERE reader IN (9702,9705,9707)

运行该代码,得到的查询结果如图4.23所示。

图4.23 查询BookInfo表中9702、9705和9707读者所借阅的图书记录

在大多数情况下,OR运算符与IN运算符可以实现相同的功能。如实例21,采用OR运算符实现,代码如下:

        USE Library
        SELECT *
        FROM BookInfo
        WHERE reader =9702
        OR reader =9705
        OR reader =9707

执行该代码,会得到同样的查询结果。

IN运算符与OR运算符可以实现相同的功能,然而使用IN运算符更为简洁,特别是当选择的条件很多时,只需在括号内用逗号间隔各条件即可,其运行效率也比OR运算符要高。另外,使用IN运算符,其后面所有的条件可以是另一条SELECT语句,即子查询。这在后面的章节中会有详细介绍。

4.3.7 模糊查询——LIKE

在实际的应用中,用户不会总是能够给出精确的查询条件。因此,经常需要根据一些并不确切的线索来搜索信息。Transact-SQL提供了LIKE子句来进行这类模糊搜索。

语法格式可表示如下:

        表达式 [NOT] LIKE  条件

LIKE子句在大多数情况下会与通配符配合使用。SQL Server提供了4种通配符供用户灵活实现复杂的模糊查询条件,如表4.6所示。

表4.6 SQL Server提供的通配符及功能

★ 注意 ★

所有通配符都只有在LIKE子句中才有意义,否则通配符会被当作普通字符处理。

下面就具体介绍各通配符的使用。

1.“%”通配符

“%”通配符表示任意字符的匹配,且不计字符的多少。如“计算机%”表示匹配以字符串“计算机”开头的任意字符串;“%计算机”表示匹配以字符串“计算机”结尾的任意字符串;“%计算机%”表示匹配含有字符串“计算机”的任意字符串。

实例23 使用“%”通配符进行模糊查询

本实例实现在BookInfo表中,查询图书名称中含有“数据库”字符串的所有图书记录。代码如下:

        USE Library
        SELECT *
        FROM BookInfo
        WHERE bookname LIKE '%数据库%'

运行该代码,得到的查询结果如图4.24所示。

图4.24 查询BookInfo表中书名中含有“数据库”字符串的所有图书记录

可见,图书名称中只要含有“数据库”字符串的记录均出现在结果表中,而不论其在书名中的位置。

此外,使用“%”通配符还可以指定开头和结尾同时匹配的方式,这在实际应用中使用也较多。

实例24 使用“%”通配符实现头、尾匹配

本实例实现在BookInfo表中,查询图书名称中以“VC”字符串开头,以“基础”字符串结尾的所有图书记录。代码如下:

        USE Library
        SELECT *
        FROM BookInfo
        WHERE bookname LIKE 'VC%基础'

运行该代码,得到的查询结果如图4.25所示。

图4.25 查询BookInfo表中书名中以“VC”开头,以“基础”结尾的的所有图书记录

另外,“%”通配符还经常用在NOT LIKE语句中实现排除查询。

实例25 使用“%”通配符实现排除查询

本实例实现在BookInfo表中,查询所有的不是基础类的图书记录,即书名中不能包含“基础”字符串。代码如下:

        USE Library
        SELECT *
        FROM BookInfo
        WHERE bookname NOT LIKE '%基础%'

运行该代码,得到的查询结果如图4.26所示。

图4.26 查询BookInfo表中所有的非基础类图书记录

2.“_”通配符

与“%”通配符不同,“_”通配符只能匹配任何单个字符。比如,“_ean”表示将查找以“ean”结尾的所有4个字母的字符串(“Dean”、“2ean”、“Sean”等)。当然,要表示两个字符的匹配,就需要使用两个“_”通配符,即写成“__”即可。

★ 说明 ★

只有在用户确定所要查询的字符串的个数,只是不确定其中的一个或几个字符的确切值时,才能使用“_”通配符。

实例26 使用“_”通配符实现模糊查询

本实例在BookInfo表中,需要查询书名以“数字”开头,以“处理”结尾的6个字的图书记录,可采用如下的代码:

        USE Library
        SELECT *
        FROM BookInfo
        WHERE bookname LIKE '数字__处理'

运行该代码,得到的查询结果如图4.27所示。

图4.27 查询BookInfo表中以“数字”开头,以“处理”结尾的6个字的图书记录

另外,使用“_”通配符时需要注意,如果查询“计算机__”,即匹配以“计算机”开头的5个字的字符串,此时字符串“计算机”、“计算机_”(以“计算机”开头的少于6个字符的字符串)均满足匹配要求。这一点在查询匹配固定数目字符时,特别重要。

实例27 使用“_”通配符实现固定数目字符查询

本实例要实现在BookInfo表中,查询书名为6个字符的图书记录,如采用如下的代码:

        USE Library
        SELECT *
        FROM BookInfo
        WHERE bookname LIKE '______'

运行该代码,得到的查询结果如图4.27所示。

图4.28 查询结果

从图4.28所示的结果可见,数目字符数少于等于6个的记录均满足查找条件。而如果要实现实例的要求,就必须排除书名字符数少于6个的记录,此时可采用下面的代码:

        USE Library
        SELECT *
        FROM BookInfo
        WHERE bookname LIKE '______'                    --6个下画线
        AND bookname NOT LIKE '_____'                       --5个下画线

此时,运行该代码,结果如图4.29所示。

图4.29 满足要求的查询结果

3.“[]”通配符

“[]”通配符用于指定范围(如[a-f])或集合(如[abcdef])中的任何单个字符。只要满足这些字符其中之一,且位置出现在“[]”通配符位置的字符串就满足查询条件。比如“[C-P]arsen'”,将查找以“arsen”结尾,并且以介于“C”与“P”之间的任何单个字符开始的字符串,如Carsen、Larsen、Karsen等。

可以将通配符模式匹配字符作为文字字符使用。若要将通配符作为文字字符使用,请将通配符放在方括号中。表4.7列出了几个使用LIKE关键字和“[ ]”通配符的示例。

表4.7 LIKE关键字和“[ ]”通配符示例

当然,各通配符也可以组合使用,实现复杂的模糊查询。

实例28 使用“[]”通配符实现模糊查询

本实例要实现在BookInfo表中,查询所有的书名以英文字母开头的图书记录,实现代码如下:

        USE Library
        SELECT *
        FROM BookInfo
        WHERE bookname LIKE '[a-Z]%'

运行该代码,得到的查询结果如图4.30所示。

4.“[^]”通配符

与“[]”通配符相反,“[^]”通配符用于匹配没有在方括号中列出的字符。其用法与“[]”通配符完全相同,如“de[^l]%'”将查找以“de”开始,并且其后的字母不为“l”的所有字符串。

图4.30 查询BookInfo表中书名以英文字母开头的图书记录

实例29 使用“[^]”通配符实现模糊查询

本实例要实现在BookInfo表中,查询所有的书名不以英文字母开头的图书记录,实现代码如下:

        USE Library
        SELECT *
        FROM BookInfo
        WHERE bookname LIKE '[^a-Z]%'

运行该代码,得到的查询结果如图4.31所示。

图4.31 查询BookInfo表中书名不以英文字母开头的图书记录

5.ESCAPE定义转义符

使用LIKE进行模糊查询时,当“%”、“_”和“[]”符号单独出现时,就会被认为是通配符。但有时可能需要搜索包含一个或多个特殊通配符的字符串,例如,数据库表中可能存储含百分号(%) 的折扣值。若要搜索作为字符而不是通配符的百分号,必须提供ESCAPE关键字和转义符。

如下面的语句:

        LIKE  '%T%'   ESCAPE  'T'

就使用了ESCAPE关键字定义了转义字符“T”,这样,T就成为转义符,将字符串“%T%”中的第二个百分符(%)作为实际值,而不是通配符。当然,第一个百分符(%)仍然被看作是通配符,因此满足该查询条件的字符串为所有以“%”结尾的字符串。

如果LIKE子句中的转义符后面没有字符,则该转义无效,并且LIKE返回FALSE。如果转义符后面的字符不是通配符,则将放弃转义符,并将该转义符后面的字符作为该模式中的常规字符处理。

假设有一个表X,列col的值有:“[xyz]”、“%xyz”、“x_yz”和“xyzw”。如果要在X中查找以%开头的字符串,可使用如下的语句:

        SELECT col
        FROM X
        WHERE col like 'a%%'
        ESCAPE 'a'

在这个查询中'a'是转义字符,表示紧跟在它后面的字符已被转义。读者可以自己实现这个实例。

4.4 排序查询结果

SELECT语句获得的数据一般是没有排序的(使用DISTINCT关键字除外)。为了方便阅读和使用,最好对查询的结果进行一次排序。在SQL语言中,用于排序的是ORDER BY子句。

4.4.1 基本排序——ORDER BY

使用ORDER BY子句可以指定在SELECT语句返回的列中所使用的排序顺序。其语法格式可表示如下:

        ORDER BY 表达式1 [ ASC | DESC]  [,表达式2[ ASC | DESC][,…n]]

其中,表达式是用于排序的列。可以用于多列进行排序,各列在ORDER BY子句中的顺序决定了排序过程中的优先级。

在SQL Server中,使用ORDER BY子句,需要注意下面几点:

· ntext、text、image或xml类型的列,不能用于ORDER BY子句。

· 在默认的情况下,ORDER BY按升序进行排列,即默认使用的是ASC关键字。如果用户特别要求按降序进行排列,必须使用DESC关键字。

· 除非同时指定TOP,否则ORDER BY子句在视图、内联函数、派生表和子查询中无效。

· 空值(NULL)被视为最低的可能值。

· 在与SELECT…INTO语句一起使用以从另一来源插入行时,ORDER BY子句不能保证按指定的顺序插入这些行。

· ORDER BY子句一定要放在所有子句的最后(无论包含多少子句)。

实例30 基本排序操作

本实例要实现在BookInfo表中,查询所有的bookid、bookname、pubdate、price和reader信息,并将结果按照pubdate的升序排序。实现代码如下:

        USE Library
        SELECT bookid,bookname,pubdate,price,reader
        FROM BookInfo
        ORDER BY pubdate

运行该代码,得到的查询结果如图4.32所示。

图4.32 按照pubdate的升序排序的查询结果

可见,查询结果按照pubdate字段的升序顺序排列。

对于时间、数值类型的字段排序,其排序规则就是按照时间的早晚、数值的大小进行的;对于字符型,则是依照其ASCII码的先后顺序进行的。

如果要使用降序的方式排列数据,则需要指定DESC关键字。在排序时,空值(NULL)被视为最低的可能值。如下面的实例:

实例31 降序排序操作

本实例同样实现在BookInfo表中,查询所有的bookid、bookname、pubdate、price和reader信息,但结果按照reader列的降序排序。实现代码如下:

        USE Library
        SELECT bookid,bookname,pubdate,price,reader
        FROM BookInfo
        ORDER BY reader DESC

运行该代码,得到的查询结果如图4.33所示。

图4.33 按照reader列的降序排序的查询结果

可见,按照降序的方式排列,NULL值排在了最后。

另外,也可以采用查询列之外的其他列作为查询的排序列。

实例32 使用查询列之外的列,进行排序操作

本实例实现在BookInfo表中,查询所有的bookid、bookname、pubdate和price信息,结果按照reader列的降序排序。实现代码如下:

        USE Library
        SELECT bookid,bookname,pubdate,price
        FROM BookInfo
        ORDER BY reader DESC

运行该代码,得到的查询结果如图4.34所示。

图4.34 按照reader列的降序排序的查询结果

可见,虽然reader列不在查询的列的范围内,但通过与实例31结果的比较可知,查询结果仍然按照reader列的降序方式排列。

4.4.2 对多列进行排序

使用ORDER BY子句也可以根据两列或多列的结果进行排序,只要用逗号分隔开不同的排序关键字就可以了。如下面的语句:

        ORDER BY  col1 DESC,col2,……,colm

其实际排序结果是根据ORDER BY子句后面列名的顺序确定优先级的。即查询结果首先以列col1的降序顺序进行排序,而只有当列col1出现相同的信息时,这些相同的信息再按col2列的升序进行排序,依此类推。

实例33 对多列进行排序操作

本实例实现在BookInfo表中,查询所有的图书信息,结果按照publish列的降序和pubdate列的升序顺序排序。实现代码如下:

        USE Library
        SELECT *
        FROM BookInfo
        ORDER BY publish DESC,pubdate

运行该代码,得到的查询结果如图4.35所示。

图4.35 多列排序的查询结果

可见,系统根据排序的优先级,首先依据publish字段的记录进行降序排序(汉语拼音的首字母),当publish字段的记录相同时,再依据pubdate字段的记录进行升序排序。

另外,ORDER BY子句除了可以根据列名进行排序外,还支持根据列的相对位置(即序号)进行排序。

如下面的语句:

        SELECT       col1,col2,……,coln
        FROM         table_name
        ORDER BY     2,1

此时,ORDER BY子句后面的序号实际上就是SELECT关键词后面的列名的序号。此时ORDER BY子句就等同于ORDER BY col2,col1。

实例34 使用序号对多列进行排序操作

本实例实现在BookInfo表中,查询所有的bookid、bookname、publish、pubdate和price信息,并通过使用序号的方式对结果按照publish列的降序和pubdate列的升序顺序排序。实现代码如下:

        USE Library
        SELECT bookid,bookname,publish,pubdate,price
        FROM BookInfo
        ORDER BY 3 DESC,4

运行该代码,得到的查询结果如图4.36所示。

当ORDER BY所需要的列不在SELECT语句所选的范围内时,采用列的序号显然是行不通的。当然,可以将列别与序号混合起来解决这个问题。

图4.36 使用序号进行多列排序的查询结果

4.5 小结

本章重点介绍SELECT语句的基本语法结构,以及其中涉及的SELECT子句、WHERE子句和ORDER BY子句的使用。掌握了这些,基本的查询操作就不成问题。另外,读者要特别注意4.1节介绍的SELECT语句各子句的顺序及执行步骤。在后面的章节中,将陆续介绍其他复杂的查询操作。