185.流程设计
1.语句块
语句块是程序中一个相对独立的执行单元,它是由关键字BEGIN…END括起来而形成的代码段。其中,BEGIN用于标识语句块的开始,END则标识语句块的结束。语句块可以嵌套定义。
语句块通常与IF、WHILE等控制语句一起使用,以界定这些控制语句的作用范围。这在下面介绍控制语句的部分会涉及到。
2 IF语句
SQL Server支持两种形式的IF语句:IF…和IF…ELSE…句型。
2.1 IF…句型
该句型的语法格式如下:
IF expression { sql_statement | statement_block }
expression为布尔表达式,如果该表达式中含有 SELECT 语句,必须用圆括号将 SELECT 语句括起来;sql_statement表示SQL语句;statement_block表示语句块(下同)。如果expression的返回值为TRUE,则执行IF后面的语句或语句块,否则什么都不执行。
IF语句的结构流程图如图6.2所示。
【例6.6】 查询学号为“20170202”的学生,如果该学生成绩及格则将其姓名和成绩打印出来。
该查询要求可用局部变量和IF…句型来实现:
USE MyDatabase GO DECLARE @no char(8), @name char(8), @avgrade numeric(3,1) SET @no = '20170202' SELECT @name = s_name, @avgrade = s_avgrade FROM student WHERE s_no = @no; IF @avgrade>60.0 BEGIN PRINT @name PRINT @avgrade END GO
2.2 IF…ELSE…句型
有时候在作出判断以后,对不满足条件表达式的情况也要进行相应的处理,这时可以选用IF…ELSE…句型。其语法格式如下:
IF expression { sql_statement1 | statement_block1 } ELSE { sql_statement2 | statement_block2}
该语句的结构流程图如图6.3所示。
【例6.7】 对于给定学号的查询,如果平均成绩不及格的则打印姓名和平均成绩,否则打印学号即可。实现代码如下:
USE MyDatabase GO DECLARE @no char(8), @name char(8), @avgrade numeric(3,1) SET @no = '20170202' SELECT @name = s_name, @avgrade = s_avgrade FROM student WHERE s_no = @no; IF @avgrade<60.0 BEGIN PRINT @name PRINT @avgrade END ELSE PRINT @no GO
2.3 IF … ELSE IF … ELSE …句型
当需要作两次或两次以上的判断并根据判断结果作出执行选择时,一般要使用IF … ELSE IF … ELSE …句型。该句型的语法格式如下:
IF expression1 { sql_statement1 | statement_block1 } ELSE IF expression2 {sql_statement2 | statement_block2} [ELSE IF expression3 {sql_statement3 | statement_block3} …] ELSE {sql_statementn | statement_blockn}
上述语句的结构流程图如图6.4所示。
【例6.8】 运用多分支的IF句型来查询并实现分等级打印学生成绩。
USE MyDatabase GO DECLARE @no char(8), @name char(8), @avgrade numeric(3,1) SET @no = '20170202' SELECT @name = s_name, @avgrade = s_avgrade FROM student WHERE s_no = @no; IF @avgrade>=90.0 PRINT '优秀' ELSE IF @avgrade>=80.0 PRINT '良好' ELSE IF @avgrade>=70.0 PRINT '中等' ELSE IF @avgrade>=60.0 PRINT '及格' ELSE PRINT '不及格' GO
3 CASE语句
IF语句一般用于一个判断有少量出口(特别是两个出口)的情况。但在许多时候可能遇到一个判断有很多出口的情况,这时如果仍然使用IF语句,可能使得语句在结构上变得非常复杂。但如果换用CASE语句,将使得语句代码变得很精炼、简洁。 在SQL Server中,CASE语句实际上是被当作一个函数来执行,执行后都有一个返回值。它有两种类型,一种是简单式,一种是搜索式。
3.1简单式CASE函数
以下是简单式CASE函数的语法格式:
CASE input_expression WHEN when_expression THEN result_expression [ ...n ] [ ELSE else_result_expression ] END
执行时,首先计算表达式input_expression的值,然后将计算结果与WHEN子句中的表达式进行比较,如果相等则计算THEN后面的表达式result_expression,并将得到的值作为整个CASE函数的值,同时退出CASE函数;如果结果与所有WHEN子句中的表达式的值都不相等,则计算ELSE后面的表达式else_result_expression。不管最后是计算哪个表达式,它的结果都将作为整个CASE函数的返回值。
when_expression 是任意有效的表达式。但input_expression 及每个WHEN 子句中的表达式when_expression的数据类型必须相同或是可隐式转换的数据类型。
【例6.9】 首先使用SELECT语句查询学生的学号,然后用CASE函数对学生的姓名和爱好进行简要说明。
实现代码如下:
SELECT 学号 = s_no, 姓名及爱好 = CASE s_no WHEN '20170201' THEN '刘洋,游泳' WHEN '20170202' THEN '王晓珂,登山' WHEN '20170203' THEN '王伟志,滑雪' WHEN '20170204' THEN '岳志强,支部书记' WHEN '20170205' THEN '贾簿,足球' WHEN '20170206' THEN '李思思,爱好不详' WHEN '20170207' THEN '蒙恬,长跑' WHEN '20170208' THEN '张宇,山地自行车' ELSE '没有这个人‘ END FROM student
该语句执行后结果如下:
学号 姓名及爱好
--------------------------------------------
20170201 刘洋,游泳
20170202 王晓珂,登山
20170203 王伟志,滑雪
20170204 岳志强,支部书记
20170205 贾簿,足球
20170206 李思思,爱好不详
20170207 蒙恬,长跑
20170208 张宇,山地自行车
3.2搜索式CASE函数
以下是搜索式CASE函数的语法格式:
CASE WHEN expression THEN result_expression [ ...n ] [ ELSE else_result_expression ] END
在搜索式CASE函数中,关键字CASE后面没有表达式,且WHEN后面的表达式expression已被限定为布尔表达式(返回值为TRUE或FALSE)。执行时,依次从上到下计算每个WHEN子句中表达式expression的值,如果值为TRUE则计算该WHEN子句中表达式result_expression的值,并将该值返回作为CASE函数的值;如果所有WHEN子句中表达式expression的值均为FALSE,则计算ELSE后面表达式else_result_expression的值并将其返回作为CASE函数的值。
【例6.10】 使用搜索式CASE函数来显示学生的成绩等级。
相应代码如下:
SELECT 学号 = s_no, 姓名 = s_name, 成绩等级 = CASE WHEN s_avgrade>90.0 THEN '优秀' WHEN s_avgrade>80.0 THEN '良好' WHEN s_avgrade>70.0 THEN '中等' WHEN s_avgrade>60.0 THEN '及格' ELSE '不及格' END FROM student
该语句执行后结果如下:
学号 姓名 成绩等级
-------------------------------------------------
20170201 刘洋 优秀
20170202 王晓珂 良好
20170203 王伟志 良好
20170204 岳志强 中等
20170205 贾簿 不及格
20170206 李思思 及格
20170207 蒙恬 中等
20170208 张宇 不及格
【例6.11】 为保证指导学生的质量,现在很多高校都从工作量上限制教师指导学生的人数。假设某高校制定如下的指导工作量公式,其中n为指导的学生人数:
指导工作量 =
请重新创建结构如表5.9所示的数据表(见例5.2),要求用上述公式重新定义表示工作量的字段c_hour,其他字段不变。
根据上述要求,为表supervisor 重新编写CREATE TABLE语句代码,结果如下:
CREATE TABLE supervisor( t_no int PRIMARY KEY, t_name varchar(8) NOT NULL, s_n int NOT NULL CHECK(s_n>=0 and s_n<=20), c_hour AS CASE WHEN s_n<=8 THEN 15* s_n WHEN s_n<=10 THEN 15*8+12*(s_n-8) ELSE 15*8+12*2+5 END );
用上述CREATE TABLE语句重新创建数据表supervisor,然后用下列INSERT语句添加数据:
insert supervisor values('19970101','方琼',7) insert supervisor values('19970102','赵构',9) insert supervisor values('19970103','李方正',15)
结果表supervisor中的数据如下:
t_no t_name s_n c_hour
---------------------------------------------------------------
19970101 方琼 7 105
19970102 赵构 9 132
19970103 李方正 15 149
可以看到,只要正确设置字段s_n的值,工作量字段c_hour的值会自动计算产生。在软件开发中,如果能够灵活地利用CASE函数定义计算列,可以大大较少前台代码的编写工作量。
4 WHILE语句
WHILE语句是典型的循环控制语句,其语法格式如下:
WHILE expression {sql_statement | statement_block}
在WHILE语句中,只要表达式expression的值为真,就重复执行循环体中的语句。如果布尔表达式中含有SELECT语句,则必须用括号将SELECT语句括起来。
WHILE语句也可以结合BREAK和CONTINUE语句一起使用,它们可以嵌入循环体内部,用于控制WHILE循环中语句的执行。
其中,当执行到BREAK语句时,程序将无条件退出当前的循环体,执行出现在END关键字(循环体结束的标记)后面的语句;
当执行到CONTINUE语句时,程序将不执行CONTINUE关键字后面的所有语句,提前结束本次循环(但没有退出循环体,这是与BREAK语句的不同之处),并重新开始新的一轮循环。
WHILE语句的结构流程图如图6.5所示。
【例6.12】 如果学生成绩的平均值低于95分则循环执行对每个学生的成绩增加0.5%。在循环过程中,如果发现最高成绩超过99分则退出循环;在加分过程中,当成绩的平均值大于或等于75.5分时打印出当前成绩的平均值。
USE MyDatabase; GO DECLARE @max numeric(3,1),@avg numeric(3,1); SET @avg = (SELECT AVG(s_avgrade) FROM student) SET @max = (SELECT MAX(s_avgrade) FROM student) WHILE @avg < 95 BEGIN IF @max > 99 BREAK --退出循环体 UPDATE student SET s_avgrade = s_avgrade + s_avgrade * 0.005 SET @avg = (SELECT AVG(s_avgrade) FROM student) SET @max = (SELECT MAX(s_avgrade) FROM student) IF @avg < 75.5 CONTINUE --结束本次循环 PRINT '当前平均成绩:'+STR(@avg, 5, 1); END
5 GOTO语句
GOTO语句是一种无条件转移语句,可以实现程序的执行流程从一个地方转移到另外的任意一个地方。与IF语句结合,GOTO语句也可以实现WHILE语句的循环功能。但是使用GOTO语句会降低程序的可读性,所以在一般情况下不提倡在程序中使用GOTO语句。
使用GOTO语句时,首先要定义标签,然后才能使用GOTO语句。其语法格式如下:
Label: {sql_statement | statement_block} [IF …] GOTO Label;
其中,Label为定义的标签,它是GOTO语句转向的依据。
标签必须符合标识符命名规则。无论是否使用 GOTO 语句,标签均可作为注释方法使用。
当执行到语句“GOTO Label”时,执行流程将无条件转到标签Label所指向的地址,并从该地址起依次往下执行所遇到的语句。
【例6.13】 使用了GOTO语句来实现1到100的累加,结果放在局部变量@sum中,最后将结果打印出来。
DECLARE @s int, @sum int SET @s = 0 SET @sum = 0 label1: SET @s = @s + 1 SET @sum = @sum + @s IF @s <> 100 GOTO label1 PRINT @sum
6 TRY…CATCH语句
类似于其他高级语言,Transact-SQL也有异常的捕获和处理语句——TRY…CATCH语句。该语句的语法格式如下:
BEGIN TRY { sql_statement | statement_block } END TRY BEGIN CATCH { sql_statement | statement_block } END CATCH[ ; ]
当TRY块内的语句产生错误时,则会将控制传递给CATCH块的第一个语句;当TRY块所包含的代码中没有错误时,则在TRY块中最后一个语句完成后将控制传递给紧跟在END CATCH 语句之后的语句。
【例子】下例中第二条插入语句有错误,所以在执行到该语句时程序将转到CATCH块中执行打印语句。这时第一条插入语句已经成功执行,而第三条插入语句则未能执行到,所以只有第一条数据被插入,而其他数据没有被插入到数据库中。
USE MyDatabase; GO DELETE FROM student GO BEGIN TRY INSERT student VALUES('20170201','刘洋','女','1997-02-03','计算机应用技术',98.5,'计算机系'); -- 下面语句中,时间常量'1997-09-201'格式错误 INSERT student VALUES('20170202','王晓珂','女','1997-09-201','计算机软件与理论',88.1,'计算机系'); INSERT student VALUES('20170203','王伟志','男','1996-12-12','智能科学与技术',89.8,'智能技术系'); END TRY BEGIN CATCH PRINT N'插入操作有错误。' END CATCH;
7 RETURN语句
RETURN语句用于从过程、批处理或语句块中无条件退出,RETURN之后的语句不被执行。其语法如下:
RETURN [integer_expression]
RETURN可以后跟整型表达式,当执行到RETURN语句的时候先计算该表达式的值,然后返回该值。
如果将RETURN语句嵌入存储过程,该语句不能返回空值。
如果某个过程试图返回空值,则将生成警告消息并返回0值。
8 WAITFOR语句
WAITFOR语句用于设置指定程序段的执行时间,包括指定程序段在某一时刻执行或者在某一段时间间隔之后自动执行。其语法格式如下:
WAITFOR { DELAY 'time_to_pass' | TIME 'time_to_execute' }
DELAY子句用于设定WAITFOR语句所要等待的时间(这个时间过后即执行WAITFOR后面的语句),时间的长短由参数time_to_pass说明(但只能包含时间部分,不能包含日期部分),最长为24小时;TIME子句用于设定WAITFOR语句等待的终结时刻,由参数time_to_execute说明,可以使用datetime数据类型接受的格式,但也只能包含时间部分。
【例6.14】 设置在下午5:30(17:30)执行学生成绩查询。
USE MyDatabase; GO WAITFOR TIME '17:30'; SELECT 姓名 = s_name, 平均成绩 = s_avgrade FROM student
如果要求上述查询在1小时20分钟后执行,则可以使用下面的代码实现:
USE MyDatabase; GO WAITFOR DELAY '01:20'; SELECT 姓名 = s_name, 平均成绩 = s_avgrade FROM student;