当前位置: 首页 > 工具软件 > Pro*C > 使用案例 >

Pro*C的学习笔记

云凌
2023-12-01

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 反斜线符: //  问号: /?
  单引号: /' 双引号: /"

 类似资料: