第六章 嵌入式SQL(E-SQL)简介3 第一节 什么是嵌入SQL语言?3 1.1 嵌入SQL程序的组成元素3 1.2 什么是静态SQL和动态SQL?4 1.3 什么是SQLCA?4 1.4 什么是SQLDA?5 第二节 SYBASE SQL SERVER嵌入式SQL语言5 2.1 一个嵌入SQL语言的简单例子5 2.2 嵌入SQL的处理过程6 2.3 嵌入SQL语句总览7 2.3.1 宿主变量7 2.3.2 连接数据库12 2.3.3 数据的查询和修改13 2.3.4 游标的使用13 2.3.5 SQLCA15 2.3.6 WHENEVER16 2.3.7 批处理18 2.3.8 事务18 2.4动态SQL语句18 2.4 .1 动态修改19 2.4.2 动态游标20 2.4.3 SQLDA23 2.4.4 DESCRIBE语句27 2.5 两个例子程序27 2.5.1 TELECOM程序27 2.5.2 ADHOC程序29 第三节 IBM DB2嵌入SQL语言35 3.1 一个简单示例35 3.2 嵌入SQL语句37 3.2.1宿主变量37 3.2.2单行查询39 3.2.3多行查询39 3.2.4插入、删除和修改操作40 3.2.5 SQLCA43 3.2.6事务45 3.3 DB2的嵌入SQL程序处理过程46 3.4 DB2的动态SQL嵌入语句53 3.4.1 基本方法53 3.4.2 动态游标55 3.4.3 SQLDA55 第四节 ORACLE数据库的嵌入SQL语言66 4.1 基本的SQL语句66 4.1.1宿主变量和指示符66 4.1.2 查询68 4.1.3 修改数据68 4.1.4 游标68 4.2 嵌入PL/SQL71 4.3 动态SQL语句72 4.3.1 ORACLE动态SQL语句的一些特点72 4.3.2 使用动态SQL的四种方法72 4.3.3 SQLDA75 第五节INFORMIX的嵌入SQL/C语言89 5.1 一个简单的入门例子89 5.2 宿主变量91 5.3 嵌入SQL的处理过程96 5.4 动态SQL语言96 5.4.1 SQLDA97 第六节MICROSOFT SQL SERVER7嵌入式SQL语言105 6.1 一个嵌入SQL语言的简单例子105 6.2 嵌入SQL的处理过程106 6.3 嵌入SQL语句112 6.3.1 声明嵌入SQL语句中使用的C变量112 6.3.2 连接数据库115 6.3.3 数据的查询和修改115 6.3.4 游标的使用116 6.3.5 SQLCA117 6.3.6 WHENEVER118 6.4动态SQL语句119 6.4 .1 动态修改119 6.4.2 动态游标120 6.4.3 SQLDA122 6.4.4 DESCRIBE语句130 6.5 API130第六章 嵌入式SQL(E-SQL)简介 第一节 什么是嵌入SQL语言? SQL是一种双重式语言,它既是一种用于查询和更新的交互式数据库语言,又是一种应用程序进行数据库访问时所采取的编程式数据库语言。SQL语言在这两种方式中的大部分语法是相同的。在编写访问数据库的程序时,必须从普通的编程语言开始(如C语言),再把SQL加入到程序中。所以,嵌入式SQL语言就是将SQL语句直接嵌入到程序的源代码中,与其他程序设计语言语句混合。专用的SQL预编译程序将嵌入的SQL语句转换为能被程序设计语言(如C语言)的编译器识别的函数调用。然后,C编译器编译源代码为可执行程序。 各个数据库厂商都采用嵌入SQL语言,并且都符合ANSI/ISO的标准。所以,如果采用合适的嵌入SQL语言,那么可以使得你的程序能够在各个数据库平台上执行(即:源程序不用做修改,只需要用相应数据库产品的预编译器编译即可)。当然,每个数据库厂商又扩展了ANSI/ISO的标准,提供了一些附加的功能。这样,也使得每个数据库产品在嵌入SQL方面有一些区别。本章的目标是,对所有的数据库产品的嵌入SQL做一个简单、实用的介绍。 当然,嵌入SQL语句完成的功能也可以通过应用程序接口(API)实现。通过API的调用,可以将SQL语句传递到DBMS,并用API调用返回查询结果。这个方法不需要专用的预编译程序。 1.1 嵌入SQL程序的组成元素 我们以IBM的DB2嵌入SQL为例,来看看嵌入SQL语句的组成元素。 例1、连接到SAMPLE数据库,查询LASTNAME为JOHNSON的FIRSTNAME信息。 #include <stdio.h> #include <stdlib.h> #include <string.h> #include "util.h" #include <sqlca.h> EXEC SQL INCLUDE SQLCA; (1) main() { EXEC SQL BEGIN DECLARE SECTION; (2) char firstname[13]; char userid[9]; char passwd[19]; EXEC SQL END DECLARE SECTION; EXEC SQL CONNECT TO sample; (3) EXEC SQL SELECT FIRSTNME INTO :firstname (4) FROM employee WHERE LASTNAME = 'JOHNSON';(4) printf( "First name = %s\n", firstname ); EXEC SQL CONNECT RESET; (5) return 0; } 上面是一个简单的静态嵌入SQL语句的应用程序。它包括了嵌入SQL的主要部分: (1)中的include SQLCA语句定义并描述了SQLCA的结构。SQLCA用于应用程序和数据库之间的通讯,其中的SQLCODE返回SQL语句执行后的结果状态。 (2)在BEGIN DECLARE SECTION和END DECLARE SECTION之间定义了宿主变量。宿主变量可被SQL语句引用,也可以被C语言语句引用。它用于将程序中的数据通过SQL语句传给数据库管理器,或从数据库管理器接收查询的结果。在SQL语句中,主变量前均有“:”标志以示区别。 (3)在每次访问数据库之前必须做CONNECT操作,以连接到某一个数据库上。这时,应该保证数据库实例已经启动。 (4)是一条选择语句。它将表employee中的LASTNAME为“JOHNSON”的行数据的FIRSTNAME查出,并将它放在firstname变量中。该语句返回一个结果。可以通过游标返回多个结果。当然,也可以包含update、insert和delete语句。 (5)最后断开数据库的连接。 从上例看出,每条嵌入式SQL语句都用EXEC SQL开始,表明它是一条SQL语句。这也是告诉预编译器在EXEC SQL和“;”之间是嵌入SQL语句。如果一条嵌入式SQL语句占用多行,在C程序中可以用续行符“\”。 1.2 什么是静态SQL和动态SQL? 嵌入SQL语言,分为静态SQL语言和动态语言两类。静态SQL语言,就是在编译时已经确定了引用的表和列。宿主变量不改变表和列信息。可以使用主变量改变查询参数值,但是不能用主变量代替表名或列名。 动态SQL语言就是:不在编译时确定SQL的表和列,而是让程序在运行时提供,并将SQL语句文本传给DBMS执行。静态SQL语句在编译时已经生成执行计划。而动态SQL语句,只有在执行时才产生执行计划。动态SQL语句首先执行PREPARE语句要求DBMS分析、确认和优化语句,并为其生成执行计划。DBMS还设置SQLCODE以表明语句中发现的错误。当程序执行完“PREPARE”语句后,就可以用EXECUTE语句执行执行计划,并设置SQLCODE,以表明完成状态。 1.3 什么是SQLCA? 应用程序执行时,每执行一条SQL语句,就返回一个状态符和一些附加信息。这些信息反映了SQL语句的执行情况,它有助于用户分析应用程序的错误所在。这些信息都存放在sqlca.h的sqlca结构中。如果一个源文件中包含SQL语句,则必须要在源程序中定义一个SQLCA结构,而且名为SQLCA。最简单的定义方法是在源文件中加入一些语句:EXEC SQL INCLUDE sqlca.h。每个数据库产品都提供了SQLCA结构。 1.4 什么是SQLDA? 我们知道,动态SQL语句在编译时可能不知道有多少列信息。在嵌入SQL语句中,这些不确定的数据是通过SQLDA完成的。SQLDA的结构非常灵活,在该结构的固定部分,指明了多少列等信息,在该结构的后面有一个可变长的结构,说明每列的信息。在从数据库获得数据时,就可以采用SQLDA来获得每行的数据。各个数据库产品的SQLDA结构都不完全相同。 第二节 SYBASE SQL Server嵌入式SQL语言 2.1 一个嵌入SQL语言的简单例子 我们首先来看一个简单的嵌入式SQL语言的程序(C语言):用sa(口令为password)连接数据库服务器,并将所有书的价格增加10%。这个例子程序如下: 例1、 /*建立通讯区域*/ Exec sql include sqlca; main() { /*声明宿主变量*/ EXEC SQL BEGIN DECLARE SECTION; char user[30],passwd[30]; EXEC SQL END DECLARE SECTION; /*错误处理*/ EXEC SQL WHENEVER SQLERROR CALL err_p(); /*连接到SQL SERVER服务器*/ printf("\nplease enter your userid "); gets(user); printf("\npassword "); gets(passwd); exec sql connect :user identified by :passwd; exec sql use pubs2; EXEC SQL update titles set price=price*1.10; EXEC SQL commit work; /*断开数据库服务器的连接*/ Exec sql disconnect all; return (0); } /*错误处理程序*/ err_p() { printf("\nError occurred: code %d.\n%s", \ sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); } 从上面这个例子,我们看出嵌入SQL的基本特点是: 1、每条嵌入式SQL语句都用EXEC SQL开始,表明它是一条SQL语句。这也是告诉预编译器在EXEC SQL和“;”之间是嵌入SQL语句。 2、如果一条嵌入式SQL语句占用多行,在C程序中可以用续行符“\”,在Fortran中必须有续行符。其他语言也有相应规定。 3、每一条嵌入SQL语句都有结束符号,如:在C中是“;”。 4、嵌入SQL语句的关键字不区分大小写。 5、可以使用“/*….*/”来添加注释。也可以使用“--”来添加注释。 2.2 嵌入SQL的处理过程 嵌入SQL的处理过程如下图所示: 图6-1 SYBASE SQL SERVER嵌入SQL程序处理过程 嵌入SQL程序的后缀为.cp。嵌入SQL处理的第一步是预编译。预编译器(cpre.exe)处理过程分为两个小步: 第一小步:语法分析。检查嵌入SQL语句的语法正确性。 第二小步:增加一些预编译器变量,并注释了所有的嵌入的SQL语句,将嵌入SQL语句转换为对client-library中函数的调用(注意:在连接时,编译后的代码需要使用client-library中的库文件)。如果在编译时,设置一些选项,则生成存储过程。预编译后可能产生3个文件:一个C文件(肯定产生),一个列表文件(需要在编译时设置选项,才能产生)和一个isql脚本文件(需要在编译时设置选项,才能产生)。列表文件包含了输入文件的源语句和一些警告信息和错误信息。Isql脚本文件包含了预编译器产生的存储过程脚本文件。这个存储过程是用T-SQL写的。总之,预编译器的处理方法是,注释了嵌入的SQL语句,用一些特定的函数代替。 第二步是C源程序的编译和链接。cl是编译和链接命令的集成命令,编译的结果是产生.obj,在链接时,将C的系统库和SQL Server提供的库文件同目标文件连接在一起。最后生成.exe。也可以使用SET LIB语句设置库文件的环境信息。 2.3 嵌入SQL语句总览 除了print、readtext和writetext外,大多数的Transact-SQL语句都可以在嵌入SQL中使用。嵌入SQL语句的语法为:“exec sql [at connection_name] sql_statement; ”。那么,你可以用Transact-SQL语句来替代sql_statement 就可以完成嵌入SQL的编写。(同T-SQL相比,嵌入SQL提供了:自动数据类型转换、动态SQL、SQLCA数据结构等功能。) 但是,也有一些嵌入式SQL所特有的语句,有些嵌入式SQL语句的名字同Transact-SQL语句相同,但是语句的语法有所不同。 嵌入SQL语句应该包含五个步骤: 1)、通过SQLCA建立应用程序和SQL SERVER的SQL通信区域。 2)、声明宿主变量。 3)、连接到SQL SERVER。 4)、通过SQL语句操作数据。 5)、处理错误和结果信息。 嵌入式SQL语句分为静态SQL语句和动态SQL语句两类。下面我们按照功能讲解这些语句。本节讲解静态SQL语句的作用。动态SQL语句将在下一节讲解。同动态SQL相关的一些语句也在下一节中讲解。 2.3.1 宿主变量 1)、声明方法 宿主变量(host variable)就是在嵌入式SQL语句中引用主语言说明的程序变量(如例中的user[31]变量)。如: EXEC SQL BEGIN DECLARE SECTION; char user[31],passwd[31]; EXEC SQL END DECLARE SECTION; ………… exec sql connect :user identified by :passwd; …………. 在嵌入式SQL语句中使用主变量前,必须采用BEGIN DECLARE SECTION 和END DECLARE SECTION之间给主变量说明。这两条语句不是可执行语句,而是预编译程序的说明。主变量是标准的C程序变量。嵌入SQL语句使用主变量来输入数据和输出数据。C程序和嵌入SQL语句都可以访问主变量。 另外,在定义宿主变量时也可以使用client-library定义的数据类型,如:CS_CHAR。这些定义存放在cspublic.h文件中。如: EXEC SQL BEGIN DECLARE SECTION; CS_CHAR user[30],passwd[30]; EXEC SQL END DECLARE SECTION; client-library定义的数据类型共有:CS_BINARY、CS_BIT、 CS_BOOL、 CS_CHAR、 CS_DATETIME、CS_DATETIME4、 CS_DECIMAL、 CS_FLOAT、 CS_REAL、CS_IMAGE、 CS_INT、 CS_MONEY、 CS_MONEY4、 CS_NUMERIC、CS_RETCODE、 CS_SMALLINT、 CS_TEXT、 CS_TINYINT、CS_VARBINARY、 CS_VARCHAR、 CS_VOID。 为了便于识别主变量,当嵌入式SQL语句中出现主变量时,必须在变量名称前标上冒号(:)。冒号的作用是,告诉预编译器,这是个主变量而不是表名或列名。不能在声明时,初始化数组变量。 由上可知,SYBASE SQL SERVER使用宿主变量传递数据库中的数据和状态信息到应用程序,应用程序也通过宿主变量传递数据到SYBASE数据库。根据上面两种功能,宿主变量分为输出宿主变量和输入宿主变量。在SELECT INTO和FETCH语句之后的宿主变量称作“输出宿主变量”,这是因为从数据库传递列数据到应用程序。如: exec sql begin declare section; CS_CHAR id[5]; exec sql end declare section; exec sql select title_id into :id from titles where pub_id = "0736" and type = "business"; 除了SELECT INTO和FETCH语句外的其他SQL语句(如:INSERT、UPDATE等语句)中的宿主变量,称为“输入宿主变量”。这是因为从应用程序向数据库输入值。如: exec sql begin declare section; CS_CHAR id[7]; CS_CHAR publisher[5]; exec sql end declare section; ... exec sql delete from titles where title_id = :id; exec sql update titles set pub_id = :publisher where title_id = :id; 另外,也可以通过宿主变量获得存储过程的执行状态信息。如: exec sql begin declare section; CS_SMALLINT retcode; exec sql end declare section; exec sql begin transaction; exec sql exec :retcode = update_proc; if (retcode != 0) { exec sql rollback transaction; 也可以通过宿主变量获得存储过程的返回值。如: exec sql exec a_proc :par1 out, :par2 out; 2)、主变量的数据类型 SYBASE SQL SERVER支持的数据类型与程序设计语言支持的数据类型之间有很大差别。这些差别对主变量影响很大。一方面,主变量是一个用程序设计语言的数据类型说明并用程序设计语言处理的程序变量;另一方面,在嵌入SQL语句中用主变量保存数据库数据。所以,在嵌入SQL语句中,必须映射C数据类型为合适的SQL Server数据类型。必须慎重选择主变量的数据类型。在SQL SERVER中,预编译器能够自动转换兼容的数据类型。请看下面这个例子: EXEC SQL BEGIN DECLARE SECTION; int hostvar1 = 39; char *hostvar2 = "telescope"; float hostvar3 = 355.95; EXEC SQL END DECLARE SECTION; EXEC SQL UPDATE inventory SET department = :hostvar1 WHERE part_num = "4572-3"; EXEC SQL UPDATE inventory SET prod_descrip = :hostvar2 WHERE part_num = "4572-3"; EXEC SQL UPDATE inventory SET price = :hostvar3 WHERE part_num = "4572-3"; 在第一个update语句中,department列为smallint数据类型(integer ),所以应该把hostvar1定义为int数据类型(integer)。这样的话,从C到SQL Server的hostvar1可以直接映射。在第二个update语句中,prod_descip列为varchar数据类型,所以应该把hostvar2定义为字符数组。这样的话,从C到SQL Server的hostvar2可以从字符数组映射为varchar数据类型。在第三个update语句中,price列为money数据类型。在C语言中,没有相应的数据类型,所以用户可以把hostvar3定义为C的浮点变量或字符数据类型。SQL Server可以自动将浮点变量转换为money数据类型(输入数据),或将money数据类型转换为浮点变量(输出数据)。 注意的是,如果数据类型为字符数组,那么SQL Server会在数据后面填充空格,直到填满该变量的声明长度(CS_CHAR数据类型除外)。 下表列出了C的数据类型和SQL SERVER数据类型的一些兼容关系: 可兼容的C数据类型分配的SQL Server数据类型SYBASE提供的数据类型描述 shortSmallintCS_SMALLINT2字节整数 IntSmallintCS_SMALLINT2字节整数 LongIntCS_INT4字节整数 FloatRealCS_REAL4字节浮点数 DoubleFloatCS_FLOAT8字节浮点数 CharCarchar[X]VARCHARCS_CHAR字符数据类型 Unsigned charBinaryVarbinaryCS_BINARYBinary数据类型 Unsigned char tinyintCS_TINYINT1字节整数 无DatetimeCS_DATETIME8字节datetime类型 无SmalldatetimeCS_DATETIME44字节datetime类型 无DecimalCS_DECIMALDecimal数据类型 无numericCS_NUMERICNumeric数据类型 无MoneyCS_MONEY8字节money类型 无smallmoneyCS_MONEY44字节money类型 Unsigned char TextCS_TEXT文本数据类型 Unsigned char imageCS_IMAGE图象数据类型 无booleanCS_BITBit数据类型 因为C没有date或time数据类型,所以SQL Server的date或time列将被转换为字符。缺省情况下,使用以下转换格式:mm dd yyyy hh:mm:ss[am | pm]。你也可以使用字符数据格式将C的字符数据存放到SQL Server的date列上。你也可以使用Transact-SQL中的convert语句来转换数据类型。如:SELECT CONVERT(char, date, 8) FROM sales。 下表是从SQL SERVER数据类型到C的数据类型的转换关系: SQL SERVER 数据类型C数据类型 CS_TINYINTCS_SMALLINTCS_INTCS_REALCS_CHARCS_MONEYCS_DATETIME char可以可以可以可以可以可以可以 varchar可以可以可以可以可以可以可以 bit可以可以可以可以可以可以 binary可以可以可以可以可以可以 tinyint可以可以可以可以可以可以 smallint可以可以可以可以可以可以 int可以可以可以可以可以可以 float可以可以可以可以可以可以 money可以可以可以可以可以可以 datetime可以可以 decimal可以可以可以可以可以可以 numeric可以可以可以可以可以可以 下表是从C的数据类型到SQL SERVER数据类型的转换关系: C数据类型SQL SERVER数据类型 tinyintbitsmallintintfloatcharmoneydatetimedecimalnumeric Unsigned char可以可以可以可以可以需要自己转换可以可以可以 Short int可以可以可以可以可以需要自己转换可以可以可以 Long int可以可以可以可以可以需要自己转换可以可以可以 Double float可以可以可以可以可以需要自己转换可以可以可以 Char需要自己转换需要自己转换需要自己转换需要自己转换需要自己转换可以需要自己转换可以需要自己转换需要自己转换 money可以可以可以可以可以可以可以可以可以 datetime需要自己转换可以 3)、主变量和NULL 大多数程序设计语言(如C)都不支持NULL。所以对NULL的处理,一定要在SQL中完成。我们可以使用主机指示符变量(host indicator variable)来解决这个问题。在嵌入式SQL语句中,主变量和指示符变量共同规定一个单独的SQL类型值。如: EXEC SQL SELECT price INTO :price :price_nullflag FROM titles WHERE au_id = "mc3026" 其中,price是主变量,price_nullflag是指示符变量。 使用指示符变量的语法为:: host_variable [[indicator] : indicator_variable]。其中,indicator可以不写。针对宿主变量是输出宿主变量,还是输入宿主变量。指示符变量共分两种情况。 情况1:同输出宿主变量一起使用,则indicator_varibale为: l-1。表示相应列值为NULL。表示主变量应该假设为NULL。(注意:宿主变量的实际值是一个无关值,不予考虑)。 l0。表示非NULL值。该变量存放了非NULL的列值。 l>0。表示宿主变量包含了列值的截断值。该指示变量存放了该列值的实际长度。 下面是一个同输出宿主变量一起使用的指示变量的例子: exec sql begin declare section; CS_CHAR id[6]; CS_SMALLINT indic; CS_CHAR pub_name[41]; exec sql end declare section; exec sql select pub_id into :id indicator :indic from titles where title like "%Stress%"; if (indic == -1) { printf("\npub_id is null"); } else { exec sql select pub_name into :pub_name from publishers where pub_id = :id; printf("\nPublisher: %s", pub_name); 情况2:同输入宿主变量一起使用,则indicator_varibale为: l-1。表示主变量应该假设为NULL。(注意:宿主变量的实际值是一个无关值,不予考虑)。应该将NULL赋值给相应列。 l0。表示非NULL值。该变量存放了非NULL值。应该将宿主变量的值赋值给相应列。 对于以下语句: EXEC SQL SELECT price INTO :price :price_nullflag FROM titles WHERE au_id = "mc3026" 如果不存在mc3026写的书,那么price_nullflag为-1,表示price为NULL;如果存在,则price为实际的价格。下面我们再看一个update的例子: EXEC SQL UPDATE closeoutsale SET temp_price = :saleprice :saleprice_null, listprice = :oldprice; 如果saleprice_null是-1,则上述语句等价为: EXEC SQL UPDATE closeoutsale SET temp_price = null, listprice = :oldprice; 我们也可以在指示符变量前面加上“INDICATOR”关键字,表示后面的变量为指示符变量。如: EXEC SQL UPDATE closeoutsale SET temp_price = :saleprice INDICATOR :saleprice_null; 指示符变量也是宿主变量,定义指示符变量同定义宿主变量一样。它应该是一个2个字节的整数(short或CS_SMALLINT)。 2.3.2 连接数据库 在程序中,使用CONNECT语句来连接数据库。该语句的完整语法为: exec sql connect : user [identified by : password] [at : connection_name] [using : server] [labelname labelname labelvalue labelvalue...] 其中, lserver为服务器名。如省略,则为本地服务器名。 lconnection_name为连接名。可省略。如果你仅仅使用一个连接,那么无需指定连接名。可以使用SET CONNECTION来使用不同的连接。 luser为登录名。 lpassword为密码。 如:使用my_id用户和passes密码连接到SYBASE服务器。 exec sql begin declare section; CS_CHAR user[16]; CS_CHAR passwd[16]; CS_CHAR server[BUFSIZ]; exec sql end declare section; strcpy(server,"SYBASE"); strcpy(passwd,"passes"); strcpy(user, "my_id"); exec sql connect :user identified by :passwd using :server; 请看下面这些例子来理解连接名的使用方法。 ... exec sql begin declare section; CS_CHAR user[16]; CS_CHAR passwd[16]; CS_CHAR name; CS_INT value, test; CS_CHAR server_1[BUFSIZ]; CS_CHAR server_2[BUFSIZ]; exec sql end declare section; ... strcpy (server_1, "sybase1"); strcpy (server_2, "sybase2"); strcpy(user, "my_id"); strcpy(passwd, "mypass"); exec sql connect :user identified by :passwd at connection_2 using :server_2; exec sql connect :user identified by :passwd using :server_1; /* 下面这个语句使用了"server_1"的连接*/ exec sql select royalty into :value from authors where author = :name; if (value == test) { /* 下面这个语句使用了"connection_2"连接 */ exec sql at connection_2 update authors set column = :value*2 where author = :name; 在嵌入SQL语句中,使用DISCONNECT语句断开数据库的连接。其语法为: DISCONNECT [connection_name | ALL | CURRENT] 其中,connection_name为连接名。ALL表示断开所有的连接。CURRENT表示断开当前连接。断开连接会回滚当前事务、删除临时表、关闭游标和释放锁等。 2.3.3 数据的查询和修改 可以使用SELECT INTO语句查询数据,并将数据存放在主变量中。如:查询lastname为stringer的firstname信息。 EXEC SQL SELECT au_fname INTO :first_name from authors where au_lname = "stringer"; 使用DELETE语句删除数据。其语法类似于Transact-SQL中的DELETE语法。如: EXEC SQL DELETE FROM authors WHERE au_lname = 'White' 使用UPDATE语句可以更新数据。其语法类似Transact-SQL中的UPDATE语法。如: ` EXEC SQL UPDATE authors SET au_fname = 'Fred' WHERE au_lname = 'White' 使用INSERT语句可以插入新数据。其语法就是Transact-SQL中的INSERT语法。如: EXEC SQL INSERT INTO homesales (seller_name, sale_price) real_estate('Jane Doe', 180000.00); 多行数据的查询和修改请参见下一节——游标。 2.3.4 游标的使用 用嵌入式SQL语句查询数据分成两类情况。一类是单行结果,一类是多行结果。对于单行结果,可以使用SELECT INTO语句;对于多行结果,你必须使用cursor(游标)来完成。游标(Cursor)是一个与SELECT语句相关联的符号名,它使用户可逐行访问由SQL Server返回的结果集。先请看下面这个例子,这个例子的作用是逐行打印staff表的id、name、dept、 job、years、salary和comm的值。 ……….. EXEC SQL DECLARE C1 CURSOR FOR SELECT id, name, dept, job, years, salary, comm FROM staff; EXEC SQL OPEN c1; while (SQLCODE == 0) { /* SQLCODE will be zero if data is successfully fetched */ EXEC SQL FETCH c1 INTO :id, :name, :dept, :job, :years, :salary, :comm; if (SQLCODE == 0) printf("%4d %12s %10d %10s %2d %8d %8d", id, name, dept, job, years, salary, comm); } EXEC SQL CLOSE c1; ……… 从上例看出,你首先应该定义游标结果集,即定义该游标的SELECT语句返回的行的集合。然后,使用FETCH语句逐行处理。 值得注意的是,嵌入SQL语句中的游标定义选项同Transact-SQL 中的游标定义选项有些不同。必须遵循嵌入SQL语句中的游标定义选项。 1)、声明游标: 如:EXEC SQL DECLARE C1 CURSOR FOR SELECT id, name, dept, job, years, salary, comm FROM staff; 其中,C1是游标的名称。 2)、打开游标 如:EXEC SQL OPEN c1; 完整语法为:EXEC SQL OPEN 游标名 [USING 主变量名 | DESCRIPTOR 描述名]。关于动态OPEN游标的描述见第四节。 3)、取一行值 如:EXEC SQL FETCH c1 INTO :id, :name, :dept, :job, :years, :salary, :comm; 关于动态FETCH语句见第四小节。 4)、关闭游标 如:EXEC SQL CLOSE c1; 关闭游标的同时,会释放由游标添加的锁和放弃未处理的数据。在关闭游标前,该游标必须已经声明和打开。另外,程序终止时,系统会自动关闭所有打开的游标。 也可以使用UPDATE语句和DELETE语句来更新或删除由游标选择的当前行。使用DELETE语句删除当前游标所在的行数据的具体语法如下: DELETE [FROM] {table_name | view_name} WHERE CURRENT OF cursor_name 其中, ltable_name是表名,该表必须是DECLARE CURSOR中SELECT语句中的表。 lview_name是视图名,该视图必须是DECLARE CURSOR中SELECT语句中的视图。 lcursor_name是游标名。 请看下面这个例子,逐行显示firstname和lastname,询问用户是否删除该信息,如果回答“是”,那么删除当前行的数据。 EXEC SQL DECLARE c1 CURSOR FOR SELECT au_fname, au_lname FROM authors ; EXEC SQL OPEN c1; while (SQLCODE == 0) { EXEC SQL FETCH c1 INTO :fname, :lname; if (SQLCODE == 0) { printf("%12s %12s\n", fname, lname); printf("Delete? "); scanf("%c", &reply); if (reply == 'y') { EXEC SQL DELETE FROM authors WHERE CURRENT OF c1; printf("delete sqlcode= %d\n", SQLCODE(ca)); } } } EXEC SQL CLOSE c1; 2.3.5 SQLCA DBMS是通过SQLCA(SQL通信区)向应用程序报告运行错误信息。SQLCA是一个含有错误变量和状态指示符的数据结构。通过检查SQLCA,应用程序能够检查出嵌入式SQL语句是否成功,并根据成功与否决定是否继续往下执行。预编译器自动会在嵌入SQL语句中插入SQLCA数据结构。在程序中可以使用EXEC SQL INCLUDE SQLCA,目的是告诉SQL预编译程序在该程序中包含一个SQL通信区。也可以不写,系统会自动加上SQLCA结构。 下表是SQLCA结构中的变量和作用: 变量 数据类型 作用 sqlcaid char 包含“sqlca”的字符串 sqlcabc long SQLCA的长度 sqlcode long 包含最近一次语句执行的返回代码 sqlwarn[0] 到 sqlwarn[7] char 警告标志。如果是“W”,那么表示有警报信息。 sqlerrm.sqlerrmc[ ] char 错误信息。 sqlerrm.sqlerrml long 错误信息的长度。 sqlerrp char 检测错误或警告信息的过程。 sqlerrd[6] long 警告或错误的详细信息。[2]中存放影响行的个数。 下面仔细讲解几个重要的变量。 1)、SQLCODE SQLCA结构中最重要的部分是SQLCODE变量。在执行每条嵌入式SQL语句时,DBMS在SQLCA中设置变量SQLCODE值,以指明语句的完成状态: 1、0该语句成功执行,无任何错误或报警。 2、<0 出现了严重错误。 3、>0 出现了报警信息。 4、100没有数据存在。在FETCH语句中,表示到达结果集的末尾。在UPDATE、 DELETE、INSERT语句中,表示没有满足条件的数据。 例:显示错误信息。 printf("\nError occurred: code %d.\n%s", sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc); 在SYBASE SQL SERVER中,也可以单独定义SQLCODE。如: long SQLCODE; exec sql open cursor pub_id; while (SQLCODE == 0) { exec sql fetch pub_id into :pub_name; ….. 2)、SQLSTATE SQLSTATE变量也是SQLCA结构中的成员。它同SQLCODE一样,都是返回错误信息。SQLSTATE是在SQLCODE之后产生的。这是因为,在制定SQL2标准之前,各个数据库厂商都采用SQLCODE变量来报告嵌入式SQL语句中的错误状态。但是,各个厂商没有采用标准的错误描述信息和错误值来报告相同的错误状态。所以,标准化组织增加了SQLSTATE变量,规定了通过SQLSTATE变量报告错误状态和各个错误代码。因此,目前使用SQLCODE的程序仍然有效,但也可用标准的SQLSTATE错误代码编写新程序。值得注意的是,Open client emebeded SQL/C11.1.x并不完全支持SQLSTATE。 SQLSTATE是一个字符串参数。具体含义如下: 值 作用 00XXX 成功 01XXX 警告 02XXX 不存在数据 其他值 错误 2.3.6 WHENEVER 在每条嵌入式SQL语句之后立即编写一条检查SQLCODE/SQLSTATE值的程序,是一件很繁琐的事情。为了简化错误处理,可以使用WHENEVER语句。该语句是SQL预编译程序的指示语句,而不是可执行语句。它通知预编译程序在每条可执行嵌入式SQL语句之后自动生成错误处理程序,并指定了错误处理操作。 用户可以使用WHENEVER语句通知预编译程序去如何处理三种异常处理: lWHENEVER SQLERROR action:表示一旦sql语句执行时遇到错误信息,则执行action,action中包含了处理错误的代码(SQLCODE<0)。 lWHENEVER SQLWARNING action:表示一旦sql语句执行时遇到警告信息,则执行aciton,即action中包含了处理警报的代码(SQLCODE=1)。 lWHENEVER NOT FOUND action:表示一旦sql语句执行时没有找到相应的元组,则执行action,即action包含了处理没有查到内容的代码(SQLCODE=100)。 针对上述三种异常处理,用户可以指定预编译程序采取以下三种行为(action): lWHENEVER …GOTO:通知预编译程序产生一条转移语句。 lWHENEVER…CONTINUE:通知预编译程序让程序的控制流转入到下一个主语言语句。 lWHENEVER…CALL:通知预编译程序调用函数。 其完整语法如下: WHENEVER {SQLWARNING | SQLERROR | NOT FOUND} {CONTINUE | GOTO stmt_label | CALL function()} 例:WHENEVER的作用 EXEC SQL WHENEVER sqlerror GOTO errormessage1; EXEC SQL DELETE FROM homesales WHERE equity < 10000; EXEC SQL DELETE FROM customerlist WHERE salary < 40000; EXEC SQL WHENEVER sqlerror CONTINUE; EXEC SQL UPDATE homesales SET equity = equity - loanvalue; EXEC SQL WHENEVER sqlerror GOTO errormessage2; EXEC SQL INSERT INTO homesales (seller_name, sale_price) real_estate('Jane Doe', 180000.00); . . . errormessage1: printf("SQL DELETE error: %ld\n, sqlcode); exit(); errormessage2: printf("SQL INSERT error: %ld\n, sqlcode); exit(); WHENEVER语句是预编译程序的指示语句。在上面这个例子中,由于第一个WHENEVER语句的作用,前面两个DELETE语句中任一语句内的一个错误会在errormessage1中形成一个转移指令。由于一个WHENEVER语句替代前面WHENEVER语句,所以,嵌入式UPDATE语句中的一个错误会直接转入下一个程序语句中。嵌入式INSERT语句中的一个错误会在errormessage2中产生一条转移指定。 从上面例子看出,WHENEVER/CONTINUE语句的主要作用是取消先前的WHENEVER语句的作用。WHENEVER语句使得对嵌入式SQL错误的处理更加简便。应该在应用程序中普遍使用,而不是直接检查SQLCODE的值。 2.3.7 批处理 嵌入SQL也支持批处理。如: exec sql insert into TABLE1 values (:val1) insert into TABLE2 values (:val2) insert into TABLE3 values (:val3); SYBASE SQL SERVER将在EXEC SQL和“;”之间的所有T-SQL语句作为一个批来处理。在上例中,会将这3个语句作为一组来处理。 2.3.8 事务 SYBASE SQL SERVER预编译器能够处理两种事务模式:ANSI/ISO事务模式和T-SQL模式。在T-SQL模式中,除非有begin transaction外,每个语句都会做提交。可以在编译时设置事务模式。ANSI/ISO模式是系统的缺省模式。嵌入SQL的事务语法和T-SQL的事务语法是相同的。 2.3.8.1 T-SQL事务模式 1)、开始事务 exec sql [at connect_name] begin transaction [ transaction_name]; 2)、保存事务回滚点 exec sql [at connect_name] save transaction [ savepoint_name]; 3)、提交事务 exec sql [at connect_name] commit transaction [ transaction_name]; 4)、回滚事务 exec sql [at connect_name] rollback transaction [ savepoint_name | transaction_name]; 2.3.8.2 ANSI/ISO事务模式 该模式没有begin transaction和save transaction。在应用程序中,只要遇到以下语句,就表示事务开始:delete、insert、select、update、open和exec。当遇到commit work或rollback work,就表示事务结束。也就是说,commit和rollback表示当前事务结束,下一个事务开始。 2.4动态SQL语句 前一节中讲述的嵌入SQL语言都是静态SQL语言,即在编译时已经确定了引用的表和列。主变量不改变表和列信息。在上几节中,我们使用主变量改变查询参数,但是不能用主变量代替表名或列名。否则,系统报错。动态SQL语句就是来解决这个问题。 动态SQL语句的目的是,不是在编译时确定SQL的表和列,而是让程序在运行时提供,并将SQL语句文本传给DBMS执行。静态SQL语句在编译时已经生成执行计划。而动态SQL语句,只有在执行时才产生执行计划。动态SQL语句首先执行PREPARE语句要求DBMS分析、确认和优化语句,并为其生成执行计划。DBMS还设置SQLCODE以表明语句中发现的错误。当程序执行完“PREPARE”语句后,就可以用EXECUTE语句执行执行计划,并设置SQLCODE,以表明完成状态。 使用动态SQL,共分成四种方法: 方法 支持的SQL语句 实现方法 1 该语句内不包含宿主变量,该语句不是查询语句 execute immediate 2 该语句内包含输入宿主变量 ,该语句不是查询语句 prepare和execute 3 包含已知数目的输入宿主变量或列的查询 prepare和fetch 4 包含未知数目的输入宿主变量或列的查询 prepare和fetch,用描述符 按照功能和处理上的划分,动态SQL应该分成两类来解释:动态修改和动态查询。方法1和方法2完成动态修改(参见2.4.1)。方法3和方法4完成了动态查询(参见2.4.2和2.4.3)。 2.4 .1 动态修改 方法1和方法2完成动态修改。对于方法1,表示要执行一个完整的T-SQL语句,该语句没有宿主变量,不是一个查询语句。因为没有宿主变量来带入不同的参数,所以不能通过方法1来重复执行修改语句。具体语法为: exec sql [at connection_name] execute immediate {: host_variable | string}; 其中,host_variable和string是存放完整T-SQL语句。 例:提示用户输入被更新书的条件,然后组合成为一个完整的SQL语句,并执行更新。 exec sql begin declare section; CS_CHAR sqlstring[200]; exec sql end declare section; char cond[150]; exec sql whenever sqlerror call err_p(); exec sql whenever sqlwarning call warn_p(); strcpy(sqlstring, "update titles set price=price*1.10 where "); printf("Enter search condition:"); scanf("%s", cond); strcat(sqlstring, cond); exec sql execute immediate :sqlstring; exec sql commit work; 对于方法2,可以执行一个包含输入宿主变量的动态修改语句。该方法要使用PREPARE语句和EXECUTE语句。PREPARE语句是动态SQL语句独有的语句。其语法为: PREPARE 语句名 FROM 宿主变量|字符串 该语句接收含有SQL语句串的宿主变量,并把该语句送到DBMS。DBMS编译语句并生成执行计划。在语句串中包含一个“?”表明参数,当执行语句时,DBMS需要参数来替代这些“?”。PREPRARE执行的结果是,DBMS用语句名标志准备后的语句。SQL SERVER编译后的语句以临时存储过程的形式存放在缓冲区中。语句名类似于游标名,是一个SQL标识符。在执行SQL语句时,EXECUTE语句后面是这个语句名。请看下面这个例子: EXEC SQL BEGIN DECLARE SECTION; char prep[] = "INSERT INTO mf_table VALUES(?,?,?)"; char name[30]; char car[30]; double num; EXEC SQL END DECLARE SECTION; EXEC SQL PREPARE prep_stat FROM :prep; while (SQLCODE == 0) { strcpy(name, "Elaine"); strcpy(car, "Lamborghini"); num = 4.9; EXEC SQL EXECUTE prep_stat USING :name, :car, :num; } 在这个例子中,prep_stat是语句名,prep宿主变量的值是一个INSERT语句,包含了三个参数(3个“?”)。PREPARE的作用是,DBMS编译这个语句并生成执行计划,并把语句名标志这个准备后的语句。值得注意的是,PREPARE中的语句名的作用范围为整个程序,所以不允许在同一个程序中使用相同的语句名在多个PREPARE语句中。 EXECUTE语句是动态SQL独有的语句。它的语法如下: EXECUTE 语句名 USING 宿主变量 | DESCRIPTOR 描述符名 请看上面这个例子中的“EXEC SQL EXECUTE prep_stat USING :name, :car, :num;”语句,它的作用是,请求DBMS执行PREPARE语句准备好的语句。当要执行的动态语句中包含一个或多个参数标志时,在EXECUTE语句必须为每一个参数提供值,如::name、:car和:num。这样的话,EXECUTE语句用宿主变量值逐一代替准备语句中的参数标志(“?”),从而,为动态执行语句提供了输入值。 使用主变量提供值,USING子句中的主变量数必须同动态语句中的参数标志数一致,而且每一个主变量的数据类型必须同相应参数所需的数据类型相一致。各主变量也可以有一个伴随主变量的指示符变量。当处理EXECUTE语句时,如果指示符变量包含一个负值,就把NULL值赋予相应的参数标志。除了使用主变量为参数提供值,也可以通过SQLDA提供值(见节2.4.4)。 2.4.2 动态游标 使用动态游标可以完成方法3。 游标分为静态游标和动态游标两类。对于静态游标,在定义游标时就已经确定了完整的SELECT语句。在SELECT语句中可以包含主变量来接收输入值。当执行游标的OPEN语句时,主变量的值被放入SELECT语句。在OPEN语句中,不用指定主变量,因为在DECLARE CURSOR语句中已经放置了主变量。请看下面静态游标的例子: EXEC SQL BEGIN DECLARE SECTION; char szLastName[] = "White"; char szFirstName[30]; EXEC SQL END DECLARE SECTION; EXEC SQL DECLARE author_cursor CURSOR FOR SELECT au_fname FROM authors WHERE au_lname = :szLastName; EXEC SQL OPEN author_cursor; EXEC SQL FETCH author_cursor INTO :szFirstName; 动态游标和静态游标不同。以下是动态游标使用的句法(请参照本小节后面的例子来理解动态游标)。 1)、声明游标: 对于动态游标,在DECLARE CURSOR语句中不包含SELECT语句。而是,定义了在PREPARE中的语句名,PREPARE语句规定与查询相关的语句名称。具体语法为: exec sql [at connection_name] declare cursor_name cursor for statement_name; 如:EXEC SQL DECLARE author_cursor CURSOR FOR select_statement; 值得注意的是,声明动态游标是一个可执行语句,应该在PREPARE语句后执行。 2)、打开游标 完整语法为:OPEN 游标名 [USING 主变量名 | DESCRIPTOR 描述名] 在动态游标中,OPEN语句的作用是使DBMS定位相关的游标在第一行查询结果前。当OPEN语句成功执行完毕后,游标处于打开状态,并为FETCH语句做准备。OPEN语句执行一条由PREPARE语句预编译的语句。如果动态查询正文中包含有一个或多个参数标志时,OPEN语句必须为这些参数提供参数值。USING子句的作用就是规定参数值。可以使用主变量提供参数值,也可以通过描述名(即SQLDA)提供参数值。如:EXEC SQL OPEN author_cursor USING :szLastName;。 3)、取一行值 FETCH语法为:FETCH 游标名 INTO USING DESCRIPTOR 描述符名。 动态FETCH语句的作用是,把游标移到下一行,并把这一行的各列值送到SQLDA中。注意的是,静态FETCH语句的作用是用主变量表接收查询到的列值。在方法3中,使用的是静态FETCH语句获得值。动态FETCH语句只在方法4中使用。 4)、关闭游标 如:EXEC SQL CLOSE c1; 关闭游标的同时,会释放由游标添加的锁和放弃未处理的数据。在关闭游标前,该游标必须已经声明和打开。另外,程序终止时,系统会自动关闭所有打开的游标。 总之,在动态游标的DECLARE CURSOR语句中不包含SELECT语句。而是,定义了在PREPARE中的语句名,用PREPARE语句规定与查询相关的语句名称。当PREPARE语句中的语句包含了参数,那么在OPEN语句中必须指定提供参数值的主变量或SQLDA。动态DECLARE CURSOR语句是一个可执行语句。该子句必须在OPEN、FETCH、CLOSE语句之前使用。请看下面这个例子,描述了完成方法3的五个步骤:PREPARE、DECLARE、OPEN、FETCH和CLOSE。 …… EXEC SQL BEGIN DECLARE SECTION; char szCommand[] = "SELECT au_fname FROM authors WHERE au_lname = ?"; char szLastName[] = "White"; char szFirstName[30]; EXEC SQL END DECLARE SECTION; EXEC SQL PREPARE select_statement FROM :szCommand; EXEC SQL DECLARE author_cursor CURSOR FOR select_statement; EXEC SQL OPEN author_cursor USING :szLastName; EXEC SQL FETCH author_cursor INTO :szFirstName; EXEC SQL CLOSE author_cursor; ……… 下面是一个实现方法3的实际例子。提示用户输入排序的条件,并把符合条件的书信息显示出来。 …… exec sql begin declare section; CS_CHAR sqlstring[200]; CS_FLOAT bookprice,condprice; CS_CHAR booktitle[200]; exec sql end declare section; char orderby[150]; exec sql whenever sqlerror call err_p(); exec sql whenever sqlwarning call warn_p(); strcpy(sqlstring, "select title,price from titles\ where price>? order by "); printf("Enter the order by clause:"); scanf("%s", orderby); strcat(sqlstring, orderby); exec sql prepare select_state from :sqlstring; exec sql declare select_cur cursor for select_state; condprice = 10; /* 可以提示用户输入这个值*/ exec sql open select_cur using :condprice; exec sql whenever not found goto end; for (;;) { exec sql fetch select_cur into :booktitle,:bookprice; printf("%20s %bookprice=%6.2f\n", booktitle, bookprice); } end: exec sql close select_cur; exec sql commit work; ……….. 2.4.3 SQLDA 要实现方法4,则需要使用SQLDA(也可以使用SQL Descriptors,请读者参阅帮助信息)。可以通过SQLDA为嵌入SQL语句提供不确定的输入数据和从嵌入SQ语句中输出不确定数据。理解SQLDA的结构是理解动态SQL的关键。 我们知道,动态SQL语句在编译时可能不知道有多少列信息。在嵌入SQL语句中,这些不确定的数据是通过SQLDA完成的。SQLDA的结构非常灵活,在该结构的固定部分,指明了多少列等信息(如下图中的sqld=2,表示为两列信息),在该结构的后面,有一个可变长的结构(sd_column结构),说明每列的信息。 SQLDA结构 Sd_Sqld=2 Sd_column ……
Sd_datafmt Sd_Sqllen Sd_sqldata ….. Sd_datafmt Sd_Sqllen Sd_Sqldata …..
图6-2 SQLDA结构示例 具体SQLDA的结构在sqlda.h中定义,是: typedef struct _sqlda { CS_SMALLINT sd_sqln; CS_SMALLINT sd_sqld; struct _sd_column { CS_DATAFMT sd_datafmt; CS_VOID *sd_sqldata; CS_SMALLINT sd_sqlind; CS_INT sd_sqllen; CS_VOID*sd_sqlmore; } sd_column[1]; } syb_sqlda; typedef syb_sqlda SQLDA; 从上面这个定义看出,SQLDA是一种由两个不同部分组成的可变长数据结构。从位于SQLDA开端的sd_sqln到sd_sqld为固定部分,用于标志该SQLDA,并规定这一特定的SQLDA的长度。而后是一个或多个sd_column结构 ,用于标志列数据或参数。当用SQLDA把参数送到执行语句时,每一个参数都是一个sd_column结构;当用SQLDA返回输出列信息时,每一列都是一个sd_column 结构。具体每个元素的含义为: lSd_Sqln。分配的sd_column结构的个数。等价于可以允许的最大输入参数的个数或输出列的个数。 lSd_Sqld。目前使用的sd_column结构的个数。 lSd_column[].sd_datafmt。标志同列相关的CS_DATAFMT结构。 lSd_column[].sd_Sqldata。指向数据的地址。注意,仅仅是一个地址。 lSd_column[].sd_sqllen。sd_sqldata指向的数据的长度。 lSd_column[].sd_Sqlind。代表是否为NULL。如果该列不允许为NULL,则该字段不赋值;如果该列允许为NULL,则:该字段若为0,表示数据值不为NULL,若为-1,表示数据值为NULL。 lSd_column[].sd_sqlmore。保留为将来使用。 下面我们来看一个具体的例子。这个例子是通过output_descriptor查询数据库中的数据,是通过input_descriptor传递参数。这个例子的作用是,模拟一个动态查询,并显示查询结果。动态查询的执行过程如下: 1)、如同构造动态UPDATE语句或DELETE语句的方法一样,程序在缓冲器中构造一个有效的SELECT语句。 2)、程序用PREPARE语句把动态查询语句送到DBMS,DBMS准备、确认和优化语句,并生成一个应用计划。 3)、动态DECLARE CURSOR语句说明查询游标,动态DECLARE CURSOR语句规定与动态SELECT语句有关的语句名称。如:例子中的statement。 4)、程序用DESCRIBE语句请求DBMS提供SQLDA中描述信息,即告诉程序有多少列查询结果、各列名称、数据类型和长度。DESCRIBE语句只用于动态查询。具体见下一节。 5)、为SQLDA申请存放一列查询结果的存储块(即:sqldata指向的数据区),也为SQLDA的列的指示符变量申请空间。程序把数据区地址和指示符变量地址送入SQLDA,以告诉DBMS向何处回送查询结果。 6)、动态格式的OPEN语句。即打开存放查询到的数据集(动态SELECT语句产生的数据)的第一行。 7)、动态格式的FETCH语句把游标当前行的结果送到SQLDA。(动态FETCH语句和静态FETCH语句的不同是:静态FETCH语句规定了用主变量接收数据;而动态FETCH语句是用SQLDA接收数据。)并把游标指向下一行结果集。 8)、CLOSE语句关闭游标。 具体程序如下: exec sql include sqlca; exec sql include sqlda; ... /*input_ descriptor是通过SQLDA传递参数,output_descriptor是通过SQLDA返回列数据*/ SQLDA *input_descriptor, *output_descriptor; CS_SMALLINT small; CS_CHAR character[20]; /*申请空间*/ input_descriptor = (SQLDA *)malloc(SYB_SQLDA_SIZE(3)); /*设置参数的最大个数*/ input_descriptor->sqlda_sqln = 3; /*申请空间*/ output_descriptor = (SQLDA *)malloc(SYB_SQLDA_SIZE(3)); /*设置列数的最大值*/ output_descriptor->sqlda_sqln = 3; *p_retcode = CS_SUCCEED; /*连接数据库服务器*/ exec sql connect "sa" identified by password; /* 创建一张example表,并插入一些例子数据,用于演示SQLDA的使用*/ exec sql drop table example; exec sql create table example (fruit char(30), number int); exec sql insert example values ('tangerine', 1); exec sql insert example values ('pomegranate', 2); exec sql insert example values ('banana', 3); /* 准备和描述查询语句*/ exec sql prepare statement from "select fruit from example where number = ?"; /*describe语句的作用是,将查询所需要的参数信息存放在input_descriptor中*/ exec sql describe input statement using descriptor input_descriptor; /*设置SQLDA中指向参数数据的地址信息(sqldata)和数据长度(sqlda_sqllen)*/ input_descriptor->sqlda_column[0].sqlda_datafmt.datatype =CS_SMALLINT_TYPE; input_descriptor->sqlda_column[0].sqlda_sqldata = &small; input_descriptor->sqlda_column[0].sqlda_sqllen = sizeof(small); small = 2; /*将查询语句的列信息存放在output_descriptor中*/ exec sql describe output statement using descriptor output_descriptor; if (output_descriptor->sqlda_sqld != 1 || output_descriptor->sqlda_column[0].sqlda_datafmt.datatype != CS_CHAR_TYPE) FAIL; else printf("first describe output \n"); /*设置存放列数据的地址信息*/ output_descriptor->sqlda_column[0].sqlda_sqldata = character; output_descriptor->sqlda_column[0].sqlda_datafmt.maxlength = 20; /*通过input_descriptor将输入参数带入查询语句,并将结果通过output_descriptor带出*/ exec sql execute statement into descriptor output_descriptor \ using descriptor input_descriptor; /*打印结果---单行结果*/ printf("expected pomegranate, got %s\n",character); /*释放申请的内存空间*/ exec sql deallocate prepare statement; /* 多行结果示例。对多行查询语句做准备和描述操作*/ exec sql prepare statement from \ "select number from example where fruit = ?"; /*为多行结果声明游标*/ exec sql declare c cursor for statement; exec sql describe input statement using descriptor input_descriptor; /*设置查询的参数地址信息*/ input_descriptor->sqlda_column->sqlda_sqldata = character; input_descriptor->sqlda_column->sqlda_datafmt.maxlength =CS_NULLTERM; /*设置参数值为banana,也可以提示用户输入这些信息*/ strcpy(character, "banana"); input_descriptor->sqlda_column->sqlda_sqllen = CS_NULLTERM; /*打开游标*/ exec sql open c using descriptor input_descriptor; /*设置输出列的信息*/ exec sql describe output statement using descriptor output_descriptor; /*设置存放数据的地址信息*/ output_descriptor->sqlda_column->sqlda_sqldata = character; output_descriptor->sqlda_column->sqlda_datafmt.datatype =CS_CHAR_TYPE; output_descriptor->sqlda_column->sqlda_datafmt.maxlength = 20; output_descriptor->sqlda_column->sqlda_sqllen = 20; output_descriptor->sqlda_column->sqlda_datafmt.format = (CS_FMT_NULLTERM | CS_FMT_PADBLANK); exec sql fetch c into descriptor output_descriptor; /*打印列的数据*/ printf("expected pomegranate, got %s\n", character); exec sql commit work; ………. 上面这个例子是典型的动态查询程序。该程序中演示了PREPARE语句和DESCRIBE语句的处理方式,以及为程序中检索到的数据分配空间。要注意程序中如何设置sqlda_column结构中的的各个变量。这个程序也演示了OPEN、FETCH和CLOSE语句在动态查询中的应用。值得注意的是,FETCH语句只使用了SQLDA,不使用主变量。由于程序中预先申请了sqlda_column结构中的SQLDATA空间,所以DBMS知道将查询到的数据保存在何处。该程序还考虑了查询数据为NULL的处理。 值得注意的是,SQDA结构不是SQL标准。每个数据库厂商的实现方式有可能不同。 2.4.4 DESCRIBE语句 该语句只有动态SQL才有。该语句是在PREPARE语句之后,在OPEN语句之前使用。该语句的作用是,设置SQLDA中的描述信息,如:列名、数据类型和长度等。DESCRIBE语句的语法为: DESCRIBE 语句名 INTO 描述符名 如:exec sql describe output statement using descriptor output_descriptor;。 在执行DESCRIBE前,用户必须给出SQLDA中的SQLN的值(表示最多有多少列),该值也说明了SQLDA中最多有多少个sqlda_column结构。然后,执行DESCRIBE语句,该语句填充每一个sqlda_column结构。每个sqlda_column结构中的相应列为: lSd_datafmt结构:列名等信息。 lSd_sqllen列:给出列的长度。 注意,sd_sqldata列不填充。由程序在FETCH语句之前,给出数据缓冲器地址和指示符地址。 2.5 两个例子程序 2.5.1 TELECOM程序 该程序是模拟电信费用查询。 #include <stdio.h> #include <stdlib.h> #include <string.h> #if defined ( DB2 ) #define SQLNOTFOUND 100 #include <sql.h> #elif defined ( ORA7 ) #define SQLNOTFOUND 1403 #endif
#if defined (SYBASE) #define SQLNOTFOUND100 #endif EXEC SQL INCLUDE sqlca; EXEC SQL BEGIN DECLARE SECTION; char user[30]; char passwd[30]; char Usr_name[61]; char Dev_no[9]; long Call_flg; char Called_arno[11]; char Called_no[15]; char Call_dat[21]; double Call_dur; double Call_rate; double Call_fee; double Add_fee; char as_dev_no[9]; EXEC SQL END DECLARE SECTION; void main(){ char statusbuf[1024], s[30]; /*连接到SQL SERVER服务器*/ printf("\nplease enter your userid "); gets(user); printf("\npassword "); gets(passwd); exec sql connect :user identified by :passwd; exec sql use pubs2; /*输入想要查询的电话号码*/ printf("\nPlease enter the telephone number:"); gets(as_dev_no ); /*声明游标*/ EXEC SQL DECLARE c1 CURSOR FOR SELECT bas_infot.Usr_name, auto10a_list.Dev_no, auto10a_list.Call_flg, auto10a_list.Called_arno, auto10a_list.Called_no , auto10a_list.Call_dat, auto10a_list.Call_dur, auto10a_list.Call_rate, auto10a_list.Call_fee, FROM auto10a_list, bas_infot WHERE ( auto10a_list.Dev_no = bas_infot.Dev_no ) AND auto10a_list.Dev_no = :as_dev_no; /*打开游标,指向查询相关电话信息的结果集*/ EXEC SQL OPEN c1; /* :rk.2:erk. */ do{ /*取出一行数据到各个变量*/ EXEC SQL FETCH c1 INTO :Usr_name, :Dev_no, :Call_flg, :Called_arno, :Called_no, :Call_dat, :Call_dur, :Call_rate, :Call_fee, :Add_fee;
if( (sqlca.sqlcode == SQLNOTFOUND) || (sqlca.sqlcode <0) ) break; /*显示数据*/ printf("%s,%s,%d,%s,%s,%s,%7.0f,%8.3f,%7.2f,%6.2f\n" , Usr_name, Dev_no, Call_flg, Called_arno, Called_no, Call_dat, Call_dur, Call_rate, Call_fee, Add_fee ); }while(1); EXEC SQL CLOSE c1; EXEC SQL DEALLOCATE CURSOR c1; Exec sql disconnect all; return (0); } 2.5.2 ADHOC程序 该程序的功能是:用户输入任意SQL语句,并执行和打印结果。 #include <stdio.h> #include <stdlib.h> #include <string.h> /* Defines for BINDING */ /*初试化SQLDA*/ int init_da (SQLDA **DAPointer, int DAsqln); /*为存放列数据的sd_column结构申请空间*/ int alloc_host_vars (SQLDA *sqldaPointer); /*释放SQLDA所申请的空间*/ void free_da (SQLDA *sqldaPointer); /*获取列名信息*/ char * readColName (SQLDA *sqldaPointer, short sd_columnIndex, char * buffer); /*获取列数据*/ char * readCol (SQLDA *sqldaPointer, short sd_columnIndex, char * buffer); #ifdef __cplusplus } #endif /*定义最大列数*/ #define MAX_COLUMNS255
#define MAX_CHAR_FOR_DOUBLE20 #define MAX_CHAR_FOR_LONG15 #define MAX_CHAR_FOR_DATETIME30 #define MAX_CHAR_FOR_DEFAULT100 EXEC SQL INCLUDE SQLCA ; EXEC SQL INCLUDE SQLDA ; #define SQLSTATE sqlca.sqlstate #define SQLCODE sqlca.sqlcode /*处理SQL语句*/ int process_statement( char * ) ; int main() { int rc ; char st[1024]; char tmpstr[1024]; /*获得SQL语句*/ printf("Please enter the any sql statement:"); gets( st); /* 处理该语句 */ rc = process_statement( st ) ; /*打印处理结果*/ printf( "%d", rc); printf("the sqlcode is %d",SQLCODE); } /****************************************************************************** * FUNCTION : process_statement * 处理SQL语句 *****************************************************************************/ int process_statement ( char * sqlInput ) { int counter = 0 ; SQLDA * sqldaPointer ; short sqlda_d ; /* Total columns */ short idx; char buffer[4096]; char varname[1024]; char colnamelist[4096]; EXEC SQL BEGIN DECLARE SECTION ; char st[1024] ; EXEC SQL END DECLARE SECTION ; strcpy( st, sqlInput ) ; /* 为SQLDA结构申请空间" */ if (init_da( &sqldaPointer, MAX_COLUMNS ) == -1) { return -1; } /*准备SQL语句*/ EXEC SQL PREPARE statement1 from :st ; if (SQLCODE < 0) { free_da(sqldaPointer); return SQLCODE; } /*获取查询列的信息到SQLDA结构*/ EXEC SQL DESCRIBE statement1 USING DESCRIPTOR sqldaPointer ; /* 如果SQLCODE为0,则表示为SELECT语句 */ if ( SQLCODE != 0 ) { free_da(sqldaPointer); return SQLCODE; } /* end if */ sqlda_d = sqldaPointer->sd_sqld ; if ( sqlda_d > 0 ) { /* 为存放列数据的sd_column结构申请空间 */ if (alloc_host_vars( sqldaPointer ) == -1) {free_da(sqldaPointer); return -1; } /*声明游标*/ EXEC SQL DECLARE pcurs CURSOR FOR statement1 ; /打开游标*/ EXEC SQL OPEN pcurs ; if (SQLCODE < 0) return SQLCODE; /*取一行数据到SQLDA结构*/ EXEC SQL FETCH pcurs INTO DESCRIPTOR sqldaPointer; if (SQLCODE < 0) { EXEC SQL CLOSE pcurs ; return SQLCODE; } /*显示列标题 */ colnamelist[0] = 0; for ( idx=0; idx< sqlda_d; idx++) { strcat(colnamelist, readColName(sqldaPointer, idx, buffer)); if (idx < sqlda_d -1) strcat(colnamelist, ","); } /* 显示行数据*/ while ( SQLCODE == 0 ) { counter++ ; for ( idx=0; idx< sqlda_d; idx++) printf("%s",readCol(sqldaPointer, idx, buffer)); EXEC SQL FETCH pcurs INTO DESCRIPTOR sqldaPointer ; } /* endwhile */ /*关闭游标*/ EXEC SQL CLOSE pcurs ; EXEC SQL DEALLOCATE CURSOR pcurs; /* 释放为SQLDA申请的空间 */ free_da( sqldaPointer ) ; } else { /* 不是SELECT语句*/ EXEC SQL EXECUTE statement1 ; free_da( sqldaPointer ) ; if (SQLCODE < 0) return SQLCODE; } /* end if */ return( 0 ) ; } /* end of program : ADHOC.CP */ /******************************************************************************* PROCEDURE : init_da *为SQLDA分配空间。使用SQLDASIZE 获得SQLDA的大小。如果返回-1,则表示分配 *空间不成功。 ******************************************************************************/ int init_da (SQLDA **DAPointer, int DAsqln) { int idx; *DAPointer = (SQLDA *)malloc(SYB_SQLDA_SIZE(DAsqln)); if (*DAPointer == NULL) return (-1); memset (*DAPointer, '\0', SYB_SQLDA_SIZE(DAsqln)); (*DAPointer)->sd_sqln = DAsqln; (*DAPointer)->sd_sqld = 0; return 0; } /******************************************************************************* FUNCTION : alloc_host_vars *为存放列数据的sd_column结构申请空间。如果返回-1,则表示不能获得足够内存。 ******************************************************************************/ int alloc_host_vars (SQLDA *sqldaPointer) { short idx; for (idx = 0; idx < sqldaPointer->sd_sqld; idx++) { switch (sqldaPointer->sd_column[idx].sd_datafmt.datatype ) { case CS_CHAR_TYPE: case CS_VARCHAR_TYPE: sqldaPointer->sd_column[idx].sd_datafmt.datatype = CS_CHAR_TYPE; sqldaPointer->sd_column[idx].sd_sqldata = (char *) malloc (sqldaPointer->sd_column[idx].sd_sqllen + 1 ); sqldaPointer->sd_column[idx].sd_sqllen ++; sqldaPointer->sd_column[idx].sd_datafmt.format = CS_FMT_NULLTERM; break; case CS_TINYINT_TYPE: case CS_SMALLINT_TYPE: case CS_INT_TYPE: case CS_VOID_TYPE: case CS_USHORT_TYPE: sqldaPointer->sd_column[idx].sd_datafmt.datatype = CS_CHAR_TYPE; sqldaPointer->sd_column[idx].sd_sqldata = (char *) malloc (MAX_CHAR_FOR_LONG); sqldaPointer->sd_column[idx].sd_sqllen = MAX_CHAR_FOR_LONG; sqldaPointer->sd_column[idx].sd_datafmt.format = CS_FMT_NULLTERM; break; case CS_REAL_TYPE: case CS_FLOAT_TYPE: case CS_BIT_TYPE: case CS_MONEY_TYPE: case CS_MONEY4_TYPE: sqldaPointer->sd_column[idx].sd_datafmt.datatype = CS_CHAR_TYPE; sqldaPointer->sd_column[idx].sd_sqldata = (char *) malloc (MAX_CHAR_FOR_DOUBLE); sqldaPointer->sd_column[idx].sd_sqllen = MAX_CHAR_FOR_DOUBLE; sqldaPointer->sd_column[idx].sd_datafmt.format = CS_FMT_NULLTERM; break; case CS_DATETIME_TYPE: case CS_DATETIME4_TYPE: sqldaPointer->sd_column[idx].sd_datafmt.datatype = CS_CHAR_TYPE; sqldaPointer->sd_column[idx].sd_sqldata = (char *) malloc (MAX_CHAR_FOR_DATETIME); sqldaPointer->sd_column[idx].sd_sqllen = MAX_CHAR_FOR_DATETIME; sqldaPointer->sd_column[idx].sd_datafmt.format = CS_FMT_NULLTERM; break; case CS_NUMERIC_TYPE: case CS_DECIMAL_TYPE: sqldaPointer->sd_column[idx].sd_datafmt.datatype = CS_CHAR_TYPE; sqldaPointer->sd_column[idx].sd_sqldata = (char *) malloc (sqldaPointer->sd_column[idx].sd_datafmt.precision + 3 ); sqldaPointer->sd_column[idx].sd_sqllen = sqldaPointer->sd_column[idx].sd_datafmt.precision + 3; sqldaPointer->sd_column[idx].sd_datafmt.format = CS_FMT_NULLTERM; break; default: sqldaPointer->sd_column[idx].sd_datafmt.datatype = CS_CHAR_TYPE; sqldaPointer->sd_column[idx].sd_sqldata = (char *) malloc (MAX_CHAR_FOR_DEFAULT); sqldaPointer->sd_column[idx].sd_sqllen = MAX_CHAR_FOR_DEFAULT; sqldaPointer->sd_column[idx].sd_datafmt.format = CS_FMT_NULLTERM; break; } /* endswitch */ if (sqldaPointer->sd_column[idx].sd_sqldata == NULL) { return (-1); } } /* endfor */ return 0; } /******************************************************************************* FUNCTION : free_da * 释放SQLDA申请的空间。 ******************************************************************************/ void free_da (SQLDA *sqldaPointer) { short idx; for (idx = 0; idx < sqldaPointer->sd_sqld; idx++) { free (sqldaPointer->sd_column[idx].sd_sqldata); } /* endfor */ free (sqldaPointer); } /******************************************************************************* PROCEDURE : readColName * 返回列名 ******************************************************************************/ char * readColName (SQLDA *sqldaPointer, short sd_columnIndex, char * buffer) { strcpy(buffer, sqldaPointer->sd_column[sd_columnIndex].sd_datafmt.name); return buffer; } /******************************************************************************* PROCEDURE : readCol * 返回列数据。 ******************************************************************************/ char * readCol (SQLDA *sqldaPointer, short sd_columnIndex, char * buffer){ short numBytes; short idx, ind ; /* Array idx variables */ /* Variables for decoding packed decimal data */ char tmpstr[1024]; short collen; char *dataptr; /* 检查是否为NULL */ if ( sqldaPointer->sd_column[sd_columnIndex].sd_sqlind ) { buffer[0] = 0; return buffer; } /*返回列数据到buffer变量*/ strcpy( buffer, (char *) sqldaPointer->sd_column[ sd_columnIndex ].sd_sqldata); return buffer; } /* COMMENT OUT OFF */ 第三节 IBM DB2嵌入SQL语言 DB2支持SQL嵌入到C/C++、JAVA、COBOL、FORTRAN和REXX等语言。本节以SQL嵌入C/C++为例子,讲解静态的嵌入SQL编程和动态的嵌入SQL编程。 静态SQL嵌入C语言编程是指,应用程序在书写时,每个SQL语句的大部分都已确定下来(如:查询的表、列和语句的格式等),唯一不确定的是查询语句中某些特定变量的值,这些值可以在执行时由变量传进去,但是,值的类型要事先确定。 3.1 一个简单示例 首先,我们来看一个嵌入静态SQL语句的C程序。 例1、连接到SAMPLE数据库,查询LASTNAME为JOHNSON的FIRSTNAME信息。 #include <stdio.h> #include <stdlib.h> #include <string.h> #include "util.h" #include <sqlca.h> EXEC SQL INCLUDE SQLCA; (1) #define CHECKERR(CE_STR) if (check_error (CE_STR, &sqlca) != 0) return 1; int check_error (char eString[], struct sqlca *caPointer) { char eBuffer[1024]; char sBuffer[1024]; short rc, Erc; if (caPointer->sqlcode != 0) { printf ("--- error report ---\n"); printf ("ERROR occured : %s.\nSQLCODE : %ld\n", eString, caPointer->sqlcode); } return 0; } int main(int argc, char *argv[]) { EXEC SQL BEGIN DECLARE SECTION; (2) char firstname[13]; char userid[9]; char passwd[19]; EXEC SQL END DECLARE SECTION; 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; (3) CHECKERR ("CONNECT TO SAMPLE"); } else { printf ("\nUSAGE: static [userid passwd]\n\n"); return 1; } /* endif */ EXEC SQL SELECT FIRSTNME INTO :firstname FROM employee WHERE LASTNAME = 'JOHNSON';(4) CHECKERR ("SELECT statement"); (5) printf( "First name = %s\n", firstname ); EXEC SQL CONNECT RESET; (6) CHECKERR ("CONNECT RESET"); return 0; } /* end of program : STATIC.SQC */ 上面是一个简单的静态嵌入SQL语句的应用程序。它包括了静态嵌入SQL的主要部分。 (1)中的include SQLCA语句定义并描述了SQLCA的结构。SQLCA用于应用程序和数据库之间的通讯,其中的SQLCODE返回SQL语句执行后的结果状态。 (2)在BEGIN DECLARE SECTION和END DECLARE SECTION之间定义了主变量。主变量可被SQL语句引用,也可以被C语言语句引用。它用于将程序中的数据通过SQL语句传给数据库管理器,或从数据库管理器接收查询的结果。在SQL语句中,主变量前均有“:”标志以示区别。 (3)在每次访问数据库之前必须做CONNECT操作,以连接到某一个数据库上。这时,应该保证数据库实例已经启动。 (4)是一条选择语句。它将表employee中的LASTNAME为“JOHNSON”的行数据的FIRSTNAME查出,并将它放在firstname变量中。该语句返回一个结果。可以通过游标返回多个结果。 (5)在该程序中通过调用宏CHECKERR(即调用函数check_error)来返回SQL语句执行的结果。Check_error函数在下面讲解。 (6)最后断开数据库的连接。 从上例看出,每条嵌入式SQL语句都用EXEC SQL开始,表明它是一条SQL语句。这也是告诉预编译器在EXEC SQL和“;”之间是嵌入SQL语句。如果一条嵌入式SQL语句占用多行,在C程序中可以用续行符“\”。 3.2 嵌入SQL语句 3.2.1宿主变量 1)、声明方法 宿主变量就是在嵌入式SQL语句中引用主语言说明的程序变量(如上例中的firstname变量)。如: …………. EXEC SQL SELECT FIRSTNME INTO :firstname (4) FROM employee WHERE LASTNAME = 'JOHNSON'; …………. 在嵌入式SQL语句中使用宿主变量前,必须采用BEGIN DECLARE SECTION 和END DECLARE SECTION之间给宿主变量说明。这两条语句不是可执行语句,而是预编译程序的说明。宿主变量是标准的C程序变量。嵌入SQL语句使用宿主变量把数据库中查询到的值返回给应用程序(称为输出宿主变量),也用于将程序中给定的值传到SQL语句中(称为输入宿主变量)。显然,C程序和嵌入SQL语句都可以访问宿主变量。 在使用宿主变量前,请注意以下几点: l宿主变量的长度不能超过30字节。开始的字母不能是EXEC和SQL。 l宿主变量必须在被引用之前定义。 l一个源程序文件中可以有多个SQL说明段。 l宿主变量名在整个程序中必须是唯一的。 2)、宿主变量的数据类型 宿主变量是一个用程序设计语言的数据类型说明并用程序设计语言处理的程序变量;另外,在嵌入SQL语句中用宿主变量保存数据库数据。所以,在嵌入SQL语句中,必须映射C数据类型为合适的DB2数据类型。必须慎重选择宿主变量的数据类型。请看下面这个例子: EXEC SQL BEGIN DECLARE SECTION; short hostvar1 = 39; char *hostvar2 = "telescope"; EXEC SQL END DECLARE SECTION; EXEC SQL UPDATE inventory SET department = :hostvar1 WHERE part_num = "4572-3"; EXEC SQL UPDATE inventory SET prod_descrip = :hostvar2 WHERE part_num = "4572-3"; 在第一个update语句中,department列为smallint数据类型,所以应该把hostvar1定义为short数据类型。这样的话,从C到DB2的hostvar1可以直接映射。在第二个update语句中,prod_descip列为varchar数据类型,所以应该把hostvar2定义为字符数组。这样的话,从C到DB2的hostvar2可以从字符数组映射为varchar数据类型。 下表列出了C的数据类型和DB2的数据类型的一些转换关系: DB2数据类型C数据类型 Smallintshort IntegerLong Decimal(p,s)无 DoubleDouble DateChar[11] TimeChar[9] TimestampChar[27] Char(X)Char[X+1] Varchar(X)Char[X+1] Graphic(X)Wchar_t[X+1] Vargraphic(X)Wchar_t[X+1] 因为C没有date或time数据类型,所以DB2的date或time列将被转换为字符。缺省情况下,使用以下转换格式:mm dd yyyy hh:mm:ss[am | pm]。你也可以使用字符数据格式将C的字符数据存放到DB2的date列上。对于DECIMAL数据类型,在C语言中也没有对应的数据类型。但可以使用char数据类型实现。 3)、宿主变量和NULL 大多数程序设计语言(如C)都不支持NULL。所以对NULL的处理,一定要在SQL中完成。我们可以使用主机指示符变量来解决这个问题。在嵌入式SQL语句中,宿主变量和指示符变量共同规定一个单独的SQL类型值。指示变量和前面宿主变量之间用一个空格相分隔。如: EXEC SQL SELECT price INTO :price :price_nullflag FROM titles WHERE au_id = "mc3026" 其中,price是宿主变量,price_nullflag是指示符变量。指示符变量的值为: l-1。表示宿主变量应该假设为NULL。(注意:宿主变量的实际值是一个无关值,不予考虑)。 l=0。表示宿主变量不是NULL。 l>0。表示宿主变量不是NULL。而且宿主变量对返回值作了截断,指示变量存放了截断数据的长度。 所以,上面这个例子的含义是:如果不存在mc3026写的书,那么price_nullflag为-1,表示price为NULL;如果存在,则price为实际的价格。 指示变量也是一种宿主变量,也需要在程序中定义,它对应数据库系统中的数据类型为SMALLINT。为了便于识别宿主变量,当嵌入式SQL语句中出现宿主变量时,必须在变量名称前标上冒号(:)。冒号的作用是,告诉预编译器,这是个宿主变量而不是表名或列名。 3.2.2单行查询 单行查询是通过SELECT INTO语句完成。当这条语句执行时,查询的结果送入INTO所标志的变量中。如果SQLCODE是100,或者SQLSTATE是02000,则说明没有查询到结果或返回结果为NULL,这时,宿主变量不改变,否则,宿主变量中将包含查询的结果。如: …………. EXEC SQL SELECT FIRSTNME INTO :firstname FROM employee WHERE LASTNAME = 'JOHNSON'; …………. 3.2.3多行查询 对于多行结果,必须使用游标来完成。游标是一个与SELECT语句相关联的符号名,它使用户可逐行访问由DB2返回的结果集。下面这个例子演示了游标的使用方法。这个例子的作用是,逐行打印出每个经理的名字和部门。 #include <stdio.h> #include <stdlib.h> #include <string.h> #include "util.h" 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; 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 */ 在上面这个程序中, (1)定义了一个游标,并指明游标的名字为C1,同时给出了相对于游标的查询语句和游标类型(UPDATE)。 (2)打开游标。系统执行查询语句,建立结果表,将游标指针指向第一条记录之前。 (3)FETCH语句将指针的下一条记录取出,将记录中的数据存放在相应的宿主变量中。同时指针下移。 (4)用CLOSE关闭游标。 3.2.4插入、删除和修改操作 DB2中的插入、删除和修改操作同SQL语句中INSERT、DELETE和UPDATE语句类似。只需在相应的SQL语句前加上EXEC SQL即可。请看下面这个例子: 例、将staff表中所有工作为“Mgr”的职工的工作改变为“clerk”,并将staff表中所有工作为“sale”的职工信息删除。最后插入一新行。 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <sqlenv.h> #include "util.h" EXEC SQL INCLUDE SQLCA; (1) #define CHECKERR(CE_STR) if (check_error (CE_STR, &sqlca) != 0) return 1; int main(int argc, char *argv[]) { EXEC SQL BEGIN DECLARE SECTION; (2) char statement[256]; char userid[9]; char passwd[19]; char jobUpdate[6]; EXEC SQL END DECLARE SECTION; printf( "\nSample C program: UPDAT \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; (3) CHECKERR ("CONNECT TO SAMPLE"); } else { printf ("\nUSAGE: updat [userid passwd]\n\n"); return 1; } /* endif */ strcpy (jobUpdate, "Clerk"); EXEC SQL UPDATE staff SET job = :jobUpdate WHERE job = 'Mgr'; (4) CHECKERR ("UPDATE STAFF"); printf ("All 'Mgr' have been demoted to 'Clerk'!\n" ); strcpy (jobUpdate, "Sales"); EXEC SQL DELETE FROM staff WHERE job = :jobUpdate; (5) CHECKERR ("DELETE FROM STAFF"); printf ("All 'Sales' people have been deleted!\n"); EXEC SQL INSERT INTO staff VALUES (999, 'Testing', 99, :jobUpdate, 0, 0, 0); (6) CHECKERR ("INSERT INTO STAFF"); printf ("New data has been inserted\n"); EXEC SQL ROLLBACK; (7) CHECKERR ("ROLLBACK"); printf( "On second thought -- changes rolled back.\n" ); EXEC SQL CONNECT RESET; CHECKERR ("CONNECT RESET"); return 0; } /* end of program : UPDAT.SQC */ 上述语句: (1)包含SQLCA结构。该结构用于将SQL语句执行的结果信息返回给应用程序。 (2)宿主变量定义。 (3)连接到DB2的SAMPLE数据库。 (4)UPDATE语句将staff表中所有工作为“Mgr”的职工的工作改变为“clerk”。 (5)DELETE语句将staff表中所有工作为“sale”的职工信息删除。 (6)INSERT语句插入一新行。 指定位置的UPDATE语句和DELETE语句 游标操作除了可以将多行的查询结果返回给应用程序,它还可以与UPDATE语句和DELETE相结合,根据游标当前的位置,对指针所指的这个行数据执行UPDATE操作和DELETE操作。它的语法为: UPDATE…. WHERE CURRENT OF cursor_name DELETE [FROM] {table_name | view_name} WHERE CURRENT OF cursor_name 请看下面这个例子。这个例子的作用是:将部门大于40的员工的工作改变为“clerk”,部门小于等于40的员工信息删除。 #include <stdio.h> #include <stdlib.h> #include <string.h> #include "util.h" 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; printf( "Sample C program: OPENFTCH\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: openftch [userid passwd]\n\n"); return 1; } /* endif */ EXEC SQL DECLARE c1 CURSOR FOR SELECT name, dept FROM staff WHERE job='Mgr' FOR UPDATE OF job; EXEC SQL OPEN c1; CHECKERR ("OPEN CURSOR"); do { EXEC SQL FETCH c1 INTO :pname, :dept; if (SQLCODE != 0) break; if (dept > 40) { printf( "%-10.10s in dept. %2d will be demoted to Clerk\n", pname, dept ); EXEC SQL UPDATE staff SET job = 'Clerk' WHERE CURRENT OF c1; CHECKERR ("UPDATE STAFF"); } else { printf ("%-10.10s in dept. %2d will be DELETED!\n", pname, dept); EXEC SQL DELETE FROM staff WHERE CURRENT OF c1; CHECKERR ("DELETE"); } /* endif */ } while ( 1 ); EXEC SQL CLOSE c1; 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 : OPENFTCH.SQC */ 在通过游标进行UPDATE和DELETE操作时,相应的游标在定义时必须加上FOR UPDATE子句。操作时,游标必须打开,而且正指向一行数据。 3.2.5 SQLCA 应用程序执行时,每执行一条SQL语句,就返回一个状态符和一些附加信息。这些信息反映了SQL语句或API的执行情况,它有助于用户分析应用程序的错误所在。这些信息都存放在一个定义在sqlca.h的sqlca结构中。如果一个源文件中后SQL语句,则必须要在源程序中定义一个SQLCA结构,而且名为SQLCA。最简单的定义方法是在源文件中加入一些语句:EXEC SQL INCLUDE sqlca.h 下面,我们首先看看SQLCA的结构: SQL_STRUCTURE sqlca { _SQLOLDCHAR sqlcaid[8]; /* Eyecatcher = 'SQLCA ' */ long sqlcabc; /* SQLCA size in bytes = 136 */ #ifdef DB2_SQL92E long sqlcade; /* SQL return code */ #else long sqlcode; /* SQL return code */ #endif short sqlerrml; /* Length for SQLERRMC */ _SQLOLDCHAR sqlerrmc[70]; /* Error message tokens */ _SQLOLDCHAR sqlerrp[8]; /* Diagnostic information */ long sqlerrd[6]; /* Diagnostic information */ _SQLOLDCHAR sqlwarn[11]; /* Warning flags */ #ifdef DB2_SQL92E _SQLOLDCHAR sqlstat[5]; /* State corresponding to SQLCODE */ #else _SQLOLDCHAR sqlstate[5]; /* State corresponding to SQLCODE */ #endif }; 结构中各个字段的作用是: lSqlcaid:包含字符串“SQLCA”。 lSqlcabc:包含SQLCA结构的长度。 lSqlcode:该值反映了SQL语句执行后的状态,0表示SQL执行成功;<0表示SQL语句执行出错;>0反映了一些特殊情况(如:没有查询结果)。不同的数据库产品,该值代表的含义可能不同。 lSqlerrml:sqlerrmc域中数据的实际长度。 lSqlerrmc:由0个或多个字符串组成,它对返回的值给予一个更详细的解释。 lSqlerrp:包含一些对用户没有用的信息。 lSqlwarn:包含了一些警告信息。 lSqlstate:长度为5的字符串。它表示SQL语句执行的结果。它的每一个含义是遵循ANSI/SQL 92标准。各个数据库产品的sqlstate域的含义都是相同的。 为了更方便地读取sqlca中SQL语句执行后的结果或错误,DB2提供了一个函数——sqlaintp,它在sql.h中声明:sqlaintp(msgbuf,bufsize,linesize,sqlcaptr)。其中msgbuf中存放信息;bufsize中存放了msgbuf的长度;linesize中存放了两个执行符之间的字符长度。函数的返回值为正,代表信息的长度;为负代表没有信息返回。 下面这个例子解释了sqlca和sqlaintp的使用方法: int check_error (char eString[], struct sqlca *caPointer) { char eBuffer[1024]; char sBuffer[1024]; short rc, Erc; /*通过SQLCODE来判断是否出错*/ if (caPointer->sqlcode != 0) { printf ("--- error report ---\n"); printf ("ERROR occured : %s.\nSQLCODE : %ld\n", eString, caPointer->sqlcode); /* 获取SQLSTATE信息*/ rc = sqlogstt (sBuffer, 1024, 80, caPointer->sqlstate); /*获取调用API的错误信息*/ Erc = sqlaintp (eBuffer, 1024, 80, caPointer); /* Erc中存放了eBuffer 的长度*/ if (Erc > 0) printf ("%s", eBuffer); if (caPointer->sqlcode < 0) { /*错误信息*/ if (rc == 0) { printf ("\n%s", sBuffer); } printf ("--- end error report ---\n"); return 1; } else { /* 仅仅是警告信息 */ if (rc == 0) { printf ("\n%s", sBuffer); } printf ("--- end error report ---\n"); printf ("WARNING - CONTINUING PROGRAM WITH WARNINGS!\n"); return 0; } /* endif */ } /* endif */ return 0; } 在每条SQL语句执行后都返回一个SQLCA结构,SQLCA结构中记载了SQL语句执行后的结果信息。用户可以根据返回信息执行各种操作。DB2也提供了WHENEVER语句。具体可参见SQL SERVER中的WHENEVER。但是,在DB2中,没有WHENEVER…CALL这个处理。 3.2.6事务 所谓事务,就是一系列应用程序和数据库之间交互操作的集合。一旦一个事务开始执行,则事务中的操作要么全部执行,要么全部不执行。 l事务开始:DB2事务是隐式开始的,除了下列的一些语句,其他任何一个可执行的SQL语句都隐式地开始一个事务。 BEGIN DECLARE SECTION END DECLARE SECTION DECLARE CURSOR INCLUDE SQLCA INCLUDE SQLDA WHENEVER l事务结束:事务由一个可执行的SQL语句开始,后面执行的所有SQL语句都将属于同一个事务,该事务一直遇到COMMIT或ROLLBACK命令时才结束。 COMMIT操作的作用是,结束当前的事务,事务对数据库所做的修改永久化。ROLLBACK的作用是,结束当前的事务,将被修改的数据恢复到事务执行以前的状态,即取消事务执行产生的影响。当程序结束时,系统自动隐式地执行COMMIT操作,如果系统检测到死锁等故障,则隐式地执行ROLLBACK操作。 3.3 DB2的嵌入SQL程序处理过程 嵌入SQL程序处理,由一个源程序创建为一个可执行文件的过程。如下图所示: 图6-3 嵌入SQL处理过程 从上图看出,首先对源文件做预编译(precompiler),生成两个部分文件:一部分是纯的C程序源文件,它们和其他的C程序源文件一起,经过编译和连接生成可执行的程序(executable program);而另一部分是bind文件或package文件。Bind文件经过binder操作以后,也生成为package文件。所谓package,实际上是SQL语句的访问计划。所以,预编译器将源程序中的SQL语句提出来,生成他们的访问计划,并将访问计划存放在数据库管理器中。当执行程序并遇到访问数据库的命令时,它将到数据库管理器中寻找属于它的访问计划,然后按照访问计划中所设计的方法对数据库进行访问。具体来说: 第一步、预编译 源程序生成以后,在源程序中嵌入了许多SQL语句,而SQL语句是宿主语言编译器所不认识的,所以在用宿主语言编译器进行编译、连接之前必须将SQL语句分离出来,这就是预编译所做的工作。DB2中预编译操作是通过PREP命令执行的,PREP命令首先将源程序中的所有有关SQL语句全部注释起来,对它进行分析和语法检查。如果源程序中的SQL语句全部书写正确,则将这些SQL语句转换成C语言可以识别的一系列的API函数。这些函数可以在函数执行时访问数据库,然后将源文件中所有用于生成数据库管理器的PACKAGE的数据提出组合成一个BIND文件。也可以直接生成一个PACKAGE,但这相当于在预编译后又执行了一次BIND操作。在预编译时,对整个源程序文件中的所有变量做统一处理而不根据变量的生命期来处理,所以宿宿主变量在整个程序中是唯一的。下面讲解预编译的步骤: 1)、连接到一个数据库,该操作是为BIND做准备。操作如下: db2 connect to cicstest 2)、执行预编译命令,假设源文件为adhoc.sqc,则: db2 prep adhoc.sqc bindfile 下面我们对预编译的几种输出文件进行讨论。 l预编译后生成的C语言源文件:该文件中原有的SQL语句,已经全部加上注释并转换成了C语言可以识别的API调用。 lBIND文件:如果在预编译时使用BINDFILE选项,则生成BIND文件,BIND文件的后缀为.bnd,BIND文件可以在将来使用BIND命令来生成PACKAGE。 db2 bind adhoc.bnd 如果在预编译时,只生成BIND文件,那么即使在预编译时,不能访问某些数据库对象,系统也只是报警,而不会报错。如果使用PACKAGE选项,则生成PACKAGE。如果有MESSAGE选项,则生成信息文件,它包含了所有的返回信息,如:警报、错误等。它便于程序员对源程序做进一步的修改。 第二步、编译和连接 在预编译后,程序中只有C语言语句,它们都可以为C语言的编译器所识别。所以,可以按照一般的方法进行编译和连接,但在将SQL语句转换以后,在C语言程序中,又引入了许多一般的C语言系统所没有的INCLUDE文件和函数库,这些均在DB2的SDK中。所以,要生成可执行的程序,就必须安装DB2的SDK,并且做以下设置: set INCLUDE=$(DB2PATH)\include;%include% set LIB=$(DB2PATH)\lib;%LIB% 下面是编译和连接: cl -o adhoc.exe adhoc.c 生成的可执行文件必须与数据库管理器中的PACKAGE相结合,才能执行。 下面对BIND做进一步解释。 PACKAGE是DB2为SQL语句制定的访问计划。通过precompile之后,源程序中的SQL语句部分就被分离出来。PACKAGE就是根据具体的SQL语句和数据库中的信息生成的针对每条SQL语句的访问计划,它存放在DB2数据库服务器上。应用程序执行到SQL语句时,就到相应的服务器上去找它们的PACKAGE,数据库服务器根据PACKAGE执行具体的数据库操作。所以,如果一个应用程序访问了多个数据库服务器,则该应用程序应在它访问到的数据库服务器上均生成相应的PACKAGE。因此,当遇到这种情况时,推荐的方式是将源程序分成若干个文件,每个文件只访问一个服务器,然后分别进行预编译。 执行PREP命令时,加上选项PACKAGE或不注明BINDFILE、SYNTAX或SQLFLAG选项,这时BIND操作将自动进行。 直接使用BIND命令从BIND文件中生成PACKAGE存放在数据库管理器中。BIND完成的功能是,从数据库中找到SQL语句所涉及的表,查看SQL语句中提到的表名及属性是否与数据库中的表名和属性相匹配,以及应用程序开发者是否有权限查询或修改应用程序中所涉及到的表及属性。这就是为什么在预编译之前要连接到相应的数据库上的原因。在做BIND操作后,在数据库管理器中就生成一个PACKAGE,PACKAGE的名字与源程序的文件名字相同。 一个源文件在数据库中可有多个PACKAGE存在。为了区分不同的PACKAGE,DB2中引入了时间戳的概念。在PREP执行时,系统对生成的修改过的C语言程序和BIND文件以及PACKAGE中都加入了一个时间戳。BIND文件在生成PACKAGE时也将时间戳传递下来,修改过的C语言程序在生成可执行程序时,同样也将时间戳传递下去。当应用程序运行时,可执行程序是通过时间戳找到相应的PACKAGE,如果时间戳不匹配,则说明版本更新,需要做BIND。 本章第一个例子被编译后所生成的C程序: static char sqla_program_id[40] = {111,65,65,66,65,73,65,70,89,65,78,71,90,72,32,32,68,69,77,79, 68,66,50,32,67,65,51,54,75,75,67,81,48,49,49,49,49,50,32,32}; #include "sqladef.h" static struct sqla_runtime_info sqla_rtinfo = {{'S','Q','L','A','R','T','I','N'}, sizeof(wchar_t), 0, {' ',' ',' ',' '}}; #line 1 "demodb2.sqc" #include <stdio.h> #include <stdlib.h> #include <string.h> #include "util.h" #include <sqlca.h> /* EXEC SQL INCLUDE SQLCA; */ /* SQL Communication Area - SQLCA - structures and constants */ #include "sqlca.h" struct sqlca sqlca; #line 6 "demodb2.sqc" #define CHECKERR(CE_STR) if (check_error (CE_STR, &sqlca) != 0) return 1; int check_error (char eString[], struct sqlca *caPointer) { char eBuffer[1024]; char sBuffer[1024]; short rc, Erc; if (caPointer->sqlcode != 0) { printf ("--- error report ---\n"); printf ("ERROR occured : %s.\nSQLCODE : %ld\n", eString, caPointer->sqlcode); } return 0; } int main(int argc, char *argv[]) { /* EXEC SQL BEGIN DECLARE SECTION; */ #line 21 "demodb2.sqc" char firstname[13]; char userid[9]; char passwd[19]; /* EXEC SQL END DECLARE SECTION; */ #line 25 "demodb2.sqc"
printf( "Sample C program: STATIC\n" ); if (argc == 1) { /* EXEC SQL CONNECT TO sample; */ { #line 28 "demodb2.sqc" sqlastrt(sqla_program_id, &sqla_rtinfo, &sqlca); #line 28 "demodb2.sqc" sqlaaloc(2,1,1,0L); { struct sqla_setd_list sql_setdlist[1]; #line 28 "demodb2.sqc" sql_setdlist[0].sqltype = 460; sql_setdlist[0].sqllen = 7; #line 28 "demodb2.sqc" sql_setdlist[0].sqldata = (void*)"sample"; #line 28 "demodb2.sqc" sql_setdlist[0].sqlind = 0L; #line 28 "demodb2.sqc" sqlasetd(2,0,1,sql_setdlist,0L); } #line 28 "demodb2.sqc" sqlacall((unsigned short)29,4,2,0,0L); #line 28 "demodb2.sqc" sqlastop(0L); } #line 28 "demodb2.sqc" 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; */ { #line 34 "demodb2.sqc" sqlastrt(sqla_program_id, &sqla_rtinfo, &sqlca); #line 34 "demodb2.sqc" sqlaaloc(2,3,2,0L); { struct sqla_setd_list sql_setdlist[3]; #line 34 "demodb2.sqc" sql_setdlist[0].sqltype = 460; sql_setdlist[0].sqllen = 7; #line 34 "demodb2.sqc" sql_setdlist[0].sqldata = (void*)"sample"; #line 34 "demodb2.sqc" sql_setdlist[0].sqlind = 0L; #line 34 "demodb2.sqc" sql_setdlist[1].sqltype = 460; sql_setdlist[1].sqllen = 9; #line 34 "demodb2.sqc" sql_setdlist[1].sqldata = (void*)userid; #line 34 "demodb2.sqc" sql_setdlist[1].sqlind = 0L; #line 34 "demodb2.sqc" sql_setdlist[2].sqltype = 460; sql_setdlist[2].sqllen = 19; #line 34 "demodb2.sqc" sql_setdlist[2].sqldata = (void*)passwd; #line 34 "demodb2.sqc" sql_setdlist[2].sqlind = 0L; #line 34 "demodb2.sqc" sqlasetd(2,0,3,sql_setdlist,0L); } #line 34 "demodb2.sqc" sqlacall((unsigned short)29,5,2,0,0L); #line 34 "demodb2.sqc" sqlastop(0L); } #line 34 "demodb2.sqc" CHECKERR ("CONNECT TO SAMPLE"); } else { printf ("\nUSAGE: static [userid passwd]\n\n"); return 1; } /* endif */ /* EXEC SQL SELECT FIRSTNME INTO :firstname FROM employee WHERE LASTNAME = 'JOHNSON'; */ { #line 44 "demodb2.sqc" sqlastrt(sqla_program_id, &sqla_rtinfo, &sqlca); #line 44 "demodb2.sqc" sqlaaloc(3,1,3,0L); { struct sqla_setd_list sql_setdlist[1]; #line 44 "demodb2.sqc" sql_setdlist[0].sqltype = 460; sql_setdlist[0].sqllen = 13; #line 44 "demodb2.sqc" sql_setdlist[0].sqldata = (void*)firstname; #line 44 "demodb2.sqc" sql_setdlist[0].sqlind = 0L; #line 44 "demodb2.sqc" sqlasetd(3,0,1,sql_setdlist,0L); } #line 44 "demodb2.sqc" sqlacall((unsigned short)24,1,0,3,0L); #line 44 "demodb2.sqc" sqlastop(0L); } #line 44 "demodb2.sqc" CHECKERR ("SELECT statement"); printf( "First name = %s\n", firstname ); /* EXEC SQL CONNECT RESET; */ { #line 47 "demodb2.sqc" sqlastrt(sqla_program_id, &sqla_rtinfo, &sqlca); #line 47 "demodb2.sqc" sqlacall((unsigned short)29,3,0,0,0L); #line 47 "demodb2.sqc" sqlastop(0L); } #line 47 "demodb2.sqc" CHECKERR ("CONNECT RESET"); return 0; } /* end of program : STATIC.SQC */ 生成的BIND文件为demodb2.bnd。 3.4 DB2的动态SQL嵌入语句 所谓静态SQL的编程方法,就是指在预编译时SQL语句已经基本确定,即访问的表或视图名、访问的列等信息已经确定。但是,有时整个SQL语句要到执行的时候才能确定下来,而且SQL语句所访问的对象也要到执行时才能确定。这就需要通过动态SQL语句完成。 3.4.1 基本方法 执行动态SQL语句的程序,主要有三条语句来完成: 1)、PREPARE语句。由于动态SQL语句在执行时才能确定,所以DB2中使用一个字符型的宿主变量来存放相应的SQL语句。但是,这条语句在预编译时不存在,因而在预编译时无法编译这个SQL语句。但是,存放在宿主变量中的语句必须转化为可执行的格式后才能执行。PREPARE命令的作用是完成编译的工作。即相当于静态SQL中的BIND操作,在数据库管理器中生成PACKAGE,并给出一个语句的名字。在后面的DESCRIBE、EXECUTE和OPEN等命令都使用这个名字来访问相应的SQL语句。SQL语句在做PREPARE操作时,不可使用宿主变量来表示参数,但可以在相应的位置上使用“?”来表示该位置上应该有一个参数。PREPARE还将生成相应的SQLDA结构。 2)、EXECUTE语句。该语句的作用是执行已经做过PREPARE操作的SQL语句。在EXECUTE语句中,将针对PREPARE语句中的每个参数标志给出相应的参数值,这些参数值可以有宿主变量传递,他们的类型应该匹配。EXECUTE命令不能做SELECT操作,这是因为EXECUTE语句无法返回结果,要执行SELECT操作,应该通过游标完成。 EXECUTE IMMEDIATE语句是PERPARE语句和EXECUTE语句的综合,它对语句做PREPARE,生成可执行模式,在执行。 3)、DESCRIBE语句。将执行过的PREPARE的SQL语句结果信息存放在SQLDA结构。 例、查询表名不是STAFF的表信息。 #include <stdio.h> #include <stdlib.h> #include <string.h> #include "util.h" 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 table_name[19]; /*st[80]宿主变量存放SQL语句*/ char st[80]; char parm_var[19]; char userid[9]; char passwd[19]; EXEC SQL END DECLARE SECTION; printf( "Sample C program: DYNAMIC\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: dynamic [userid passwd]\n\n"); return 1; } /* endif */ strcpy( st, "SELECT tabname FROM syscat.tables" ); strcat( st, " WHERE tabname <> ?" ); /*对st做PREPARE操作,st中的“?”表示参数*/ EXEC SQL PREPARE s1 FROM :st; CHECKERR ("PREPARE"); /*定义游标*/ EXEC SQL DECLARE c1 CURSOR FOR s1; strcpy( parm_var, "STAFF" ); /*打开游标,并用parm_var代替SELECT语句中的“?”参数*/ EXEC SQL OPEN c1 USING :parm_var; CHECKERR ("OPEN"); do { /*用FETCH语句从结果集中取出结果*/ EXEC SQL FETCH c1 INTO :table_name; if (SQLCODE != 0) break; printf( "Table = %s\n", table_name ); } while ( 1 ); /*关闭游标*/ EXEC SQL CLOSE c1; CHECKERR ("CLOSE"); EXEC SQL COMMIT; CHECKERR ("COMMIT"); EXEC SQL CONNECT RESET; CHECKERR ("CONNECT RESET"); return 0; } /* end of program : DYNAMIC.SQC */ 从上面这个例子看出,动态SQL语句同静态SQL语句的不同之处在于,要使用PREPARE语句操作具体的SQL语句,然后使用EXECUTE或者游标等方式执行。 3.4.2 动态游标 1)、动态游标的DECLARE语句 动态游标对应的SQL语句应该是一个用PREPARE操作从文本形式转换趁个可执行形式的语句。在上例子中: EXEC SQL DECLARE c1 CURSOR FOR s1; 其中s1就是PREPARE操作后的可执行语句,c1是游标的名字。 2)、动态游标的OPEN语句 动态OPEN操作的作用是:将宿主变量或SQLDA结构中的值取出,填充到PREPARE操作后的SQL语句中标有“?”的参数的位置,并执行查询。宿主变量或SQLDA结构中的值应该与参数有一一对应的关系,而且数据类型符合。如上例子中: EXEC SQL OPEN c1 USING :parm_var; 3)、动态游标的FETCH语句 FETCH语句是从结果集中取出一行,将结果送入宿主变量列表或SQLDA结构中。关于SQLDA结构,见下节。如上例子中: EXEC SQL FETCH c1 INTO :table_name; 3.4.3 SQLDA SQLDA结构的作用与宿主变量相同,可用于应用程序与数据库之间的数据交换,它适用于:在编写程序时不确定要使用的变量个数和数据类型或不确定输出数据的列数,如:动态输入的SELECT语句,就必须使用SQLDA结构来获得查询的数据。SQLDA用于描述数据的类型、长度、变量的值和数据项的个数。在应用程序中使用SQLDA,必须在程序中定义SQLDA。即加入以下语句:EXEC SQL INCLUDE SQLDA。 1)、SQLDA结构 SQLDA的定义存放在sqlda.h中。具体为: SQL_STRUCTURE sqlname /* Variable Name */ { short length; /* Name length [1..30] */ _SQLOLDCHAR data[30]; /* Variable or Column name */ }; SQL_STRUCTURE sqlvar /* Variable Description */ { short sqltype; /* Variable data type */ short sqllen; /* Variable data length */ _SQLOLDCHAR *SQL_POINTER sqldata; /* Pointer to variable data value */ short *SQL_POINTER sqlind; /* Pointer to Null indicator */ struct sqlname sqlname; /* Variable name */ }; SQL_STRUCTURE sqlda { _SQLOLDCHAR sqldaid[8]; /* Eye catcher = 'SQLDA ' */ long sqldabc; /* SQLDA size in bytes=16+44*SQLN */ short sqln; /* Number of SQLVAR elements */ short sqld; /* # of columns or host vars. */ struct sqlvar sqlvar[1]; /* first SQLVAR element */ };
下图形象的描述了用SQLDA来存放两列数据。 SQLDA结构 Sqld=2 sqlvar …… Sqltype=500 Sqllen sqldata ….. Sqltype=501 Sqllen Sqldata …..
图6-4 SQLDA结构示例 从上面这个定义看出,SQLDA是一种由两个不同部分组成的可变长数据结构。从位于SQLDA开端的sqldaid到sqld为固定部分,用于标志该SQLDA,并规定这一特定的SQLDA的长度。而后是一个或多个sqlvar结构 ,用于标志列数据。当用SQLDA把参数送到执行语句时,每一个参数都是一个sqlvar结构;当用SQLDA返回输出列信息时,每一列都是一个sqlvar结构。具体每个元素的含义为: lSqldaid。用于输入标志信息,如:“SQLDA”。 lSqldabc。SQLDA数据结果的长度。应该是16+44*SQLN。Sqldaid、sqldabc、sqln和sqld的总长度为16个字节。而sqlvar结构的长度为44个字节。 lSqln。分配的Sqlvar结构的个数。等价于输入参数的个数或输出列的个数。 lSqld。目前使用的sqlvar结构的个数。 lSqltype。代表参数或列的数据类型。它是一个整数数据类型代码。如:500代表smallint。具体每个整数的含义见下表: SQL 列类型 SQLTYPE值 SQLTYPE 的字符名 DATE 384/385 SQL_TYP_DATE / SQL_TYP_NDATE TIME 388/389 SQL_TYP_TIME / SQL_TYP_NTIME TIMESTAMP 392/393 SQL_TYP_STAMP / SQL_TYP_NSTAMP n/a2 400/401 SQL_TYP_CGSTR / SQL_TYP_NCGSTR BLOB 404/405 SQL_TYP_BLOB / SQL_TYP_NBLOB CLOB 408/409 SQL_TYP_CLOB / SQL_TYP_NCLOB DBCLOB 412/413 SQL_TYP_DBCLOB / SQL_TYP_NDBCLOB VARCHAR 448/449 SQL_TYP_VARCHAR / SQL_TYP_NVARCHAR CHAR 452/453 SQL_TYP_CHAR / SQL_TYP_NCHAR LONG VARCHAR 456/457 SQL_TYP_LONG / SQL_TYP_NLONG n/a3 460/461 SQL_TYP_CSTR / SQL_TYP_NCSTR VARGRAPHIC 464/465 SQL_TYP_VARGRAPH / SQL_TYP_NVARGRAPH GRAPHIC 468/469 SQL_TYP_GRAPHIC / SQL_TYP_NGRAPHIC LONG VARGRAPHIC 472/473 SQL_TYP_LONGRAPH / SQL_TYP_NLONGRAPH FLOAT 480/481 SQL_TYP_FLOAT / SQL_TYP_NFLOAT REAL4 480/481 SQL_TYP_FLOAT / SQL_TYP_NFLOAT DECIMAL5 484/485 SQL_TYP_DECIMAL / SQL_TYP_DECIMAL INTEGER 496/497 SQL_TYP_INTEGER / SQL_TYP_NINTEGER SMALLINT 500/501 SQL_TYP_SMALL / SQL_TYP_NSMALL n/a 804/805 SQL_TYP_BLOB_FILE / SQL_TYPE_NBLOB_FILE n/a 808/809 SQL_TYP_CLOB_FILE / SQL_TYPE_NCLOB_FILE n/a 812/813 SQL_TYP_DBCLOB_FILE / SQL_TYPE_NDBCLOB_FILE n/a 960/961 SQL_TYP_BLOB_LOCATOR / SQL_TYP_NBLOB_LOCATOR n/a 964/965 SQL_TYP_CLOB_LOCATOR / SQL_TYP_NCLOB_LOCATOR n/a 968/969 SQL_TYP_DBCLOB_LOCATOR / SQL_TYP_NDBCLOB_LOCATOR 我们知道,SQLDA的作用是应用程序与数据库之间交换数据。对于从数据库向应用程序输出数据,则SQLDA存放了每列的信息,如:数据类型、长度和值。输出数据要同FETCH语句结合;对于从应用程序向数据库输入数据,则要同OPEN或EXECUTE操作结合。请看下面这个ADHOC例子,来理解SQLDA的作用。这个例子很经典,它的功能是处理任意输入的SQL语句,并返回结果。 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sqlenv.h> #include <sqlcodes.h> /*包含SQLDA结构的定义*/ #include <sqlda.h> #include "util.h" /*包含SQLCA结构的定义*/ EXEC SQL INCLUDE SQLCA; /* 'check_error' is a function found in the util.c program */ #define CHECKERR(CE_STR) check_error (CE_STR, &sqlca) #define SQLSTATE sqlca.sqlstate /*初试化SQLDA*/ int init_da (SQLDA **DAPointer, int DAsqln); /*为存放列数据的sd_column结构申请空间*/ int alloc_host_vars (SQLDA *sqldaPointer); /*释放SQLDA所申请的空间*/ void free_da (SQLDA *sqldaPointer); /*获取列名信息*/ char * readColName (SQLDA *sqldaPointer, short sd_columnIndex, char * buffer); /*获取列数据*/ char * readCol (SQLDA *sqldaPointer, short sd_columnIndex, char * buffer); /*处理SQL语句*/ int process_statement (char[1000]); #define MAX_COLUMNS 255 int main(void) { int rc; char sqlInput[255]; char st[1000]=""; char Transaction; char tmpstr[1024]; /*定义宿主变量*/ EXEC SQL BEGIN DECLARE SECTION; (3) char server[9]; char userid[9]; char passwd[19]; EXEC SQL END DECLARE SECTION; printf("Sample C program : ADHOC interactive SQL\n"); /* 初试化与数据库的连接 */ do { printf("input the server (database) which you wish to attach to : \n"); gets (sqlInput); strcpy(server,sqlInput); printf("input your userid : \n"); gets (sqlInput); strcpy (userid, sqlInput); printf("input your passwd : \n"); gets (sqlInput); strcpy (passwd, sqlInput); printf("CONNECTING TO %s\n",server); /*连接数据库*/ EXEC SQL CONNECT TO :server USER :userid USING :passwd; /*检查连接是否成功*/ CHECKERR("CONNECT TO DATABASE"); } while (SQLCODE != 0); /* enddo */ printf("CONNECTED TO %s\n",server); /* Enter the continuous command line loop. */ while ( 1 ) { /*提示用户输入要操作的SQL语句*/ printf ("Enter an SQL statement or 'quit' to Quit :\n"); gets (sqlInput); if (strcmp(sqlInput, "quit") == 0) { break; } else if (strlen(sqlInput) == 0) { /* Don't process the statement */ printf ("\tNo characters entered.\n"); } else if (sqlInput[strlen(sqlInput) - 1] == '\\') { /* 查看是否有续行 */ strcpy (st, "\0"); do { strncat (st, sqlInput, strlen(sqlInput) -1); gets (sqlInput); } while (sqlInput[strlen(sqlInput) - 1] == '\\'); strcat (st, sqlInput); /* 处理SQL语句 */ rc = process_statement (st); } else { strcpy (st, sqlInput); /* 处理输入的SQL语句*/ rc = process_statement (st); } /* end if */ } /* end while */ printf ("Enter 'c' to COMMIT or Any Other key to ROLLBACK the transaction :\n"); Transaction = getc(stdin); if (Transaction == 'c') { printf("COMMITING the transactions.\n"); /提交结果*/ EXEC SQL COMMIT; CHECKERR ("COMMIT"); } else { /* 撤消语句的执行结果*/ printf("ROLLING BACK the transactions.\n"); EXEC SQL ROLLBACK; CHECKERR ("ROLLBACK"); }; /* endif */ /*断开数据库的连接*/ EXEC SQL CONNECT RESET; CHECKERR ("CONNECT RESET"); return 0; } /************************************************************************ * 函数 : process_statement * This function processes the inputted statement and then prepares the * procedural SQL implementation to take place. *************************************************************************/ int process_statement (char sqlInput[1000]) { int counter = 0; struct sqlda *sqldaPointer; short sqlda_d ; /* Total columns */ short idx; char buffer[4096]; char varname[1024]; char colnamelist[4096]; /*声明一个宿主变量,用于存放SQL语句*/ EXEC SQL BEGIN DECLARE SECTION; char st[1000]; EXEC SQL END DECLARE SECTION; /*向宿主变量中存放SQL语句*/ strcpy(st, sqlInput); /* 分配SQLDA空间,以存放查询结果 */ init_da (&sqldaPointer, 1); EXEC SQL PREPARE statement1 from :st; if (CHECKERR ("PREPARE") != 0) return SQLCODE; /*获得返回结果的描述信息,填入SQLDA结构*/ EXEC SQL DESCRIBE statement1 INTO :*sqldaPointer; /* 判断DESCRIBE是否正确执行*/ if (SQLCODE != 0 && SQLCODE != SQL_RC_W236 && SQLCODE != SQL_RC_W237 && SQLCODE != SQL_RC_W238 && SQLCODE != SQL_RC_W239) { /* An unexpected warning/error has occured. Check the SQLCA. */ if (CHECKERR ("DESCRIBE") != 0) return SQLCODE; } /* end if */ /*如果SQLDA结构中的sqld大于0,则表明是一个SELECT语句,sqld值是列的个数*/ if (sqldaPointer->sqld > 0) { /*判断是否有LOB列,若是,则需要双倍的SQLDA空间*/ if (strncmp(SQLSTATE, "01005", sizeof(SQLSTATE)) == 0) { /* this output contains columns that need a DOUBLED SQLDA */ SETSQLDOUBLED (sqldaPointer, SQLDOUBLED); init_da (&sqldaPointer, sqldaPointer->sqld * 2); } else { /*否则,只需要一个SQLDA */ init_da (&sqldaPointer, sqldaPointer->sqld); } /* end if */ /* 对SQLDA 重新赋值*/ EXEC SQL DESCRIBE statement1 INTO :*sqldaPointer; if (CHECKERR ("DESCRIBE") != 0) return SQLCODE; /* 给SQLDA分配合适的内存空间*/ alloc_host_vars (sqldaPointer); /* 声明游标*/ EXEC SQL DECLARE pcurs CURSOR FOR statement1; /*打开游标*/ EXEC SQL OPEN pcurs; if (CHECKERR ("OPEN") != 0) return SQLCODE; /*查询一行,存放在SQLDA中*/ EXEC SQL FETCH pcurs USING DESCRIPTOR :*sqldaPointer; if (CHECKERR ("FETCH") != 0) return SQLCODE; /* 从SQLDA中获得列标题信息,并显示之*/ colnamelist[0] = 0; for ( idx=0; idx< sqlda_d; idx++) { strcat(colnamelist, readColName(sqldaPointer, idx, buffer)); If (idx < sqlda_d -1) strcat(colnamelist, ","); } printf(“%s\n”,colnamelist); /*显示所有的行数据*/ while ( SQLCODE == 0 ) { counter++ ; for ( idx=0; idx< sqlda_d; idx++) printf(“%s”,readCol(sqldaPointer, idx, buffer)); EXEC SQL FETCH pcurs USING DESCRIPTOR :*sqldaPointer ; } /* endwhile */ /*关闭游标*/ EXEC SQL CLOSE pcurs; if (CHECKERR ("CLOSE CURSOR") != 0) return SQLCODE; printf ("\n %d record(s) selected\n\n", counter); /* 释放SQLDA申请的空间 */ free_da(sqldaPointer); } else { /*不是SELECT语句,则执行SQL语句 */ EXEC SQL EXECUTE statement1; if (CHECKERR ("executing the SQL statement") != 0) return SQLCODE; } /* end if */ return SQLCODE; } /* end of program : ADHOC.SQC */ /************************************************************************** * PROCEDURE : init_da *为SQLDA分配空间。其中SQLDASIZE的作用,是计算SQLDA的大小。返回-1, *表示无法分配空间。 **************************************************************************/ int init_da (struct sqlda **DAPointer, int DAsqln) { int idx; *DAPointer = (struct sqlda *) malloc (SQLDASIZE(DAsqln)); if (*DAPointer == NULL) return (-1); memset (*DAPointer, '\0', SQLDASIZE(DAsqln)); strncpy((*DAPointer)->sqldaid, "SQLDA ", sizeof ((*DAPointer)->sqldaid)); (*DAPointer)->sqldabc = (long)SQLDASIZE(DAsqln); (*DAPointer)->sqln = DAsqln; (*DAPointer)->sqld = 0; return 0; } /********************************************************************** * FUNCTION : alloc_host_vars *为sqlvar结构申请空间。返回-1表示申请失败。 ***********************************************************************/ int alloc_host_vars (struct sqlda *sqldaPointer) { short idx; unsigned int memsize =0; long longmemsize =0; int precision =0; for (idx = 0; idx < sqldaPointer->sqld; idx++) { switch (sqldaPointer->sqlvar[idx].sqltype ) { case SQL_TYP_VARCHAR: case SQL_TYP_NVARCHAR: case SQL_TYP_LONG: case SQL_TYP_NLONG: case SQL_TYP_DATE: case SQL_TYP_NDATE: case SQL_TYP_TIME: case SQL_TYP_NTIME: case SQL_TYP_STAMP: case SQL_TYP_NSTAMP: sqldaPointer->sqlvar[idx].sqltype = SQL_TYP_NCSTR; sqldaPointer->sqlvar[idx].sqldata = (char *SQL_POINTER) malloc ((sqldaPointer->sqlvar[idx].sqllen)); memsize = (sqldaPointer->sqlvar[idx].sqllen); break; case SQL_TYP_DECIMAL: case SQL_TYP_NDECIMAL: precision = ((char *)&(sqldaPointer->sqlvar[idx].sqllen))[0]; sqldaPointer->sqlvar[idx].sqldata = (char *SQL_POINTER) malloc ((precision + 2) /2); memsize = (precision +2) /2; break; default: sqldaPointer->sqlvar[idx].sqldata = (char *SQL_POINTER) malloc (sqldaPointer->sqlvar[idx].sqllen); memsize = sqldaPointer->sqlvar[idx].sqllen; break; } /* endswitch */ if (sqldaPointer->sqlvar[idx].sqldata == NULL) { return (-1); } else { memset (sqldaPointer->sqlvar[idx].sqldata,'\0',memsize); } /* endif */ /*为sqlind申请空间*/ if ( sqldaPointer->sqlvar[idx].sqltype & 1 ) { /* Allocate storage for short int */ sqldaPointer->sqlvar[idx].sqlind = (short *)malloc(sizeof(short)); /* Detect memory allocation error */ if ( sqldaPointer->sqlvar[idx].sqlind == NULL ) { return(-1) ; } else { /* initialize memory to zero */ memset(sqldaPointer->sqlvar[idx].sqldata,'\0',sizeof(short)); } /* endif */ } /* endif */ } /* endfor */ return 0; } /************************************************************************** * FUNCTION : free_da * 释放SQLDA 申请的空间。 **************************************************************************/ void free_da (struct sqlda *sqldaPointer) { short idx; for (idx = 0; idx < sqldaPointer->sqld; idx++) { free (sqldaPointer->sqlvar[idx].sqldata); if (sqldaPointer->sqlvar[idx].sqltype & 1) { free (sqldaPointer->sqlvar[idx].sqlind); } } /* endfor */ free (sqldaPointer); } /************************************************************************** * PROCEDURE : readColName * 返回列名信息 **************************************************************************/ char * readColName (struct sqlda *sqldaPointer, short sqlvarIndex, char * buffer) { strncpy(buffer, sqldaPointer->sqlvar[sqlvarIndex].sqlname.data, sqldaPointer->sqlvar[sqlvarIndex].sqlname.length); return buffer; } /************************************************************************** * PROCEDURE : readCol * 返回一行数据 **************************************************************************/ char * readCol (struct sqlda *sqldaPointer, short sqlvarIndex, char * buffer){ short numBytes; short idx, ind ; /* Array idx variables */ /* Variables for decoding packed decimal data */ short bottom, point ; unsigned short top, precision, scale; char tmpstr[1024]; short pos; short collen; char *dataptr; /* 检查是否为null */ if ( sqldaPointer->sqlvar[sqlvarIndex].sqltype & 1 &&\ *(sqldaPointer->sqlvar[sqlvarIndex].sqlind) < 0 ) { buffer[0] = 0; return buffer; } dataptr = (char *) sqldaPointer->sqlvar[ sqlvarIndex ].sqldata; collen = sqldaPointer->sqlvar[ sqlvarIndex ].sqllen; switch ( sqldaPointer->sqlvar[ sqlvarIndex ].sqltype ) { case SQL_TYP_INTEGER: /* long */ case SQL_TYP_NINTEGER: /* long with null indicator */ sprintf(buffer, "%ld", * ( long *) dataptr ) ; break ; case SQL_TYP_SMALL: /* short */ case SQL_TYP_NSMALL: /* short with null indicator */ sprintf(buffer, "%d", * ( short *) dataptr ) ; break ; case SQL_TYP_DECIMAL: /* decimal */ case SQL_TYP_NDECIMAL: /* decimal with null indicator */ /* Determine the scale and precision */ precision = ((char *)&(collen))[0]; scale = ((char *)&(collen))[1]; /************************************************************************* *计算精度 *************************************************************************/ if ((precision %2) == 0) precision += 1; /* Calculate the total number of bytes */ idx = ( short ) ( precision + 2 ) / 2 ; point = precision - scale ; pos = 0; /* Determine the sign */ bottom = *(dataptr + idx -1) & 0x000F ; /* sign */ if ( (bottom == 0x000D) || (bottom == 0x000B) ) { buffer[pos++]='-'; } /* Decode and print the decimal number */ for (pos=0, ind=0; ind < idx; ind++) { top = *(dataptr + ind) & 0x00F0 ; top = (top >> 4 ) ; bottom = *(dataptr + ind) & 0x000F ; if ( point-- == 0 ) buffer[pos++]='.' ; buffer[pos++]='0' + top ; /*************************************************************************/ /*忽略最后一位(符号位) */ /*************************************************************************/ if ( ind < idx - 1 ) { /* sign half byte ? */ if ( point-- == 0 ) buffer[pos++] = '.'; buffer[pos++] = '0'+ bottom; } } buffer[pos] = 0; break ; case SQL_TYP_FLOAT: /* double */ case SQL_TYP_NFLOAT: /* double with null indicator */ sprintf(buffer, "%e", * (double *) dataptr) ; break ; case SQL_TYP_CHAR: /* fixed length character string */ case SQL_TYP_NCHAR: /* fixed length character string with null indicator */ strncpy(buffer, dataptr, collen); buffer[collen] = 0; collen--; while ((collen >=0) && (buffer[collen] == ' ')) buffer[collen--] = 0; break; case SQL_TYP_LSTR: /* varying length character string, 1-byte length */ case SQL_TYP_NLSTR: /* varying length character string, 1-byte length, with null indicator */ /* Initialize blen to the value the length field in the varchar data structure. */ collen = *dataptr; /* Advance the data pointer beyond the length field */ dataptr+=sizeof(char); strncpy(buffer, dataptr, collen); buffer[collen] = 0; break ; case SQL_TYP_CSTR: /* null terminated varying length character string */ case SQL_TYP_NCSTR: /* null terminate varying length character string with null indicator */ strcpy(buffer, dataptr); break ; default: buffer[0] = 0; } return buffer; } /* COMMENT OUT OFF */ 第四节 ORACLE数据库的嵌入SQL语言 4.1 基本的SQL语句 4.1.1宿主变量和指示符 1)、声明方法 同其他数据库管理器一样,ORACLE使用宿主变量传递数据库中的数据和状态信息到应用程序,应用程序也通过宿主变量传递数据到ORACLE数据库。根据上面两种功能,宿主变量分为输出宿主变量和输入宿主变量。在SELECT INTO和FETCH语句之后的宿主变量称作“输出宿主变量”,这是因为从数据库传递列数据到应用程序。除了SELECT INTO和FETCH语句外的其他SQL语句中的宿主变量,称为“输入宿主变量”。这是因为从应用程序向数据库输入值。如:INSERT、UPDATE等语句。请看下面这个例子: int emp_number; char temp[20]; VARCHAR emp_name[20]; /* get values for input host variables */ printf("Employee number? "); gets(temp); emp_number = atoi(temp); printf("Employee name? "); gets(emp_name.arr); emp_name.len = strlen(emp_name.arr); EXEC SQL INSERT INTO EMP (EMPNO, ENAME) VALUES (:emp_number, :emp_name); 在上面这个例子中,其中的emp_number和emp_name就是宿主变量。值得注意的是,它同其他数据库的区别是,定义宿主变量可以不需要BEGIN DECLARE SECTION和END DECLARE SECTION。 2)、指示符变量 大多数程序设计语言(如C)都不支持NULL。所以对NULL的处理,一定要在SQL中完成。我们可以使用主机指示符变量来解决这个问题。在嵌入式SQL语句中,主变量和指示符变量共同规定一个单独的SQL类型值。指示符变量是一个2字节的整数。 针对输入宿主变量和输出宿主变量,指示变量共有下面几种情况: 同输入宿主变量一起使用时: -1 Oracle将null赋值给列,即宿主变量应该假设为NULL。 >=0 Oracle将宿主变量的实际值赋值给列。 同输出宿主变量一起使用时: -1 表示该列的输出值为NULL。 0 Oracle已经将列的值赋给了宿主变量。列值未做截断。 >0 Oracle将列的值截断,并赋给了宿主变量。指示变量中存放了这个列的实际长度。 -2 Oracle将列的值截断,并赋给了宿主变量。但是这个列的实际长度不能确定。 从数据库中查询数据时,可以使用指示符变量来测试NULL: EXEC SQL SELECT ename, sal INTO :emp_name, :salary FROM emp WHERE :commission INDICATOR :ind_comm IS NULL ... 注意,不能使用关系操作符来比较NULL,这是因为NULL和任何操作都为false。如: EXEC SQL SELECT ename, sal INTO :emp_name, :salary FROM emp WHERE comm = :commission 如果comm列的某些行存在NULL,则该SELECT语句不能返回正确的结果。应该使用下面这个语句完成: EXEC SQL SELECT ename, sal INTO :emp_name, :salary FROM emp WHERE (comm = :commission) OR ((comm IS NULL) AND (:commission INDICATOR :ind_comm IS NULL)); 4.1.2 查询 如果是单行查询,则应该使用SELECT INTO语句。如果是多行查询,应该使用游标或宿主变量数组。如:单行查询的一个例子: EXEC SQL SELECT ename, job, sal + 2000 INTO :emp_name, :job_title, :salary FROM emp WHERE empno = :emp_number; 在嵌入SQL语句中,也可以使用子查询。如: EXEC SQL INSERT INTO emp2 (empno, ename, sal, deptno) SELECT empno, ename, sal, deptno FROM emp WHERE job = :job_title; 4.1.3 修改数据 1)、插入数据 使用INSERT语句插入数据。其语法同ANSI SQL语法类似。如: EXEC SQL INSERT INTO emp (empno, ename, sal, deptno) VALUES (:emp_number, :emp_name, :salary, :dept_number); 2)、更新数据 使用UPDATE语句更新数据。其语法同ANSI SQL语法类似。如: EXEC SQL UPDATE emp SET sal = :salary, comm = :commission WHERE empno = :emp_number; 3)、删除数据 使用DELETE语句删除数据。其语法同ANSI SQL语法类似。如: EXEC SQL DELETE FROM emp WHERE deptno = :dept_number; 4.1.4 游标 用嵌入式SQL语句查询数据分成两类情况。一类是单行结果,一类是多行结果。对于单行结果,可以使用SELECT INTO语句;对于多行结果,你必须使用游标来完成。游标是一个与SELECT语句相关联的符号名,它使用户可逐行访问由ORACLE返回的结果集。使用游标,应该包含以下四个步骤。 1)、定义游标 使用DECLARE语句完成。如: EXEC SQL DECLARE emp_cursor CURSOR FOR SELECT ename FROM emp WHERE deptno = :dept_number; 值得注意的是,不能在同一个文件中定义两个相同名字的游标。游标的作用范围是全局的。 2)、打开游标 使用OPEN语句完成。如: EXEC SQL OPEN emp_cursor; 3)、取一行值 使用FETCH语句完成。如: EXEC SQL FETCH emp_cursor INTO :emp_name; 4)、关闭游标 使用CLOSE语句完成。它完成的功能是:释放资源,如占用内存,锁等。如:EXEC SQL CLOSE emp_cursor; 5)、使用游标修改数据 我们可以使用CURRENT OF子句来完成修改数据。如: EXEC SQL DECLARE emp_cursor CURSOR FOR SELECT ename, sal FROM emp WHERE job = 'CLERK' FOR UPDATE OF sal; ... EXEC SQL OPEN emp_cursor; EXEC SQL WHENEVER NOT FOUND GOTO ... for (;;) { EXEC SQL FETCH emp_cursor INTO :emp_name, :salary; ... EXEC SQL UPDATE emp SET sal = :new_salary WHERE CURRENT OF emp_cursor; } 值得注意的是,在使用CURRENT OF子句来完成修改数据时,在OPEN时会对数据加上排它锁。这个锁直到有COMMIT或ROLLBACK语句时才释放。 以下是使用游标修改数据的一个完整例子: ... /* 定义游标 */ EXEC SQL DECLARE emp_cursor CURSOR FOR SELECT ename, job FROM emp WHERE empno = :emp_number FOR UPDATE OF job; /* 打开游标 */ EXEC SQL OPEN emp_cursor; /* break if the last row was already fetched */ EXEC SQL WHENEVER NOT FOUND DO break; /* 循环取值*/ for (;;) { EXEC SQL FETCH emp_cursor INTO :emp_name, :job_title; /* 更新当前游标所在的行的数据*/ EXEC SQL UPDATE emp SET job = :new_job_title WHERE CURRENT OF emp_cursor; } ... /* 关闭游标 */ EXEC SQL CLOSE emp_cursor; EXEC SQL COMMIT WORK RELEASE; ... 下面这个例子完整演示了静态游标的使用方法。这个例子的作用是,获得部门编号,通过游标来显示这个部门中的所有雇员信息。 #include <stdio.h> /* 声明宿主变量 */ char userid[12] = "SCOTT/TIGER"; char emp_name[10]; int emp_number; int dept_number; char temp[32]; void sql_error(); /*包含SQLCA */ #include <sqlca.h> main() { emp_number = 7499; /* 处理错误*/ EXEC SQL WHENEVER SQLERROR do sql_error("Oracle error"); /* 连接到Oracle数据库*/ EXEC SQL CONNECT :userid; printf("Connected.\n"); /* 声明游标 */ EXEC SQL DECLARE emp_cursor CURSOR FOR SELECT ename FROM emp WHERE deptno = :dept_number; printf("Department number? "); gets(temp); dept_number = atoi(temp); /* 打开游标*/ EXEC SQL OPEN emp_cursor; printf("Employee Name\n"); printf("-------------\n"); /* 循环处理每一行数据,如果无数据,则退出*/ EXEC SQL WHENEVER NOT FOUND DO break; while (1) { EXEC SQL FETCH emp_cursor INTO :emp_name; printf("%s\n", emp_name); } EXEC SQL CLOSE emp_cursor; EXEC SQL COMMIT WORK RELEASE; exit(0); } /错误处理程序*/ void sql_error(msg) char *msg; { char buf[500]; int buflen, msglen; EXEC SQL WHENEVER SQLERROR CONTINUE; EXEC SQL ROLLBACK WORK RELEASE; buflen = sizeof (buf); sqlglm(buf, &buflen, &msglen); printf("%s\n", msg); printf("%*.s\n", msglen, buf); exit(1); } 4.2 嵌入PL/SQL 嵌入PL/SQL和嵌入SQL不同。嵌入PL/SQL提供了很多嵌入SQL不具有的优点,如:更好的性能、更灵活的表达方式。能够自己定义过程和函数。如: PROCEDURE create_dept (new_dname IN CHAR(14), new_loc IN CHAR(13), new_deptno OUT NUMBER(2)) IS BEGIN SELECT deptno_seq.NEXTVAL INTO new_deptno FROM dual; INSERT INTO dept VALUES (new_deptno, new_dname, new_loc); END create_dept; 其中的IN/OUT,表示参数模式。IN是传递参数值到过程,而OUT是从过程传递参数值到调用者。 但是,如果使用这些扩展的功能,也会造成同其他数据库厂商的嵌入SQL的不兼容。 4.3 动态SQL语句 4.3.1 ORACLE动态SQL语句的一些特点 ORACLE DBMS进入市场的时间早于DB2,其动态SQL支持是以IBM的system/R原型为基础的。因此,ORACLE支持的动态SQL与IBM的DB2标准有不同。虽然ORACLE和DB2在很大程度上是兼容的,但是在使用参数标志、SQLDA格式及支持数据类型转换等方面都有差异。 DB2中不允许在PREPARE的动态语句中引用宿主变量,而是用问号来标志语句中的参数,然后用EXECUTE或OPEN语句来规定参数值。ORACLE允许用户用宿主变量规定动态语句中的参数。 而且,ORACLE支持的DESCRIBE语句同DB2有一些区别。如: 从已经PREPARE后的动态查询语句中获得对查询结果列的信息的语句为: EXEC SQL DESCRIBE SELECT LIST FOR qrystmt INTO qry_sqlda; 等价于DB2的: EXEC SQL DESCRIBE qrystmt INTO qry_sqlda; 从已经PREPARE后的动态查询语句中获得对查询参数的说明的语句为: EXEC SQL DESCRIBE BIND LIST FOR qrystmt INTO qry_sqlda; 该ORACLE语句没有对应的DB2语句。用户只能按照当前需要的参数和SQLDA的结构对SQLDA赋值。然后再在OPEN语句或EXECUTE语句中使用SQLDA结构。 4.3.2 使用动态SQL的四种方法 使用动态SQL,共分成四种方法: 方法 支持的SQL语句 1 该语句不包含宿主变量,该语句不是查询语句 2 该语句包含输入宿主变量 ,该语句不是查询语句 3 包含已知数目的输入宿主变量或列的查询 4 包含未知数目的输入宿主变量或列的查询 l方法1:使用EXECUTE IMMEDIATE命令实现,具体语法为: EXEC SQL EXECUTE IMMEDIATE { :host_string | string_literal }; 其中,host_variable和string是存放完整T-SQL语句。 请看下面这个例子。这个例子的作用是执行用户随意输入的合法的SQL语句。 char dyn_stmt[132]; ... for (;;) { printf("Enter SQL statement: "); gets(dyn_stmt); if (*dyn_stmt == '\0') break; /* dyn_stmt now contains the text of a SQL statement */ EXEC SQL EXECUTE IMMEDIATE :dyn_stmt; } ... EXECUTE IMMEDIATE命令的作用是:分析该语句的语法,然后执行该语句。方法1适合于仅仅执行一次的语句。 l方法2:方法支持的语句可以包含输入宿主变量。这个语句首先做PREPARE操作,然后通过EXECUTE执行。PREPARE语句的语法为: EXEC SQL PREPARE statement_name FROM { :host_string | string_literal }; 该语句接收含有SQL语句串的宿主变量,并把该语句送到ORACLE。ORACLE编译语句并生成执行计划。在语句串中包含一个“?”表明参数,当执行语句时,ORACLE需要参数来替代这些“?”。PREPRARE执行的结果是,DBMS用语句名标志准备后的语句。在执行SQL语句时,EXECUTE语句后面是这个语句名。EXECUTE语句的语法为: EXECUTE 语句名 USING 宿主变量 | DESCRIPTOR 描述符名 它的作用是,请求ORACLE执行PREPARE语句准备好的语句。当要执行的动态语句中包含一个或多个参数标志时,在EXECUTE语句必须为每一个参数提供值。这样的话,EXECUTE语句用宿主变量值逐一代替准备语句中的参数标志(“?”或其他占位符),从而,为动态执行语句提供了输入值。 使用主变量提供值,USING子句中的主变量数必须同动态语句中的参数标志数一致,而且每一个主变量的数据类型必须同相应参数所需的数据类型相一致。各主变量也可以有一个伴随主变量的指示符变量。当处理EXECUTE语句时,如果指示符变量包含一个负值,就把NULL值赋予相应的参数标志。除了使用主变量为参数提供值,也可以通过SQLDA提供值。 请看下面这个例子。这个例子的作用是删除用户指定的雇员信息。 ... int emp_number INTEGER; char delete_stmt[120], search_cond[40];; ... strcpy(delete_stmt, "DELETE FROM EMP WHERE EMPNO = :n AND "); printf("Complete the following statement's search condition--\n"); printf("%s\n", delete_stmt); gets(search_cond); strcat(delete_stmt, search_cond); EXEC SQL PREPARE sql_stmt FROM :delete_stmt; for (;;) { printf("Enter employee number: "); gets(temp); emp_number = atoi(temp); if (emp_number == 0) break; EXEC SQL EXECUTE sql_stmt USING :emp_number; } l方法三:是指查询的列数或输入宿主变量数在预编译时已经确定,但是数据库中的对象,如表、列名等信息未确定。这些对象名不能是宿主变量。这时,必须通过以下语句来完成: PREPARE statement_name FROM { :host_string | string_literal }; DECLARE cursor_name CURSOR FOR statement_name; OPEN cursor_name [USING host_variable_list]; FETCH cursor_name INTO host_variable_list; CLOSE cursor_name; 如:下面这个例子演示用方法3完成动态查询: char select_stmt[132] = "SELECT MGR, JOB FROM EMP WHERE SAL < :salary"; EXEC SQL PREPARE sql_stmt FROM :select_stmt; EXEC SQL DECLARE emp_cursor CURSOR FOR sql_stmt; EXEC SQL OPEN emp_cursor USING :salary; EXEC SQL FETCH emp_cursor INTO :mgr_number, :job_title; EXEC SQL CLOSE emp_cursor; l方法四:在预编译时,查询的列数或者宿主变量的个数不能确定,因为不知道具体的返回个数,所以不能使用输出宿主变量。这是因为你不知道应该定义多少个宿主变量。这时,就需要SQLDA结构和DESCRIBE命令。SQLDA包含了动态查询的列描述信息。对于输入宿主变量,也可以使用SQLDA来完成不确定的参数说明。要完成方法四,必须通过以下语句来完成: EXEC SQL PREPARE statement_name FROM { :host_string | string_literal }; EXEC SQL DECLARE cursor_name CURSOR FOR statement_name; EXEC SQL DESCRIBE BIND VARIABLES FOR statement_name INTO bind_descriptor_name; EXEC SQL OPEN cursor_name [USING DESCRIPTOR bind_descriptor_name]; EXEC SQL DESCRIBE [SELECT LIST FOR] statement_name INTO select_descriptor_name; EXEC SQL FETCH cursor_name USING DESCRIPTOR select_descriptor_name; EXEC SQL CLOSE cursor_name; 在上述语句中,DESCRIBE SELECT LIST的作用是将PREPARE后的动态查询语句的列名、数据类型、长度等信息保存在SQLDA中。DESCRIBE BIND VARIABLES的作用是,检查PREPARE后的动态查询语句的每个占位符的名字、数据类型、长度等信息。并将它存放在SQLDA中,然后,使用SQLDA提示用户数据参数值。 值得注意的是,方法之间可以混合使用。如:在一个查询中,列的个数确定,但是查询中的占位符不确定,这时,你可以结合方法3和方法4,即使用方法3的FETCH语句和方法4的OPEN语句,如:EXEC SQL FETCH emp_cursor INTO host_variable_list; 反之,如果查询中占位符的个数确定,而列数不确定,则你可以使用方法3的OPEN语句,如:EXEC SQL OPEN cursor_name [USING host_variable_list]; 这里,我们讲解的是嵌入SQL,对于嵌入PL/SQL,有一些区别。简单来说,主要有两点: l预编译器将PL/SQL块中的所有宿主变量都作为输入宿主变量。 l不能对PL/SQL块使用FETCH命令。 l占位符不用声明,可以是任何名字。如: INSERT INTO emp (empno, deptno) VALUES (:e, :d) DELETE FROM dept WHERE deptno = :num OR loc = :loc 其中的e、d、num和loc就是占位符。 4.3.3 SQLDA SQLDA存放了输出数据的信息,或存放了输入数据的信息。可以使用SQLSQLDAAlloc(runtime_context, size, name_length, ind_name_length)来分配空间。 SQLDA结构的定义存放在sqlda.h文件中。它的内容为: struct SQLDA { long N; /* Descriptor size in number of entries */ char **V; /*Ptr to Arr of addresses of main variables */ long *L; /* Ptr to Arr of lengths of buffers */ short *T; /* Ptr to Arr of types of buffers */ short **I; /* Ptr to Arr of addresses of indicator vars */ long F; /* Number of variables found by DESCRIBE */ char **S; /* Ptr to Arr of variable name pointers */ short *M; /* Ptr to Arr of max lengths of var. names */ short *C; /* Ptr to Arr of current lengths of var. names */ char **X; /* Ptr to Arr of ind. var. name pointers */ short *Y; /* Ptr to Arr of max lengths of ind. var. names */ short *Z; /* Ptr to Arr of cur lengths of ind. var. names */ }; 其中,上述变量的含义为: lN:可以容纳的列的最大数目或参数的最大数目。它对应于DB2的SQLDA的SQLN字段。 lF:当前SQLDA中的实际列数或参数个数。它对应于DB2的SQLDA的SQLD字段。 lT:指明数据类型。它对应于DB2的SQLVAR结构中的SQLTYPE字段。 lV:指向字符数组。该字符数组可能是列的数据,或传送参数的数据。它对应于DB2的SQLVAR结构中的SQLDATA字段。 lL:给出列或参数值的长度。它对应于DB2的SQLVAR结构中的SQLLEN字段。 lI:指向指示符变量,标志数据是否为NULL。它对应于DB2的SQLVAR结构中的SQLIND字段。 lS:指向存放列名或参数名的字符数组。它对应于DB2的SQLVAR结构中的SQLNAME结构的data[]。 lM:指向一个整数,该整数是S的申请长度。在DB2中,SQLVAR结构中的SQLNAME结构的data[30]的大小是固定的,即是30。而ORACLE中是可变的。其大小为M指向的整数。 lC:指向一个整数,该整数是S的实际长度。它对应于DB2的SQLVAR结构中的SQLNAME结构的 length。 lX:指向一个字符数组。该字符数组存放了指示符变量的名称,指示符变量表示传递的参数是否为NULL。DB2中无相应的对应字段。这个缓冲区仅仅供DESCRIBE BIND LIST语句使用。 lY:指向一个整数,该整数是X的申请的最大长度。DB2中无相应的对应字段。 lZ:指向一个整数,该整数是X的实际长度。DB2中无相应的对应字段。 ORACLE的数据类型分成两种情况:内部数据类型和外部数据类型。ORACLE的内部数据类型是ORACLE在数据库中存放数据的类型,在使用DESCRIBE SELECT LIST命令,就返回内部数据类型代码。下表是所有的内部数据类型: Oracle 内部数据类型 代码 VARCHAR2 1 NUMBER 2 LONG 8 ROWID 11 DATE 12 RAW 23 LONG RAW 24 CHARACTER (or CHAR) 96 MLSLABEL 106 外部数据类型是输入宿主变量和输出宿主变量存放数据的类型。DESCRIBE BIND VARIABLES命令将SQLDA中的数据类型代码置为0。所以,必须在OPEN语句前设置外部数据类型代码,以告诉ORACLE是什么外部数据类型。下表是具体的外部数据类型: 外部数据类型 代码 C数据类型 VARCHAR2 1 char[n] NUMBER 2 char[n] (n 22) INTEGER 3 int FLOAT 4 float STRING 5 char[n+1] VARNUM 6 char[n] (n 22) DECIMAL 7 float LONG 8 char[n] VARCHAR 9 char[n+2] ROWID 11 char[n] DATE 12 char[n] VARRAW 15 char[n] RAW 23 unsigned char[n] LONG RAW 24 unsigned char[n] UNSIGNED 68 unsigned int DISPLAY 91 char[n] LONG VARCHAR 94 char[n+4] LONG VARRAW 95 unsigned char[n+4] CHAR 96 char[n] CHARF 96 char[n] CHARZ 97 char[n+1] MLSLABEL 106 char[n] 当ORACLE从用户程序中接收参数值并向用户程序传送查询结果时,就在自己的内部数据格式与它所运行的计算机系统的数据格式之间自动进行数据转换。DESCRIBE SELECT L IST命令可以返回ORACLE的内部数据类型。对于字符数据,内部数据类型同外部数据类型是相一致的;而有些内部数据类型对应到外部数据类型后,导致处理复杂化,如:你想将NUMBER数据类型的值处理为C中的FLOAT,那么你可以设置相应的T值为FLOAT(4)和L值为FLOAT的长度。在FETCH时,ORACLE自动在内部数据类型和外部数据类型之间转换。 在DB2的SQLVAR结构中,列的说明信息、数据等存放在一个单独的sqlvar结构中。而在ORACLE数据库中,不存在一个单独的结构来说明每列的信息。而是通过数组的方式实现。如下图所示,描述了1个输入参数,参数名为bonus。假设的最大参数个数为3。 SQLDA结构 N=3 V L T I F=1 describe设置 S N C X Y Z 图6-5 SQLDA结构示例
下面这个例子是一个adhoc程序。用户输入任何合法的SQL语句(可以带参数),该程序能够处理这个语句,并打印出结果。这个例子非常经典,说明使用SQLDA的两个功能。 #include <stdio.h> #include <string.h> #include <setjmp.h>
/* 列的最大数目或宿主变量的最大个数*/ #define MAX_ITEMS 40 /* 列名的最大长度或指示符的最大长度*/ #define MAX_VNAME_LEN 30 #define MAX_INAME_LEN 30 #ifndef NULL #define NULL 0 #endif char *dml_commands[] = {"SELECT", "select", "INSERT", "insert", "UPDATE", "update", "DELETE", "delete"}; EXEC SQL BEGIN DECLARE SECTION; char dyn_statement[1024]; EXEC SQL VAR dyn_statement IS STRING(1024); EXEC SQL END DECLARE SECTION; EXEC SQL INCLUDE sqlca; EXEC SQL INCLUDE sqlda; SQLDA *bind_dp; SQLDA *select_dp; extern SQLDA *SQLSQLDAAlloc(); extern void sqlnul(); /* Define a buffer to hold longjmp state info. */ jmp_buf jmp_continue; /* A global flag for the error routine. */ int parse_flag = 0; main() { int oracle_connect(); int alloc_descriptors(); int get_dyn_statement(); int set_bind_variables(); int process_select_list(); int i; /*连接到数据库 */ if (oracle_connect() != 0) exit(1); /* 为SQLDA分配空间*/ if (alloc_descriptors(MAX_ITEMS, MAX_VNAME_LEN, MAX_INAME_LEN) != 0) exit(1); /* 处理SQL 语句*/ for (;;) { i = setjmp(jmp_continue); /* 获取SQL语句。输入"exit"表示退出 */ if (get_dyn_statement() != 0) break; /* 对该SQL语句做PREPARE操作 */ EXEC SQL WHENEVER SQLERROR DO sql_error(); parse_flag = 1; /* Set a flag for sql_error(). */ EXEC SQL PREPARE S FROM :dyn_statement; parse_flag = 0; /* Unset the flag. */ /*声明游标*/ EXEC SQL DECLARE C CURSOR FOR S; /* 提示用户输入参数值*/ set_bind_variables(); /* 打开游标 */ EXEC SQL OPEN C USING DESCRIPTOR bind_dp; /* 处理语句,并输出结果*/ process_select_list(); /*输出处理的行数. */ for (i = 0; i < 8; i++) { if (strncmp(dyn_statement, dml_commands[i], 6) == 0) { printf("\n\n%d row%c processed.\n", sqlca.sqlerrd[2], sqlca.sqlerrd[2] == 1 ? '\0' : 's'); break; } } } /* end of for(;;) statement-processing loop */ /* 释放申请的空间*/ for (i = 0; i < MAX_ITEMS; i++) { if (bind_dp->V[i] != (char *) 0) free(bind_dp->V[i]); free(bind_dp->I[i]); /* MAX_ITEMS were allocated. */ if (select_dp->V[i] != (char *) 0) free(select_dp->V[i]); free(select_dp->I[i]); /* MAX_ITEMS were allocated. */ } SQLSQLDAFree(SQL_SINGLE_RCTX, bind_dp); SQLSQLDAFree(SQL_SINGLE_RCTX, select_dp); EXEC SQL WHENEVER SQLERROR CONTINUE; /* 关闭游标*/ EXEC SQL CLOSE C; EXEC SQL COMMIT WORK RELEASE; puts("\nHave a good day!\n"); EXEC SQL WHENEVER SQLERROR DO sql_error(); return; } /*连接数据库函数*/ oracle_connect() { EXEC SQL BEGIN DECLARE SECTION; VARCHAR username[128]; VARCHAR password[32]; EXEC SQL END DECLARE SECTION; /提示用户输入用户名*/ printf("\nusername: "); fgets((char *) username.arr, sizeof username.arr, stdin); fflush(stdin); username.arr[strlen((char *) username.arr)-1] = '\0'; username.len = strlen((char *) username.arr); /*提示用户输入口令*/ printf("password: "); fgets((char *) password.arr, sizeof password.arr, stdin); fflush(stdin); password.arr[strlen((char *) password.arr) - 1] = '\0'; password.len = strlen((char *) password.arr); EXEC SQL WHENEVER SQLERROR GOTO connect_error; /*连接数据库*/ EXEC SQL CONNECT :username IDENTIFIED BY :password; printf("\nConnected to ORACLE as user %s.\n", username.arr); return 0; /*连接错误处理*/ connect_error: fprintf(stderr, "Cannot connect to ORACLE as user %s\n", username.arr); return -1; } /*为SQLDA分配空间*/ alloc_descriptors(size, max_vname_len, max_iname_len) int size; int max_vname_len; int max_iname_len; { int i; /*SQLSQLDAAlloc的第一个参数是SQL语句的最大列数或输入宿主变量的最大个数。 *第二个参数,是指列名的最大长度,或参数名的最大长度。 *第三个参数,是指指示符变量名的最大长度。*/ /*给SQLDA分配空间,下面这个SQLDA用于输入参数*/ if ((bind_dp = SQLSQLDAAlloc(SQL_SINGLE_RCTX, size, max_vname_len, max_iname_len)) == (SQLDA *) 0) { fprintf(stderr, "Cannot allocate memory for bind descriptor."); return -1; /* Have to exit in this case. */ } /*给SQLDA分配空间,下面这个SQLDA用于动态查询*/ if ((select_dp = SQLSQLDAAlloc (SQL_SINGLE_RCTX, size, max_vname_len, max_iname_len)) == (SQLDA *) 0) { fprintf(stderr,"Cannot allocate memory for select descriptor."); return -1; } /*设置最大的列数,或最大的变量数*/ select_dp->N = MAX_ITEMS; /* 给存放指示符变量值和存放数据的变量申请空间。*/ for (i = 0; i < MAX_ITEMS; i++) { bind_dp->I[i] = (short *) malloc(sizeof (short)); select_dp->I[i] = (short *) malloc(sizeof(short)); bind_dp->V[i] = (char *) malloc(1); select_dp->V[i] = (char *) malloc(1); } return 0; } /*获得SQL语句,可略看*/ get_dyn_statement() { char *cp, linebuf[256]; int iter, plsql; int help(); for (plsql = 0, iter = 1; ;) { if (iter == 1) { printf("\nSQL> "); dyn_statement[0] = '\0'; } fgets(linebuf, sizeof linebuf, stdin); fflush(stdin); cp = strrchr(linebuf, '\n'); if (cp && cp != linebuf) *cp = ' '; else if (cp == linebuf) continue; if ((strncmp(linebuf, "EXIT", 4) == 0) || (strncmp(linebuf, "exit", 4) == 0)) { return -1; } else if (linebuf[0] == '?' || (strncmp(linebuf, "HELP", 4) == 0) || (strncmp(linebuf, "help", 4) == 0)) { help(); iter = 1; continue; } if (strstr(linebuf, "BEGIN") || (strstr(linebuf, "begin"))) { plsql = 1; } strcat(dyn_statement, linebuf); if ((plsql && (cp = strrchr(dyn_statement, '/'))) || (!plsql && (cp = strrchr(dyn_statement, ';')))) { *cp = '\0'; break; } else { iter++; printf("%3d ", iter); } } return 0; } /*设置宿主变量的信息*/ set_bind_variables() { int i, n; char bind_var[64]; /* 通过DESCRIBE语句,将处理语句的参数名、数据类型等信息存放在bind_dp中*/ EXEC SQL WHENEVER SQLERROR DO sql_error(); bind_dp->N = MAX_ITEMS; /* Init. count of array elements. */ EXEC SQL DESCRIBE BIND VARIABLES FOR S INTO bind_dp; /* F小于0,表示SQLSQLDAAlloc()分配的空间不够,即实际参数的个数超出预算的最大值. */ if (bind_dp->F < 0) { printf("\nToo many bind variables (%d), maximum is %d.\n", -bind_dp->F, MAX_ITEMS); return; } /* 将N(最大值)设置为实际的参数个数*/ bind_dp->N = bind_dp->F; /* 提示用户输入参数值,并设置SQLDA的其他相关值,如:长度等。*/ for (i = 0; i < bind_dp->F; i++) { printf ("\nEnter value for bind variable %.*s: ", (int)bind_dp->C[i], bind_dp->S[i]); fgets(bind_var, sizeof bind_var, stdin); /* 获得长度,去掉NULL结束符 */ n = strlen(bind_var) - 1; /*设置参数长度 */ bind_dp->L[i] = n; /* 分配存放参数数据的内存空间 */ bind_dp->V[i] = (char *) realloc(bind_dp->V[i], (bind_dp->L[i] + 1)); /* 将数据放在这个内存空间中 */ strncpy(bind_dp->V[i], bind_var, n); /* 设置指示符变量的值*/ if ((strncmp(bind_dp->V[i], "NULL", 4) == 0) || (strncmp(bind_dp->V[i], "null", 4) == 0)) *bind_dp->I[i] = -1; else *bind_dp->I[i] = 0; /* 设置数据类型为CHAR,ORACLE会根据列的数据类型自动转换 */ bind_dp->T[i] = 1; } } /*处理语句*/ process_select_list() { int i, null_ok, precision, scale; /*如果不是查询语句,则设置F(即返回的列数)为0*/ if ((strncmp(dyn_statement, "SELECT", 6) != 0) && (strncmp(dyn_statement, "select", 6) != 0)) { select_dp->F = 0; return; } /* 如果是SELECT语句,则通过DESCRIBE函数返回列名、数据类型、长度和是否为NULL标志*/ select_dp->N = MAX_ITEMS; EXEC SQL DESCRIBE SELECT LIST FOR S INTO select_dp; /* 如果F小于0。则表示比预定的列数要多。*/ if (select_dp->F < 0) { printf ("\nToo many select-list items (%d), maximum is %d\n", -(select_dp->F), MAX_ITEMS); return; } /* 设置最大列数为实际列数*/ select_dp->N = select_dp->F; /* 为每列分配空间。 SQLNumberPrecV6() 函数的作用是从select_dp->L[i]获得精度和长度。 SQLColumnNullCheck() 函数的作用是检查该列是否为NULL。*/ printf ("\n"); for (i = 0; i < select_dp->F; i++) { /* 关闭最高位*/ SQLColumnNullCheck (&(select_dp->T[i]), &(select_dp->T[i]), &null_ok); switch (select_dp->T[i]) { case 1 : /* CHAR */ break; case 2 : /* NUMBER ,获得精度和范围*/ SQLNumberPrecV6 (SQL_SINGLE_RCTX, &(select_dp->L[i]), &precision, &scale); /* 如果精度为0,则设置为最大值40 */ if (precision == 0) precision = 40; if (scale > 0) select_dp->L[i] = sizeof(float); else select_dp->L[i] = sizeof(int); break; case 8 : /* LONG*/ select_dp->L[i] = 240; break; case 11 : /* ROWID datatype */ select_dp->L[i] = 18; break; case 12 : /* DATE datatype */ select_dp->L[i] = 9; break; case 23 : /* RAW datatype */ break; case 24 : /* LONG RAW datatype */ select_dp->L[i] = 240; break; } /* 申请空间给SQLDA来存放数据*/ if (select_dp->T[i] != 2) select_dp->V[i] = (char *) realloc(select_dp->V[i], select_dp->L[i] + 1); else select_dp->V[i] = (char *) realloc(select_dp->V[i], select_dp->L[i]); /* 输出列名*/ if (select_dp->T[i] == 2) if (scale > 0) printf ("%.*s ",select_dp->L[i]+3, select_dp->S[i]); else printf ("%.*s ", select_dp->L[i], select_dp->S[i]); else printf ("%-.*s ", select_dp->L[i], select_dp->S[i]); /* 除了LONG RAW和NUMBER,其他数据类型转换为字符型数据类型*/ if (select_dp->T[i] != 24 && select_dp->T[i] != 2) select_dp->T[i] = 1; /* 将 NUMBER数据类型转换为浮点型数据类型或int数据类型*/ if (select_dp->T[i] == 2) if (scale > 0) select_dp->T[i] = 4; /* float */ else select_dp->T[i] = 3; /* int */ } printf ("\n\n"); /* 取出每一行数据*/ EXEC SQL WHENEVER NOT FOUND GOTO end_select_loop; for (;;) { EXEC SQL FETCH C USING DESCRIPTOR select_dp; /*输出列数据。除了float和int数据类型,其他数据类型都被转为字符型*/ for (i = 0; i < select_dp->F; i++) { if (*select_dp->I[i] < 0) if (select_dp->T[i] == 4) printf ("%-*c ",(int)select_dp->L[i]+3, ' '); else printf ("%-*c ",(int)select_dp->L[i], ' '); else if (select_dp->T[i] == 3) /* int datatype */ printf ("%*d ", (int)select_dp->L[i], *(int *)select_dp->V[i]); else if (select_dp->T[i] == 4)/* float datatype*/ printf ("%*.2f ", (int)select_dp->L[i], *(float *)select_dp->V[i]); else /* character string */ printf ("%-*s ", (int)select_dp->L[i], select_dp->V[i]); } printf ("\n"); } end_select_loop: return; } help() { puts("\n\nEnter a SQL statement or a PL/SQL block"); puts("at the SQL> prompt."); puts("Statements can be continued over several"); puts("lines, except within string literals."); puts("Terminate a SQL statement with a semicolon."); puts("Terminate a PL/SQL block"); puts("(which can contain embedded semicolons)"); puts("with a slash (/)."); puts("Typing \"exit\" (no semicolon needed)"); puts("exits the program."); puts("You typed \"?\" or \"help\""); puts(" to get this message.\n\n"); } sql_error() { int i; /* ORACLE error handler */ printf ("\n\n%.70s\n",sqlca.sqlerrm.sqlerrmc); if (parse_flag) printf("Parse error at character offset %d.\n", sqlca.sqlerrd[4]); EXEC SQL WHENEVER SQLERROR CONTINUE; EXEC SQL ROLLBACK WORK; longjmp(jmp_continue, 1); } SQLDA中的sqldata存放着指向数据的地址。你可以认为,如果查询出的数据是整数(如:258),那么这个地址是指向整数的地址(也就是说,INTEL是高位在后,低位在前。所以第一个字节为00000001,第二个字节为00000010),如果查询出的数据是字符(‘2’),那么这个地址是指向字符的地址(也就是说,第一个字节为00110010—2的ASCII码)。又因为,sqldata声明的是指向字符的指针,所以,你必须按照不同的数据类型做转换,即:对于整数,应该是(*(int *)sqldata),告诉系统,sqldata目前指向的数据应该按照整数来解释。如果按照字符来解释,那么第一个字符是ASCII值为1的字符,显然不正确。从数据库向sqldata赋值时,是直接赋值。如:*p=*q,*(P+1)=*(Q+1)。如果从数据库查询出数据为258,则存放在sqlda中也是258,存放格式为:第一个字节为00000001,第二个字节为00000010。你可以执行以下语句,来体会上述论述。 #include <stdio.h> main() { int li_i; int * lp_int; char * lp_char; char lc_char; li_i=258; lp_char=malloc(10); lp_int=lp_char; *lp_int=258; /*(lp_char+2)='\0';*/ printf("*lp_char=%d\n",*((int *)lp_char)); printf("*lp_char=%s\n",lp_char); return; } 第五节INFORMIX的嵌入SQL/C语言 5.1 一个简单的入门例子 例1、查询customer表中所有lname的第一个字符小于C的顾客信息。 #include <stdio.h> /*定义两个常量*/ EXEC SQL define FNAME_LEN 15; EXEC SQL define LNAME_LEN 15; main() { /*声明宿主变量*/ EXEC SQL BEGIN DECLARE SECTION; char fname[ FNAME_LEN + 1 ]; char lname[ LNAME_LEN + 1 ]; EXEC SQL END DECLARE SECTION; printf( "DEMO1 Sample ESQL Program running.\n\n"); /*出错处理,如果返回错误信息,则停止该程序*/ EXEC SQL WHENEVER ERROR STOP; /*连接到stores7数据库*/ EXEC SQL connect to 'stores7'; /*声明一个游标*/ EXEC SQL DECLARE democursor cursor for select fname, lname into :fname, :lname from customer where lname < 'C'; /*打开游标*/ EXEC SQL open democursor; /*如果SQLSTATE不等于“00”,那么表示到达了数据集的尾部(02),或者产生了错误(大于02)*/ for (;;) { EXEC SQL fetch democursor; if (strncmp(SQLSTATE, "00", 2) != 0) break; printf("%s %s\n",fname, lname); } /*打印错误信息*/ if (strncmp(SQLSTATE, "02", 2) != 0) printf("SQLSTATE after fetch is %s\n", SQLSTATE); /*关闭游标*/ EXEC SQL close democursor; /*释放游标占用的资源*/ EXEC SQL free democursor; /*断开数据库服务器的连接*/ EXEC SQL disconnect current; printf("\nDEMO1 Sample Program over.\n\n"); } 从上面这个例子,我们看出嵌入SQL的基本特点是: 1、每条嵌入式SQL语句都用EXEC SQL开始,表明它是一条SQL语句。这也是告诉预编译器在EXEC SQL和“;”之间是嵌入SQL语句。 2、如果一条嵌入式SQL语句占用多行,在C程序中可以用续行符“\”,在Fortran中必须有续行符。其他语言也有相应规定。 3、每一条嵌入SQL语句都有结束符号,如:在C中是“;”。 4、嵌入SQL语句的关键字不区分大小写。 5、可以使用“/*….*/”来添加注释。 从上面这个例子看出,INFORMIX数据库的嵌入SQL语句的格式同其他数据库基本相同。但是,它也有它自己本身的一些特点。本节把重点放在INFORMIX数据库所独有的一些语句或处理方式。 5.2 宿主变量 宿主变量就是在嵌入式SQL语句中引用主语言说明的程序变量。如: EXEC SQL connect to :hostvar; 1)、定义宿主变量 方法1:采用BEGIN DECLARE SECTION 和END DECLARE SECTION之间给主变量说明。如: EXEC SQL BEGIN DECLARE SECTION; char fname[ FNAME_LEN + 1 ]; char lname[ LNAME_LEN + 1 ]; EXEC SQL END DECLARE SECTION; 方法2:在每个变量的数据类型前加上“$”。如: $int hostint; $double hostdbl; ESQL/C对宿主变量的大小写敏感。但是,ESQL/C的关键字、语句标志符、游标名大小写不敏感。在SQL语句中,除了使用“:”来标志宿主变量外,还可以使用“$”。当然,“:”是ANSI标准。如:EXEC SQL connect to $hostvar。对于注释,可以使用“--”,也可以使用标准的“/*…*/”。 2)、宿主变量和NULL 方法1:使用指示符变量。 方法2:使用函数risnull()和rsetnull()。 3)、指示符变量 大多数程序设计语言(如C)都不支持NULL。所以对NULL的处理,一定要在SQL中完成。我们可以使用主机指示符变量来解决这个问题。在嵌入式SQL语句中,宿主变量和指示符变量共同规定一个单独的SQL类型值。指示变量和前面宿主变量之间用一个空格相分隔。如: EXEC SQL select lname, company into :name INDICATOR :nameind, :comp INDICATOR :compind nameind是name变量的指示符,而compind是comp变量的指示符。 可以通过以下三种方法使用指示符变量: 方法1、使用INDICATOR关键字。 :hostvar INDICATOR :indvar 方法2、 :hostvar :indvar 方法3、使用$符号。 $hostvar $indvar。 无论采用哪种方法,都是实现指示符变量的作用。即:当宿主变量hostvar应该返回NULL时,指示符变量为-1。当宿主变量hostvar应该返回不是NULL而且无需截断时,指示符变量为0。当返回值太大而需要截断时,指示符变量是截断前数据的长度。SQLSTATE会返回01004错误信息。请看下面这个例子: EXEC SQL BEGIN DECLARE SECTION; char name[16]; char comp[20]; short nameind; short compind; EXEC SQL END DECLARE SECTION; EXEC SQL select lname, company into :name INDICATOR :nameind, :comp INDICATOR :compind from customer where customer_num = 105; 如果对应105的company为NULL,则compind小于0,如果lname的结果大于15个字节,那么name包含前15个字符。 4)、宿主变量的数据类型 INFROMIX ESQ/C的宿主变量数据类型除了标准C的数据类型外,可以是它自己定义的数据类型。如: lvarchar数据类型 EXEC SQL BEGIN DECLARE SECTION; varchar varc_name[n + 1]; EXEC SQL END DECLARE SECTION; lint8数据类型 EXEC SQL BEGIN DECLARE SECTION; int8 int8_var1; ifx_int8_t int8_var2; EXEC SQL BEGIN DECLARE SECTION; lfixchar数据类型 EXEC SQL BEGIN DECLARE SECTION; boolean flag; fixchar my_boolflag; int id; EXEC SQL END DECLARE SECTION; lDecimal数据类型 #define DECSIZE 16 struct decimal { short dec_exp; short dec_pos; short dec_ndgts; char dec_dgts[DECSIZE]; }; typedef struct decimal dec_t; lDatetime数据类型 EXEC SQL include datetime; EXEC SQL BEGIN DECLARE SECTION; datetime year; /* will cause an error */ datetime year to day year, today; /* ambiguous */ EXEC SQL END DECLARE SECTION; lInterval hour等数据类型 EXEC SQL BEGIN DECLARE SECTION; interval day(3) to day accrued_leave, leave_taken; interval hour to second race_length; interval scheduled; EXEC SQL END DECLARE SECTION; l其他数据类型 EXEC SQL BEGIN DECLARE SECTION; loc_t my_simple_lo; EXEC SQL END DECLARE SECTION; my_simole_lo.loc_loctype = LOCMEMORY; l在INFORMIX数据库中,'\0'表示为NULL。如: id = 1; flag = '\0'; /* valid boolean assignment to FALSE */ EXEC SQL insert into table2 values (:id, :flag); /* inserts FALSE */ 在以SQL为基础的DBMS支持的数据类型与程序设计语言支持的数据类型之间有很大差别。如果你通过宿主变量从数据库取值,或者通过宿主变量向数据库插入值,都存在数据类型转换的问题。 下表列出了C的数据类型、ESQL/C定义的数据类型和SQL数据类型的兼容关系: SQL数据类型 ESQL/C定义的数据类型 C的数据类型 BOOLEAN boolean BYTE loc_t CHAR(n) CHARACTER(n) fixchar [n] 或string [n+1] char [n + 1] 或 char * DATE date 4-byte integer DATETIME datetime或ordtime_t DECIMAL decimal或dec_t DEC decimal或dec_t NUMERIC decimal或dec_t MONEY decimal或dec_t FLOAT double DOUBLE double PRECISION double INT8 int8或ifx_int8_t INTEGER 4-byte integer INT 4-byte integer INTERVAL interval or intrvl_t LVARCHAR lvarchar char [n + 1] orchar * NCHAR(n) fixchar [n] orstring [n+1] char [n + 1] orchar * NVARCHAR(m) varchar[m+1] orstring [m+1] char [m+1] SERIAL 4-byte integer SERIAL8 int8 or ifx_int8_t SMALLFLOAT float REAL float SMALLINT 2-byte integer TEXT loc_t VARCHAR(m,x) varchar[m+1] or string [m+1] char d[m+1] BLOB ifx_lo_t CLOB ifx_lo_t LIST(e) collection MULTISET(e) collection Opaque data type lvarchar,fixed binary或var binary ROW(...) row SET(e) collection 下表是INFORMIX数据库服务器支持的数据类型和类型代码: SQL数据类型 类型代码 类型代码值 CHAR SQLCHAR 0 SMALLINT SQLSMINT 1 INTEGER SQLINT 2 FLOAT SQLFLOAT 3 SMALLFLOAT SQLSMFLOAT 4 DECIMAL SQLDECIMAL 5 SERIAL SQLSERIAL 6 DATE SQLDATE 7 MONEY SQLMONEY 8 DATETIME SQLDTIME 10 BYTE SQLBYTES 11 TEXT SQLTEXT 12 VARCHAR SQLVCHAR 13 INTERVAL SQLINTERVAL 14 NCHAR SQLNCHAR 15 NVARCHAR SQLNVCHAR 16 INT8 SQLINT8 17 SERIAL8 SQLSERIAL8 18 LVARCHAR SQLLVARCHAR 43 BOOLEAN SQLBOOL 45 SET SQLSET 19 MULTISET SQLMULTISET 20 LIST SQLLIST 21 ROW SQLROW 22 Varying-length opaqueType SQLUDTVAR 40 Fixed-length opaque type SQLUDTFIXED 41 SENDRECV (client-side only) SQLSENDRECV 44 下表是ESQL/C定义的数据类型和类型代码,这些定义存放在各个头文件中。
ESQL/C数据类型 类型代码 类型代码值 char CCHARTYPE 100 short int CSHORTTYPE 101 int4 CINTTYPE 102 long CLONGTYPE 103 float CFLOATTYPE 104 double CDOUBLETYPE 105 dec_t或decimal CDECIMALTYPE 107 fixchar CFIXCHARTYPE 108 string CSTRINGTYPE 109 date CDATETYPE 110 dec_t或decimal CMONEYTYPE 111 datetime或dtime_t CDTIMETYPE 112 loc_t CLOCATORTYPE 113 varchar CVCHARTYPE 114 intrvl_t 或interval CINVTYPE 115 char CFILETYPE 116 int8 CINT8TYPE 117 collection (Universal Data Option) CCOLTYPE 118 lvarchar CLVCHARTYPE 119 fixed binary CFIXBINTYPE 120 var binary (Universal Data Option) CVARBINTYPE 121 boolean CBOOLTYPE 122 row (Universal Data Option) CROWTYPE 123 INFORMIX的ESQL/C提供了很多函数来处理数据类型,这些函数的参数就是ESQL/C定义的数据类型。如:dectoasc()的作用是转换数据类型是decimal的值为ASCII。 5.3 嵌入SQL的处理过程 INFORMIX的预编译器为esql。嵌入SQL包含一些组件:嵌入SQL的库文件,提供访问数据库服务器、操作各种数据类型、出错信息的处理等函数。嵌入SQL的头文件(UNIX环境:$INFORMIXDIR/incl/esql下,WINDOWS环境:%INFORMIXDIR%\incl\esql下),提供程序用的数据结构、常数和宏的定义信息。Esql是预编译器。UNIX系统下,是finderr程序获得INFORMIX的错误信息,WINDOWS平台下是find error获得错误信息。还有一些GLS locale文件,提供一些特定的locale信息。在WINDOWS平台下,还有另外一些文件,如:setnet32、ilogin、regcopy、esqlmf程序。 创建嵌入SQL/C的程序的一般步骤:程序的后缀可以是.ec或.ecp。 1、定义宿主变量。 2、访问数据库。 3、操作。 4、完成后,使用esql命令来预编译。如:esql demo1.ec。在预编译后,程序中只有C语言语句,它们都可以为C语言的编译器所识别。所以,可以按照一般的方法进行编译和连接,但在将SQL语句转换以后,在C语言程序中,又引入了许多一般的C语言系统所没有的结构、变量和函数,因此应该设置INCLUDE和LIB的设置。最后生成的可执行文件。 5.4 动态SQL语言 所谓静态SQL的编程方法,就是指在预编译时SQL语句已经基本确定,即访问的表或视图名、访问的列等信息已经确定。但是,有时整个SQL语句要到执行的时候才能确定下来,而且SQL语句所访问的对象也要到执行时才能确定。这就需要通过动态SQL语句完成。动态SQL语句的处理步骤是: 1、组合SQL语句。 2、PREPARE。PREPARE语句是动态SQL语句独有的语句。其语法为: PREPARE 语句名 FROM 宿主变量|字符串 该语句接收含有SQL语句串的宿主变量,并把该语句送到DBMS。DBMS编译语句并生成执行计划。在语句串中包含一个“?”表明参数,当执行语句时,DBMS需要参数来替代这些“?”。PREPRARE执行的结果是,DBMS用语句名标志编译后的语句。在执行SQL语句时,EXECUTE语句后面是这个语句名。请看下面这个例子: EXEC SQL prepare slct_id from 'select company from customer where customer_num = ?'; 可以通过SQLCA检查PREPARE操作是否成功。 3、EXECUTE或OPEN。 EXECUTE语句的语法如下: EXECUTE 语句名 USING 宿主变量 | DESCRIPTOR 描述符名 它的作用是,请求DBMS执行PREPARE语句准备好的语句。当要执行的动态语句中包含一个或多个参数标志时,在EXECUTE语句必须为每一个参数提供值。这样的话,EXECUTE语句用宿主变量值逐一代替准备语句中的参数标志(“?”),从而,为动态执行语句提供了输入值。 如果是多行查询,则使用游标,使用OPEN USING语句传递参数;如果是单行查询,则使用SELECT INTO。如果是修改数据:则使用EXECUTE USING语句。如果知道参数个数,就可以使用宿主变量。如果不知道参数个数,则必须使用DESCRIBE语句。下表总结了动态SQL语句的处理方法: 语句类型是否有输入参数执行的方法 INSERT、DELETE、UPDATE没有EXECUTE INSERT、DELETE、UPDATE有(数据类型和个数确定)EXECUTE …USING INSERT、DELETE、UPDATE有(数据类型和个数不确定)EXECUTE...USINGSQL DESCRIPTOR或EXECUTE...USINGDESCRIPTOR SELECT(返回多行)无OPEN SELECT(返回多行)有(数据类型和个数确定)OPEN…USING SELECT(返回多行)有(数据类型和个数不确定)OPEN...USINGSQL DESCRIPTOR或OPEN...USINGDESCRIPTOR SELECT(返回一行)无EXECUTE...INTO SELECT(返回一行,但是返回的数据类型和个数不确定)无EXECUTE...INTODESCRIPTOR或EXECUTE...INTOSQL DESCRIPTOR SELECT(返回一行)有EXECUTE...INTO...USING SELECT(返回一行,但是返回的数据类型和个数不确定)有EXECUTE...INTO...USING SQLDESCRIPTOR或EXECUTE...INTO...USINGDESCRIPTOR 4、释放资源。 5.4.1 SQLDA 可以通过SQLDA为嵌入SQL语句提供输入数据和从嵌入SQ语句中输出数据。理解SQLDA的结构是理解动态SQL的关键。 我们知道,动态SQL语句在编译时可能不知道有多少列信息。在嵌入SQL语句中,这些不确定的数据是通过SQLDA完成的。SQLDA的结构非常灵活,在该结构的固定部分,指明了多少列等信息(如下图中的sqld=2,表示为两列信息),在该结构的后面,有一个可变长的结构(SQLVAR结构),说明每列的信息。 SQLDA结构 Sqld=2 sqlvar Desc_name Desc_occ Desc_next Sqltype=500 Sqllen sqldata ….. Sqltype=501 Sqllen Sqldata …..
图6-6 SQLDA结构示例 具体SQLDA的结构在sqlda.h中定义,是: struct sqlvar_struct { short sqltype;/* variable type*/ short sqllen;/* length in bytes*/ char *sqldata;/* pointer to data*/ short *sqlind;/* pointer to indicator*/ char *sqlname;/* variable name*/ char *sqlformat;/* reserved for future use */ short sqlitype;/* ind variable type*/ short sqlilen;/* ind length in bytes*/ char *sqlidata;/* ind data pointer*/ }; struct sqlda { short sqld; struct sqlvar_struct *sqlvar; char desc_name[19];/* descriptor name */ short desc_occ;/* size of sqlda structure */ struct sqlda *desc_next;/* pointer to next sqlda struct */ }; #endif /* _SQLDA */ 从上面这个定义看出,SQLDA是一种由三个不同部分组成的可变长数据结构。位于SQLDA开端的sqldaid用于标志该SQLDA描述了多少列的信息;而后是一个或多个sqlvar结构 ,用于标志列数据。当用SQLDA把参数送到执行语句时,每一个参数都是一个sqlvar结构;当用SQLDA返回输出列信息时,每一列都是一个sqlvar结构。第三部分是SQLDA结构的描述信息部分。具体每个元素的含义为: lSqld。目前使用的sqlvar结构的个数。即输出列的个数。 lSqlvar。指向sqlvar_struct结构。 即指向描述第一列信息的sqlvar结构。 lDesc_name。Sqlda的名称。 lDesc_occ。Sqlda结构的大小。 lDesc_next。指向下一个SQLDA结构。 lSqltype。代表参数或列的数据类型。它是一个整数数据类型代码。具体每个整数的含义见第二节。 l Sqllen。代表传送数据的长度。如:2,即代表二字节整数。如果是字符串,则该数据为字符串中的字符数量。 lSqldata。指向数据的地址。注意,仅仅是一个地址。 lSqlind。代表是否为NULL。如果该列不允许为NULL,则该字段不赋值;如果该列允许为NULL,则:该字段若为0,表示数据值不为NULL,若为-1,表示数据值为NULL。 lSqlname。代表列名或变量名。它是一个结构。包含length和data。Length是名字的长度;data是名字。 lSqlformat。保留为以后使用。 lSqlitype。指定用户定义的指示符变量的数据类型。 lSqlilen。指定用户定义的指示符变量的长度。 lSqlidata。指向用户定义的指示符变量所存放的数据。 下面这个ADHOC程序非常经典,演示了SQLDA的作用。模拟一个不确定的查询,然后通过SQLDA来获得数据,并打印出来。 EXEC SQL include locator.h; EXEC SQL include sqltypes.h; #define BLOBSIZE 32276; main() { int i = 0; int row_count; /**** Step 1: 声明一个SQLDA结构,来存放查询的数据 ********/ struct sqlda *da_ptr; /*连接到数据库服务器*/ EXEC SQL connect to 'stores7'; if ( SQLCODE < 0 ) { printf("CONNECT failed: %d\n", SQLCODE) exit(0); } /* 创建一个临时表,模拟一个不确定列和表的环境*/ EXEC SQL create table blob_tab (int_col integer, blob_col byte); /* load_db函数是往blob_tab表插入数据,读者不用关心它的代码*/ load_db(); /* PREPARE查询语句 */ EXEC SQL prepare selct_id 'select * from tab1'; /* Step 2: 使用describe函数完成两个功能:一是为sqlda分配空间, 二是获取语句信息,并存放在SQLDA结构中。*/ EXEC SQL describe selct_id into da_ptr; /* Step 3: 初试化sqlda结构,如:为列分配空间,改变数据类型等。*/ row_size = init_sqlda(da_ptr, 0); /* 为PREPARE的SELECT语句声明和打开游标*/ EXEC SQL declare curs for selct_id; EXEC SQL open curs; while (1) { /* Step 4: 执行fetch操作,将一行数据存放在sqlda结构中*/ EXEC SQL fetch curs using descriptor da_ptr; /* 是否到达最后一行?,若是,则退出。 */ if ( SQLCODE == SQLNOTFOUND ) break; /* Step 5: 从SQLDA中打印数据,使用sqlca.sqlerrd[2]来获得查询的行数*/ printf("\n===============\n"); printf("FETCH %d\n", i++); printf("==============="); print_sqlda(da_ptr, ((FetArrSize == 0) ? 1 : sqlca.sqlerrd[2])); /* Step 6: 循环执行FETCH,直到处理完所有的行(SQLCODE为SQLNOTFOUND)*/ } /* Step 7: 释放申请的内存空间,如游标、SQLDA、创建的临时表等*/ EXEC SQL free selct_id; EXEC SQL close curs; EXEC SQL free curs; free_sqlda(da_ptr); cleanup_db(); } /************************************************************************ * 函数: init_sqlda() * 作用: 为SQLDA申请空间 * 返回值: 0 正确,否则有错误 ************************************************************************/ int init_sqlda(in_da, print) struct sqlda *in_da; int print; { int i, j, row_size=0, msglen=0, num_to_alloc; struct sqlvar_struct *col_ptr; loc_t *temp_loc; char *type; if (print) printf("columns: %d. ", in_da->sqld); /* Step 1: 获得一行数据的长度 */ for (i = 0, col_ptr = in_da->sqlvar; i < in_da->sqld; i++, col_ptr++) /* msglen变量存放查询数据的所有列的长度和。*/ msglen += col_ptr->sqllen; /* get database sizes */ /* 为col_ptr->sqllen 重新赋值,该值是在C下的大小。如:在数据库中的字符串,在C中应该多一个字节空间来存放NULL的结束符。*/ col_ptr->sqllen = rtypmsize(col_ptr->sqltype, col_ptr->sqllen); /*row_size变量存放了在C程序中的所有列的长度和。这个值是应用程序为存放一行数据所需要申请的内存空间*/ row_size += col_ptr->sqllen; } if (print) printf("Total row size = %d\n", row_size); /* Step 2: 设置FetArrSize值*/ if (FetArrSize == -1) /* if FetArrSize not yet initialized */ { if (FetBufSize == 0) /* if FetBufSize not set */ FetBufSize = 4096; /* default FetBufSize */ FetArrSize = FetBufSize/msglen; } num_to_alloc = (FetArrSize == 0)? 1: FetArrSize; /* 设置sqlvar_struct结构中的数据类型为相应的C的数据类型*/ for (i = 0, col_ptr = in_da->sqlvar; i < in_da->sqld; i++, col_ptr++) { switch(col_ptr->sqltype) { case SQLCHAR: type = "char "; col_ptr->sqltype = CCHARTYPE; break; case SQLINT: type = "int "; col_ptr->sqltype = CINTTYPE; break; case SQLBYTES: case SQLTEXT: if (col_ptr->sqltype == SQLBYTES) type = "blob "; else type = "text "; col_ptr->sqltype = CLOCATORTYPE; /* Step 3 :只有数据类型为TEXT 和BLOB时,才执行。为存放TEXT 或BYTE列数据申请空间*/ temp_loc = (loc_t *)malloc(col_ptr->sqllen * num_to_alloc); if (!temp_loc) { fprintf(stderr, "blob sqldata malloc failed\n"); return(-1); } col_ptr->sqldata = (char *)temp_loc; /* Step 4:只有数据类型为TEXT 和BLOB时,才执行。初试化loc_t结构*/ byfill(temp_loc, col_ptr->sqllen*num_to_alloc ,0); for (j = 0; j< num_to_alloc; j++, temp_loc++) { temp_loc->loc_loctype = LOCMEMORY; temp_loc->loc_bufsize = BLOBSIZE; temp_loc->loc_buffer = (char *)malloc(BLOBSIZE); if (!temp_loc->loc_buffer) { fprintf(stderr, "loc_buffer malloc failed\n"); return(-1); } temp_loc->loc_oflags = 0; /* clear flag */ } /* end for */ break; default: /* 其他数据类型*/ fprintf(stderr, "not yet handled(%d)!\n", col_ptr->sqltype); return(-1); } /* switch */ /* Step 5: 为指示符变量申请空间*/ col_ptr->sqlind = (short *) malloc(sizeof(short) * num_to_alloc); if (!col_ptr->sqlind) { printf("indicator malloc failed\n"); return -1; /* Step 6 :为存放非TEXT 和BLOB的数据类型的sqldata申请空间.注意的是,申请的地址是(char *),在输出数据时,要按照相应的数据类型做转换。*/ if (col_ptr->sqltype != CLOCATORTYPE) { col_ptr->sqldata = (char *) malloc(col_ptr->sqllen * num_to_alloc); if (!col_ptr->sqldata) { printf("sqldata malloc failed\n"); return -1; } if (print) printf("column %3d, type = %s(%3d), len=%d\n", i+1, type, col_ptr->sqltype, col_ptr->sqllen); } /* end for */ return msglen; } /************************************************************************ * 函数: print_sqlda * 作用: 打印存放在SQLDA结构中的数据。 ************************************************************************/ void print_sqlda(sqlda, count) struct sqlda *sqlda; int count; { void *data; int i, j; loc_t *temp_loc; struct sqlvar_struct *col_ptr; char *type; char buffer[512]; int ind; char i1, i2; /* 打印列数 (sqld) 和行数*/ printf("\nsqld: %d, fetch-array elements: %d.\n", sqlda->sqld, count); /* 外循环:针对每一行数据循环处理 */ for (j = 0; j < count; j ++) { if (count > 1) { printf("record[%4d]:\n", j); printf("col | type | id | len | ind | rin | data "); printf("| value\n"); printf("--------------------------------------------"); printf("------------------\n"); } /* 内循环: 针对每一列数据处理*/ for (i = 0, col_ptr = sqlda->sqlvar; i < sqlda->sqld; i++, col_ptr++) { data = col_ptr->sqldata + (j*col_ptr->sqllen); switch (col_ptr->sqltype) { case CFIXCHARTYPE: case CCHARTYPE: type = "char"; if (col_ptr->sqllen > 40) sprintf(buffer, " %39.39s<..", data); else sprintf(buffer, "%*.*s", col_ptr->sqllen, col_ptr->sqllen, data); break; case CINTTYPE: type = "int"; sprintf(buffer, " %d", *(int *) data); break; case CLOCATORTYPE: type = "byte"; temp_loc = (loc_t *)(col_ptr->sqldata + (j * sizeof(loc_t))); sprintf(buffer, " buf ptr: %p, buf sz: %d, blob sz: %d", temp_loc->loc_buffer, temp_loc->loc_bufsize, temp_loc->loc_size); break; default: type = "??????"; sprintf(buffer, " type not implemented: ", "can't print %d", col_ptr->sqltype); break; } /* end switch */ i1 = (col_ptr->sqlind==NULL) ? 'X' : (((col_ptr->sqlind)[j] != 0) ? 'T' : 'F'); i2 = (risnull(col_ptr->sqltype, data)) ? 'T' : 'F'; printf("%3d | %-6.6s | %3d | %3d | %c | %c | ", i, type, col_ptr->sqltype, col_ptr->sqllen, i1, i2); printf("%8p |%s\n", data, buffer); } /* end for (i=0...) */ } /* end for (j=0...) */ } /************************************************************************ * 函数: free_sqlda * 作用: 释放以下对象申请的内存空间 * o loc_buffer memory (used by TEXT & BYTE) * o sqldata memory * o sqlda structure ************************************************************************/ void free_sqlda(sqlda) struct sqlda *sqlda; { int i,j, num_to_dealloc; struct sqlvar_struc *col_ptr; loc_t *temp_loc; for (i = 0, col_ptr = sqlda->sqlvar; i < sqlda->sqld; i++, col_ptr++) { if ( col_ptr->sqltype = CLOCATORTYPE ) { /* Free memory for blob buffer of each element in fetch array */ num_to_dealloc = (FetArrSize == 0)? 1: FetArrSize; temp_loc = (loc_t *) col_ptr->sqldata; for (j = 0; j< num_to_dealloc; j++, temp_loc++) free(temp_loc->loc_buffer); } /* Free memory for sqldata (contains fetch array) */ free(col_ptr->sqldata); } /* Free memory for sqlda structure */ free(sqlda); } 第六节Microsoft SQL Server7嵌入式SQL语言 6.1 一个嵌入SQL语言的简单例子 我们首先来看一个简单的嵌入式SQL语言的程序(C语言):在YANGZH服务器的pubs数据库上查询lastname为“White”的firstname。用sa(口令为password)连接数据库服务器。这个例子程序如下: 例1、查询lastname为“White”的firstname的信息。 main() { EXEC SQL BEGIN DECLARE SECTION; char first_name[50]; char last_name[] = "White"; EXEC SQL END DECLARE SECTION; EXEC SQL CONNECT TO YANGZH.pubs USER sa.password; EXEC SQL SELECT au_fname INTO :first_name from authors where au_lname = :last_name; printf("first name: %s\n",first_name); return (0); } 从上面这个例子,我们看出嵌入SQL的基本特点是: 1、每条嵌入式SQL语句都用EXEC SQL开始,表明它是一条SQL语句。这也是告诉预编译器在EXEC SQL和“;”之间是嵌入SQL语句。 2、如果一条嵌入式SQL语句占用多行,在C程序中可以用续行符“\”,在Fortran中必须有续行符。其他语言也有相应规定。 3、每一条嵌入SQL语句都有结束符号,如:在C中是“;”。 如果你需要在NT平台编写C的嵌入SQL语句,那么你必须保证设置了以下环境: lWindows NT Workstation 3.51或以上;或者Windows NT Server 3.51或以上 lVC++版本2.0或以上;或者其他100%兼容的编译器和连接器。 lSQL Server6.0或以上 如果你需要在Windows95/98平台编写C的嵌入SQL语句,那么你必须保证设置了以下环境: lWindows 95/98 lVC++版本2.0或以上;或者其他100%兼容的编译器和连接器。 lSQL Server6.0或以上 6.2 嵌入SQL的处理过程 下面我们来说明整个嵌入SQL的处理过程。以下是生成可执行文件的步骤: 1、在命令提示符下执行 …\VC98\bin\vcvar32.bat,作用是设置C的环境信息。 2、在命令提示符下执行:nmake –f demo.mk 。执行后,生成demo.exe。 其中,demo.mk为(在NT平台): demo.exe:demo.sqc set include=e:\mssql7\devtools\include;%include%; nsqlprep demo.sqc cl -o demo.exe e:\mssql7\devtools\lib\sqlakw32.lib \ e:\mssql7\devtools\lib\caw32.lib demo.c 其中, l“set include=e:\mssql7\devtools\include;%include%;”的作用是,说明整个头文件的路径信息。即包含sqlca.h和sqlda.h路径信息。在嵌入SQL程序中,无需使用“#include <sqlca.h>”和“#include <sqlda.h>”语句,这是因为nsqlprep.exe预编译器会自动将这些语句插入预编译后的C程序中。 l“nsqlprep demo.sqc”是SQL Server7的预编译处理。Nsqlprep.exe是SQL Server7的预编译器。处理的结果产生C的程序,如demo.c。demo.c的程序为: 例2、demo.c程序 /* ===== demo.c =====*/ /* ===== NT doesn't need the following... */ #ifndef WIN32 #define WIN32 #endif #define _loadds #define _SQLPREP_ #include <sqlca.h> #include <sqlda.h> #include <string.h> #define SQLLENMAX(x) ( ((x) > 32767) ? 32767 : (x) ) short ESQLAPI _loadds sqlaaloc( unsigned short usSqlDaId, unsigned short sqld, unsigned short stmt_id, void far *spare);
short ESQLAPI _loadds sqlxcall( unsigned short usCallType, unsigned short usSection, unsigned short usSqldaInId, unsigned short usSqlDaOutId, unsigned short usSqlTextLen, char far *lpszSQLText); short ESQLAPI _loadds sqlacall( unsigned short usCallType, unsigned short usSection, unsigned short usSqldaInId, unsigned short usSqlDaOutId, void far *spare); short ESQLAPI _loadds sqladloc( unsigned short usSqldaInId, void far *spare); short ESQLAPI _loadds sqlasets( unsigned short cbSqlText, void far *lpvSqlText, void far *spare); short ESQLAPI _loadds sqlasetv( unsigned short usSqldaInId, unsigned short sqlvar_index, unsigned short sqltype, unsigned short sqllen, void far *sqldata, void far *sqlind, void far *spare); short ESQLAPI _loadds sqlastop( void far *spare); short ESQLAPI _loadds sqlastrt( void far *pid, void far *spare, void far *sqlca); short ESQLAPI _loadds sqlausda( unsigned short sqldaId, void far *lpvSqlDa, void far *spare); extern struct tag_sqlca far sql_sqlca; extern struct tag_sqlca far *sqlca; struct sqla_program_id2 { unsigned short length; unsigned short rp_rel_num; unsigned short db_rel_num; unsigned short bf_rel_num; unsigned char sqluser[30]; unsigned char sqlusername[30]; unsigned char planname[256]; unsigned char contoken[8]; unsigned char buffer[8]; }; static struct sqla_program_id2 program_id = {340,2,0,0," ","","demo","VVVLKcBo"," "}; static void far* pid = &program_id; #line 1 "demo.sqc" main() { #line 5 /* EXEC SQL BEGIN DECLARE SECTION; */ #line 5 char first_name[50]; char last_name[] = "White"; #line 8 /* EXEC SQL END DECLARE SECTION; */ #line 8 #line 10 /* EXEC SQL CONNECT TO YANGZH.pubs USER sa.password; */ #line 11 #line 10 { #line 10 sqlastrt((void far *)pid, (void far *)0, (struct tag_sqlca far *)sqlca); #line 10 sqlxcall(30, 1, 0, 0, 42, (char far *)" CONNECT TO YANGZH.pubs USER sa.password "); #line 10 SQLCODE = sqlca->sqlcode; #line 10 sqlastop((void far *)0L); #line 10 } #line 12 #line 12 /* EXEC SQL SELECT au_fname INTO :first_name from authors where au_lname = :last_name; */ #line 13 #line 12 { #line 12 sqlastrt((void far *)pid, (void far *)0, (struct tag_sqlca far *)sqlca); #line 12 sqlaaloc(1, 1, 2, (void far *)0); #line 12 sqlasetv(1, 0, 462,(short) SQLLENMAX(sizeof(first_name)),(void far *)&first_name, (void far *)0,0L); #line 12 sqlaaloc(2, 1, 2, (void far *)0); #line 12 sqlasetv(2, 0, 462, (short) SQLLENMAX(sizeof(last_name)), (void far *)last_name, (void far *)0, (void far *)0L); #line 12 sqlxcall(24, 2, 2, 1, 60, (char far *)" SELECT au_fname from authors where au_lname = @p1 "); #line 12 SQLCODE = sqlca->sqlcode; #line 12 sqlastop((void far *)0L); #line 12 } #line 14 printf("first name: %s\n",first_name); return (0); } long SQLCODE; 从这个程序看出,预编译器的处理方法是,注释了嵌入的SQL语句,用一些特定的函数代替,如sqlxcall。这些函数调用sqlakw32.dll,而sqlakw32.dll调用了DB-Library(ntwdblib.dll)来访问SQL Server服务器。所以,必须保证应用程序能够访问到sqlakw32.dll、ntwdblib.dll和dbnmpntw.dll。预编译器nsqlprep.exe有很多选项,具体这些选项信息可参见帮助。 l“cl -o demo.exe e:\mssql7\devtools\lib\sqlakw32.lib \ e:\mssql7\devtools\lib\caw32.lib demo.c”的作用是C源程序的编译和链接。cl是编译和链接命令的集成命令,编译的结果是产生demo.obj,在链接时,将C的系统库和SQL Server提供的库文件(sqlakw32.lib和caw32.lib)同目标文件连接在一起。最后生成demo.exe。也可以使用“SET LIB=e:\mssql7\devtools\LIB;%LIB%”语句设置库文件的环境信息。 设置SQL Server相关的头文件和库文件环境信息,也可以执行mssql7\devtools\samples\esqlc\setenv.bat程序完成。 在运行demo.exe程序时,对于每个SQL语句,都调用相应的运行中的服务(如:sqlakw32.dll)。如果该语句是静态SQL语句,那么该服务执行SQL语句或执行一个已编译成功的存储过程(可以在编译时使用/SQLACCESS选项为静态SQL语句创建存储过程);如果该语句是动态SQL语句,那么该服务将SQL语句送到SQL Server上处理。这个服务调用DB-Library,在客户和服务器之间传递数据。这些数据要么存放在主变量中,要么存放在SQLDA结构中。执行SQL语句的错误信息存放在SQLCA数据结构中。 下图总结了整个处理过程。 6-7 嵌入SQL程序处理过程 一个应用中的静态SQL语句,可以在运行时才发送到服务器端处理(类似动态SQL语句),或者生成执行计划(access plan)。一个执行计划就是一些存储过程。每个静态SQL语句可以生成一个存储过程。在预编译时,可以创建执行计划。如果在预编译时,服务器不可访问,那么预编译器创建绑定文件(bind file)。绑定文件就是用来创建执行计划中的存储过程的一些Transact-SQL脚本。在运行应用程序前,你可以通过OSQL执行绑定文件。上面这个例子,在预编译时,未指定“/DB”和“/PASS”选项(用于生成执行计划)也未指定“/BIND”选项(用于生成绑定文件),所以我们生成的应用程序对SQL语句的处理是采用类似动态SQL语句的处理方式,即在运行时才将语句送到服务器端处理。 Nsqlprep.exe编译器的作用是,找出SQL语句,语法分析这些语句,创建执行计划或绑定文件,最终生成C程序。 当然,以上步骤的完整,也可以在VC++(版本2.0以上)集成环境中完成。 6.3 嵌入SQL语句 下表是所有的嵌入式SQL语句,“*”表示嵌入式SQL语句的名字同Transact-SQL语句相同。 BEGIN DECLARE SECTION PREPARE CLOSE* SELECT INTO* CONNECT TO SET ANSI_DEFAULTS DECLARE CURSOR* SET CONCURRENCY DELETE (POSITIONED)* SET CONNECTION DELETE (SEARCHED)* SET CURSOR_CLOSE_ON_COMMIT DESCRIBE SET CURSORTYPE DISCONNECT SET FETCHBUFFER END DECLARE SECTION SET OPTION EXECUTE* SET SCROLLOPTION EXECUTE IMMEDIATE UPDATE (POSITIONED)* FETCH* UPDATE (SEARCHED)* GET CONNECTION WHENEVER OPEN* 嵌入式SQL语句分为静态SQL语句和动态SQL语句两类。下面我们按照功能讲解这些语句。本节讲解静态SQL语句的作用。动态SQL语句将在下一节讲解。同动态SQL相关的一些语句也在下一节中讲解。 6.3.1 声明嵌入SQL语句中使用的C变量 1)、声明方法 主变量(host variable)就是在嵌入式SQL语句中引用主语言说明的程序变量(如例1中的last_name[]变量)。如: EXEC SQL BEGIN DECLARE SECTION; char first_name[50]; char last_name[] = "White"; EXEC SQL END DECLARE SECTION; ………… EXEC SQL SELECT au_fname INTO :first_name from authors where au_lname = :last_name; …………. 在嵌入式SQL语句中使用主变量前,必须采用BEGIN DECLARE SECTION 和END DECLARE SECTION之间给主变量说明。这两条语句不是可执行语句,而是预编译程序的说明。主变量是标准的C程序变量。嵌入SQL语句使用主变量来输入数据和输出数据。C程序和嵌入SQL语句都可以访问主变量。 值得注意的是,主变量的长度不能超过30字节。 2)、主变量的数据类型 在以SQL为基础的DBMS支持的数据类型与程序设计语言支持的数据类型之间有很大差别。这些差别对主变量影响很大。一方面,主变量是一个用程序设计语言的数据类型说明并用程序设计语言处理的程序变量;另一方面,在嵌入SQL语句中用主变量保存数据库数据。所以,在嵌入SQL语句中,必须映射C数据类型为合适的SQL Server数据类型。必须慎重选择主变量的数据类型。在SQL SERVER中,很多数据类型都能够自动转换。请看下面这个例子: EXEC SQL BEGIN DECLARE SECTION; int hostvar1 = 39; char *hostvar2 = "telescope"; float hostvar3 = 355.95; EXEC SQL END DECLARE SECTION; EXEC SQL UPDATE inventory SET department = :hostvar1 WHERE part_num = "4572-3"; EXEC SQL UPDATE inventory SET prod_descrip = :hostvar2 WHERE part_num = "4572-3"; EXEC SQL UPDATE inventory SET price = :hostvar3 WHERE part_num = "4572-3"; 在第一个update语句中,department列为smallint数据类型(integer ),所以应该把hostvar1定义为int数据类型(integer)。这样的话,从C到SQL Server的hostvar1可以直接映射。在第二个update语句中,prod_descip列为varchar数据类型,所以应该把hostvar2定义为字符数组。这样的话,从C到SQL Server的hostvar2可以从字符数组映射为varchar数据类型。在第三个update语句中,price列为money数据类型。在C语言中,没有相应的数据类型,所以用户可以把hostvar3定义为C的浮点变量或字符数据类型。SQL Server可以自动将浮点变量转换为money数据类型(输入数据),或将money数据类型转换为浮点变量(输出数据)。 注意的是,如果数据类型为字符数组,那么SQL Server会在数据后面填充空格,直到填满该变量的声明长度。 在ESQL/C中,不支持所有的unicode数据类型(如:nvarchar、nchar和ntext)。对于非unicode数据类型,除了datetime、smalldatetime、money和smallmoney外(decimal和numeric数据类型部分情况下不支持),都可以相互转换。 下表列出了C的数据类型和datetime、smalldatetime、money、smallmoney、decimal和numeric数据类型的一些转换关系: C数据类型分配的SQL Server数据类型Datetime或smalldatetimeMoney或smallmoneyDecimal或numeric shortSmallint不可以不可以不可以 IntSmallint不可以不可以不可以 LongInt不可以不可以不可以 FloatReal不可以不可以不可以 DoubleFloat不可以不可以不可以 CharCarchar[X]可以可以可以 Void *pBinary(2)可以可以可以 Char bytetinyint不可以不可以不可以 因为C没有date或time数据类型,所以SQL Server的date或time列将被转换为字符。缺省情况下,使用以下转换格式:mm dd yyyy hh:mm:ss[am | pm]。你也可以使用字符数据格式将C的字符数据存放到SQL Server的date列上。你也可以使用Transact-SQL中的convert语句来转换数据类型。如:SELECT CONVERT(char, date, 8) FROM sales。 3)、主变量和NULL 大多数程序设计语言(如C)都不支持NULL。所以对NULL的处理,一定要在SQL中完成。我们可以使用主机指示符变量(host indicator variable)来解决这个问题。在嵌入式SQL语句中,主变量和指示符变量共同规定一个单独的SQL类型值。如: EXEC SQL SELECT price INTO :price:price_nullflag FROM titles WHERE au_id = "mc3026" 其中,price是主变量,price_nullflag是指示符变量。指示符变量共有两类值: l-1。表示主变量应该假设为NULL。(注意:主变量的实际值是一个无关值,不予考虑)。 l>0。表示主变量包含了有效值。该指示变量存放了该主变量数据的最大长度。 所以,上面这个例子的含义是:如果不存在mc3026写的书,那么price_nullflag为-1,表示price为NULL;如果存在,则price为实际的价格。 下面我们再看一个update的例子: EXEC SQL UPDATE closeoutsale SET temp_price = :saleprice :saleprice_null, listprice = :oldprice; 如果saleprice_null是-1,则上述语句等价为: EXEC SQL UPDATE closeoutsale SET temp_price = null, listprice = :oldprice; 我们也可以在指示符变量前面加上“INDICATOR”关键字,表示后面的变量为指示符变量。如: EXEC SQL UPDATE closeoutsale SET temp_price = :saleprice INDICATOR :saleprice_null; 值得注意的是,不能在WHERE语句后面使用指示符变量。如: EXEC SQL DELETE FROM closeoutsale WHERE temp_price = :saleprice :saleprice_null; 你可以使用下面语句来完成上述功能: if (saleprice_null == -1) { EXEC SQL DELETE FROM closeoutsale WHERE temp_price IS null; } else { EXEC SQL DELETE FROM closeoutsale WHERE temp_price = :saleprice; } 为了便于识别主变量,当嵌入式SQL语句中出现主变量时,必须在变量名称前标上冒号(:)。冒号的作用是,告诉预编译器,这是个主变量而不是表名或列名。 6.3.2 连接数据库 在程序中,使用“CONNECT TO”语句来连接数据库。该语句的完整语法为: CONNECT TO {[server_name.]database_name} [AS connection_name] USER [login[.password] | $integrated] 其中, lserver_name为服务器名。如省略,则为本地服务器名。 ldatabase_name为数据库名。 lconnection_name为连接名。可省略。如果你仅仅使用一个连接,那么无需指定连接名。可以使用SET CONNECTION来使用不同的连接。 llogin为登录名。 lpassword为密码。 在上例中的“ EXEC SQL CONNECT TO YANGZH.pubs USER sa.password; ”,服务器是YANGZH,数据库为pubs,登录名为sa,密码为password。缺省的超时时间为10秒。如果指定连接的服务器没有响应这个连接请求,或者连接超时,那么系统会返回错误信息。我们可以使用“SET OPTION”命令设置连接超时的时间值。 在嵌入SQL语句中,使用DISCONNECT语句断开数据库的连接。其语法为: DISCONNECT [connection_name | ALL | CURRENT] 其中,connection_name为连接名。ALL表示断开所有的连接。CURRENT表示断开当前连接。请看下面这些例子来理解CONNECT和DISCONNECT语句。 EXEC SQL CONNECT TO caffe.pubs AS caffe1 USER sa; EXEC SQL CONNECT TO latte.pubs AS latte1 USER sa; EXEC SQL SET CONNECTION caffe1; EXEC SQL SELECT name FROM sysobjects INTO :name; EXEC SQL SET CONNECTION latte1; EXEC SQL SELECT name FROM sysobjects INTO :name; EXEC SQL DISCONNECT caffe1; EXEC SQL DISCONNECT latte1; 在上面这个例子中,第一个select 语句查询在caffe服务器上的pubs 数据库。第二个SELECT语句查询在latte服务器上的pubs数据库。当然,你也可以使用“EXEC SQL DISCONNECT ALL; ”来断开所有的连接。 6.3.3 数据的查询和修改 可以使用SELECT INTO语句查询数据,并将数据存放在主变量中。如上例中的: EXEC SQL SELECT au_fname INTO :first_name from authors where au_lname = :last_name; 使用DELETE语句删除数据。其语法类似于Transact-SQL中的DELETE语法。如: EXEC SQL DELETE FROM authors WHERE au_lname = 'White' 使用UPDATE语句可以更新数据。其语法就是Transact-SQL中的UPDATE语法。如: `EXEC SQL UPDATE authors SET au_fname = 'Fred' WHERE au_lname = 'White' 使用INSERT语句可以插入新数据。其语法就是Transact-SQL中的INSERT语法。如: EXEC SQL INSERT INTO homesales (seller_name, sale_price) real_estate('Jane Doe', 180000.00); 多行数据的查询和修改请参见下一节——游标。 6.3.4 游标的使用 用嵌入式SQL语句查询数据分成两类情况。一类是单行结果,一类是多行结果。对于单行结果,可以使用SELECT INTO语句;对于多行结果,你必须使用cursor(游标)来完成。游标(Cursor)是一个与SELECT语句相关联的符号名,它使用户可逐行访问由SQL Server返回的结果集。先请看下面这个例子,这个例子的作用是逐行打印staff表的id、name、dept、 job、years、salary和comm的值。 EXEC SQL DECLARE C1 CURSOR FOR SELECT id, name, dept, job, years, salary, comm FROM staff; EXEC SQL OPEN c1; while (SQLCODE == 0) { /* SQLCODE will be zero if data is successfully fetched */ EXEC SQL FETCH c1 INTO :id, :name, :dept, :job, :years, :salary, :comm; if (SQLCODE == 0) printf("%4d %12s %10d %10s %2d %8d %8d", id, name, dept, job, years, salary, comm); } EXEC SQL CLOSE c1; 从上例看出,你首先应该定义游标结果集,即定义该游标的SELECT语句返回的行的集合。然后,使用FETCH语句逐行处理。 值得注意的是,嵌入SQL语句中的游标定义选项同Transact-SQL 中的游标定义选项有些不同。必须遵循嵌入SQL语句中的游标定义选项。 1)、声明游标: 如:EXEC SQL DECLARE C1 CURSOR FOR SELECT id, name, dept, job, years, salary, comm FROM staff; 2)、打开游标 如:EXEC SQL OPEN c1; 完整语法为:OPEN 游标名 [USING 主变量名 | DESCRIPTOR 描述名]。关于动态OPEN游标的描述见第四节。 3)、取一行值 如:EXEC SQL FETCH c1 INTO :id, :name, :dept, :job, :years, :salary, :comm; 关于动态FETCH语句见第四节。 4)、关闭游标 如:EXEC SQL CLOSE c1; 关闭游标的同时,会释放由游标添加的锁和放弃未处理的数据。在关闭游标前,该游标必须已经声明和打开。另外,程序终止时,系统会自动关闭所有打开的游标。 也可以使用UPDATE语句和DELETE语句来更新或删除由游标选择的当前行。使用DELETE语句删除当前游标所在的行数据的具体语法如下: DELETE [FROM] {table_name | view_name} WHERE CURRENT OF cursor_name 其中, ltable_name是表名,该表必须是DECLARE CURSOR中SELECT语句中的表。 lview_name是视图名,该视图必须是DECLARE CURSOR中SELECT语句中的视图。 lcursor_name是游标名。 请看下面这个例子,逐行显示firstname和lastname,询问用户是否删除该信息,如果回答“是”,那么删除当前行的数据。 EXEC SQL DECLARE c1 CURSOR FOR SELECT au_fname, au_lname FROM authors FOR BROWSE; EXEC SQL OPEN c1; while (SQLCODE == 0) { EXEC SQL FETCH c1 INTO :fname, :lname; if (SQLCODE == 0) { printf("%12s %12s\n", fname, lname); printf("Delete? "); scanf("%c", &reply); if (reply == 'y') { EXEC SQL DELETE FROM authors WHERE CURRENT OF c1; printf("delete sqlcode= %d\n", SQLCODE(ca)); } } } 6.3.5 SQLCA DBMS是通过SQLCA(SQL通信区)向应用程序报告运行错误信息(见3.4中的例子)。SQLCA是一个含有错误变量和状态指示符的数据结构。通过检查SQLCA,应用程序能够检查出嵌入式SQL语句是否成功,并根据成功与否决定是否继续往下执行。预编译器自动在嵌入SQL语句中包含SQLCA数据结构(见第二节的例子demo.c)。在程序中可以使用EXEC SQL INCLUDE SQLCA,目的是告诉SQL预编译程序在该程序中包含一个SQL通信区。也可以不写,系统会自动加上SQLCA结构。 1)、SQLCODE SQLCA结构中最重要的部分是SQLCODE变量。在执行每条嵌入式SQL语句时,DBMS在SQLCA中设置变量SQLCODE值,以指明语句的完成状态: 2、0该语句成功执行,无任何错误或报警。 2、<0 出现了严重错误。 3、>0 出现了报警信息。 2)、SQLSTATE SQLSTATE变量也是SQLCA结构中的成员。它同SQLCODE一样,都是返回错误信息。SQLSTATE是在SQLCODE之后产生的。这是因为,在制定SQL2标准之前,各个数据库厂商都采用SQLCODE变量来报告嵌入式SQL语句中的错误状态。但是,各个厂商没有采用标准的错误描述信息和错误值来报告相同的错误状态。所以,标准化组织增加了SQLSTATE变量,规定了通过SQLSTATE变量报告错误状态和各个错误代码。因此,目前使用SQLCODE的程序仍然有效,但也可用标准的SQLSTATE错误代码编写新程序。 6.3.6 WHENEVER 在每条嵌入式SQL语句之后立即编写一条检查SQLCODE/SQLSTATE值的程序,是一件很繁琐的事情。为了简化错误处理,可以使用WHENEVER语句。该语句是SQL预编译程序的指示语句,而不是可执行语句。它通知预编译程序在每条可执行嵌入式SQL语句之后自动生成错误处理程序,并指定了错误处理操作。 用户可以使用WHENEVER语句通知预编译程序去如何处理三种异常处理: lWHENEVER SQLERROR action:表示一旦sql语句执行时遇到错误信息,则执行action,action中包含了处理错误的代码(SQLCODE<0)。 lWHENEVER SQLWARNING action:表示一旦sql语句执行时遇到警告信息,则执行aciton,即action中包含了处理警报的代码(SQLCODE=1)。 lWHENEVER NOT FOUND:表示一旦sql语句执行时没有找到相应的元组,则执行action,即action包含了处理没有查到内容的代码(SQLCODE=100)。 针对上述三种异常处理,用户可以指定预编译程序采取以下三种行为(action): lWHENEVER …GOTO:通知预编译程序产生一条转移语句。 lWHENEVER…CONTINUE:通知预编译程序让程序的控制流转入到下一个主语言语句。 lWHENEVER…CALL:通知预编译程序调用函数。 其完整语法如下: WHENEVER {SQLWARNING | SQLERROR | NOT FOUND} {CONTINUE | GOTO stmt_label | CALL function()} 例:WHENEVER的作用 EXEC SQL WHENEVER sqlerror GOTO errormessage1; EXEC SQL DELETE FROM homesales WHERE equity < 10000; EXEC SQL DELETE FROM customerlist WHERE salary < 40000; EXEC SQL WHENEVER sqlerror CONTINUE; EXEC SQL UPDATE homesales SET equity = equity - loanvalue; EXEC SQL WHENEVER sqlerror GOTO errormessage2; EXEC SQL INSERT INTO homesales (seller_name, sale_price) real_estate('Jane Doe', 180000.00); . . . errormessage1: printf("SQL DELETE error: %ld\n, sqlcode); exit();
errormessage2: printf("SQL INSERT error: %ld\n, sqlcode); exit(); WHENEVER语句是预编译程序的指示语句。在上面这个例子中,由于第一个WHENEVER语句的作用,前面两个DELETE语句中任一语句内的一个错误会在errormessage1中形成一个转移指令。由于一个WHENEVER语句替代前面WHENEVER语句,所以,嵌入式UPDATE语句中的一个错误会直接转入下一个程序语句中。嵌入式INSERT语句中的一个错误会在errormessage2中产生一条转移指定。 从上面例子看出,WHENEVER/CONTINUE语句的主要作用是取消先前的WHENEVER语句的作用。WHENEVER语句使得对嵌入式SQL错误的处理更加简便。应该在应用程序中普遍使用,而不是直接检查SQLCODE的值。 6.4动态SQL语句 前一节中讲述的嵌入SQL语言都是静态SQL语言,即在编译时已经确定了引用的表和列。主变量不改变表和列信息。在上几节中,我们使用主变量改变查询参数,但是不能用主变量代替表名或列名。否则,系统报错。动态SQL语句就是来解决这个问题。 动态SQL语句的目的是,不是在编译时确定SQL的表和列,而是让程序在运行时提供,并将SQL语句文本传给DBMS执行。静态SQL语句在编译时已经生成执行计划。而动态SQL语句,只有在执行时才产生执行计划。动态SQL语句首先执行PREPARE语句要求DBMS分析、确认和优化语句,并为其生成执行计划。DBMS还设置SQLCODE以表明语句中发现的错误。当程序执行完“PREPARE”语句后,就可以用EXECUTE语句执行执行计划,并设置SQLCODE,以表明完成状态。 按照功能和处理上的划分,动态SQL应该分成两类来解释:动态修改和动态查询。动态修改的例子参见4.1。动态查询的例子参见4.3。 6.4 .1 动态修改 动态修改使用PREPARE语句和EXECUTE语句。PREPARE语句是动态SQL语句独有的语句。其语法为: PREPARE 语句名 FROM 主变量 该语句接收含有SQL语句串的主变量,并把该语句送到DBMS。DBMS编译语句并生成执行计划。在语句串中包含一个“?”表明参数,当执行语句时,DBMS需要参数来替代这些“?”。PREPRARE执行的结果是,DBMS把语句名赋给准备的语句。语句名类似于游标名,是一个SQL标识符。在执行SQL语句时,EXECUTE语句后面是这个语句名。请看下面这个例子: EXEC SQL BEGIN DECLARE SECTION; char prep[] = "INSERT INTO mf_table VALUES(?,?,?)"; char name[30]; char car[30]; double num; EXEC SQL END DECLARE SECTION; EXEC SQL PREPARE prep_stat FROM :prep; while (SQLCODE == 0) { strcpy(name, "Elaine"); strcpy(car, "Lamborghini"); num = 4.9; EXEC SQL EXECUTE prep_stat USING :name, :car, :num; } 在这个例子中,prep_stat是语句名,prep主变量的值是一个INSERT语句,包含了三个参数(3个“?”)。PREPARE的作用是,DBMS编译这个语句并生成执行计划,并把语句名赋给这个准备的语句。 值得注意的是,PREPARE中的语句名的作用范围为整个程序,所以不允许在同一个程序中使用相同的语句名在多个PREPARE语句中。 EXECUTE语句是动态SQL独有的语句。它的语法如下: EXECUTE 语句名 USING 主变量 | DESCRIPTOR 描述符名 请看上面这个例子中的“EXEC SQL EXECUTE prep_stat USING :name, :car, :num;”语句,它的作用是,请求DBMS执行PREPARE语句准备好的语句。当要执行的动态语句中包含一个或多个参数标志时,在EXECUTE语句必须为每一个参数提供值,如::name、:car和:num。这样的话,EXECUTE语句用主变量值逐一代替准备语句中的参数标志(“?”),从而,为动态执行语句提供了输入值。 使用主变量提供值,USING子句中的主变量数必须同动态语句中的参数标志数一致,而且每一个主变量的数据类型必须同相应参数所需的数据类型相一致。各主变量也可以有一个伴随主变量的指示符变量。当处理EXECUTE语句时,如果指示符变量包含一个负值,就把NULL值赋予相应的参数标志。除了使用主变量为参数提供值,也可以通过SQLDA提供值(见节4.4)。 6.4.2 动态游标 游标分为静态游标和动态游标两类。对于静态游标,在定义游标时就已经确定了完整的SELECT语句。在SELECT语句中可以包含主变量来接收输入值。当执行游标的OPEN语句时,主变量的值被放入SELECT语句。在OPEN语句中,不用指定主变量,因为在DECLARE CURSOR语句中已经放置了主变量。请看下面静态游标的例子: EXEC SQL BEGIN DECLARE SECTION; char szLastName[] = "White"; char szFirstName[30]; EXEC SQL END DECLARE SECTION; EXEC SQL DECLARE author_cursor CURSOR FOR SELECT au_fname FROM authors WHERE au_lname = :szLastName; EXEC SQL OPEN author_cursor; EXEC SQL FETCH author_cursor INTO :szFirstName; 动态游标和静态游标不同。以下是动态游标使用的句法(请参照本小节后面的例子来理解动态游标)。 1)、声明游标: 对于动态游标,在DECLARE CURSOR语句中不包含SELECT语句。而是,定义了在PREPARE中的语句名,用PREPARE语句规定与查询相关的语句名称。 2)、打开游标 完整语法为:OPEN 游标名 [USING 主变量名 | DESCRIPTOR 描述名] 在动态游标中,OPEN语句的作用是使DBMS在第一行查询结果前开始执行查询并定位相关的游标。当OPEN语句成功执行完毕后,游标处于打开状态,并为FETCH语句做准备。OPEN语句执行一条由PREPARE语句预编译的语句。如果动态查询正文中包含有一个或多个参数标志时,OPEN语句必须为这些参数提供参数值。USING子句的作用是规定参数值。 3)、取一行值 FETCH语法为:FETCH 游标名 USING DESCRIPTOR 描述符名。 动态FETCH语句的作用是,把这一行的各列值送到SQLDA中,并把游标移到下一行。(注意,静态FETCH语句的作用是用主变量表接收查询到的列值。) 在使用FETCH语句前,必须为数据区分配空间,SQLDATA字段指向检索出的数据区。SQLLEN字段是SQLDATA指向的数据区的长度。SQLIND字段指出是否为NULL。关于SQLDA,见下一节。 4)、关闭游标 如:EXEC SQL CLOSE c1; 关闭游标的同时,会释放由游标添加的锁和放弃未处理的数据。在关闭游标前,该游标必须已经声明和打开。另外,程序终止时,系统会自动关闭所有打开的游标。 在动态游标的DECLARE CURSOR语句中不包含SELECT语句。而是,定义了在PREPARE中的语句名,用PREPARE语句规定与查询相关的语句名称。当PREPARE语句中的语句包含了参数,那么在OPEN语句中必须指定提供参数值的主变量或SQLDA。动态DECLARE CURSOR语句是SQL预编译程序中的一个命令,而不是可执行语句。该子句必须在OPEN、FETCH、CLOSE语句之前使用。请看下面这个例子: EXEC SQL BEGIN DECLARE SECTION; char szCommand[] = "SELECT au_fname FROM authors WHERE au_lname = ?"; char szLastName[] = "White"; char szFirstName[30]; EXEC SQL END DECLARE SECTION; EXEC SQL DECLARE author_cursor CURSOR FOR select_statement; EXEC SQL PREPARE select_statement FROM :szCommand; EXEC SQL OPEN author_cursor USING :szLastName; EXEC SQL FETCH author_cursor INTO :szFirstName; 一个很实际的例子在4.4讲解。 6.4.3 SQLDA 可以通过SQLDA为嵌入SQL语句提供输入数据和从嵌入SQ语句中输出数据。理解SQLDA的结构是理解动态SQL的关键。 我们知道,动态SQL语句在编译时可能不知道有多少列信息。在嵌入SQL语句中,这些不确定的数据是通过SQLDA完成的。SQLDA的结构非常灵活,在该结构的固定部分,指明了多少列等信息(如下图中的sqld=2,表示为两列信息),在该结构的后面,有一个可变长的结构(SQLVAR结构),说明每列的信息。 SQLDA结构 Sqld=2 sqlvar …… Sqltype=500 Sqllen sqldata ….. Sqltype=501 Sqllen Sqldata …..
图6-8 SQLDA结构示例 具体SQLDA的结构在sqlda.h中定义,是: struct sqlda { unsigned char sqldaid[8]; // Eye catcher = 'SQLDA ' long sqldabc; // SQLDA size in bytes = 16+44*SQLN short sqln; // Number of SQLVAR elements short sqld; // Num of used SQLVAR elements struct sqlvar { short sqltype; // Variable data type short sqllen; // Variable data length // Maximum amount of data < 32K unsigned char FAR *sqldata; // Pointer to variable data value short FAR *sqlind; // Pointer to null indicator struct sqlname // Variable name { short length; // Name length [1..30] unsigned char data[30]; // Variable or column name } sqlname; } sqlvar[1]; }; 从上面这个定义看出,SQLDA是一种由两个不同部分组成的可变长数据结构。从位于SQLDA开端的sqldaid到dqld为固定部分,用于标志该SQLDA,并规定这一特定的SQLDA的长度。而后是一个或多个sqlvar结构 ,用于标志列数据。当用SQLDA把参数送到执行语句时,每一个参数都是一个sqlvar结构;当用SQLDA返回输出列信息时,每一列都是一个sqlvar结构。具体每个元素的含义为: lSqldaid。用于输入标志信息,如:“SQLDA”。 lSqldabc。SQLDA数据结果的长度。应该是16+44*SQLN。Sqldaid、sqldabc、sqln和sqld的总长度为16个字节。而sqlvar结构的长度为44个字节。 lSqln。分配的Sqlvar结构的个数。等价于输入参数的个数或输出列的个数。 lSqld。目前使用的sqlvar结构的个数。 lSqltype。代表参数或列的数据类型。它是一个整数数据类型代码。如:500代表二字节整数。具体每个整数的含义见下表: Sqltype代码说明SQL Server数据类型例子 392/39326字节长的包含日期和时间的字符串Datetime,smalldatetimechar date1[27] = Mar 7 1988 7:12PM; 444/445BinaryBinary,varbinary,image,timestampchar binary1[4097]; 452/453小于254字节的字符串Char,varcharchar mychar[255]; 456/457固定长度的长字符串textstruct TEXTVAR { short len; char data[4097];} textvar; 480/4818字节的浮点数Floatdouble mydouble1; 482/4834字节的浮点数realfloat myfloat1; 496/4974字节的整数Intlong myint1; 500/5014字节的整数Smallint,tinyint,bitshort myshort1; 462/463NULL结尾的字符串Char,varchar,textchar mychar1[41]; char * mychar2; lSqllen。代表传送数据的长度。如:2,即代表二字节整数。如果是字符串,则该数据为字符串中的字符数量。 lSqldata。指向数据的地址。注意,仅仅是一个地址。 lSqlind。代表是否为NULL。如果该列不允许为NULL,则该字段不赋值;如果该列允许为NULL,则:该字段若为0,表示数据值不为NULL,若为-1,表示数据值为NULL。 lSqlname。代表列名或变量名。它是一个结构。包含length和data。Length是名字的长度;data是名字。 下面我们来看两个具体的例子。第一个例子是通过SQLDA查询数据库中的数据。第二个例子是通过SQLDA传递参数。 我们首先看一个动态查询的例子。这个例子的作用是,由用户输入表名,查询系统表获得该表的列信息,询问用户是否显示该列数据,若是,则显示;否则,不显示。动态查询的执行过程如下: 1)、如同构造动态UPDATE语句或DELETE语句的方法一样,程序在缓冲器中构造一个有效的SELECT语句。 2)、动态DECLARE CURSOR语句说明查询游标,动态DECLARE CURSOR语句规定与动态SELECT语句有关的语句名称。如:例子中的querystmt。 3)、程序用PREPARE语句把动态查询语句送到DBMS,DBMS准备、确认和优化语句,并生成一个应用计划。 4)、程序用DESCRIBE语句请求DBMS提供SQLDA中描述信息,即告诉程序有多少列查询结果、各列名称、数据类型和长度。DESCRIBE语句只用于动态查询。具体见下一节。 5)、为SQLDA申请存放一列查询结果的存储块(即:sqldata指向的数据区),也为SQLDA的列的指示符变量申请空间。程序把数据区地址和指示符变量地址送入SQLDA,以告诉DBMS向何处回送查询结果。 6)、动态格式的OPEN语句。即打开存放查询到的数据集(动态SELECT语句产生的数据)的第一行。 7)、动态格式的FETCH语句把游标当前行的结果送到SQLDA。(动态FETCH语句和静态FETCH语句的不同是:静态FETCH语句规定了用主变量接收数据;而动态FETCH语句是用SQLDA接收数据。)并把游标指向下一行结果集。 8)、CLOSE语句关闭游标。 Main() { exec sql include sqlca; exec sql include sqlda; exec sql begin declare section; char stmbuf[2001]; char querytbl[32]; char querycol[32]; exec sql end declare section; /*静态游标*/ exec sql declare tblcurs cursor for select colname from syscolumns where tblname = :querytbl; int colcount = 0; struct sqlda * qry_da; struct sqlvar *qry_var; int I; char inbuf[101]; /*提示输入想要查询的表名*/ printf(“***Enter name of table for query: “); gets(querytbl); /*生成SELECT语句中的选择列表*/ strcpy(stmbuf,”select “); /*设置错误处理过程*/ exec sql whenever sqlerror goto handle_error; exec sql whenever not found goto no_more_columns; /*查询系统表,获得列名信息*/ exec sql open tblcurs; for( ; ; ) { exec sql fetch tblcurs into :querycol; printf(“Include column %s(y/n)?”,querycol); gets(inbuf); if(inbuf[0]= =’y’){ /*生成SELECT语句的各个列名*/ if (colcount++>0) strcat(stmtbuf, ”,”); strcat(stmtbuf,querycol); } } no_more_column: exec sql close tblcurs; /*生成SELECT语句中的FROM部分*/ strcat(stmtbuf,”from”); strcat(stmtbuf,querytbl); /*分配SQLDA空间*/ qry_da=(SQLDA *) malloc(sizeof(SQLDA)+colcount * sizeof(SQLVAR)); qry_da ->sqln = colcount; /*声明动态游标,以便逐行处理*/ exec sql declare qrycurs for querystmt; /*准备查询语句*/ exec sql prepare querystmt from :stmtbuf; /*获取SQLDA的描述信息*/ exec sql describe querystmt into qry_da; /*为存放一列数据的SQLDA申请空间*/ for( I=0; I<colcount; I++){ qry_var=qry_da ->sqlvar + I; qry_var -> sqldat = malloc(qry_var ->sqllen); qry_var -> sqlind =malloc(sizeof(short)); } /*动态OPEN语句,打开动态游标,即指向查询数据集的第一行*/ exec sql open qrycurs; exec sql whenever not found goto no_more_data; for( ; ;) { /*将查询到的一行值放在SQLDA中*/ exec sql fetch sqlcurs using descriptor qry_da; printf(“/n”); for(I=0; I<colcount; I++){ qry_var = qry_da ->sqlvar +I; /*显示列名信息*/ printf(“Column#%d(%s):”,I+1,qry_var ->sqlname); /*查看该列是否为NULL*/ if(*(qry_var ->sqlind)) !=0{ puts(“is NULL!\n”); continue; } /*按照数据类型,将它显示出来*/ switch(qry_var -> sqltype) { case 448: case 449: /*VARCHAR数据类型,直接显示*/ puts(qry_var -> sqldata) ; break; case 496: case 497: /*4位整数,转化,并显示出来*/ printf(“%ld”,*((int *) (qry_var->sqldata))); break; case 500: case 501: /*2位整数,转化,并显示出来*/ printf(“%d”,*((short *)(qry_var->sqldata))); break; case 480: case 481: /*浮点数,转化,并显示出来*/ printf(“%lf”,*((double *)(qry_var->sqldata))); break; } } } no_more_data: printf(“\nEnd of data.\n”); for (I=0;I<colcount;I++) { qry_var=qry_da->sqlvar+I; free(qry_var->sqldata); free(qry_var->sqlind); } free(qry_da); close qrycurs; exit(); } 上面这个例子是典型的动态查询程序。该程序中演示了PREPARE语句和DESCRIBE语句的处理方式,以及为程序中检索到的数据分配空间。要注意程序中如何设置SQLVAR结构中的的各个变量。这个程序也演示了OPEN、FETCH和CLOSE语句在动态查询中的应用。值得注意的是,FETCH语句只使用了SQLDA,不使用主变量。由于程序中预先申请了SQLVAR结构中的SQLDA和SQLIND空间,所以DBMS知道将查询到的数据保存在何处。该程序还考虑了查询数据为NULL的处理。 例2、用SQLDA规定输入参数。该程序在运行开始时,可以选择要更新的列和值。由于用户能够在每次运行程序时选择不同的列,所以该程序必须用SQLDA把参数传送到EXECUTE语句。这个例子仅供参考,读者只需读懂即可。 Main() { #define COLCNT 6 exec sql include sqlca; exec sql include sqlda; exec sql begin declare section; char stmbuf[2001]; exec sql end declare section; char * malloc() struct { char prompt[31]; /*列名的全名*/ char name[31]; /*列名*/ short typecode; /*数据类型代码*/ short buflen; /*数据长度*/ char selected; /*是否更新标志,y为是,n为否*/ }columns[]={“Name”,”NAME”,449,16,’n’, “Office”,”REP_OFFICE”,497,4,’n’, “Manager”,”MANAGER”,497,4,’n’, “Hire Date”,”HIRE_DATE”,449,12,’n’, “Quota”,”QUOTA”,481,8,’n’, “Sales”,”SALES”,481,8,’n’}; struct sqlda *parmda; struct sqlvar *parmvar; int parmcnt; /*参数个数*/ int empl_num; /*员工号*/ int I; int j; char inbuf[101]; /*用户输入信息*/ /* 询问用户更新哪些列*/ printf(“****Salesperson Update Program*****\n\n”); parmcnt = 1; for (I=0;I<COLCNT;I++) { printf(“Update %s column(y/n)? “,column[I].prompt); gets(inbuf); if (inbuf[0]==’y’){ column[I].selected = ‘y’; parmcnt + = 1; } } /*根据要更新的列数,分配SQLDA空间*/ parmda=malloc(16+(44*parmcnt)); strcpy(parmda->sqldaid,”SQLDA ”); parmda->sqldabc=(16+(44*parmcnt)); parmda->sqln=parmcnt; /*开始生成更新语句*/ strcpy(stmbuf,”update orders set “); j=0; /*处理列名*/ for (I=0;I++;I<COLCNT) { if (column[I].selected ==’n’) continue; if (parmcnt>0) strcat(stmbuf,’, ‘); strcat(stmbuf,column[I].name); strcat(stmbuf,”=?”); /*生成动态UPDATE语句*/ /*为sqlvar指定参数的信息,并申请存放新值的空间*/ parmvar=parmda->sqlvar + j; parmvar->sqltype = column[I].typecode; parmvar->sqllen = column[I].buflen; parmvar->sqldata = malloc(column[I].bufflen); parmvar->sqlind=malloc(2); strcpy(parmvar->sqlname.data,column[I].prompt); j+ = 1; } /*生成WHERE语句*/ strcat(stmbuf,” where empl_num = ?”); parmvar=parmda + parmcnt; parmvar->sqltype=496; parmvar->sqllen=4; parmvar->sqldata =&empl_num; parmvar->sqlind=0; parmda->sqld=parmcnt; /*编译动态SQL语句*/ exec sql prepare updatestmt from :stmbuf; if (sqlca.sqlcode < 0 ) { printf(“PREPARE error:%ld\n”,sqlca.sqlcode); exit(); } /*提示用户输入更新的新值,并将之存放在SQLDA中*/ for ( ; ; ) { printf(“\nEnter Salesperson’s Employee Number: “); scanf(“%ld”,&empl_num); if ( empl_num == 0) break; for (j=0;j<(parmcnt –1 ); J++) { parmvar=parmda + j; printf(“Enter new value for %s: “,parmvar->sqlname.data); gets(inbuf); if ( inbuf[0]= = ‘*’) { /*如果用户输入*,表示为NULL值*/ *(parmvar->sqlind) = -1; continue; } else{ *(parmvar -> sqlind)=0; switch (parmvar -> sqltype) { case 481: sscanf(inbuf,"%lf",parmvar->sqldata); break; case 449: stccpy(parmvar->sqldata,inbuf,strlen(inbuf)); parmvar->sqllen=strlen(inbuf); break; case 501: sscanf(inbuf,"%ld",parmvar->sqldata); break; } } } /*执行动态SQL语句,并经过SQLDA传递参数值*/ exec sql execute updatestmt using :parmda; if (sqlca.sqlcode) <0 ) { printf("execute error:%ld\n",sqlca.sqlcode); exit(); } } exec execute immediate "COMMIT WORK"; if (sqlca.sqlcode) printf("COMMIT error:%ld\n",sqlca.sqlcode); else printf("\n All update commited.\n"); exit(); } 在上述例子中,生成的动态UPDATE语句为: update salesreps set name =? , office=? , quota=? Where empl_num=? 该语句规定了4个参数,程序分配了一个处理四个sqlvar结构的SQLDA。用于提供参数值。 6.4.4 DESCRIBE语句 该语句只有动态SQL才有。该语句是在PREPARE语句之后,在OPEN语句之前使用。该语句的作用是,设置SQLDA中的描述信息,如:列名、数据类型和长度等。DESCRIBE语句的语法为: DESCRIBE 语句名 INTO 描述符名 如:exec sql describe querystmt into qry_da; 在执行DESCRIBE前,用户必须给出SQLDA中的SQLN的值(表示有多少列),该值也说明了SQLDA中有多少个SQLVAR结构。然后,执行DESCRIBE语句,该语句填充每一个SQLVAR结构。每个SQLVAR结构中的相应列为: lSQLNAME结构:列名放在DATA字段,列的长度放在LENGTH字段。 lSQLTYPE列:给出一个数据类型的整数代码。 lSQLLEN列:给出列的长度。 lSQLDATA列和SQLIND列:不填充。由程序在FETCH语句之前,给出数据缓冲器地址和指示符地址。 6.5 API 编写嵌入SQL程序完成的功能也可以通过DB-Library或ODBC API 的函数调用来完成。下面这个例子是使用DB-Library来连接SQL Server并执行一个简单的查询。类似于第一节中的例1。 #define DBNTWIN32 #include <sqlfront.h> #include <sqldb.h> main() { DBPROCESS *dbproc; LOGINREC *login; RETCODE r; dbinit(); login = dblogin(); if (login == NULL) return (1); DBSETLUSER(login, "sa"); DBSETLPWD(login, "password"); dbproc = dbopen(login, "YANGZH"); dbfreelogin(login); if (dbproc == NULL) return (1); dbuse(dbproc, "pubs"); dbcmd(dbproc, "select au_fname from authors where au_lname = 'White'"); r = dbsqlexec(dbproc); if (r == FAIL) return (1); while (1) { r = dbresults(dbproc); if (r == SUCCEED) { /* Process the rows with dbnextrow() */ } if ((r == FAIL) || (r == NO_MORE_RESULTS)) break; } return (0); } 嵌入SQL的程序有一些缺点。各个数据库厂商提出的嵌入SQL语言各不相同。尤其在SQLDA的定义上。另外,Microsoft SQL SERVER的嵌入SQL 的C程序不是线程安全的。如果你在一个线程应用中使用ESQL/C,那么应该仅仅在一个单一线程中调用E/SQL。最好在主线程中使用。所以,在可能的情况下,应该使用API来代替嵌入SQL。
|