第一章db2开发基础在进行db2应用开发之前,了解db2应用程序的结构,掌握相关概念,设置开发环境是很必要的。本章主要介绍这几个方面的内容。
1.1 db2应用程序开发概述1.1.1 程序结构db2应用程序包括以下几个部分:
1.声明和初始化变量
2.连接到数据库
3.执行一个或者多个事务
4.与数据库断开连接
5.结束程序
一个事务是一组数据库操作,在提交给数据库之前,必须确认完全成功执行。在嵌入式sql应用程序中,当应用程序成功地连接到一个数据库时,一个事务就自动开始了,结束于执行一条commit语句或者rollback语句。同时,下一条sql语句开始一个新的事务。
每一个应用程序的开始必须包括:
l 数据库管理器用来与宿主程序交互的所有变量和数据结构的声明
l 设置sql通信区(sqlca),提供错误处理的sql语句
注意:用java写的db2应用程序在sql语句出错时抛出一个sqlexception异常,需要在catch块里处理,而不是使用sqlca。
每个应用程序的主体包括访问和管理数据的sql语句。这些语句组成事务,事务必须包括下列语句:
l connect语句,其建立一个与数据库服务器的连接
l 一条或多条:
▲数据操纵语句(例如,select语句)
▲数据定义语句(例如,create语句)
▲数据控制语句(例如,grant语句)
l commit或者rollback语句结束事务
应用程序的结束通常包括释放程序与数据库服务器的连接和释放其他资源的sql语句。
1.1.2 开发方法选择可使用几种不同的程序设计接口来存取 db2 数据库。您可以:
l 将静态和动态 sql 语句嵌入应用程序。
l 在应用程序中编写“db2 调用层接口”(db2 cli) 的函数调用,以调用动态 sql 语句。
l 开发调用“java 数据库链接”应用程序设计接口 (jdbc api) 的 java 应用程序和小程序。
l 开发符合“数据存取对象 (dao) ”和“远程数据对象 (rdo) ” 规范的 microsoft visual basic 和 visual c++ 应用程序,以及使用“对象链接和嵌入数据库 (ole db) 桥接”的“activex 数据对象”(ado) 应用程序。
l 使用 ibm 或第三方工具如 net.data、excel、perl、“开放式数据库链接”(odbc) 最终用户工具如 lotus approach 及其程序设计语言 lotusscript 来开发应用程序。
l 要执行备份和复原数据库等管理功能,应用程序可以使用 db2 api。
应用程序存取 db2 数据库的方式将取决于想要开发的应用程序类型。例如,如果想开发数据输入应用程序,可以选择将静态 sql 语句嵌入应用程序。如果想开发在万维网 (www) 上执行查询的应用程序,可能要选择 net.data、perl 或 java。
1.2相关概念1.2.1 嵌入式sql编程嵌入式sql应用程序就是将sql语句嵌入某个宿主语言中,sql语句提供数据库接口,宿主语言提供应用程序的其他执行功能。
“结构化查询语言”(sql) 是一种数据库接口语言,它用来存取并处理 db2 数据库中的数据。可以将 sql 语句嵌入应用程序,使应用程序能执行 sql 支持的任何任务,如检索或存储数据。通过使用 db2,可以用 c/c++、cobol、fortran、java (sqlj) 以及 rexx 程序设计语言来编写嵌入式 sql 应用程序。
嵌入了 sql 语句的应用程序称为主程序。用于创建主程序的程序设计语言称为宿主语言。用这种方式定义程序和语言,是因为它们包含了 sql 语句。
对于静态 sql 语句,您在编译前就知道 sql 语句类型以及表名和列名。唯一未知的是语句正搜索或更新的特定数据值。可以用宿主语言变量表示那些值。在运行应用程序之前,要预编译、编译和捆绑静态 sql 语句。静态 sql 最好在变动不大的数据库上运行。否则,这些语句很快会过时。
相反,动态 sql 语句是应用程序在运行期构建并执行的那些语句。一个提示最终用户输入 sql 语句的关键部分(如要搜索的表和列的名称)的交互式应用程序是动态 sql 一个很好的示例。 应用程序在运行时构建 sql 语句,然后提交这些语句进行处理。
可以编写只有静态 sql 语句或只有动态 sql 语句,或者兼有两者的应用程序。
一般来说,静态 sql 语句最适合用于带有预定义事务的高性能应用程序。预订系统是这种应用程序一个很好的示例。
一般来说,动态 sql 语句最适合于必须在运行期指定事务的、要快速更改数据库的应用程序。交互式查询界面是这种应用程序一个很好的示例。
将 sql 语句嵌入应用程序时,必须按以下步骤预编译应用程序并将其与数据库捆绑:
1. 创建源文件,以包含带嵌入式 sql 语句的程序。
2. 连接数据库,然后预编译每个源文件。
预编译程序将每个源文件中的 sql 语句转换成对数据库管理程序的 db2 运行期 api 调用。预编译程序还在数据库中生成一个存取程序包,并可选择生成一个捆绑文件(如果您指定要创建一个的话)。
存取程序包包含由 db2 优化器为应用程序中的静态 sql 语句选择的存取方案。这些存取方案包含数据库管理程序执行静态 sql 语句所需的信息,以便该管理程序可以用优化器确定的最有效的方式来执行这些语句。对于动态 sql 语句,优化器在您运行应用程序时创建存取方案。
捆绑文件包含创建存取程序包所需要的 sql 语句和其他数据。可以使用捆绑文件在以后重新捆绑应用程序,而不必首先预编译应用程序。重新捆绑创建针对当前数据库状态的优化存取方案。如果应用程序将存取与预编译时所用数据库不同的数据库,则必须重新捆绑应用程序。如果数据库统计信息自上次捆绑后已经更改,建议您重新捆绑应用程序。
3. 使用主语言编译程序编译修改的源文件(以及其他无 sql 语句的文件)。
4. 将目标文件与 db2 和主语言库连接,以生成一个可执行程序。
5. 如果在预编译时未对捆绑文件进行捆绑;或者准备存取不同数据库,则应对捆绑文件进行捆绑以创建存取程序包。
6. 运行该应用程序。此应用程序使用程序包中的存取方案存取数据库。
1.2.2 预编译创建源文件之后,必须对每一个含有sql语句的宿主语言文件用prep命令进行预编译。预编译器将源文件中的sql语句注释掉,对那些语句生成db2运行时api调用。
在预编译一个应用之前,必须连接到一个数据库服务器,不论是自动连接还是显性连接。即使你在客户端工作站上预编译应用程序、预编译器在客户端产生的修改后源文件和信息,预编译器也需要使用服务器连接来执行一些确认任务。
预编译器也创建数据库管理器在处理针对某个数据库的sql语句时需要的信息。这些信息存储在一个程序包或者一个捆绑文件或者两者之中,视预编译器的选项而定。
下面是使用预编译器的一个典型例子。预编译一个名叫filename.sqc的c嵌入式sql源文件,发出下面的命令创建一个c源文件,默认名字为filename.c,和一个捆绑文件,默认名字为filename.bnd:
db2 prep filename.sqc bindfile
预编译器最多产生四种类型的输出:
l 修改后的源文件
l 程序包
l 捆绑文件
l 信息文件
1、修改后的源文件
这个文件是预编译器将sql语句转化为db2运行时api调用后,原始源文件的新版本。它被赋予了相应宿主语言的扩展名。
2、程序包
如果使用了package选项(默认的),或者没有指定任何bindfile、syntax、sqlflag选项,程序包存储在所连接到的数据库中。程序包仅仅包含执行访问本数据的sql语句时需要的所有信息。除非你用package using选项指定一个不同的名字,否则预编译器将使用源文件名字的前8个字符作为程序包名。
使用package选项时,在预编译处理过程中使用的数据库必须拥有源文件中静态sql语句参考到的所有数据库对象。例如不能够预编译一条select语句,如果参考的表在数据库中不存在。
3、捆绑文件
如果使用了bindfile选项,预编译器将创建一个捆绑文件(扩展名为.bnd),它包含创建程序包的一些数据。这个文件可以在后面用bind命令将应用捆绑到一个或多个数据库。如果指定了bindfile选项,没有指定package选项,捆绑被延缓直到执行bind命令。注意,对于命令行处理器(clp),prep默认不指定bindfile选项。因此,如果你使用clp,又想延缓捆绑,那么你必须指定bindfile选项。
如果在预编译时请求一个捆绑文件但是没有指定package选项,不会在数据库中创建程序包;对象不存在和没有权限的sqlcode被看作警告而不会被看作错误。这使得你能够预编译程序和创建一个捆绑文件,不需要参考到的对象必须存在,也不需要你拥有执行正被预编译的sql语句的权限。
4、信息文件(message file)
如果使用了messages选项,预编译器将信息重定向到指定的文件中。这些信息包括警告和错误信息,它们描述了在预编译过程中产生的问题。如果源文件没有预编译成功,使用警告和错误信息来断定问题,改正源文件,然后再预编译。如果没有使用message选项,预编译信息被写到标准输出上。
1.2.3 程序包程序包就是存储在相对应数据库中的包含数据库系统在捆绑时对特定sql语句所产生的访问策略。
所有sql语句经过编译优化后就产生可以直接对数据库进行访问的访问策略,存储于相应的数据库中。这些访问策略可以在应用程序调用相对应的sql语句时得到访问。程序包对应于特定的应用程序,但是并不是与应用程序一起存放,而是同相对应的数据库一起存放。
1.2.4 捆绑捆绑(bind)是创建数据库管理器在应用执行时为了访问数据库而需要的程序包的过程。捆绑可以在预编译时指定package选项隐含地完成,或者使用bind命令依据预编译过程中产生的捆绑文件显性地完成。
下面是使用bind命令的一个典型例子。将名为filename.bnd的捆绑文件捆绑到数据库,使用下面的命令:
db2 bind filename.bnd
每一个独立预编译的源代码模块都需要创建一个程序包。如果一个应用有5个源文件,其中3个需要预编译,那么要创建3个程序包或者3个捆绑文件。默认上,每一个程序包的名字与产生.bnd文件的源文件名字相同,但是只要前8个字符。如果新建的程序包名字与已存在于数据库中的程序包名相同,新的程序包将替换原先存在的程序包。要显性地指定一个不同的程序包名,必须在prep命令使用package using选项。
1.2.5 工作单元一个工作单元是一个单一的逻辑事务。它包含一个sql语句序列,在这个序列中的所有操作,被看作一个整体,要么都成功,要么都失败。db2支持两种类型的连接(connection)。连接类型决定一个应用程序如何与远程数据库工作,并且决定该应用程序同时能与多少个数据库工作。
(1)连接类型1
应用程序在每个工作单元中只能连接单个数据库,此时,这个工作单元称为远程工作单元(ruow, remote unit of work)
(2)连接类型2
允许应用程序在每个工作单元中连接多个数据库,此时,这个工作单元称为分布式工作单元(duow, distributed unit of work)
我们来看下面的例子:
(1)remote unit of work – type 1 connect
在这个例子中,连接类型为1,工作单元为远程工作单元(ruow),应用程序在连接到数据库db2_2之前,必须结束当前的工作单元(即事务,通过执行commit语句)。
(2)distributed unit of work – type 2 connect
在这个例子中,连接类型为2,工作单元为分布式工作单元(duow)。应用程序在连接到数据库db2_2之前,不需要结束当前的事务。在一个工作单元中,可以有多个数据库连接,但是只有一个处于激活状态,其它都处于睡眠状态。用set connection to db_name语句切换数据库连接。
1.2.6 应用程序、捆绑文件与程序包之间的关系一个程序包是一个存储在数据库中的对象,它包含在执行某个源文件中特定sql语句时所需的信息。数据库应用程序对用来创建应用程序的每一个预编译源文件使用一个程序包。每一个程序包是一个独立的实体,同一个或不同应用程序所使用的程序包之间没有任何关系。程序包在对源文件执行附带绑定的预编译时创建,或者通过绑定绑定文件创建。
数据库应用程序使用程序包的原因:提高性能和紧凑性。通过预编译sql语句,使得sql语句在创建应用程序时被编译进程序包,而不是在运行时。每一条语句都被分析,高效率的操作数串存储在程序包中。在运行时,预编译器产生的代码调用数据库管理器运行时服务apis,根据输入输出数据的变量信息,执行程序包。
预编译的优点仅仅对静态sql语句有效。动态执行的sql语句不用预编译,但是它们在需要在运行时完成处理的整个步骤。注意:不要认为一条sql语句的静态版本的执行效率一定比其动态版本的高。在某个方面,静态sql语句快是因为不需要动态语句的准备开销。在另一方面,同样的语句,动态执行会快些,是因为优化器能利用当前数据库的统计信息,而不是以前的统计信息 。有关静态sql与动态sql的比较,参照下表:
表 静态sql与动态sql的比较
考虑因素 最好的选择
执行sql语句的时间:
少于两秒 静态
2 到10 秒 两者均可
多于10秒 动态
数据一致性
统一的数据分布 静态
轻微不统一分布 两者均可
高度不统一分布 动态
范围谓词(,between,like)使用
很少使用 静态
偶然使用 两者均可
经常使用 动态
执行的重复性
很多次(10或者更多) 两者均可
几次(少于10次) 两者均可
一次 静态
查询的种类
随机 动态
固定 两者均可
运行时环境(dml/ddl)
事务处理(dml only) 两者均可
混合(dml和ddl – ddl affects packages) 动态
混合(dml和ddl – ddl does not affect packages) 两者均可
运行runstats的频度
很低 静态
正常 两者均可
频繁 动态
1.2.7 定界符如果源程序中有sql语句,源程序不能立即被源语言编译器处理。它首先要经过一个翻译过程,将sql语句翻译成源语言编译器能够理解的东西。做翻译工作的程序就叫做预编译器。预编译器识别sql语句的方法是通过定界符将sql语句标识出来。
定界符的作用是使预编译器能够识别出需被翻译的sql语句,并且必须标识出每一条嵌入的sql语句。不同的宿主语言使用不同的定界符,下表列出了四种常用语言的定界符:
语言
定界符
c;c++
exec sql
sql statement ;
cobol
exec sql
sql statement end-exec.
fortran
exec sql
sql statement
java
#sqlj {sql statement} ;
例子:
l sql语句
update templ
set workdept = ‘c02’
where workdept = ‘c01’
l 在c程序中的sql语句
exec sql
update templ
set workdept = ‘c02’
where workdept = ‘c01’ ;
l 在cobol程序中的sql语句
exec sql
update templ
set workdept = ‘c02’
where workdept = ‘c01’
end-exec.
l 在java程序中的sql语句
#sqlj {update templ set workdept = ‘c02’ where workdept = ‘c01’} ;
第二章db2应用程序设计方法本章介绍db2应用程序设计的一般方法,以及如何设置测试环境。
2.1编程方法2.1.1 访问数据在关系数据库中,必须使用sql访问请求的数据,但是可以选择如何将sql结合到应用程序中去。可以从下表列出的接口和它们支持的语言中选择:
接口
支持的语言
嵌入sql
c/c++, cobol, fortran, java (sqlj), rexx
db2 cli 和odbc
c/c++, java (jdbc)
microsoft specifications, including ado, rdo, and ole db
visual basic, visual c++
perl dbi
perl
query products
lotus approach, ibm query management facility
一、嵌入sql
嵌入sql有其优势,它可以包含静态sql或者动态sql,或者两种类型混合使用。如果在开发应用程序时,sql语句的格式和内容已经确定,应该考虑在程序中采用嵌入的静态sql。利用静态sql,执行应用程序的人暂时继承将应用程序捆绑到数据库中的用户的权限,而不需要对此人赋予其它权限(除了应用程序的执行权)。动态sql的执行需要执行应用程序的人的权限,但也有例外情况,就是在捆绑应用程序的时候使用dynamicrules bind选项。一般来讲,如果直到执行时才能确定sql语句,那么应该采用嵌入的动态sql。这比较安全,而且可以处理更多形式的sql。
注意:java语言的嵌入sql(sqlj)应用程序只能嵌入静态sql语句。然而,在sqlj应用程序中,可以通过使用jdbc调用动态sql语句。
在使用编程语言编译器前,必须对源文件进行预编译,将嵌入的sql语句转换为宿主语言的数据库服务apis。在应用程序运行之前,必须将程序中的sql捆绑到数据库里。
我们在第三章“静态sql应用编程”中有详细介绍。
二、db2 cli和odbc
db2调用级接口(db2 cli)是ibm公司数据库服务器的db2系列可调用sql接口,它是一个关系数据库数据访问的c和c++语言编程接口,它用函数调用的方式,将动态sql语句作为参数传递给数据库管理器。也就是说,一个可调用的sql接口就是一个调用动态sql语句的应用程序编程接口(api)。cli可以替代嵌入动态sql,但是与嵌入sql不同,它不需要预编译或者捆绑。
db2 cli是基于微软开放数据库连接(odbc)规范和x/open规范开发的。ibm选择这些规范是为了遵循业界标准,使熟悉这些数据库接口的应用程序开发人员能在短期内掌握cli的开发方法。
jdbc:
db2在java语言方面的支持包括jdbc,jdbc是一个与厂商无关的动态sql接口,利用它使得应用程序可以通过标准java方法调用实现数据的访问。
jdbc与db2 cli一样不需要作预编译或者捆绑,作为一个与厂商无关的标准,jdbc应用程序具有良好的移植性。用jdbc开发的应用程序只采用动态sql。
三、微软规范
开发符合“数据存取对象 (dao) ”和“远程数据对象 (rdo) ” 规范的 microsoft visual basic 和 visual c++ 应用程序,以及使用“对象链接和嵌入数据库 (ole db) 桥接”的“activex 数据对象 (ado) ”应用程序。
四、perl数据库接口
db2支持perl数据库接口(dbi)数据访问规范,使用dbd::db2驱动程序。dbd::db2驱动程序支持下列平台:
aix
operating systems
version 4.1.4 and later
c compilers
ibm c for aix version 3.1 and later
hp-ux
operating systems
hp-ux version 10.10 with patch levels: phco_6134, phkl_5837,
phkl_6133, phkl_6189, phkl_6273, phss_5956
hp-ux version 10.20
hp-ux version 11
c compilers
hp c/hp-ux version a.10.32
hp c compiler version a.11.00.00 (for hp-ux version 11)
linux
operating systems
linux redhat version 5.1 with kernel 2.0.35 and glibc version 2.0.7
c compilers
gcc version 2.7.2.3 or later
solaris
operating systems
solaris version 2.5.1
solaris version 2.6
c compilers
sparcompiler c version 4.2
windows nt
operating systems
microsoft windows nt version 4 or later
c compilers
microsoft visual c++ version 5.0 or later
从db2通用数据perl dbi网页(http://www.software.ibm.com/data/db2/perl/)上可以下载最新的dbd::db2驱动程序的最新版本以及更多信息。
五、查询工具产品
查询工具产品包括ibm查询管理工具(qmf)和lotus notes,它们支持查询开发和报表。
2.1.2 数据值控制应用程序的部分逻辑是通过控制数据库中允许的值得到实施和保护数据完整的。db2提供了几个不同的方法。
一、数据类型
数据库将每一个数据元素存储在某个表的列中,并且每一列都用一个数据类型来定义,数据类型增加了此列放置数据的限制。例如,整数类型必须是在某个固定范围内的数字。在sql语句中使用列也必须符合一定的行为,例如,数据库不能将一个整数与一个字符串比较。db2有一组内置的数据类型,定义了特性和行为。db2支持用户定义数据类型,叫做udt,它们是基于内置的数据类型定义的。
二、唯一性约束
唯一性约束防止在一个表中,在一列或多列上出现重复的值。唯一主关键字也是唯一性约束。例如,在表department的deptno列上定义一个唯一性约束,防止将相同的部门号分配给两个部门。
如果对所有使用同一个表里的数据的应用程序都要执行一个唯一性规则,应当采用唯一性约束。
三、表检查约束
表检查约束(table check constraint)限制在表中某列出现的值的范围。
四、参考完整性约束
通过定义唯一约束和外部关键字,可以定义表与表之间的关系,从而实施某些 商业规则。唯一关键和外部关键字约束的组合通常称为参考完整性约束。 外部关键字所引用的唯一约束称为父关键字。 外部关键字表示特定的父关键字,或与特定的父关键字相关。 例如,某规则可能规定每个雇员(employee 表)必须属于某现存的部门 (department 表)。因此,将 employee 表中的“部门号”定义为外部关键字,而将 department 表中的“部门号”定义为主关键字。下列图表提供参考完整性 约束的直观说明。
图 外部约束和主约束定义关系并保护数据
2.1.3 数据关系控制应用程序逻辑的另外一个主要任务是管理系统中不同实体之间的关系。例如,如果增加一个新的部门,就要创建一个新的帐号。db2提供了管理数据库中不同实体之间的管理的两种方法:参考完整性约束和触发器。
一、参考完整性约束
我们在前面已经介绍过。
二、触发器
一个触发器定义一组操作,这组操作通过修改指定基表 中数据的操作来激活。
可使用触发器来执行对输入数据的验证;自动生成新插入行的值; 为了交叉引用而读取其他表;为了审查跟踪而写入其他表; 或通过电子邮件信息支持警报。 使用触发器将导致应用程序开发及商业规则的全面实施更快速并且应用程序 和数据的维护更容易。
db2 通用数据库支持几种类型的触发器。 可定义触发器在 delete、insert 或 update 操作之前或之后激活。 每个触发器包括一组称为触发操作的 sql 语句, 这组语句可包括一个可选的搜索条件。
可进一步定义后触发器以对每一行都执行触发操作, 或对语句执行一次触发操作,而前触发器总是 对每一行都执行触发操作。
在 insert、update 或 delete 语句之前使用触发器,以便在执行触发操作之前 检查某些条件,或在将输入值存储在表中之前更改输入值。 使用后触发器,以便在必要时传播值或执行其他任务,如发送信息等,这些任务可能是触发器操作所要求的。
2.1.4 服务器上的应用逻辑db2提供了将应用程序程序的部分在数据库服务器上运行的功能,通常是为了提高性能和支持公共功能。主要方法有:存储过程,udf,触发器。
2.1.5 构造sql语句的原型当设计和编写应用程序时,利用数据库管理器的特性和一些工具来构造sql语句的原型,提高执行性能。可以按照下面的方法优化sql语句:
1.在预编译一个完整的程序之前,用命令行处理器(clp)测试其中的sql(可能不是全部)。
2.用解释设施估算程序中的delete, insert, update, 和 select语句的开销和获取访问策略。根据解释设施的输出,改写sql语句或者增加数据库对象(如索引)或者调节数据库服务器的参数 。
2.2 设置测试环境在进行db2应用开发时,需要建立测试环境。一个测试环境,应该包括:
1. 一个测试数据库. 如果应用程序要更新,插入或者删除来自表和视图的数据,那么使用测试数据检查执行情况;如果仅仅从表和视图中提取数据,可以考虑使用生产数据.
2. 测试的输入数据. 用来测试应用程序的测试数据应该是有效的,能体现所有可能输入情况. 也要用无效的数据去测试,看应用程序能否辨别出.
2.2.1 创建测试数据库可以使用clp发出create database dbname语句创建测试数据库,也可以使用数据库管理器api编写一个程序来创建测试数据库.
2.2.2 创建测试表先分析应用程序的数据需求,然后使用create table语句创建测试表.
2.2.3 生成测试数据使用下面任何一种方法将数据插入表中:
l insert...values (an sql statement) 每次可以插入一行或多行数据
l insert...select 从一个已存在的表提取数据 (基于一个select条款),并放入insert语句标识的表中.
l 用import和load工具 从定义的数据源插入大量的数据
l 用restore工具从某个数据库的备份,将数据还原到特定的测试数据库
第三章 静态sql应用编程静态sql语句,是指嵌入在宿主语言中的sql语句在预编译时完全知道。这是相对于动态sql而言的,动态sql语句的语法在运行时才知道。注意:解释语言中不支持静态sql语句,例如rexx只支持动态sql语句。
一条sql语句的结构在预编译时完全清楚,才被认为是静态的。例如,语句中涉及到的表(tables)和列的名字,在预编译时,必须完全知道,只能在运行时指定的是语句中引用的宿主变量的值。然而,宿主变量的信息,如数据类型,也必须在预编译时确定。
当静态sql语句被准备时,sql语句的可执行形式被创建,存储在数据库中的程序包里。sql语句的可执行形式可以在预编译时创建或者在捆绑时创建。不论哪种情况,sql语句的准备过程都发生在运行之前。捆绑应用程序的人需要有一定的权限,数据库管理器中的优化器还会根据数据库的统计信息和配置参数对sql语句进行优化。对静态sql语句来说,应用程序运行时,不会被优化。
3.1 静态sql程序的结构和特点3.1.1 例程下面先来看一个静态sql程序的c语言例子。这个例程演示了静态sql语句的使用,它将表中lastname列等于‘johnson’的记录的firstnme列的值输出,否则打印错误信息。
/******************************************************************************
**
** source file name = static.sqc 1.4
**
** licensed materials - property of ibm
**
*******************************************************************************/
#include
#include
#include
#include util.h
#ifdef db268k
/* need to include aslm for 68k applications */
#include
#endif
exec sql include sqlca; /* :rk.1:erk. */
#define checkerr(ce_str) if (check_error(ce_str, &sqlca) != 0) return 1;
int main(int argc, char *argv[]) {
exec sql begin declare section; /* :rk.2:erk. */
char firstname[13];
char userid[9];
char passwd[19];
exec sql end declare section;
#ifdef db268k
/* before making any api calls for 68k environment,
need to initial the library manager */
initlibrarymanager(0,kcurrentzone,knormalmemory);
atexit(cleanuplibrarymanager);
#endif
printf( sample c program: static/n );
if (argc == 1) {
exec sql connect to sample;
checkerr (connect to sample);
}
else if (argc == 3) {
strcpy (userid, argv[1]);
strcpy (passwd, argv[2]);
exec sql connect to sample user :userid using :passwd; /* :rk.3:erk. */
checkerr (connect to sample);
}
else {
printf (/nusage: static [userid passwd]/n/n);
return 1;
} /* endif */
exec sql select firstnme into :firstname /* :rk.4:erk. */
from employee
where lastname = 'johnson';
checkerr (select statement); /* :rk.5:erk. */
printf( first name = %s/n, firstname );
exec sql connect reset; /* :rk.6:erk. */
checkerr (connect reset);
return 0;
}
/* end of program : static.sqc */
这个例程中实现了一个选择至多一行(即单行)的查询,这样的查询可以通过一条select into语句来执行。select into 语句从数据库中的表选择一行数据,然后将这行数据的值赋予语句中指定的宿主变量(下节将要讨论宿主变量)。例如,下面的语句将姓为‘haas’的雇员的工资赋予宿主变量empsal:
select salary
into :empsal
from employee
where lastname='haas'
一条select into语句必须只能返回一行或者零行。如果结果集有多于一行,就会产生一个错误(sqlcode –811,sqlstate 21000)。如果查询的结果集中有多行,就需要游标(cursor)来处理这些行。在节3.2.3中介绍如何使用游标。
静态程序是如何工作的呢?
1.包括结构sqlca。 include sqlca语句定义和声明了sqlca结构,sqlca结构中定义了sqlcode和sqlstate域。数据库管理器在执行完每一条sql语句或者每一个数据库管理器api调用,都要更新sqlca结构中的sqlcode域的诊断信息。
2.声明宿主变量。sql begin declare section和end declare section 语句界定宿主变量的声明。
有些变量在sql语句中被引用。宿主变量用来将数据传递给数据库管理或者接收数据库管理器返回的数据。在sql语句中引用宿主变量时,必须在宿主变量前加前缀冒号(:)。详细信息看下节。
3.连接到数据库。应用程序必须先连接到数据库,才能对数据库进行操作。这个程序连接到sample数据库,请求以共享方式访问。其他应用程序也可以同时以共享访问方式连接数据库
4.提取数据。select into语句基于一个查询提取了一行值。这个例子从employee表中,将lastname列的值为johnson的相应行的fisrtnme列的值提取出来,置于宿主变量 firstname中。
5.处理错误。checkerr 宏/函数是一个执行错误检查的外部函数。
3.1.2 创建应用程序创建应用程序的整个过程如图所示:
3.1.3 静态sql的特点静态sql编程比动态sql编程简单些. 静态sql语句嵌入宿主语言源文件中,预编译器将sql语句转换为宿主语言编译器能够处理的数据库运行时服务api调用。
因为在捆绑应用程序时,做捆绑的人需要有一定的授权,因此最终用户不需要执行程序包里的语句的直接权限。例如,一个应用程序可以只允许某个用户更新一个表的部分数据 ,而不用将更新整个表的权利给予这个用户。这个功能通过限制嵌入的静态sql语句只能更新表中的某些列或者一定范围内的值,只将程序包的执行权限给予这个用户。
静态sql语句是持久稳固的,动态sql语句只是被缓存,直到变为无效、因为空间管理原因被清理或者数据库被关闭。如果需要,当被缓存的语句变为无效时,db2 sql编译器隐性地重新编译动态sql语句。
静态sql语句的主要优点是静态sql在数据库关闭后仍然存在,而动态sql语句在数据库关闭后就被清除了。另外,静态sql在运行时不需要db2 sql编译器来编译,相反,动态sql语句需要在运行时编译(例如,使用prepare语句)。因为db2缓存动态sql语句,这些语句也不总是需要db2编译。但是,每一次运行程序至少需要编译一次。
静态sql有性能上的优势。对简单、运行时间短的sql程序,静态sql语句 比相同目的的动态sql语句执行得快。因为静态sql语句准备执行形式的开销在预编译时间,而不是在运行时。
注意:静态sql语句的性能决定于应用程序最后一次被捆绑时数据库的统计信息。 然而,如果这些统计信息改变了,那么比较起来,等效的动态sql语句的性能可能好些。在某个使用静态sql的应用程序捆绑之后,数据库增加了一个索引,如果这个应用程序不重新捆绑,就不能利用这个索引。还有,如果在静态sql语句中使用宿主变量,优化器也不能使用表的分布信息来优化sql语句。
3.2 宿主变量和指示符变量的应用3.2.1 宿主变量的声明宿主变量(host variables) 在主应用程序中由嵌入式sql语句引用的变量。宿主变量是该应用程序中的程序设计变量,并且是在数据库中的表与应用程序工作区之间传送数据的主要机制。我们称之为“宿主变量”,是为了与通常方法声明的源语言变量区分开来,通常方法声明的变量不能被sql语句引用。宿主变量在宿主语言程序模块中以一种特殊的方式声明:必须在begin declare section和end declare section程序节内定义。
下图显示在不同编程语言中声明宿主变量的例子。
语言
例子源码
c/c++
exec sql begin declare section;
short dept=38, age=26;
double salary;
char ch;
char name1[9], name2[9];
/* c comment */
short nul_ind;
exec sql end declare section;
java
// note that java host variable declarations follow
// normal java variable declaration rules, and have
// no equivalent of a declare section
short dept=38, age=26;
double salary;
char ch;
string name1[9], name2[9];
/* java comment */
short nul_ind;
cobol
exec sql begin declare section end-exec.
01 age pic s9(4) comp-5 value 26.
01 dept pic s9(9) comp-5 value 38.
01 salary pic s9(6)v9(3) comp-3.
01 ch pic x(1).
01 name1 pic x(8).
01 name2 pic x(8).
* cobol comment
01 nul-ind pic s9(4) comp-5.
exec sql end declare section end-exec.
下面是引用宿主变量的例子
语言
例子源码
c/c++
exec sql fetch c1 into :cm;
printf( commission = %f/n, cm );
java
#sql { fetch :c1 into :cm };
system.out.println(commission = + cm);
cobol
exec sql fetch c1 into :cm end-exec
display 'commission = ' cm
在sql语句中引用宿主变量时,必须加前缀—冒号(:)。冒号的作用是将宿主变量与sql语法中的元素区分开。如果没有冒号,宿主变量会误解释为sql语句的一部分。例如:
workdept = dept
将被解释为workdept列的值等于dept列的值。在宿主语言语句中,则不需要加前缀,正常引用即可。从下图中可看出如何使用宿主变量:
db2名字空间(如表名、列名等等)不能用宿主变量指定。例如不能写如下sql语句:
select :col1 from :tabname
但是,这种类型的功能可以通过采用动态sql实现。
总的来说,宿主变量有以下特点:
l 可选的,在语句运行之前用来赋值
l 宿主语言标号在sql语句中,前面加冒号
l 宿主变量与列的数据类型必须匹配
l 对于宿主变量有以下要求:
a. 所有被嵌入sql引用的宿主变量必须在begin和end declare语句界定的代码区里声明;
b. 宿主变量的数据类型必须与列的数据类型匹配,而且尽量避免数据转换和截取;
c. 宿主变量名不能以exec、sql、sql开头;
d. 宿主变量应该被看作是模块程序的全局变量,而不是定义所在函数的局部变量;
e. 在界定区外定义的变量不能与界定区内定义的变量同名;
f. 在一个源文件中,可以有多个界定区;
g. begin declare section语句可以在程序中宿主语言规则允许变量声明的任何位置出现,宿主变量定义区以end declare section语句结束;
h. begin declare section和end declare section语句必须成对出现,并且不能嵌套;
i. 宿主变量声明可以使用sql include语句指定。另外,一个宿主变量声明区不能含有除宿主变量声明以外的语句。
3.2.2 宿主变量的使用下面我们通过几个例子来说明宿主变量的用法:
1.在insert语句中的使用
l sql语句
insert into templ (empno, lastname)
values (‘000190’, ‘jones’)
l 嵌入程序的sql语句
exec sql insert into templ (empno, lastname)
values (:empno, :name);
第一条sql语句可以在clp中发出,它也可以嵌入程序中,但是它每一次只能插入一行值,如果要插入不同的值就要重新输入,程序也要修改。
第二条sql语句只能嵌入程序中,每一次执行需要用户通过其它代码指定新值给宿主变量empno和name,宿主变量的作用是将用户指定的值传递给values子句。可以实现输入多行值(循环或多次运行程序)。
2.在set和where子句中的使用
l sql语句
update templ
set salary = salary *1.05
where jobcode = 54
l 嵌入程序的sql语句
exec sql
update templ
set salary = salary * :percent
where jobcode = :code;
3. 用宿主变量提取值。在程序中执行一个select语句时,必须提供一个存储区域来接收返回的数据,而且对于被选择(selected)的每一列,都要定义一个宿主变量。语法为:select … into :hostvaribale …。例子:
exec sql
select lastname, workdept
into :name, :deptno
from templ
where empno = :empid;
例子中定义了三个宿主变量,从表templ中选择符合条件—empno=:empid—的两列:lastname和workdept,结果存放到宿主变量name和deptno中。此形式的用法要保证只能返回单行数据,如果返回多行数据库,则不能使用这种方法。后面会介绍如果使用游标(cursor)处理多行的返回结果集。
从上面的例子可将宿主变量分为两类:
l 输入宿主变量
输入宿主变量规定需要在语句执行期间从应用程序传递给数据库管理器的值。例如,在下面的sql语句中将使用一个输入宿主变量:
select name from candidate
where name =
l 输出宿主变量
输出宿主变量规定需要在语句执行期间从数据库管理器传递给应用程序的值。例如,在下面的sql语句中将使用一个输出宿主变量:
select into from candidate
where name = ‘ hutchison ’
3.2.3 指示符变量的声明在实际中,有些对象的值未知,我们用空值表示。当我们选择数据时,如果是空值,宿主变量的内容将不会被改变,是随机的。db2数据库管理器提供了一个机制去通知用户返回数据是空值,这个机制就是指示符变量。
指示符(indicator)变量是一种特殊的宿主变量类型,它用来表示列的空值或非空值。当这些宿主变量作为输入进入数据库中时,应当在执行sql语句之前由应用程序对它们设置值。当这些宿主变量作为数据库的输出使用时,这些指示符由应用程序定义,但由db2更新和将它们返回。然后,在结果被返回时,应用程序应当检查这些指示符变量的值。
看下面一条sql语句:
select cola into :a:aind
其中a是宿主变量,aind是指示符变量。如果cola列的值不为空,db2将aind的值设置为非负(通常为0);如果cola列的值为空,db2将aind的值设置为负数(通常为-1);如果db2试图提示一个空值的存在,但是程序没有提供指示符,将会产生错误,sqlcode等于-305。
指示符变量的定义:
指示符变量的定义与宿主变量的定义方法相同,都需要在begin declare section和end declare section之间定义,并且数据类型与sql数据类型smallint对应,在c语言中为short类型。
例子:
create table templ
( empno char(6) not null,
lastname varchar(2) not null,
jobcode char(2),
workdept char(3), not null,
phoneno char(10))
exec sql
select jobcode, workdept, phoneno
into :jc:jci, :dpt, :pho:phoi
from templ
where empno = :id;
empno(6)
lastname(20)
jobcode
0-99
workdept(3)
phoneno(10)
000070
000120
000320
johnson
scott
milligan
54
?
?
c01
c01
c01
5137853210
8592743091
?
3.2.4 指示符变量的使用
指示符逻辑例子1:
exec sql
select phoneno, sex
into :phoneno:phoneind, :sex
from templ
where empno = :eno;
if (phoneind
null_phone();
else
good_phone();
在这个例子里,db2维护指示符变量,应用程序在sql语句执行后,询问指示符变量的值,调用相应的处理函数
指示符逻辑例子2:
if (some condition)
phoneind = -1;
else
phoneind = 0;
exec sql
update templ
set newphone = :newphoneno :phoneind
where empno = :eno;
在这个例子里,应用程序维护指示符变量。应用程序根据条件设置指示符变量phoneind的值。如果db2发现指示符的值为负数,那么给定行集合中的列被设置为空值,宿主变量的值被忽略;如果指示符的值为正数或者为零,宿主变量中的值被使用。
在嵌入sql语句中可以使用关键字null。下面是不使用指示符变量的一个update语句例子:
if ( some condition)
exec sql
update templ
set phoneno = null
where empno = :eno ;
else
exec sql
update templ
sest phoneno = :newphone
where empno = :eno ;
但是,这种写法有缺点:如果update语句需要修改,就要修改两处代码。
如何设置指示符变量:
谁维护指示符变量
sql语句类型
宿主变量
:cd
指示符变量
:cdi
列
jobcode
db2
select/
fetch
60
不改变
0
60
null
应用程序
update/
insert
50
n/a
0
50
null
注解:
db2在执行select和fetch语句的过程中设置指示符变量的值,应用程序应该在执行select和fetch语句后检查它们的值。
应用程序在执行update和insert语句之前设置指示符变量的值来指示db2是否在数据库中放置一个空值(null)。
上表的第一行:在一条select或者fetch语句中,如果列(jobcode)中的值不为空,值被设置到宿主变量(:cd)中,指示符变量(:cdi)的值为零;如果列中的值为空,指示符变量的值将为负数,宿主变量的值不改变。
上表的第一行:在一条update或者insert语句中,如果指示符变量(:cdi)中的值不为负数,宿主变量(:cd)中的值被放到相应的列中;如果指示符变量中的值为负数,宿主变量中的值被忽略,相应的列被设置为空(null)。
指示符变量在数值转换方面的应用:
当宿主变量的数据类型与相应列的数据类型不兼容或者不能转换时,db2也通过指示符变量通知应用程序。
数值转换由数据库管理器处理,能转换时自动完成,对程序透明;
如果列中的值不能存储到宿主变量中时(例如,列的数据类型为decimal(15),值的长度为12个数字,不能存储到integer类型的宿主变量中),指示符变量的值为-2。
指示符变量在截取方面的应用:
在sql语句执行后,如果指示符变量的值为正数,说明发生了数据截取:
—如果是时间数据类型的秒部分被截取,那么指示符变量中的值为截取的秒数
—对于其他数据类型,指示符变量表示数据库中列的数据原始长度,通常为字节数(数据库尽可能返回更多的数据)。
例子:
定义宿主变量和指示符变量:
exec sql begin declare section;
char too_little[5];
short iv1;
exec sql end declare section;
表bank_items:
item#
qty
description
101
3000
passbooks
200
100
checkbooks
…
…
…
执行的sql语句:
select description into :too_little:iv
from bank_items where item# = 200
结果:
:too_little中的值为’check’,:iv1中的值为10
3.3 使用游标处理多行结果集为了使应用程序能够提取多行结果集,sql使用了一种机制,叫做游标(cursor)。
为了理解游标的概念,我们假设数据库管理器创建了一个结果表(result table),里面包含有执行一条select语句所提取的所有行。通过标识或指向这个表的“当前行”,游标使得结果表中的行对应用程序可用。当一个是拥游标时,应用程序可以顺序从结果表中提取每一行,直到产生一个数据结束条件,这个条件就是not found条件,sqlca中的sqlcode为+100(sqlstate为02000)。执行select语句的结果集,可能有零行、一行或者更多行,决定于有多少行符合搜索的条件。
处理一个游标涉及到以下几个步骤:
1.使用declare cursor语句声明一个游标
2.使用open语句执行查询和创建结果表
3.使用fetch语句每次提取一行结果
4.使用delete或update语句处理行(如果需要)
5.使用close语句终止(关闭)游标
一个应用程序中,可以使用多个游标,但是每一个游标要有自己的declare cursor,open,close和fetch语句集。
3.3.1声明和使用游标declare cursor语句定义和命名游标,确定使用select语句要提取的行结果集。
应用程序给游标分配一个名字。这个名字在随后的open、fetch和close语句中都要被参考到。查询可以是任何有效的select语句。
下面例子展示了一条declare语句如何与一条静态select语句关联起来:
语言
源码例程
c/c++
exec sql declare c1 cursor for
select pname, dept from staff
where job=:host_var;
java (sqlj)
#sql iterator cursor1(host_var data type);
#sql cursor1 = { select pname, dept from staff
where job=:host_var };
cobol
exec sql declare c1 cursor for
select name, dept from staff
where job=:host-var end-exec.
fortran
exec sql declare c1 cursor for
+ select name, dept from staff
+ where job=:host_var
注解:declare语句的在程序中位置是任意的,但是它必须在第一次使用游标的位置之前。
3.3.2游标与工作单元的考虑commit或者rollback操作的动作随游标的不同而不同,依赖于游标的定义。
1.只读游标(read only cursors)
如果一个游标被确定为只读的,并且使用可重复读隔离级(isolation level),那么系统表仍会收集和维护工作单元需要的可重复读锁。因此,即使只读游标,应用程序周期性地发出commit语句还是很重要的。
2.有with hold选项
如果应用程序通过发出一条commit语句来完成一个工作单元,除了声明时有with hold选项的游标,所有游标将自动地被数据库管理器关闭。
用with hold声明的游标维护它访问的跨多个工作单元的资源。用with hold声明的游标受到的影响依赖于工作单元如何结束。
如果工作单元使用一条commit语句结束,已打开的定义为with hold的游标将保持打开状态。游标的位置在结果集的下一个逻辑行之前。另外,参考用with hold定义的已准备好的语句也会被保留。紧跟commit语句后面, 只有与一个某个特定游标相关联的fetch和close请求才有效。update where current of和delete where current of 语句仅仅对在同一个工作单元中提取的行有效。如果程序包在工作单元期间被重新绑定,那么所有保持的游标都会被关闭。
如果工作单元使用一条rollback语句结束,所有打开的游标被关闭,所有在工作单元中获得的锁被释放,以及所有依赖于这个工作单元的已准备好的语句被删除。
举个例子,假设templ表中有1000条记录。要更新所有雇员的工资,应该每更新100行就要发出一条commit语句。
a. 使用with hold选项声明游标:
exec sql declare emplupdt cursor with hold for
select empno, lastname, phoneno, jobcode, salary
from templ for update of salary
b. 打开游标,每一次从结果表中提取一行数据:
exec sql open emplupdt
.
.
.
exec sql fetch emplupdt
into :upd_emp, :upd_lname, :upd_tele, :upd_jobcd, :upd_wage,
c. 当想要更新或者删除一行时,使用带where current of选项的update或者delete语句。例如,要更新当前行,程序可以发出下面的语句:
exec sql update templ set salary = :newsalary
where current of emplupdt
在一条commit语句发出之后,在更新其它行之前必须发出fetch语句。
如果应用程序使用了用with hold声明的游标或者执行了多个工作单元并且有一个用with hold声明的游标跨工作单元处于打开状态,那么在程序中应该加入代码检测和处理sqlcode为-501(sqlstate为24501)的错误,这个错误由fetch或者close语句返回。
如果应用程序的程序包由于其依赖的表被删除而变得无效,程序包会自动被重新绑定。这种情况下,fetch或close语句返回sqlcode –501(sqlstate 24501),因为 数据库管理器关闭游标。在此情形下,处理sqlcode –501(sqlstate 24501)的方法决定于是否要从游标提取行数据。
l 如果要从游标提取行,打开游标,然后运行fetch语句。注意,open语句使得游标重定位到开始处。原来的位置信息丢失。
l 如果不准备从游标提取行,那么不要对游标发出任何sql请求。
with release 选项:当应用程序用with release选项关闭一个游标时,db2试图去释放游标持有的所有读锁(read locks)。游标只继续持有写锁(write locks)。如果应用程序没有用release选项关闭游标,那么在工作单元完成时,所有的读锁和写锁都被释放。
3.3.3例程游标程序
c example: cursor.sqc
#include
#include
#include
#include util.h
#ifdef db268k
/* need to include aslm for 68k applications */
#include
#endif
exec sql include sqlca;
#define checkerr(ce_str) if (check_error (ce_str, &sqlca) != 0) return 1;
int main(int argc, char *argv[]) {
exec sql begin declare section;
char pname[10];
short dept;
char userid[9];
char passwd[19];
exec sql end declare section;
#ifdef db268k
/* before making any api calls for 68k environment,
need to initial the library manager */
initlibrarymanager(0,kcurrentzone,knormalmemory);
atexit(cleanuplibrarymanager);
#endif
printf( sample c program: cursor /n );
if (argc == 1) {
exec sql connect to sample;
checkerr (connect to sample);
}
else if (argc == 3) {
strcpy (userid, argv[1]);
strcpy (passwd, argv[2]);
exec sql connect to sample user :userid using :passwd;
checkerr (connect to sample);
}
else {
printf (/nusage: cursor [userid passwd]/n/n);
return 1;
} /* endif */
exec sql declare c1 cursor for (1)
select name, dept from staff where job='mgr'
for update of job;
exec sql open c1; (2)
checkerr (open cursor);
do {
exec sql fetch c1 into :pname, :dept; (3)
if (sqlcode != 0) break;
printf( %-10.10s in dept. %2d will be demoted to clerk/n,
pname, dept );
} while ( 1 );
exec sql close c1; (4)
checkerr (close cursor);
exec sql rollback;
checkerr (rollback);
printf( /non second thought -- changes rolled back./n );
exec sql connect reset;
checkerr (connect reset);
return 0;
}
/* end of program : cursor.sqc */
java example: cursor.sqlj
import java.sql.*;
import sqlj.runtime.*;
import sqlj.runtime.ref.*;
#sql iterator cursorbyname(string name, short dept) ;
#sql iterator cursorbypos(string, short ) ;
class cursor
{ static
{ try
{ class.forname (com.ibm.db2.jdbc.app.db2driver).newinstance ();
}
catch (exception e)
{ system.out.println (/n error loading db2 driver.../n);
system.out.println (e);
system.exit(1);
}
}
public static void main(string argv[])
{ try
{ system.out.println ( java cursor sample);
string url = jdbc:db2:sample;
// url is jdbc:db2:dbname
connection con = null;
// set the connection
if (argv.length == 0)
