1、结构:
应用程序首部:
|——C外部变量说明部分
|——ORACLE设置区(设置ORACLE资源的管理)
| /*--使PROC在执行完后释放与嵌入SQL有关的资源*/
| EXEC ORACLE OPTION (RELEASE_CURSOR = YES);
|——ORACLE变量描述部分(SQL变量定义)
| EXEC SQL BEGIN DECLARE SECTION
| (SQL变量定义)
| EXEC SQL END DECLARE SECTION;
|——SQL通信区
| EXEC SQL INCLUDE SQLCA;
|—————————————————————————————————————
应用程序体:
|——函数体
|——内部说明部分
| |——C局部变量说明
| |——ORACLE内部变量说明段
| | EXEC SQL BEGIN DECLARE SECTION
| | (SQL变量定义)
| | EXEC SQL END DECLARE SECTION;
| |——SQL通讯区
| |——————————————————————————————————————
|——执行部分
|——C语句
|——SQL或PL/SQL块
| EXEC SQL CONNECT:<用户名>
| IDENTIFIED BY:<密码>
| SQL语句及游标使用;
|——————————————————————————————————————
2、关于变量声明和使用
-- 声明变量大小写区分;
-- 宿主变量必须明确定义;如果在所有函数之外声明,则该变量为全局变量;如果在函数中声明,则为该函数的局部变量;一般将宿主变量声明为全局变量;
-- SQL语句中调用变量要在变量前加冒号 ":";
-- 变量不能是SQL中保留字;
-- 指针变量也可以在SQL中调用,前面也要加冒号,而不是*号,在C中则加*;
-- 对于伪类型(Varchar型)的变量声明时要指明串的最大长度 如: VARCHAR book_name[50];
-- 在数据库表中定义为VARCHAR2,VARCHAR,CHAR的字段,在PROC中可声明为CHAR,但长度应为它们在表中定义的长度加1,因为PROC中CHAR型变量用/0做结尾;
-- 从SQL语句中取字段的值到宿主变量中时,PROC不会自动给宿主变量去掉右空格,可以用公用函数中的Trim()去除空格;
-- DATE型的处理:DATE型一般声明为CHAR(20);
-- 往表中插入DATE型数据时,一般用TO_DATE()函数进行类型转换,取出值时一般用TO_CHAR()函数进行类型转换;
3、关于SQL通信区
-- 通信区描述语句:EXEC SQL INCLUDE SQLCA;
-- 作用:提供了用户运行程序的成败记录和错误处理;
-- 其中SQLCA是一个结构体变量,是ORACLE提供给应用程序的一个接口;
-- 在SQLCA中sqlcode经常用到,它用来记录最近执行的SQL状态:
-- "0":表示该SQL语句被正确执行,无错误发生
-- >0 :表示ORACLE执行了该语句,但遇到了错误
-- <0 :表示由于数据库、系统、网络或应用程序错误等等原因ORACLE未执行该语句
注:ORACLE中对于sqlcode有详细对应情况,具体数值可查询ORACLE帮助
4、应用程序体
C和SQL语句各司其责:
-- C语句用于逻辑控制
-- SQL用于和数据库进行数据交互
连接数据库
-- EXEC SQL CONNECT:<用户名>/<密码>
或 EXEC SQL CONNECT:<用户名> IDENTIFIED BY:<密码>
注:用户名和密码是在CONNECT之前设置好的,不能把用户名或口令直接写到CONNECT语句中
如:EXEC SQL CONNECT 'SCOTT' IDENTIFIED BY 'TIGER' 是错误的
5、数据库查询和使用游标
查询语句的组成:SELECT/INTO/FROM/WHERE/CONNECT BY/UNION(联合)/INTERSECT(交叉)/MINUS(减去)/GROUP BY(分组)/HAVING/ORDER BY(排序)
-- INTO从句中的主变量称为输出主变量
-- WHERE从句中的主变量成为输入主变量
-- 对于确定单行的数据查询不使用游标,但为避免出错而多加条件到WHERE子句:AND rownum<2(限定只查询一笔数据)
多行查询和游标使用
-- 操作游标有四个命令:
-- 定义游标:DECLARE CURSOR
EXEC SQL DECLARE 游标名称 CURSOR FOR SELECT column_test FROM table_test;
-- 打开游标:OPEN CURSOR
EXEC SQL OPEN 游标名称;
-- 读取游标:FETCH
EXEC SQL FETCH 游标名称 INTO :name_column_test;
-- 关闭游标:CLOSE CURSOR
释放资源,语句如:EXEC SQL CLOSE 游标名称;
-- 循环操作取数据:
-- 步骤一:定义游标
EXEC SQL DECLARE c_test CURSOR FOR
SELECT invnum,invseq,invdate FROM invsih
WHERE invseq=1;
-- 步骤二:打开游标
EXEC SQL OPEN c_test;
-- 步骤三:循环读取数据
for(;;)
{
EXEC SQL WHENEVER NOT FOUND DO break;
/*我们的做法是check sqlcode,判断是否取到值*/
EXEC SQL FETCH c_test INTO :intInvNum,:intInvSeq,:strInvDate;
/*对于取出的数据进行一些操作*/
}
-- 步骤四:关闭游标,提交事务,断开与数据库的连接,释放资源
EXEC SQL CLOSE c_test;
/*Printf("/Fetch data OK./n")*/
EXEC SQL COMMIT WORK RELEASE;
6、错误处理:
关于WHENEVER语句:
-- WHENEVER是说明语句,不返回SQLCODE,只是根据SQLCA中的返回码指定相应的措施
-- 格式:EXEC SQL WHENEVER [SQLERROR|SQLWARNNING|NOTFOUND] [STOP|CONTINUE|GOTO<标号>];
-- 注意:
-- [STOP|CONTINUE|GOTO<标号>]的缺省值为CONTINUE;
-- SQLERROR:SQLCA.SQLCODE<0;
-- SQLWARNNING:SQLCA.SQLWARN[0]="W";
-- NOTFOUND:SQLCA.SQLCODE=1403;
关于出错处理函数:
void hSQLError(char *msg)
{
printf("/n%s,%ld,%s/n", msg,sqlca.sqlcode,(char *)sqlca.sqlerrm.sqlerrmc);
EXEC SQL ROLLBACK RELEASE;
exit(-1);
}
7、动态定义SQL语句(根据程序的运行动态产生SQL)
-- EXECUTE IMMEDIATE(立即执行);(执行UDI语句或DDL语句)
-- 表示立即执行,且只向SQLCA返回执行结果
-- 只能运行带一个参数的动态语句
-- SQL语句不能包含主变量,SQL不能是SELECT语句
-- 可以用任何参数作为EXECUTE IMMEDIATE的参数,也可以用字符串作为主变量
-- 需要手动提交事务,即执行 EXEC SQL COMMIT RELEASE;对应排错处理需要执行 EXEC SQL ROLLBACK RELEASE;
-- PREPARE 和 EXECUTE(预编译/执行);(编译一次,执行多次)
-- 语法:
EXEC SQL PREPARE <语句名> FROM :主变量;
-- 预编译SQL语句
-- 给出SQL语句的语句名
EXEC SQL EXECUTE <语句名> [USING :替换主变量];
-- 执行SQL语句
-- 注意:
-- SQL语句不能是SELECT语句
-- PREPARE和EXECUTE可包含主变量
-- PREPARE不能多次执行
-- 例子:
...
strcpy(strSQL,"");
strcpy(strSQL,"UPDATE invsih SET invseq=");
strcat(strSQL,intInvSeq);
/*-----------------Begin-------------------*/
EXEC SQL PREPARE strQryInv FROM :strSQL;
/*------------Read data from input---------*/
for(;;)
{
printf("/nPlease enter Invoice Seq.:");
scanf("%d",&intInvSeq);
if(intInvSeq<0||intInvSeq>2)
{
break;
}
EXEC SQL EXECUTE strQryInv USING :intInvSeq;
}
/*-----------------End---------------------*/
--FETCH和OPEN
-- FETCH和OPEN是对游标进行操作的
-- 执行过程:
EXEC SQL PREPARE <语句名> FROM :主变量;
|
EXEC SQL DECLARE <游标名> CURSOR FOR <语句名>;
|
EXEC SQL OPEN <游标名> [USING :替换变量1[,:替换变量2...]];
|
EXEC SQL FETCH <游标名> INTO :主变量1[,主变量2...];
|
EXEC SQL CLOSE <游标名>
-- 注意:
-- SQL语句允许使用SELECT语句
-- SELECT子句的列名不能动态改变,只能预定好
-- WHERE和ORDER BY自居可以动态改变条件
-- 例子:
...
strcpy(intInvSeq,argv[1]);
...
strcpy(strSQL,"");
strcpy(strSQL,"SELECT invnum,invdate FROM invsih WHERE invseq=");
strcat(strSQL,intInvSeq);
/*-----------------Begin-------------------*/
EXEC SQL PREPARE strQryInv FROM :strSQL;
EXEC SQL DECLARE c_test CURSOR FOR strQryInv;
EXEC SQL OPEN c_test;
/*--------Get data from Cursor c_test------*/
for(;;)
{
EXEC SQL FETCH c_test INTO :intInvNum,:strInvDate;
/*------deal with the data got-----*/
...
}
EXEC SQL CLOSE c_test;
8、关于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 c_test FOR
SELECT NVL(invnum,'') FROM invsih;
这样也不会报错。
9、PROC中调用存储过程的方法
要把存储过程放在EXEC SQL EXECUTE 和 END-EXEC;之间,如下所示:
其中:up_db_emp为存储过程,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/n",l_errno,c_errtext);
}
另外,在一支PC中调用其它的PC程式,则可以采用system函数掉用。如:
sprintf(strExec,"myReport bqy/oracle bpcs 501129");
system(strExec);
若myReport存在返回值,则可以写成如下格式获取此返回值(加入返回值为整形的)
intReturn=system(strExec);
10、程序中调用PC
要在程序中调用PC,需要以下几步骤:
-- 将程序中调用到的PC进行编译,存放于固定路径下(我们这边测试环境下通常放于/bpcs/orabatch/下)
而对于不同的环境要重新配置,主要是修改bpcson中的program路径,修改为我们要存放编译好的PC所放置的地方即可
这样每次调用bpcson去跑PC的时候,他就会到设置的路径下取PC执行
-- 程序中调用写好的Store Procedure来执行PC命令,我们这边使用的是Server(队列机制)和bpcson(立即机制)
如下:strSQL = "EXECUTE PROCEDURE server('PC程序名','数据库连接信息','数据库服务器名称','PC所需要参数,用空格隔开')"
或strSQL = "EXECUTE PROCEDURE bpcson('PC程序名','数据库连接信息','数据库服务器名称','PC所需要参数,用空格隔开')"
例:
strSQL = "EXECUTE PROCEDURE bpcson('myReport','bqy/oracle','bpcs','501129')"
详细请参照VB例子
I2ODB.dll中有相关方法。
11、SQL语句的拼写
-- strcpy和strcat
strcpy(strSQL,"");
strcpy(strSQL,"SELECT * FROM invsih WHERE shinvn='");
strcat(strSQL,strInvNum);
strcat(strSQL,"'");
-- sprintf
sprintf(strSQL,"SELECT * FROM invsih WHERE shinvn='%s'",strInvNum);
支持多个参数,优点是SQL语句整体看起来很清晰
有时候要考虑代码的格式问题,则可以这样写:
sprintf(strSQL,"SELECT * FROM invsih"
" WHERE shinvn='%s'",strInvNum);
-- 直接写死
EXEC SQL EXECUTE SELECT * FROM invsih WHERE shinvn='501129'
-- 动态拼接,参见[7]
12、关于参数的注意点
-- 参数中不可以有"~"字符出现,因为在Unix Shell中,该命令为返回当前工作路径,所以PC执行会出现错误
-- 该参数是在字符匹配中经常出现的,~代表着最大常用字符,因为ASC("~")=126,是常见字符中最大的。
13、关于PC打印报表中分页的操作
对于一个Report,有时候数据量会很大,所以会考虑到分页的情况。这里指的分页是指根据计算好的每页的行数进行一些Report格式
上的处理,如另起一页需要考虑重新打印表头、表尾等重复信息。
注意点:对于分页的理解不要考虑成动态生成分页,实质上是一次性生成Report文件,而对于Report文件内部排版是按照每页的行数和
根据实际打印纸的尺寸而定的。
分页需要注意的问题:
-- 1、首先要定义好一个页面的大小,考虑到横向打印还是竖向打印(为了定义每页的宽度和行数)
-- 2、由于报表的特殊性,每页表头可能不同,即第一页会有ReportHeader,每页会有PageHeader,所以在考虑页面行数的时候要将这部分考虑在内
-- 3、报表的结尾,即最后一页页尾也会跟其他页面不同,要单独考虑。
-- 4、每页之间强制加换页符,使得打印时可以自动完成分页功能。
例子可以参照:chpgrpt.pc
附:C语言中常用的制表符号有:
换行符: /n 水平制表符: /t 垂直制表符: /v
退格符: /b 回车符: /r 换页符: /f
鸣铃符: /a 反斜线符: // 问号: /?
单引号: /' 双引号: /"