PROC是ORACLE数据库提供的编程接口之一,其应用十分的广泛,本文通过一个具体的例子,介绍PROC编程的一些经验及应注意的地方。
例子程序:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sqlda.h>
#include <sqlcpr.h>
EXEC SQL INCLUDE sqlca;
/*RELEASE_CURSOR=YES 使PROC 在执行完后释放与嵌入SQL有关资源*/
EXEC ORACLE OPTION (RELEASE_CURSOR = YES);
EXEC SQL BEGIN DECLARE SECTION;
varchar vc_user[20];
long al_empno=0;
char ac_ename[11]="";
char ac_hiredate[20]="";
double af_sal=0;
EXEC SQL VAR ac_ename IS STRING(11);
EXEC SQL VAR ac_hiredate IS STRING(20);
EXEC SQL END DECLARE SECTION;
/*错误处理函数*/
void sql_error(char *msg)
{
printf(" %s,%ld,%s ", msg,sqlca.sqlcode,(char *)sqlca.sqlerrm.sqlerrmc);
EXEC SQL ROLLBACK RELEASE;
exit(-1);
}
main()
{
EXEC SQL WHENEVER SQLERROR DO sql_error("ORACLE ERROR: ");
/*连接数据库*/
strcpy(vc_user.arr,"scott/tiger@DEMO");
vc_user.len=16;
exec sql connect :vc_user;
EXEC SQL DECLARE cur_emp CURSOR FOR
SELECT EMPNO, ENAME,to_char(HIREDATE,'yyyy/mm/dd hh24:mi:ss'),SAL FROM EMP;
EXEC SQL OPEN cur_emp;
while(1)
{
al_empno=0;
strcpy(ac_ename,"");
strcpy(ac_hiredate,"");
af_sal=0;
EXEC SQL FETCH cur_emp INTO :al_empno, :ac_ename:ename_ind, :ac_hiredate:hiredate_ind, :af_sal:sal_ind;
if( sqlca.sqlcode == 1403)
{
break;
}
printf("empno=%ld,ename=%s,hiredate=%s,sal=%lf ",al_empno,ac_ename,ac_hiredate,af_sal);
}
EXEC SQL CLOSE cur_emp;
EXEC SQL ROLLBACK WORK RELEASE;
}
1、宿主变量的声明
在PROC中,在SQL语句中用到的变量称为宿主变量。他们应在EXEC SQL BEGIN DECLARE SECTION;与EXEC SQL EDN DECLARE SECTION;
之间声明,如上面所示.在声明宿主变量时应注意以下几点:
(1) 在数据库表中定义为VARCHAR2,VARCHAR,CHAR的字段,在PROC中可声明为CHAR,但长度应为它们在表中定义的长度加1,因为PROC中
CHAR型变量用做结尾。
如:ENAME在表中的定义为ename varchar2(10),在PROC中可定义为:
EXEC SQL BEGIN DECLARE SECTION;
char ename[11];
EXEC SQL END DECLARE SECTION;
常见错误说明:
如果插入的字符串长度大于10,如:EXEC SQL INSERT INTO EMP(ENAME) VALUES('12345678901');会出现以下错误:
error:ORA-01480: STR 赋值变量缺少空后缀。
如果定义为:
EXEC SQL BEGIN DECLARE SECTION;
char ename[15];
EXEC SQL END DECLARE SECTION;
当插入的字符串长度大于10,小于15时,如:EXEC SQL INSERT INTO EMP(ENAME) VALUES('12345678901');会出现以下错误:
error:ORA-01401: 插入的值对于列过大。
当插入的字符串长度大于15,如:EXEC SQL INSERT INTO EMP(ENAME) VALUES('12345678901234');会出现以下错误:
error:ORA-01401:STR 赋值变量缺少空后缀。
(2) 从SQL语句中取字段的值到宿主变量中时,PROC不会自动给宿主变量去掉右空格。而是以在DECLARE SECTION 中定义的长度为准(与 表中定义的无关)不足补右空格.如果不注意这一点,在PROC中进行字符串操作时(如比较相等)会出错。如:
EXEC SQL BEGIN DECLARE SECTION;
char ename[10];
EXEC SQL END DECLARE SECTION;
如果ENAME在表中的值为'abc',则取出的值为'abc ';
可用语句EXEC SQL VAR重定义CHAR型变量。这样宿主变量会自动去掉右空格。如下:
EXEC SQL BEGIN DECLARE SECTION;
char ename[11];
EXEC SQL VAR ac_ename IS STRING(11);
EXEC SQL END DECLARE SECTION;
如果ENAME在表中的值为'abc',则取出的值为'abc';
(3) 对浮点型的变量,为保证精度,最好是声明成DOUBLE型的.因为DOUBLE型的精度比FLOAT型高很多.
(4) 整型可声明为LONG型(对较长的整型,而且所用的平台支持的话,如在SUN平台上,可声明为LONG LONG型).
(5) DATE型的处理:DATE型一般声明为CHAR(20)。
往表中插入DATE型数据时,一般用TO_DATE()函数进行类型转换,取出值时一般用TO_CHAR()函数进行类型转换.
EXEC SQL select to_char(hiredate,'yyyy/mm/dd hh24:mi:ss') into :ac_hire_date from EMP where empno=1234;
EXEC SQL insert into EMP(EMPNO,HIREDATE) values(123,to_date(:ac_hiredate,'yyyy/mm/dd hh24:mi:ss');
2、宿主变量的作用范围
如果宿主变量在所有的函数之外声明,则他们是全局变量。在使用之前要注意把变量的值初始化,宿主变量也可以在某个函数的内部定义。 这时他们是局部变量。一般都习惯把宿主变量声明为全局变量。
3、数据库的连接与断开
数据库的连接有以下两种方法:
(1)
strcpy(vc_user.arr,"scott/tiger");
vc_user.len=11;
exec sql connect :vc_user;
(2)
strcpy(user,"scott");
strcpy(pass,"tiger");
exec sql connect :user identified by :pass;
注意:在有些平台上两种都可以,在有些平台上只能用第一种方法.
在PROC程序中,要记住用EXEC SQL ROLLBACK WORK RELEASE;断开与数据库的连接,并释放相关的数据库资源。
4、PROC中的NULL值的处理
如果某一字段取出的值是NULL,会报:sqlcode=-1405, sqlerr=ORA-01405: 读取的列值为 NULL
并且相应的宿主变量的值不会被改变,为执行该SQL语句之前的值. 常用的处理NULL值的方法有:
(1)采用指示器变量,此时不会有-1405错误,当必须是所以为NULL的字段都有相应的指示器变量,如果某一字段没有指示器变量,但取出的值
为NULL值,则仍然会有-1405错误.当取出的值是NULL时,相应的指示器变量变量为-1,可根据指示器变量的值做响应的处理。
(2)如果字段较多,可取字段到一个结构体中及与该结构体对应的指示器结构体中.如上面的例子中可定义结构体:
struct str_emp{
long al_empno;
char ac_ename;
char ac_hiredate;
double af_sal;
};
struct str_emp_ind{
long al_empno;
char ac_ename;
char ac_hiredate;
double af_sal;
};
struct str_emp str_emp;
strcut str_emp_ind str_emp_ind;
在取之前可用memset(&str_emp,0,sizeof(str_emp)).清空该结构体,这样如果是字符型的NULL,会为"",整型的NULL会为0,
浮点型的会为0.00。此时不会有-1405错误。
(3)也可采用NVL()函数:举例如下:
EXEC SQL DECLARE authors CURSOR FOR
SELECT EMPNO, NVL(ENAME,chr(0)),nvl(to_char(HIREDATE,'yyyy/mm/dd hh24:mi:ss'),chr(0)),NVL(SAL,0) FROM EMP;
这样也不会有-1405错误不,当取出的值是NULL时,自动用NVL()中指定的值代替.
CHR(0)也可直接用''代替,如下:
SELECT EMPNO, NVL(ENAME,''),nvl(to_char(HIREDATE,'yyyy/mm/dd hh24:mi:ss'),''),NVL(SAL,0) FROM EMP;
5、PROC中的错误的处理
所有的SQL语句都有可能出错.所以都要加以判断,但每个SQL语句后都加错误判断,太麻烦,可用一个函数如sql_error()来进行错误处理,
方法:
(1)定义ql_error()函数。
(2)在开头加上EXEC SQL WHENEVER SQLERROR DO sql_error();这样当发生sqlca.sqlcode <0 的错误时,程序自动转到sql_error()中执行. 注意:对sqlca.sqlcode >0的错误如 sqlca.sqlcode =1403 是不会转到sql_error()中执行的.
另外:在UNIX下,可以用OERR 来查找错误的描述。如: ora ORA -1405 查找错误号为-1405的描述.
6、PROC中调用存储过程的方法
要把存储过程放在EXEC SQL EXECUTE 和 END-EXEC;之间,如下所示:
其中:al_empno,ac_ename 为输入参数,l_return,l_errno,c_errtext 为输出参数。
al_empno=8888;
strcpy(ac_ename,"ABCD");
EXEC SQL EXECUTE
BEGIN
up_db_emp(:al_empno,:ac_ename,:l_return,:l_errno,:c_errtext);
END;
END-EXEC;
if (l_return != 0)
{
printf("调用UP_PB_EMP存储过程出错,errno=%ld,errtext=%s ",l_errno,c_errtext);
}
7、PROC的命令行选项:PROC编译器有很多的命令行选项,在命令行下直接不带参数运行PROC,会列出所有的命令行选项来,并有说明。
(1)储存过程:编译储存过程是要带上用户名及密码
proc USERID=scott/tiger sqlcheck=SEMANTICS ireclen=512 iname=test.cpp
(2)PARSE=NONE 对非SQL代码不进行语法分析,默认对非SQL代码也进行语法分析.
在RED HAD6.3上的ORACLE8.1.5中用PROC时,会提示:/USR/INCLUDE/STDIO.H 及其他的.H文件中有错. 可把PARSE=NONE加上,就好了.
8、注意加上:EXEC ORACLE OPTION (RELEASE_CURSOR = YES);
RELEASE_CURSOR=YES 使PROC 在执行完后释放与嵌入SQL有关资源,保证在该PROC程序执行完后,ORACLE不会锁住数据库资源,如锁表等。
如果在PROC中用到ORACA,还要在程序头加上:
EXEC ORACLE OPTION (ORACA=YES);
9、PROC中的类型转换
一、在C语言中:
(1)字符型到整型可用ATOI() ATOL(),SSCANF()
(2)整型,浮点型到字符型,可用SPRINTF()
(3)字符型到浮点型用ATOF()不行,最好用SSCANF(),举例如下:
EXEC SQL BEGIN DECLARE SECTION;
double d_demo;
float f_demo;
char ac_text[20]="222";
EXEC SQL END DECLARE SECTION;
(1)sscanf(ac_text, "%f", &d_demo);
printf("ac_text=%s,d_demo=%f ",ac_text,d_demo);
(2)sscanf(ac_text, "%lf", &d_demo);
printf("ac_text=%s,d_demo=%f ",ac_text,d_demo);
(3)sscanf(ac_text, "%f", &d_demo);
printf("ac_text=%s,d_demo=%lf ",ac_text,d_demo);
(4)sscanf(ac_text, "%lf", &d_demo);
printf("ac_text=%s,d_demo=%lf ",ac_text,d_demo);
printf("******************* ");
(5)sscanf(ac_text, "%f", &f_demo);
printf("ac_text=%s,f_demo=%f ",ac_text,f_demo);
(6)sscanf(ac_text, "%lf", &f_demo);
printf("ac_text=%s,f_demo=%f ",ac_text,f_demo);
(7)sscanf(ac_text, "%f", &f_demo);
printf("ac_text=%s,f_demo=%lf ",ac_text,f_demo);
(8)sscanf(ac_text, "%lf", &f_demo);
printf("ac_text=%s,f_demo=%lf ",ac_text,f_demo);
输出的结果:
ac_text=222.00,d_demo=0.000000
ac_text=222.00,d_demo=222.000000
ac_text=222.00,d_demo=222.000032
ac_text=222.00,d_demo=222.000000
*******************
ac_text=222.00,f_demo=222.000000
ac_text=222.00,f_demo=0.000000
ac_text=222.00,f_demo=222.000000
ac_text=222.00,f_demo=0.000000
d_demo=atof(ac_text);
printf("ac_text=%s,atof(ac_text)=%f ",ac_text,d_demo);
d_demo=atof(ac_text);
printf("ac_text=%s,atof(ac_text)=%lf ",ac_text,d_demo);
f_demo=atof(ac_text);
printf("ac_text=%s,atof(ac_text)=%f ",ac_text,f_demo);
f_demo=atof(ac_text);
printf("ac_text=%s,atof(ac_text)=%lf ",ac_text,f_demo);
输出的结果:
ac_text=222.00,atof(ac_text)=1243288.000000
ac_text=222.00,atof(ac_text)=1243288.000000
ac_text=222.00,atof(ac_text)=1243288.000000
ac_text=222.00,atof(ac_text)=1243288.000000
从上面的结果可见:
DOUBLE型应采用sscanf(ac_app_capcity, "%lf", &d_app); 打印用"%lf","%f" 都可以. (2),(4)正确
FLOAT型应采用sscanf(ac_app_capcity, "%f", &d_app); 打印用"%lf","%f" 都可以. (5),(7)正确
采用ATOF()转换的结果都是错的,所以不要用它。
二、写表或从表中取数据时:
(1)字符型与整型之间可不用转换,采用默认方式。
(2)字符型与浮点型之间可不用转换,采用默认方式。
(3)日期型与字符型之间可用TO_CHAR(),TO_DATE()。
10、PROC中的4种动态SQL简介
(1)动态SQL1: 不能是查询(SELECT)语句,并且没有宿主变量.
用法:拼一串动态SQL语句,并用EXECUTE IMMEDIATE执行,如:
EXEC SQL EXECUTE IMMEDIATE "CREATE TABLE dyn1 (col1 VARCHAR2(4))";
(2)动态SQL2: 不能是查询(SELECT)语句,并且输入的宿主变量数目是知道的,
用法:拼一串动态SQL语句,用PREPARE,EXECUTE语句执行.
strcpy(c_sql, "DELETE FROM EMP WHERE EMPNO = :?");
EXEC SQL PREPARE sql_stmt FROM :c_sql;
EXEC SQL EXECUTE sql_stmt USING :emp_number;
(3)动态SQL3: 用于创建动态查询, 并且要查询的字段及输入的宿主变量数目是知道的
用法: 拼一串动态SQL语句,用PREPARE分析该语句,并要定义一个CURSOR进行取值
如:如要查询的数据按一年12月放到12张表中。表名为user_fee_1mon, user_fee_2mon,....可采用动态SQL3来进行查询
strcpy(c_sql,"select c_user_id,c_user_name,to_char(t_date,'yyyy/mm/dd hh:mi:ss'),n_fee ");
strcat(c_sql,"from USER_FEE_");
strcat(c_sql,ac_mon);
strcat(c_sql," where c_user_id = :v1");
EXEC SQL PREPARE s FROM :c_sql;
EXEC SQL DECLARE cur_user_fee CURSOR FOR s;
EXEC SQL OPEN cur_user_fee USING :ac_user_id;
while(1)
{
EXEC SQL FETCH cur_user_fee into :c_user_id,:c_user_name,:c_date,:n_fee);
if (sqlca.sqlcode < 0)
{
/*FETCH CURSOR失败*/
printf("fetch cursor cur_user_fee fail,sqlcode=%ld,sqlserr=%s",sqlca.sqlcode,sqlca.sqlerrm.sqlerrmc);
}
if( sqlca.sqlcode == SQLNOTFOUND)
{
break;
}
}
EXEC SQL CLOSE cur_user_fee;
(4)动态SQL4:要处理的字段及输入的宿主变量数目和主变量的类型事先是不知道的,如:
INSERT INTO EMP (<unknown>) VALUES (<unknown>)
是最复杂的动态SQL,很少用,在此不做介绍。
11、SQLCA:SQL是ORACLE的一个结构体,它的域用于最近的一条SQL语句执行后的一些信息,如错误号,错误描述,警告,状态等。常用的
域介绍如下:
SQLCA.sqlcode:错误号,=0正确,=1403没取到数据
SQLCA.sqlserrm.sqlerrmc:错误描述
SQLCA.sqlerrd[3]:最近的一条SQL语句所处理的行数,如果该语句处理失败,则它的值是不定的,如果错误在一个CURSOR操作中发生,则
它的值指已成功处理的行数.在DELETE,UPDATE中,它不包含因外键约束而删除,更新的那些行,
DELETE FROM EMP WHERE DEPT='SALE';
在表EMP中删除20行,但如果表EMP与表ADDRESS有外键约束,导致表ADDRESS也被删除20行,则SQLCA.sqlerrd[3]=20,而不是40。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Pro*C介绍-内嵌SQL
[ Translate by Z.Jingwei. Document address:http://www-db.stanford.edu/~ullman/fcdb/oracle/or-proc.html ]
Pro*C介绍
内嵌SQL
--------------------------------------------------------------------------------
概要
Pro*C语法
SQL
预处理指令
语句标号
宿主变量
基础
指针
结构
数组
指示器变量
数据类型同等化
动态SQL
事务
错误处理
SQLCA
WHENEVER语句
Demo程序
C++用户
List of Embedded SQL Statements Supported by Pro*C
--------------------------------------------------------------------------------
概要
内嵌SQL是结合高级语言如C/C++的计算能力和SQL数据库处理能力的一种方法。它允许你在程序中执行任意的SQL语句。Oracle的嵌入SQL环境称为Pro*C。
Pro*C程序分两步编译。首先,Pro*C的预编译器识别出嵌入在程序中的SQL语句,并将这些语句转换为对SQL运行时库(SQL runtime library)中功能(functions)的适当调用。输出是纯C/C++代码和未被处理的纯C/C++代码。然后,用常规C/C++编译器编译代码并生成可执行程序。更详细的内容请参考Demo程序。
Demo程序。
--------------------------------------------------------------------------------
Pro*C语法
--------------------------------------------------------------------------------
SQL
所有SQL语句都要以EXEC SQL开始,并用分号";"结束。SQL语句可以放置在C/C++块中的任何地方,但可执行的(SQL)语句应该在声明语句后面。例:
{
int a;
/* ... */
EXEC SQL SELECT salary INTO :a
FROM Employee
WHERE SSN=876543210;
/* ... */
printf("The salary is %d ", a);
/* ... */
}
--------------------------------------------------------------------------------
预处理指令
能够在Pro*C中正常工作的C/C++预处理指令是#include和#if。Pro*C不能识别#define。下面的代码是错误的:
#define THE_SSN 876543210
/* ... */
EXEC SQL SELECT salary INTO :a
FROM Employee
WHERE SSN = THE_SSN;/* INVALID */
--------------------------------------------------------------------------------
语句标号
可以在SQL中跳转到C/C++标记
EXEC SQL WHENEVER SQLERROR GOTO error_in_SQL;
/* ... */
error_in_SQL:
/* do error handling */
我们会在后面的错误处理一节中讲到有关WHENEVER的含意。
错误处理一节中讲到有关WHENEVER的含意。
--------------------------------------------------------------------------------
宿主变量
--------------------------------------------------------------------------------
基础
宿主变量是连接宿主程序与数据库的关键。宿主变量表达式必须视为(resolve to)左值(能被赋值)。你可以像声明普通C变量一样,按着C的语法规则声明宿主变量。宿主变量的声明可以放置在任何C变量声明可以放置的地方。(C++用户需要使用"DECLARE SECTION";参考C++ Users) Oracle可以使用的C数据类型包括:
C++ Users) Oracle可以使用的C数据类型包括:
char
char[n]
int
short
long
float
double
VARCHAR[n] - 它是能被Pro*C的预编译器识别的预处理类型(psuedo-type)。它用来 表示由空白填充(blank-padded,译注:'')的变长字符串。Pro*C预编译器会把它转换为有一个2字节(byte)长的域和一个n字(byte)长的字符数组的结构体。
你不能指定寄存器存储类型(译注:指针)为宿主变量。
可以在SQL表达式中使用冒号":"做前缀来引用一个宿主变量,但不能在C表达式中用分号做前缀。当使用字符串作为宿主变量时,必须省略引用;Pro*C明白你正指定一个基于宿主变量声明类型的字符串(译注:这句的意思是,当定义一个字符串做为宿主变量时char *str="string",在嵌入SQL中使用时,要省略"*"而不是*str)。不能将C函数调用和多数的指针计算表达式作为宿主变量使用,即使它们确实被解释为左值。下面的代码同时说明了合法和不合法的宿主变量的引用:
int deptnos[3] = { 000, 111, 222 };
int get_deptno() { return deptnos[2]; }
int *get_deptnoptr() { return &(deptnos[2]); }
int main()
{
int x; char *y; int z;
/* ... */
EXEC SQL INSERT INTO emp(empno, ename, deptno)
VALUES(:x, :y, :z); /* LEGAL */
EXEC SQL INSERT INTO emp(empno, ename, deptno)
VALUES(:x + 1, /* LEGAL: the reference is to x */
'Big Shot', /* LEGAL: but not really a host var */
:deptnos[2]);/* LEGAL: array element is fine */
EXEC SQL INSERT INTO emp(empno, ename, deptno)
VALUES(:x, :y,
:(*(deptnos+2)));/* ILLEGAL: although it has anlvalue */
EXEC SQL INSERT INTO emp(empno, ename, deptno)
VALUES(:x, :y,
:get_deptno()); /* ILLEGAL: no function calls */
EXEC SQL INSERT INTO emp(empno, ename, deptno)
VALUES(:x, :y,
:(*get_depnoptr())); /* ILLEGAL: although it has an lvalue */
/* ... */
}
--------------------------------------------------------------------------------
指针
可以在SQL表达式中使用普通C语法声明的指针。通常使用一个冒号做前缀:
int*x;
/*...*/
EXEC SQL SELECT xyz INTO :x FROM ...;
这个SELECT语句的结果会写入*x,而不是x。
--------------------------------------------------------------------------------
结构
结构同样可以作为宿主变量使用,如下面例子:
typedef struct {
char name[21];/* one greater than column length; for '' */
int SSN;
} Emp;
/* ... */
Emp bigshot;
/* ... */
EXEC SQL INSERT INTO emp (ename, eSSN)
VALUES (:bigshot);
--------------------------------------------------------------------------------
数组
像下面这样使用宿主数组:
int emp_number[50];
char name[50][11];
/* ... */
EXEC SQL INSERT INTO emp(emp_number, name)
VALUES (:emp_number, :emp_name);
这样会一次插入所有的50个元素(tuples)。
数组只能是一维数组。例子中的char name[50][11]可能看起来与这个规则矛盾,然而,Pro*C实际上把name视为一维字符串数组而不是二维字符数组。也可以使用结构数组。
当使用数组存放查询结果时,如果宿主数组的大小(n)小于实际查询后返回的结果数量时,那么只有(查询出来的)前n个结果被填入宿主数组。
--------------------------------------------------------------------------------
指示器变量
指示器实际上是附在宿主变量后的"NULL标记"。每一个宿主变量都可以选择性的关联到一个指示器变量上。指示器变量的类型必须为2字节整形值(short类型),而且必须紧随在宿主变量后且用冒号做为前缀。你也可以使用关键字INDICATOR放在宿主变量和指示器变量之间。例如:
short indicator_var;
EXEC SQL SELECT xyz INTO :host_var:indicator_var
FROM ...;
/* ... */
EXEC SQL INSERT INTO R
VALUES(:host_var INDICATOR :indicator_var, ...);
在SELECT的INTO子句中使用的指示器变量可以用来检查返回给宿主变量的值为空(NULL)或被截断的的情况。Oracle能够赋给指示器变量的值具有下面各项意义:
-1 字段(译注:原文为column,意为该字段的列)值为NULL,宿主变量的值不确定。
0 Oracle把完整的字值赋给了宿主变量。
>0 Oracle把被截断的字段值赋给了宿主变量。指示器变量返回的整形值是字段的原始长度。
-2 Oracle把被截断的字段值赋给了宿主变量,但原字段值是不确定的。(译注:这种情况可能是数值型的值被切断后不能确定原始值)
你也可以在INSERT或UPDATE的VALUES和SET子句中使用指示器变量指明用于输入的宿主变量为NULL值。你的程序可以赋给指示器变量的值具有如下各项意义:
-1 Oracle会忽略宿主变量的值并给字段赋一个NULL值。
>=0 Oracle会将宿主变量的值赋给字段。
--------------------------------------------------------------------------------
数据类型同等化
Oracle认识两种数据类型:内部类型和外部类型。内部数据类型指示Oracle在数据库表中如何存储字段。外部数据类型指示如何格式化存储在宿主变量中用于输入或输出的值。在预编译期,每一个宿主变量会被赋予一个默认的Oracle外部数据类型。数据类型同等化允许你重载默认的同等化,并允许你控制Oracle输入数据的解释和输出数据的格式化。
同等化通过使用VAR表达式实现基于从变量到变量的转换。语法是:
EXEC SQL VAR IS [ () ];
举例来说, 假设你想从emp表中查询雇员名子,然后传给一个需要C类型(以''结尾的)字符串的程序,你不需要显式的用''来结束这个名子。如下面所示,简单地将宿主变量同等化为外部数据类型STRING:
char emp_name[21];
EXEC SQL VAR emp_name IS STRING(21);
emp表中ename字段的长是20字符(character),因此,需要分配21字符(character)以适应''结束符。Oracle的外部数据类型STRING是为C类型字符串特别设计的接口。当把ename字段值传给emp_name时,Oracle会自动用' '结尾。
也可以使用TYPE语句将用户自定义数据类型同等化为Oracle外部数据类型。语法是:
EXEC SQL TYPE IS [ () ] [REFERENCE];
可以声明一个用户自定义类型的指针,显式的如指向标量或结构的指针,或隐式的如数组,然后在TYPE表达式中使用这个类型。这种情况下,你需要在表达式后使用REFERENCE子句,如下所示:
typedef unsigned char *my_raw;
EXEC SQL TYPE my_raw IS VARRAW(4000) REFERENCE;
my_raw buffer;
/* ... */
buffer = malloc(4004);
这里,我们分配了比源类型长(4000)更多的内存,这是因为预编译器要返回长度,而且可能需要填充适当的长度以满足系统的对齐要求。
--------------------------------------------------------------------------------
动态SQL
嵌入SQL能够满足一个固定的应用,但有时动态产生完整的SQL语句也是很重要的。对于动态SQL,语句存储在字符串变量中,PREPARE把字符串转换为SQL语句,然后用EXECUTE执行这个语句。考虑下面的例子:
char *s = "INSERT INTO emp VALUES(1234, 'jon', 3)";
EXEC SQL PREPARE q FROM :s;
EXEC SQL EXECUTE q;
PREPARE和EXECUTE可放到一个语句中,像这样:
char *s = "INSERT INTO emp VALUES(1234, 'jon', 3)";
EXEC SQL EXECUTE IMMEDIATE :s;
--------------------------------------------------------------------------------
事务
Oracle Pro*C支持标准SQL定义的事务。一个事务是一组SQL语句集合,Oracle把它当作单独的单元运行。一个事务从第一个SQL语句开始,遇到"EXEC SQL COMMIT"(执行当前事务对数据库的永久修改)或"EXEC SQL ROLLBACK"(取消从事务开始到当前位置对数据库的任何修改)时结束事务。当前事务由COMMIT或ROLLBACK语句结束后,下一条可执行SQL语句将自动开始一个新事务。
如果程序结束时没有执行EXEC SQL COMMIT,则对数据库的所作的修改都将被忽略。
--------------------------------------------------------------------------------
错误处理
在每个可执行的SQL表达式之后,可以在程序中检查SQLCA显式地或用WHENEVER语句隐式地得到执行状态信息。下面将详细地介绍这两种方法。
--------------------------------------------------------------------------------
SQLCA
SQLCA(SQL Communications Area,SQL通讯区)用于在程序中检查错误和状态变化。每个可执行SQL语句执行后,Oracle 运行时会在这个结构中填入信息。
如果要使用SQLCA,你要用#include包含进sqlca.h头文件。如果在很多地方包含了头文件,你要使用#undef SQLCA来取消SQLCA的宏定义。sqlca.h中的相关程序块如下:
#ifndef SQLCA
#define SQLCA 1
struct sqlca {
/* ub1 */ char sqlcaid[8];
/* b4 */ long sqlabc;
/* b4 */ long sqlcode;
struct {
/* ub2 */ unsigned short sqlerrml;
/* ub1 */ char sqlerrmc[70];
} sqlerrm;
/* ub1 */ char sqlerrp[8];
/* b4 */ long sqlerrd[6];
/* ub1 */ char sqlwarn[8];
/* ub1 */ char sqlext[8];
};
/* ... */
sqlca域有下列的意义:
sqlcaid 该字符串域初始化为"SQLCA",以指明这是个SQL通讯区。
sqlcabc 该整数域用byte标识SQLCA结构的长度。
sqlcode 这个整数域标识最近执行的SQL语句的状态码:
0 没有错误。
>0 语句执行但捕获异常。当Oracle根据WHERE条件查找不到任何记录,或SELECT INTO或FETCH影响记录数为0时会发生这种情况。
<0 由于一个错误Oracle不能执行SQL语句。当这个错误发生时,多数情况下当前的事务应该回滚(rollback)。
sqlerrm 这个内嵌结构包含两个域:
sqlerrml - 在sqlerrmc中保存的信息文本的长。
sqlerrmc - 最大到70个字符(character)的信息文本,与sqlcode中存储的错误码相似。
sqlerrp 保留
sqlerrd 这个二进制整形数组包含6个元素:
sqlerrd[0] - 保留
sqlerrd[1] - 保留
sqlerrd[2] - 最近被执行的SQL语句影响的记录行数。
sqlerrd[3] - 保留
sqlerrd[4] - 分析最近执行语句出现错误,错误的开始字符的偏移。
sqlerrd[5] - 保留
sqlwarn 这个单字符数组包含8个元素,用于警告标志。Oracle使用字符'W'设置一个标记。
sqlwarn[0] 当其它标记被设置时设置。
sqlwarn[1] 当输出宿主变量中保存的是被截断的字段值时设置。
sqlwarn[2] 当计算SQL统计如AVG或SUM时,如果一个NULL字段不能被使用时被设置。
sqlwarn[3] 当SELECT的字段数与INTO句中指定的宿主变量数不等时设置。
sqlwarn[4] 当没有使用WHERE子句的UPDATE或DELETE语句处理了数据表中的每一行时设置。
sqlwarn[5] 当由于PL/SQL编译错误而导致procedure/function/package/package body创建命令失败时设置。
sqlwarn[6] 不再使用
sqlwarn[7] 不再使用
sqlext 保留
SQLCA只能在sqlerrm域中存储最大70字符长的错误信息。要得到更长的(或嵌套的)全部错误信息字符,可以使用sqlglm()函数:
void sqlglm(char *msg_buf, size_t *buf_size, size_t *msg_length);
msg_buf是Oracle存储错误信息的字符缓冲;buf_size指定msg_buf的长(byte); Oracle在*msg_length中存放实际的错误信息长。Oracle错误信息的最大长度是512字节(byte)。
--------------------------------------------------------------------------------
WHENEVER语句
这个表达式进行自动错误检查和处理。语法是:
EXEC SQL WHENEVER ;
Oracle自动检查的SQLCA,当条件被检测到时,程序会自动执行
可以是下列各项:
SQLWARNING - 由于Oracle返回一个警告而设置sqlwarn[0]
SQLERROR - 由于Oracle返回一个错误, sqlcode的值为负
NOT FOUND - 由于Oracle按WHERE的条件没有找到任何一条记录,或SELECT INTO或FETCH返回0条记录,而使sqlcode为正
可以为下列各项:
CONTINUE - 只要可能,程序会尝试继续运行之后的语句
DO - 程序将控制权交给一个错误处理模块
GOTO - 程序跳转到被标示的语句
STOP - 调用exit()结束程序,回滚未提交的操作
下面是WHENEVER语句的例子:
EXEC SQL WHENEVER SQLWARNING DO print_warning_msg();
EXEC SQL WHENEVER NOT FOUND GOTO handle_empty;
下面是一个更详细的例子:
/* code to find student name given id */
/* ... */
for (;;)
{
printf("Give student id number : ");
scanf("%d", &id);
EXEC SQL WHENEVER NOT FOUND GOTO notfound;
EXEC SQL SELECT studentname INTO :st_name
FROM student
WHERE studentid = :id;
printf("Name of student is %s. ", st_name);
continue;
notfound:
printf("No record exists for id %d! ", id);
}
/* ... */
注意WHENEVER表达式不遵从标准C的作用域规则,整个程序都是它的作用域。举例来说,如果下面的语句在你程序中的什么地方(如在一个循环之前):
EXEC SQL WHENEVER NOT FOUND DO break;
这个文件在这行之后出现的所有SQL语句都会受其影响。当不再需要WHENEVER 的来影响程序时(比如在你的循环之后),确保使用下面的语句取消它的使用。
EXEC SQL WHENEVER NOT FOUND CONTINUE;
--------------------------------------------------------------------------------
Demo程序
注意: 例程会创建、使用四个表:DEPT、EMP、PAY1和PAY2。注意你的数据库中可能会存在相同名子的表!
在leland系统的/afs/ir/class/cs145/code/proc下有很多例程。它们被命名为sample*.pc(C用户)和cppdemo*.pc(C++用户)。 ".pc"是Pro*C代码的扩展名。由于有几个固定的步骤复制这些文件,所以不要手工复制它们。下面介绍如何下载和设置例程:
确认你已经运行了source /afs/ir/class/cs145/all.env
在你的目录下,运行load_samples , 是你想放置例程的地方(例:load_samples sally etaoinshrdlu cs145_samples )
cd
运行make samples(C++用户运行make cppsamples)编译所有的例程。
第2步将会建立样品数据库,创建在指定的新目录,然后把文件复制到目录下。它会在例程中修改你的用户名和密码,所有你不必每次运行例程时都要输入用户名和密码。sample1 和cppdemo1也为用户提供了输入用户名和密码的接口,当你想要学习如何使用的时候。如果在第2步输入用户名和密码时产生了任何错误,只要在你的目录下运行clean_sample & lt;sample_dir>,然后重做第2步到第4步。
对于第4步,你可以单独编译每一个例程。比如单独编译sample1.pc。编译过程实际上有两句:
proc iname=sample1.pc
把内嵌SQL代码转换为相应的库调用,输出sample1.c
cc sample1.c
主成可执行的sample1
编译你自己的代码,如foo.pc,只要修改Makefile的几个变量:在SAMPLES变量中加入foo程序名,在SAMPLE_SRC变量中加入foo.pc源文件名。然后,写好foo.pc 后make foo。foo.pc被预编译为foo.c,再编译为可执行的foo。C++用户要把程序名和源文件名加入到CPPSAMPLE和CPPSAMPLE_SRC而不是SAMPLES和SAMPLE_SRC。
例程运行于下面的数据库表上:
CREATE TABLE DEPT
(DEPTNO NUMBER(2) NOT NULL,
DNAME VARCHAR2(14),
LOC VARCHAR2(13));
CREATE TABLE EMP
(EMPNO NUMBER(4) NOT NULL,
ENAME VARCHAR2(10),
JOB VARCHAR2(9),
MGR NUMBER(4),
HIREDATE DATE,
SAL NUMBER(7, 2),
COMM NUMBER(7, 2),
DEPTNO NUMBER(2));
CREATE TABLE PAY1
(ENAME VARCHAR2(10),
SAL NUMBER(7, 2));
CREATE TABLE PAY2
(ENAME VARCHAR2(10),
SAL NUMBER(7, 2));
当在第2步运行load_samples的时候这些表就自动创建好了。一些tuples(译注:没找到合适的词,把原词放这了)也插入了。你可以在程序运行前查看这些表也可以任意操作这些表(如:插入、删除或是更新tuples)。当你运行clean_sample时,这些表自动被删除。注意:clean_sample也会清理整个;;
确保在运行这个命令之前将你自己的文件转移到其它的地方!
你应该在运行它之前看一看源代码,头部的注释描述了程序都做些什么。例如,sample1从一个雇员的EMPNO 得到他的名子和工资,从EMP表中得到的那个雇员的佣金。
通过学习源程序你应该可以学到下面的东西:
如何从主机连接到Oracle
如何在C/C++中嵌入SQL
如何使用游标
如何使用宿主变量与数据库通讯
如何使用WHENEVER进行不同的错误处理动作
如何使用指示器变量检测输出中的空值
现在,你可以使用这些技术编写你自己的数据库应用程序了。And have fun!
--------------------------------------------------------------------------------
C++用户
要使用预编译器生成合适的C++代码,你需要注意下面的事项:
代码被预编译器展开。要得到C++代码,你需要在执行proc时设置CODE=CPP选项。
解析能力。proc的PARSE选项可以是下面的值:
PARSE=NONE. C预处理指令只能在声明节中被解析,所以所有的宿主变量需要在声明节中声明。
PARSE=PARTIAL. C预处理指令能被解析;然而,所有的宿主变量需要在声明节中声明。
PARSE=FULL. C预处理指令能被解析,而且宿主变量可以声明在任何地方。当CODE不为 CPP时,这是默认设置;但当CODE=CPP时,指定PARSE=FULL却是个错误。
所以,C++用户必须指定PARSE=NONE或PARSE=PARTIAL,因此这也失去了在任意地方声明宿主变量的自由。更有,宿主变量必须被包在一个声明节中,如下:
EXEC SQL BEGIN DECLARE SECTION;
// declarations...
EXEC SQL END DECLARE SECTION;
你需要使用这种方法去声明所有的宿主和指示器变量。
文件扩展名。你要指定设置CPP_SUFFIX=cc或CPP_SUFFIX=C。
头文件定位。默认情况下,proc像标准定位stdio.h文件一样查找头文件。然而C++有自己的头文件,如iostream.h,被放置在别处。所以,你需要使用SYS_INCLUDE选项指定proc查找头文件的路径。
--------------------------------------------------------------------------------
Pro*C支持的嵌入SQL语句列表
声明表达式
EXEC SQL ARRAYLEN 在PL/SQL中使用宿主变量
EXEC SQL BEGIN DECLARE SECTION
EXEC SQL END DECLARE SECTION 声明宿主变量
EXEC SQL DECLARE 给Oracle对像命名
EXEC SQL INCLUDE 从文件中复制
EXEC SQL TYPE 同等化数据类型
EXEC SQL VAR 同等化变量
EXEC SQL WHENEVER 处理运行时错误
执行表达式
EXEC SQL ALLOCATE 声明、控制Oracle数据
EXEC SQL ALTER
EXEC SQL ANALYZE
EXEC SQL AUDIT
EXEC SQL COMMENT
EXEC SQL CONNECT
EXEC SQL CREATE
EXEC SQL DROP
EXEC SQL GRANT
EXEC SQL NOAUDIT
EXEC SQL RENAME
EXEC SQL REVOKE
EXEC SQL TRUNCATE
EXEC SQL CLOSE
EXEC SQL DELETE 排序、修改Oracle数据
EXEC SQL EXPLAIN PLAN
EXEC SQL FETCH
EXEC SQL INSERT
EXEC SQL LOCK TABLE
EXEC SQL OPEN
EXEC SQL SELECT
EXEC SQL UPDATE
EXEC SQL COMMIT 处理事务
EXEC SQL ROLLBACK
EXEC SQL SAVEPOINT
EXEC SQL SET TRANSACTION
EXEC SQL DESCRIBE 使用动态SQL
EXEC SQL EXECUTE
EXEC SQL PREPARE
EXEC SQL ALTER SESSION 控制会话
EXEC SQL SET ROLE
EXEC SQL EXECUTE
END-EXEC 内嵌PL/SQL块