SQL Server 2008宝典(第2版)
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

第4部分 数据库管理篇

第8章 T-SQL程序

本章包括

◆ T-SQL简介

◆ T-SQL中的批处理

◆ T-SQL中的数据类型转换

◆ T-SQL中的常量和变量

◆ T-SQL中的常用函数

◆ 数据库对象的引用方法

◆ T-SQL中的注释

◆ T-SQL中的运算符

◆ T-SQL中的流程控制

◆ T-SQL中的关键字

在前面的章节中,介绍了很多使用T-SQL操作数据库、数据表及数据记录的方法,使用这些方法可以方便灵活地访问SQL Server数据库。然而,只使用单个T-SQL语言来操作数据的话,是远远不够的。T-SQL还可以像其他编程语言一样,使用流程来进行程序控制,完成更强大的功能。

8.1 T-SQL概述

8.1.1 什么是T-SQL

T-SQL是Transact-SQL的简写,是使用SQL Server的核心,所有与SQL Server实例通信的应用程序,都是通过发送T-SQL语句到服务器来完成对数据库的操作。

T-SQL与在本书1.5节中介绍的SQL稍有不同,SQL是结构化查询语言(Structured Query Language),是目前关系型数据库管理系统中使用得最广泛的查询语言。T-SQL是在SQL上发展而来的,T-SQL在SQL的基础上添加了流程控制,是SQL语言的扩展。SQL是几乎所有的关系型数据库都支持的语言,而T-SQL是Microsoft SQL Server支持的语言。

注意 在SQL Server 2008上可以运行的T-SQL程序,在其他数据库,例如Access和Oracle上就不一定可以运行。

8.1.2 T-SQL的语法约定

任何一种语言都会有其语法约定,T-SQL也不例外,本书5.3.2节曾经对T-SQL的语法约定进行过介绍,在此不再赘述。表8.1是对T-SQL语法约定的总结。

表8.1 T-SQL语法约定

8.2 数据库对象的引用方法

在SQL Server 2008中,数据库对象包括数据表、视图、存储过程、用户自定义函数等。这些数据库对象除了在命名时需要遵循命名规则之外,在引用时,同样需要遵循引用规则。下面是对数据库对象引用方法的介绍。

8.2.1 引用数据库对象的方法

在SQL Server 2008中,除了另外指定外,所有对数据库对象的T-SQL引用都是由以下四个部分组成的名称。

    [ server_name. [database_name]. [schema_name]. | database_name.[schema_name]. |
    schema_name. ] object_name

其中:

◆ server_name:链接服务器名称或远程服务器名称。

◆ database_name:如果对象是驻留在SQL Server的本地实例中,则database_name是SQL Server数据库的名称。如果对象在链接服务器中,则database_name是OLE DB目录。

◆ schema_name:如果对象在SQL Server数据库中,则schema_name是包含对象的架构的名称。如果对象在链接服务器中,则schema_name是OLE DB架构名称。

◆ object_name:对象名称。

说明 链接服务器通常用于处理分布式查询。SQL Server链接服务器通过OLE DB来访问的其他类型数据库,例如Access和Oracle等。远程服务器通常也是SQL Server服务器,是通过SQL Server客户端所连接的SQL Server服务器。

虽然T-SQL对数据库对象的引用包括四个部分,但在实际运用中,往往可以将其简写,简写方法如表8.2所示。

表8.2 数据库对象引用的简写

在什么情况下可以将对数据库对象的引用简写呢?一般来说,有以下几种情况可以简写。

◆ 当要访问的数据库对象与正在使用的数据库不在同一台服务器上,那么就一定要指定Server名。

◆ 当要访问的数据库对象与正在使用的数据库在同一台服务器上,但不在同一个数据库中时,那么可以省略Server名,但一定要指定database名。

◆ 当要访问的数据库对象在正在使用的数据库上,那么可以省略Server和database名,但要指定schema名。

◆ 当要访问的数据库对象与正在使用的架构相同时,那么就可以只写object名了。

说明 简单一点说,什么不同就使用什么,什么相同就省略什么。

8.2.2 架构

在前面多次提到了schema,也就是架构,那么什么是架构呢?架构是形成单个命名空间的数据库实体的集合。命名空间是一个集合,其中每个元素的名称都是唯一的。在同一个架构中不能有相同类型的相同名称的数据库对象。例如,为了避免名称冲突,同一架构中不能有两个同名的表。两个表只有在位于不同的架构中时才可以同名。

在SQL Server 2008中,默认的架构是dbo。如果在创建数据库对象时没有指定架构,那么默认将数据库对象放在dbo架构里,如图8.1所示。数据表、视图、存储过程的架构都是dbo。

图8.1 默认架构名

在创建数据库对象时,可以指定其所属架构,如图8.2所示,在【属性】窗格的【架构】下拉列表框中可以选择架构名。

图8.2 更改默认架构

8.2.3 创建自定义架构

在SQL Server里,除了图8.2所示的几个系统内置的架构之外,还允许用户创建自定义架构。下面以Northwind数据库为例,创建一个名为“myschema”的架构。

step 1 启动SQL Server Management Studio,在【对象资源管理器】窗格中展开树形目录,定位到【Northwind】→【安全性】→【架构】选项。

step 2 右击【架构】选项,在弹出的快捷菜单中选择【新建架构】选项,打开图8.3所示的【架构-新建】对话框。在【架构名称】文本框中输入架构名称,在【架构所有者】文本框中输入架构的所有者名称(也可以不输入)。

图8.3 新建架构

step 3 单击【确定】按钮完成操作。

8.3 T-SQL中的批处理

在SQL Server 2008中,可以一次执行多个T-SQL语句,这样多个T-SQL语句称为“批”。SQL Server 2008会将一批T-SQL语句当成一个执行单元,将其编译后一次执行,而不是将一个个T-SQL语句编译后再一个个执行。

8.3.1 一次执行多个T-SQL语句

例一:执行一个更新语句。

执行一个更新语句,更改“类别”表中类别名称为“图书”的说明字段,并查看更改前后的记录。其代码如下:

    INSERT类别(类别名称,说明)
        VALUES (’图书’, ’各种图书’)
    SELECT * FROM类别
        WHERE类别名称=N’图书’
    UPDATE类别
        SET说明=N’计算机、时尚生活等图书’
        WHERE类别名称=N’图书’
    SELECT * FROM类别
        WHERE类别名称=N’图书’

运行结果如图8.4所示。在第1个“1行受影响”之前的内容,是第1个Select语句的查询结果;第2个“1行受影响”显示的是Update语句的结果;在第2个“1行受影响”与第3个“1行受影响”之间显示的内容是第2个Select语句的查询结果。

图8.4 T-SQL的批处理

8.3.2 使用GO语句分隔多个批

在SQL Server 2008中同样允许一次使用多个批,不同的批之间用“GO”来分隔。查询编辑器会自动根据GO指令来将T-SQL语句分为多个批来编译执行。例如,将例一中的代码改写如下:

    SELECT * FROM类别
        WHERE类别名称=N’图书’
    UPDATE类别
        SET说明=N’计算机、时尚生活等图书’
        WHERE类别名称=N’图书’
    GO
    SELECT * FROM类别
        WHERE类别名称=N’图书’
    GO

在查询编辑器遇到第一个“GO”指令时,就先将GO之前的Select语句和Update语句传递给SQL Server编译并运行,然后再读取GO之后的语句。在第二次遇到GO指定时,再把两个GO之间的语句传递给SQL Server编译并运行。这么一来,以上代码就分为两次传递给SQL Server,是两个批。

注意 GO并不是T-SQL语句,只有查询编辑器才能识别并处理。在编写其他应用程序时不能使用GO指令。由于批与批之间是独立的,所以,当其中一个批出现错误时,不会影响其他批的运行。

8.4 T-SQL中的注释

在T-SQL程序中加入注释语句,可以增加程序的可读性。SQL Server不会对注释的内容进行编译和执行。在T-SQL中支持两种注释方式,下面分别介绍。

8.4.1 --注释

--注释的有效范围只到该行结束,也就是说,从--开始,到本行结束为止,都被认为是注释的内容。如果有多行注释内容,每一行的最前面都必须加上--。例如,给例一中的代码加上注释:

    --先插入一条记录
    INSERT类别(类别名称,说明)
        VALUES (’图书’, ’各种图书’)
    --查看插入记录的内容
    SELECT * FROM类别
        WHERE类别名称=N’图书’
    --更新记录内容
    --将“说明”字段内容改为“计算机、时尚生活等图书”
    UPDATE类别
        SET说明=N’计算机、时尚生活等图书’
        WHERE类别名称=N’图书’
    --查看更新后的记录内容
    SELECT * FROM类别
        WHERE类别名称=N’图书’

8.4.2 /*……*/注释

由于--可以注释的范围只有一行,当要进行比较长的注释时,用--进行注释就会显得很麻烦。这种情况下,可以使用/*……*/来进行注释。/*……*/可以对多行语句进行注释,其有效范围是从“/*”开始,到“*/”结束,中间可以跨越多行。例如,给例一中的代码加上注释:

    /*
    下面代码可以完成以下操作:
    1、查看类别表中类别名称为“图书”的记录内容
    2、将类别表中类别名称为“图书”的记录的说明字段的内容改为“计算机、时尚生活等图书”
    3、查看修改后的结果
    */
    SELECT * FROM类别
        WHERE类别名称=N’图书’
    UPDATE类别
        SET说明=N’计算机、时尚生活等图书’
        WHERE类别名称=N’图书’
    SELECT * FROM类别
        WHERE类别名称=N’图书’

8.5 T-SQL数据类型及转换

在SQL Server中,无论是数据表的字段、常量、变量、表达式还是参数,都具有一个相对应的数据类型。数据类型是一种属性,用于指定对象可保存的数据的类型:整数数据、字符数据、货币数据、日期和时间数据、二进制字符串等。具体的数据类型及其使用范围请参考本书6.2节,下面介绍数据类型的转换。

8.5.1 使用Cast转换数据类型

当要对不同类型的数据进行运算时,必须先将其转换成相同的数据类型。在SQL Server里提供了两个函数可以进行数据类型的转换,其中一个就是Cast。Cast的语法代码为:

    CAST ( expression AS data_type [ (length ) ])

其中,expression为任何有效的表达式;data_type为要转换的数据类型;length为数据类型的长度,一般只针对nchar,nvarchar,char,varchar,binary和varbinary这几种数据类型使用,是可选参数。

例二:转换单价数据。

查看产品及单价,并在一列中显示出来,其代码如下:

    SELECT产品名称+ ’的单价为:' + CAST(单价AS VARCHAR(10)) + ’元’
        AS产品介绍
        FROM产品

运行结果如图8.5所示。

图8.5 使Cast的运行结果

在例二中,由于“单价”字段是money类型,在和字符型数据进行连接运算时,必须将其转换成字符型数据。CAST(单价AS VARCHAR(10))的作用是将“单价”字段内容转换成为varchar(10)的数据类型。

8.5.2 使用Convert转换数据类型

Convert( )函数与Cast( )函数类似,作用也是转换数据类型,其语法代码如下:

    CONVERT ( data_type [ ( length ) ] , expression [ , style ] )

其中,data_type为要转换的数据类型,length为数据类型的长度,expression为任何有效的表达式,style是样式。此函数一般用于将datetime或smalldatetime数据转换为字符数据(nchar,nvarchar,char,varchar,nchar或nvarchar数据类型)的日期格式的样式,或者用于将float,real,money或smallmoney数据转换为字符数据(nchar,nvarchar,char,varchar,nchar或nvarchar数据类型)的字符串格式的样式。如果style为NULL,则返回的结果也为NULL。

从Convert的语法代码可以看出,Convert与Cast的区别是可以指定转换的样式。

例三:使用Convert转换列。

查看订单的订购日期、到货日期和发货日期,其代码如下:

    SELECT订单ID,
        CONVERT(varchar(20),订购日期,1) AS订购日期,
        CONVERT(varchar(20),到货日期,102) AS到货日期,
        CONVERT(varchar(20),发货日期,103) AS发货日期
        FROM订单

运行结果如图8.6所示。

图8.6 使用Convert转换数据类型

由图8.6可以看出,指定不同样式之后,转换的数据类型显示也不相同,表8.3列出了样式代码与输出样式的对应关系。

表8.3 style代码说明

8.5.3 隐式数据类型转换

事实上,在进行不同类型的数据运算时,也不一定都必须使用Cast( )或Convert( )函数进行数据类型转换。在SQL Server里,系统会自动将一些数据类型进行转换,这种转换被称为“隐式转换”,而用Cast( )和Convert( )函数转换数据类型被称为“显式转换”。

例四:隐式转换数据类型。

查看“产品”表中的产品库存量所值的资金,代码如下:

    SELECT产品名称,单价*库存量AS积压资金
        FROM产品
        WHERE单价*库存量>0

在以上代码中,“单价”是money数据类型,而“库存量”是smallint数据类型,进行不同数据类型之间的运算,本应先将其转换为相同的数据类型,但是在本例中并没有对数据进行显示转换,因为SQL Server 2008已经将其隐式转换为相同类型了。

当然,SQL Server 2008不能将所有的数据类型都隐式转换,甚至有些数据类型连显式转换都不行。图8.7显示了哪些数据类型可以隐式转换、哪些数据类型可以显式转换、哪些数据类型不能转换。

图8.7 可转换的数据类型

8.5.4 数据类型转换时的注意事项

在进行数据类型转换时,有些事情是必须要了解的:

◆ 在发生隐式转换时,SQL Server 2008会尽量将数据转换成数据范围大的那一种数据类型,以便可以容纳更多的数据。例如smallint类型与int类型数据相加,SQL Server 2008会先将smallint类型转换成int类型后再相加。每个数据类型的使用范围请参照本书6.2节。

◆ 不是每种数据类型都可以相互转换,也不是所有数据类型都可以隐式转换。

◆ 在进行某些数据类型转换时,可能会损失精度。例如,12.34转换成int型时,会将小数部分舍去。

8.6 T-SQL运算符

运算符是一种用来指定要在一个或多个表达式中执行某种操作的符号。例如,“+”表示两个表达式进行相加操作,“*”表示两个表达式进行相乘操作。T-SQL所使用的运算符可以分为算术运算符、赋值运算符、位运算符、比较运算符、逻辑运算符、字符串串联运算符和一元运算符七种。

8.6.1 算术运算符

算术运算符是对两个表达式执行数学运算,这两个表达式可以是精确数字型或近似数字型。表8.4列出了所有的算术运算符,其中,“+”和“-”运算符也可以用datetime和smalldatetime值进行算术运算。

表8.4 算术运算符

8.6.2 赋值运算符

T-SQL里只有一个赋值运算符,它就是等号(=)。赋值运算符的作用是给变量赋值,也可以使用赋值运算符在列标题和定义列值的表达式之间建立关系。

8.6.3 位运算符

位运算符用于在两个表达式之间按位进行逻辑运算,这两个表达式可以是整数或二进制数据类型。表8.5列出了所有的位运算符。

表8.5 位运算符

8.6.4 比较运算符

比较运算符用于判断两个表达式是否相同,返回TRUE或FALSE的布尔数据类型。除了text,ntext和image数据类型的表达式外,比较运算符可以用于所有的表达式。表8.6列出了所有的比较运算符。

表8.6 比较运算符

8.6.5 逻辑运算符

逻辑运算符用于对某些条件进行判断,判断其为TRUE或FALSE,与比较运算符一样,返回的是布尔数据类型。表8.7列出了所有的逻辑运算符。

表8.7 逻辑运算符

8.6.6 字符串串联运算符

T-SQL里只有一个字符串串联运算符,它就是加号(+)。字符串串联运算符的作用是将字符串串联起来。

8.6.7 一元运算符

一元运算符只能对一个表达式进行操作。表8.8列出了所有的一元运算符。

表8.8 一元运算符

8.7 运算符的优先级

当一个复杂的表达式里有多个运算符时,运算符的优先级将决定运算的先后次序。例如“1+2*3”,是先算乘法后算加法,而不是先算加法后算乘法。如果希望某部分能够优先运算,可以用括号括起来,在有多层括号存在时,内层的运算优先。表8.9由高到低列出了运算符的优先级别。

表8.9 运算符的优先级

当一个表达式中的两个运算符有相同的运算符优先级别时,将按照它们在表达式中的位置对其从左到右进行求值。

8.8 T-SQL中的常量

常量,也称为文字值或标量值,是一个代表特定值的符号,是一个不变的值。常量的格式取决于它所表示的值的数据类型,数据类型不同,常量也会有不同的表达方式。

8.8.1 字符串常量

字符串常量是定义在单引号内的一串字符。如果字符串内容本身含有单引号,可以用连续两个单引号来表示,例如:

    ‘ABC’
    ‘abc’
    ‘1234567’
    ‘Ab123’
    ‘SQL Server 2008服务器大全’
    ‘I' ' m back'                        --当字符串中有单引号时,则用两个单引号来表示
    ‘'                                   --空字符串

如果在数据库的属性选项里将【允许带引号的标识符】选项设置为FALSE,也就是将QUOTED_IDENTIFIER选项设置为OFF,那么也可以用双引号来表示字符串。此时,常量中的单引号不需要再特别定义。

8.8.2 Unicode常量

Unicode常量的表示方法与字符串常量的表示方法相同,只是Unicode常量必须有一个大写的N来区别字符串常量。例如:

    N‘ABC’
    N‘abc’
    N‘1234567’
    N‘Ab123’
    N‘SQL Server 2008服务器大全’
    N‘I' ' m back’
    N‘’

对于Unicode常量来说,无论其内容是字母还是汉字,都用两个字节来存储每个字符。

8.8.3 二进制常量

二进制常量必须是以0x开头的十六进制数字字符串,这些常量可以不用引号括起来,例如:

    0xAE
    0x123456
    0x12AF
    0x                              --空的二进制常量,其长度为零

8.8.4 bit常量

bit常量只能使用数据0或1表示,并且不能放在引号中。如果使用一个大于1的数字来定义bit常量,那么这个数据会被强制转换为1。

虽然在SQL Server Management Studio中,打开数据表看到的bit类型的字段内容为TRUE或FALSE,但是在T-SQL中使用bit常量时,还是用1或0来表示TRUE或FALSE。

8.8.5 datetime常量

datetime常量是用单引号括起来的日期或时间字符的字符串,只要输入的日期能明显分辨出年月日,不论用哪种年月日的表达式都可以视为正确的输入,例如:

    ‘August 3, 2006’
    ‘3 August,2006‘
    ‘2006-8-3’
    ‘060803’
    ‘06/08/06’
    ’16:35:56’
    ’06:35 PM’
    ‘2006-8-3 16:35:56’
    ‘3/8/06 6:35:56 PM’

8.8.6 integer常量

integer常量是没有用引号括起来的、不包含小数点的数字,例如:

    1234
    67
    +98
    -37

8.8.7 decimal常量

decimal常量是没有用引号括起来的、包含小数点的数字,例如:

    1234.567
    67.0
    +235.29
    -764.2

8.8.8 float和real常量

float和real常量是用科学记数法来表示数字,例如:

    123.4E5
    0.123E-2
    +123.4E5
    -0.123E-2

8.8.9 money常量

money常量是以货币符号(例如$)开头的数值,例如:

    $12
    $13.45
    +$12
    -$13.45

8.8.10 uniqueidentifier常量

uniqueidentifier常量是表示GUID的字符串,可以使用字符或二进制字符串格式指定,例如:

    0xff19966f868b11d0b42d00c04fc964ff
    '6F9619FF-8B86-D011-B42D-00C04FC964FF'

8.8.11 在T-SQL中使用常量

在T-SQL中,可以用多种方式来使用常量。

◆ 作为算术表达式中的常量,例如:

    SELECT产品名称,单价+$10 AS价格
        FROM产品

◆ 在WHERE子句中,作为比较字段的数据值,例如:

    SELECT *
        FROM产品
        WHERE单价>$10

◆ 为变量赋值,例如:

    DECLARE @abc int
    SET @abc=123

◆ 在Update的set子句或Insert的values子句里指定字段的数据值,例如:

    UPDATE类别SET说明=N’精彩图书’
        WHERE类别名称=N’图书’
    INSERT类别 (类别名称)  VALUES (N’手提电脑等’)

◆ 在Print或Raiserror语句里指定输出的消息文本,例如:

    PRINT ’完成操作’

◆ 作为条件语句(例如If语句和Case函数)中要判断的值,例如:

    IF @@ERROR >0
        PRINT N’出错了’

8.9 T-SQL中的变量

T-SQL中的变量可以分为局部变量和全局变量两种,局部变量是以@开头命名的变量,全局变量是以@@开头命名的变量。

8.9.1 局部变量

局部变量是由用户自定义的变量,这些变量可以用来存储数值型、字符串型等数据,也可以存储函数或存储过程返回的值。使用Declare语句可以声明局部变量,其语法代码如下:

    DECLARE
        { @local_variable [AS] data_type }
          [ , ...n]

其中的参数说明如下。

@local_variable:局部变量名称。

data_type:局部变量的数据类型,但不能是text, ntext或image数据类型。

用Set语句和Select语句可以为变量赋值,其语法代码如下:

    SET @local_variable=value
    SELECT @local_variable=value

用Select语句和Print语句可以显示变量内容,其语法代码如下:

    SELECT @local_variable
    PRINT @local_variable

例五:定义局部变量。

定义局部变量并对其赋值,然后显示其内容。代码如下:

    DECLARE @name varchar(20)
    DECLARE @age int, @sex bit
    SET @name=’张三’
    SET @age=20
    SELECT @sex=1
    SELECT @name
    SELECT @age
    SELECT @sex
    PRINT @name
    PRINT @age
    PRINT @sex

技巧 一个Declare语句可以定义多个变量,变量与变量之间用逗号分开。

运行结果如图8.8所示,最后三行为Print输出的结果,其他内容为Select输出的结果。

图8.8 局部变量输出结果

使用Select语句对变量赋值比使用Set语句的应用范围要广且灵活,它可以将查询结果赋值给变量。

例六:使用Select语句对变量赋值。

输出雇员ID为1的雇员姓名和出生日期,其代码如下:

    DECLARE @name varchar(20)
    DECLARE @birthday datetime
    SELECT @name=姓氏+名字,@birthday=出生日期
        FROM雇员
        WHERE雇员ID=1
    PRINT ’雇员姓名:' +@name
    PRINT ’雇员生日:' +CONVERT(varchar(50), @birthday,102)

运行结果如图8.9所示,由于使用了Select语句将查询出来的数据存储到局部变量中,所以在结果显示中也不会输出查询结果。

图8.9 使用Select赋值的结果

如果在例六的代码中,没有指定Where子句,那么在进行Select操作时会返回多个查询结果,此时只会将Select结果集中的最后一条记录的相应字段内容赋值给对应的局部变量中。如果Select返回的结果集为空,则不对局部变量赋值,局部变量保持原值不变。例如,将例六中的代码改为:

    DECLARE @name varchar(20)
    DECLARE @birthday datetime
    set @name=’未知’
    SELECT @name=姓氏+名字,@birthday=出生日期
        FROM雇员
        WHERE雇员ID=1000
    PRINT ’雇员姓名:' +@name
    PRINT ’雇员生日:' +CONVERT(varchar(50), @birthday,102)

其运行结果如图8.10所示,@name保持原来的值,而@birthday也未赋值,所以为NULL。

图8.10 查询结果为空则不赋值

局部变量的有效范围为当前批中,也就是从Declare开始,到GO结束。如果没有GO语句,则有效范围可以扩展到所有代码结束。将例六中的代码修改如下:

    DECLARE @name varchar(20)
    DECLARE @birthday datetime
    SELECT @name=姓氏+名字,@birthday=出生日期
        FROM雇员
        WHERE雇员ID=1
    PRINT ’雇员姓名:' +@name
    PRINT ’雇员生日:' +CONVERT(varchar(50), @birthday,102)
    GO
    PRINT ’雇员姓名:' +@name
    PRINT ’雇员生日:' +CONVERT(varchar(50), @birthday,102)

输出结果如图8.11所示,在GO语句之前的局部变量有效,在GO之后再使该局部变量将会报错。

图8.11 局部变量的有效范围

8.9.2 全局变量

全局变量是由系统提供的,用于存储一些系统信息。只可以使用全局变量,不可以自定义全局变量。

例七:定义全局变量。

查看Select后的记录集里的记录数,并查看SQL Server 2008自启动以来的连接数,其代码如下:

    SELECT * FROM雇员
    PRINT ’一共查询了’+CAST(@@ROWCOUNT AS varchar(5))+’条记录’
    SELECT ' SQL Server 2008启动以来尝试的连接数:' +
        CAST(@@CONNECTIONS AS varchar(10))

其运行结果如图8.12所示,@@ROWCOUNT记录了上次运行T-SQL所影响的记录数,@@CONNECTIONS记录的是SQL Server自上次启动以来尝试的连接数,无论连接是成功还是失败。

图8.12 使用全局变量

T-SQL中提供的全局变量比较多,表8.10列出了一些常用的全局变量。

表8.10 T-SQL常用全局变量

8.10 T-SQL的流程控制

T-SQL在SQL的基础上添加了流程控制。在T-SQL中可以使用If或While等流程控制语句来对条件进行判断,再依照判断的结果决定下一步的操作是什么。T-SQL中的流程控制语句包括If,While,Case,Goto,Waitfor和Return这6种。

8.10.1 Begin…End语句

Begin…End通常用来表示一个语句块,其中可以包含一组T-SQL语句,凡是在这个语句块里的所有代码,都是属于同一个流程控制。其语法代码如下:

    BEGIN
        {
          sql_statement | statement_block
        }
    END

其中,sql_statement和statement_block为任何有效的T-SQL语句或语句组。Begin…End语句通常与If和While语句搭配使用。

例八:Begin…End语句与If语句搭配使用。

If语句代码实现的功能:查看“产品”表里的蕃茄酱的单价是否低于20元,如果低于20元,查看其订购量。

    USE Northwind
    DECLARE @price money
    DECLARE @productid int
    DECLARE @count int
    SELECT @price=单价,@productid=产品ID
        FROM产品
        WHERE产品名称=N’蕃茄酱’
    IF @price<$20
        BEGIN
            PRINT ’蕃茄酱的单价低于20元’
            SELECT @count=sum(订单明细.数量)
                FROM订单JOIN订单明细
                ON订单.订单ID=订单明细.订单ID
                WHERE订单明细.产品ID=@productid
            PRINT ’其订购量为:' + CAST(@count AS varchar(5))
        END

在以上代码中,Begin与End之间有三个T-SQL语句,它们同属于一个If条件判断。如果Begin…End之间只有一个T-SQL语句,则可以省略Begin…End。例八的代码也可以改写为如下代码:

    USE Northwind
    DECLARE @price money
    DECLARE @productid int
    SELECT @price=单价,@productid=产品ID
        FROM产品
        WHERE产品名称=N’蕃茄酱’
    IF @price<$20
        SELECT sum(订单明细.数量)
            FROM订单JOIN订单明细
            ON订单.订单ID=订单明细.订单ID
            WHERE订单明细.产品ID=@productid

8.10.2 lf…Else语句

If…Else语句是条件判断语句,其语法代码为:

    IF Boolean_expression
        { sql_statement | statement_block }
    [ ELSE
        { sql_statement | statement_block } ]

其解释为:当Boolean_expression为真时,执行If语句块里的语句,否则执行Else语句块里的语句。其中,Else语句块可以省略。

例九:使用If…Else语句。

查看“产品”表里的蕃茄酱的单价是否低于8元,如果低于8元,查看其订购量,否则查看其库存量。其代码如下:

    USE Northwind
    DECLARE @price money
    DECLARE @productid int
    DECLARE @count int
    SELECT @price=单价,@productid=产品ID
        FROM产品
        WHERE产品名称=N’蕃茄酱’
    IF @price<$20
        BEGIN
            PRINT ’蕃茄酱的单价低于20元’
            SELECT @count=sum(订单明细.数量)
                FROM订单JOIN订单明细
                ON订单.订单ID=订单明细.订单ID
                WHERE订单明细.产品ID=@productid
            PRINT ’其订购量为:' + CAST(@count AS varchar(5))
        END
    ELSE
        BEGIN
            PRINT ’蕃茄酱的单价高于20元’
            SELECT @count=sum(库存量)
                FROM产品
                WHERE产品ID=@productid
            PRINT ’其库存量为:' + CAST(@count AS varchar(5))
        END

If…Else语句允许嵌套,也就是说在If…Else语句里还可以包含其他If…Else语句,例如:

    USE Northwind
    DECLARE @price money
    SELECT @price=单价
        FROM产品
        WHERE产品名称=N’蕃茄酱’
    IF @price<$20
            PRINT ’蕃茄酱的单价低于20元’
    ELSE
        BEGIN
            IF $20<=@price and @price<=40
                PRINT ’蕃茄酱的单价在20元与40元之间’
            ELSE
                PRINT ’蕃茄酱的单价大于40元’
        END

8.10.3 While语句

While语句是个循环语句,其语法代码如下:

    WHILE Boolean_expression
        { sql_statement | statement_block }
        [ BREAK ]
        { sql_statement | statement_block }
        [ CONTINUE ]
        { sql_statement | statement_block }

其解释为:当Boolean_expression为TRUE时,执行While语句块的代码,直到Boolean_expression为FALSE为止。如果要在中途中止循环,可以使用Break或Continue语句。Break语句用于跳出目前所执行的循环,Continue语句用于中止执行代码,跳回到While的判断语句重新进行条件判断,再根据判断结果决定是否进入循环。

例十:使用While语句。

输出产品编号为10以内的产品名,其代码如下:

    USE Northwind
    DECLARE @id int
    DECLARE @productname varchar(40)
    SET @id=1
    WHILE @id<10
        BEGIN
            SELECT @productname=产品名称FROM产品
                WHERE产品ID=@id
            PRINT @productname
            SET @id=@id+1
        END

While语句也可以和If语句一起使用以完成更强大的功能。下面演示While语句与If语句、Break语句和Continue语句搭配使用。

例十一:While语句和If语句一起使用。

输入产品编号小于100并且为奇数的产品名称,其代码如下:

    USE Northwind
    DECLARE @id int
    DECLARE @productname varchar(40)
    DECLARE @maxid int
    SELECT @maxid=MAX(产品ID) FROM dbo.产品        --查看产品表里最大的编号是多少
    SET @id=0
    WHILE @id<100
        BEGIN
            SET @id=@id+1                             --编号自加1
            IF @id % 2=1
                PRINT ' ***********'                   --如果编号为奇数则准备输出产品名称
            ELSE
                CONTINUE   --如果编号为偶数则不执行后面的代码,直接跳回While语句进行判断
            SELECT @productname=产品名称
                FROM产品
                WHERE产品ID=@id
            IF @@ROWCOUNT=1                          --判断Select查询出来的行数是否为1
                PRINT @productname                     --如果为1则输出产品名称
            ELSE
                BEGIN
                    IF @id > @maxid    --如果不为1则判断产品编号是否超过产品表中最大编号
                        BREAK                      --如果超过产品表中最大编号则跳出整个循环
                    ELSE
                        PRINT ’没有产品ID号为“' +CAST(@id AS varchar(5))+' ”的记录’
                END
        END

8.10.4 Case语句

Case语句也是条件判断语句的一种,可以完成比If语句更强的判断。在If语句中,如果需要判断的条件很多,将会用到大量的If嵌套,例如:

    USE Northwind
    DECLARE @price money
    SELECT @price=单价
        FROM产品
        WHERE产品名称=N’蕃茄酱’
    IF @price<$20
            PRINT ’蕃茄酱的单价低于20元’
    ELSE
        BEGIN
            IF $20<=@price and @price<40
                PRINT ’蕃茄酱的单价在20元与40元之间’
            ELSE
                BEGIN
                    IF $40<=@price and @price<=80
                        PRINT ’蕃茄酱的单价在40元与80元之间’
                    ELSE
                          PRINT ’蕃茄酱的单价大于80元’
                  END
          END

从以上代码可以看出,判断的条件越多时,If语句的嵌套情况就越多,代码看起来就越乱、越复杂,可读性就越差。使用Case语句可以轻松解决该问题。Case语句的语法代码有两种格式:一种是简单的Case代码,用于将某个表达式与一组简单的表达式进行比较以确定结果;另一种是搜索的Case代码,用于计算一组布尔表达式以确定结果。其语法代码如下:

简单的Case语法代码:

    CASE input_expression
        WHEN when_expression THEN result_expression
        [ ...n ]
        [
        ELSE else_result_expression
        ]
    END

搜索的Case语法代码:

    CASE
        WHEN Boolean_expression THEN result_expression
        [ ...n ]
        [
        ELSE else_result_expression
        ]
    END

在简单的Case语法代码中,系统会将input_expression的值与每一个when_expression的值进行比较。如果相同的话,返回Then语句之后的表达式,如果都不同相等,返回Else语句之后的表达式,如果没有Else语句,则返回NULL。

例十二:使用Case语句。

查看类别ID为2的类别名称,其代码如下:

    USE Northwind
    DECLARE @categoryname varchar(15)
    DECLARE @outstr varchar(100)
    SELECT @categoryname=类别名称
        FROM类别
        WHERE  类别ID=2
    SET @outstr=CASE @categoryname
        WHEN ’饮料’ THEN ’类别:饮料’
        WHEN ’调味品’ THEN ’类别:调味品’
        WHEN ’点心’ THEN ’类别:点心’
        WHEN ’日用品’ THEN ’类别:日用品’
        WHEN ’特制品’ THEN ’类别:特制品’
        WHEN ’海鲜’ THEN ’类别:海鲜’
        ELSE ’其他类别’
    END
    PRINT @outstr

从以上代码可以看出,简单的Case语句在条件成立时会返回一个表达式的值,而不是去执行T-SQL语句,此时的Case语句更像一个函数。在T-SQL程序中可以对Case返回的值进行处理,例如例十一中,将Case语句返回的值存放在一个变量中,在后续的程序中再次使用它。Case语句也常用在Select语句中,可以灵活地输出一些数据表中没有的字段内容。

例十三:Case语句用在Select语句中。

查询“产品”表中的产品名称及类别名称,其代码如下:

    USE Northwind
    SELECT产品名称,CASE类别ID
            WHEN 1 THEN ’饮料’
            WHEN 2 THEN ’调味品’
            WHEN 3 THEN ’点心’
            WHEN 4 THEN ’日用品’
            WHEN 5 THEN ’谷类/麦片’
            WHEN 6 THEN ’肉/家禽’
            WHEN 7 THEN ’特制品’
            WHEN 8 THEN ’海鲜’
            ELSE ’其他类’
            END AS类别
        FROM产品

搜索的Case语句可以用于多条件的判断。用于多条件判断时,在Case关键字后不带任何表达式,而When之后必须为一个逻辑表达式,当表达式为真时返回Then之后的值。例如:

    USE Northwind
    DECLARE @price money
    DECLARE @returnstr varchar(50)
    SELECT @price=单价
        FROM产品
        WHERE产品名称=N’蕃茄酱’
    SET @returnstr=CASE
        WHEN @price<$20 THEN ’蕃茄酱的单价低于20元’
        WHEN $20<=@price and @price<40 THEN ’蕃茄酱的单价在20元与40元之间’
        WHEN $40<=@price and @price<=80 THEN ’蕃茄酱的单价在40元与80元之间’
        ELSE ’蕃茄酱的单价大于80元’
        END
    PRINT @returnstr

8.10.5 Goto语句

Goto语句可以让程序跳到一个指定的标签处并执行其后的代码。Goto语句和标签可以在过程、批处理和语句块中的任何位置使用,也可以嵌套使用。其语法代码如下:

    label:
    GOTO label

例十四:使用Goto语句。

输出产品蕃茄酱的单价,其代码如下:

    USE Northwind
    DECLARE @price money
    DECLARE @returnstr varchar(50)
    SELECT @price=单价
        FROM产品
        WHERE产品名称=N’蕃茄酱’
    IF @price<$20
        GOTO print20                                                   --跳转到标签print20
    IF $20<=@price and @price<40
        GOTO print40                                                   --跳转到标签print40
    IF $40<=@price and @price<=80
        GOTO print80                                                   --跳转到标签print80
    GOTO other                                                          --跳转到标签other
    print20:
        PRINT ’蕃茄酱的单价低于20元’
        GOTO theEnd                                                    --跳转到标签theEnd
    print40:
        PRINT ’蕃茄酱的单价在20元与40元之间’
        GOTO theEnd                                                    --跳转到标签theEnd
    print80:
        PRINT ’蕃茄酱的单价在40元与80元之间’
        GOTO theEnd                                                    --跳转到标签theEnd
    other:
        PRINT ’蕃茄酱的单价大于80元’
    theEnd:

Label标签可以在Goto语句之前或之后,并没有限制。Goto语句也可以从多个循环中直接跳出,而Break语句只可以跳出一个循环。

注意 使用Goto语句会使程序的可读性变差,能不使用时建议不要使用。Goto语句只能从While循环或If判断的内部往外部跳,不能从外部往内部跳。Goto语句只能在当前批中跳转,不能跳转到其他批中。

8.10.6 Waitfor语句

Waitfor语句用于延迟后续的代码执行,或等到指定的时间后再执行后续的代码,其语法代码如下:

    WAITFOR
    {
        DELAY ' time_to_pass'
      | TIME ' time_to_execute'
    }

其中:

DELAY:指定延迟时间,在经过该时间之后再继续执行后继的代码,最长可以是24小时。

time_to_pass:延迟的时间,必须是datetime型的数据,但不能指定日期。

TIME:指定延迟到某个时间后再执行后继的代码。

time_to_execute:指定要延迟到的时间,必须是datetime型的数据,不能指定日期。

例十五:使用Waitfor语句。

先执行一个查询语句,然后等待10秒再执行一个查询语句,其代码如下:

    USE Northwind
    SELECT  单价FROM产品WHERE产品名称=N’蕃茄酱’
    GO
    WAITFOR DELAY '00:00:10'
    SELECT  单价FROM产品WHERE产品名称=N’蕃茄酱’
    GO

例十六:按指定时间执行查询。

先执行一个查询语句,然后在指定的时间(例如在15点57分10秒时)再执行一个查询语句,其代码如下:

    USE Northwind
    SELECT  单价FROM产品WHERE产品名称=N’蕃茄酱’
    GO
    WAITFOR TIME '15:57:10'
    SELECT  单价FROM产品WHERE产品名称=N’蕃茄酱’
    GO

8.10.7 Return语句

Return语句会终止当前T-SQL语句的执行,从查询或过程中无条件地退出来,并且可以返回一个整数值给调用该代码的程序。与Break和Goto语句不同,Return可以在任何时候从过程、批处理或语句块中退出,而不是跳出某个循环或跳到某个位置。

Return一般用于存储过程或自定义的函数中,其语法代码如下:

    RETURN [ integer_expression ]

8.10.8 Try…Catch语句

Try…Catch语句类似于C#或C++语句中的异常处理,当执行Try语法块中的代码出现错误时,系统将会把控制传递给Catch语法块去处理。其语法代码为:

    BEGIN TRY
        { sql_statement | statement_block }
    END TRY
    BEGIN CATCH
        { sql_statement | statement_block }
    END CATCH

例十七:使用Try…Catch语句。

删除“类别”表中类别编号为5的类别记录,其代码如下:

    USE Northwind
    BEGIN TRY
        DELETE类别WHERE类别ID=5
    END TRY
    BEGIN CATCH
        PRINT ’出错信息为:' + ERROR_MESSAGE()
        DELETE产品WHERE类别ID=5
        DELETE类别WHERE类别ID=5
    END CATCH

由于“产品”表中的“类别ID”是外键,并且删除规则为“无操作”,所以当“产品”表中有“类别ID”为5的记录时,在“类别”表中是无法删除“类别ID”为5的记录的。在本例中,使用Try语句来判断在“类别”表中删除记录是否出错,如果出错,将控制权交给Catch语句,在Catch语句中,先将“产品”表中相应的记录删除,再将“类别”表中的记录删除。其运行结果如图8.13所示。

图8.13 使用Try…Catch语句

在例十七中,使用了ERROR_MESSAGE函数来获取错误消息,表8.11列出了能获取导致Catch块执行的错误消息的函数。

表8.11 获取错误消息的函数

8.10.9 Execute语句

Execute语句可以用来执行存储过程、用户自定义函数或批中的命令字符串。在SQL Server 2008中,Execute语句还可以向链接服务器发送传递命令。严格来说,Execute语句不属于T-SQL流程控制语句,但它在T-SQL程序中使用频率很高,所以在此将其单独介绍。Execute运行存储过程或函数的语法代码如下:

    [ { EXEC | EXECUTE } ]
        {
          [ @return_status=]                                        --存储过程的返回状态
          { module_name [ ; number ] | @module_name_var }
          [ [ @parameter=]                                         --参数
          { value                                                      --参数值
                            | @variable [ OUTPUT ]                 --返回型参数
                            | [ DEFAULT ]                            --默认值
                            }
          ]
          [ , ...n ]
          [ WITH RECOMPILE ]                       --执行模块后,强制编译、使用和放弃新计划
        }
    [; ]

Execute运行字符串的语法代码如下:

    { EXEC | EXECUTE }
          ( { @string_variable                                      --字符串变量
          | [ N ]' tsql_string' } [ + ...n ] )                    --字符串常量
        [ AS { LOGIN | USER }=' name ' ]                         --要模拟的上下文登录名
    [; ]

Execute向链接服务器发送传递命令的语法代码:

    { EXEC | EXECUTE }
          ( { @string_variable                                      --字符串变量
          | [ N ] ' command_string' } [ + ...n ]                  --字符串常量
          [ {, { value | @variable [ OUTPUT ] } } [...n] ]
          )
        [ AS { LOGIN | USER }=' name ' ]                         --要模拟的上下文登录名
        [ AT linked_server_name ]                                    --链接服务器名
    [; ]

例十八:查看所有的数据表和视图。

查看当前数据库中所有数据表和视图,其代码如下:

    sp_tables

或者:

    EXEC sp_tables

或者:

    EXECUTE sp_tables

例十九:查询所有数据表。

查看当前数据库中所有数据表,其代码如下:

    EXEC sp_tables @table_type="' TABLE' "

例二十:用查询结果新建表。

将订单表里1991年到2000年的订单分别放在一个新建的数据表中。其代码如下:

    USE Northwind
    DECLARE @execstr varchar(1000)
    DECLARE @year int
    SET @year=2000
    WHILE @year>1990
        BEGIN
            set @execstr=' SELECT * FROM订单WHERE YEAR(订购日期)='
                +CAST(@year AS varchar(4))                    --将查询语句放在一个变量中
            EXEC (@execstr)                                      --执行变量中的查询语句
            --当该年的订单数不为零时将查询出来的记录插入到一个新表中
            IF @@ROWCOUNT >0
                --执行括号里的T-SQL语句
                EXECUTE (' SELECT * INTO订单_' +@year
                    +' FROM订单WHERE YEAR(订购日期)=' +@year)
            SET @year=@year -1
        END

8.11 T-SQL的常用函数

SQL Server 2008为T-SQL提供了很多函数,每个函数都能实现不同的功能,例如前面介绍过的Count( )函数和Sum( )函数等。SQL Server 2008将函数分为聚合函数、配置函数、游标函数、日期和时间函数、数学函数、元数据函数、行集函数、安全函数、字符串函数、系统统计函数、文本和图像函数以及其他函数十二类。下面介绍一些常用的函数。

8.11.1 聚合函数

聚合函数可以将多个值合并为一个值,其作用是对一组值进行计算,并返回计算后的值,常与Select语句的Group by子句一起使用。除了Count之外,其他聚合函数都会忽略NULL。表8.12列出的是常用的聚合函数。

表8.12 常用的聚合函数

8.11.2 日期和时间函数

日期和时间函数可以用来更改日期和时间的值,其作用是对日期和时间型的数据进行处理,并返回一个字符串、数字或日期和时间的值。表8.13列出的是常用的日期和时间函数。

表8.13 常用的日期和时间函数

8.11.3 数学函数

数学函数的作用是对数字型数据进行处理,并返回处理结果。表8.14列出的是常用的数学函数。

表8.14 常用的数学函数

8.11.4 字符串函数

字符串函数的作用是对字符串数据进行处理,并返回一个字符串或数值。表8.15列出的是常用的字符串函数。

表8.15 常用的字符串函数

8.11.5 文本和图像函数

文本和图像函数可以更改text和image的值,其作用是对文本或图像输入的值或列进行处理,并返回有关该值的信息。表8.16列出的是常用的文本和图像函数。

表8.16 常用的文本和图像函数

8.11.6 配置函数

配置函数可以返回有关配置设置的信息。表8.17列出的是常用的配置函数,其函数所完成的功能请参照本章8.9.2节。

表8.17 常用的配置函数

8.11.7 游标函数

游标函数可以返回有关游标状态的信息。表8.18列出的是常用的游标函数。

表8.18 常用的游标函数

8.11.8 元数据函数

元数据函数可以返回数据库和数据库对象的属性信息。元数据是描述数据的数据,通常用于描述数据的结构和意义。表8.19列出的是常用的元数据函数。

表8.19 常用的元数据函数

8.11.9 行集函数

行集函数可以返回在T-SQL语句中表引用所有位置使用的行集。表8.20列出的是所有行集函数,由于行集函数用得很少,所以在此就不详细介绍了,有兴趣的读者可以去查阅联机帮助。

表8.20 行集函数

8.11.10 安全函数

安全函数返回有关用户和角色的信息。表8.21列出的是常用的安全函数。

表8.21 常用的安全函数

8.11.11 系统统计函数

系统统计函数可以返回有关SQL Server性能的信息。表8.22列出的是常用系统统计函数,其函数所完成的功能请参照第8.9.2节。

表8.22 常用的系统统计函数

8.11.12 其他函数

其他函数包括加密函数、系统函数,以及一些前面没有介绍到的函数。表8.23列出的是加密函数,表8.24列出的是常用的系统函数。

表8.23 加密函数

表8.24 常用的系统函数

8.12 不应使用的关键字

在SQL Server 2008中,有一些英文单词用于定义、操作和访问数据库,例如Select和Update等,这些单词称为保留的关键字。保留的关键字一般都是T-SQL语法的一部分,用于让SQL Server分析和理解T-SQL语句,因此,不建议使用这些关键字作为标识符和对象名来使用。尽管在T-SQL中使用保留的关键字作为标识符和对象名在语法上是可行的,但必须使用方括号来区分标识符,例如:

SELECT  *  FROM  [select]

表8.25列出了SQL Server 2008中的保留关键字,这些关键字不建议用做标识符和对象名。

表8.25 SQL Server 2008的保留关键字

8.13 小结

T-SQL是使用SQL Server的核心,所有与SQL Server实例通信的应用程序都是通过发送T-SQL语句到服务器来完成对数据库的操作。T-SQL在SQL的基础上添加了流程控制,是SQL语言的扩展。

数据库对象的引用都是通过“服务器名.数据库名.架构名.对象名”来完成的,其中某些部分可以省略。在SQL Server 2008中,可以一次执行多个T-SQL语句。使用Cast和Convert可以转换数据类型。T-SQL程序与其他高级编程语言一样,包括运算符、常量、变量、流程控制语句以及众多函数。了解这些运算符、常量、变量、流程控制语句和函数之后,才能得心应手地编写T-SQL程序。

第9章将会介绍SQL Server 2008中视图的使用方法。