第六章 嵌入式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
#include
#include
#include "util.h"
#include
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
#include
#include
#if defined ( DB2 )
#define SQLNOTFOUND 100
#include
#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
#include
#include
/* 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 */