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 ”和“#include ”语句,这是因为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 #include #include #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; Iqry_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; Iqry_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;Iqry_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;Iprintf(“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++;Iif (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 #include 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。
| | |