Oracle Pro*C 程序开发


Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 Oracle Pro*C 程序开发 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 1 前言 .................................................................................................................................................1 1.1 读者范围 .................................................................................................................................1 1.2 内容组织 .................................................................................................................................1 1.3 约定 .........................................................................................................................................1 2 新特性介绍......................................................................................................................................2 2.1 ORACIE9I RELEASE 2 (9.2) 新特性..........................................................................................2 2.2 ORACLE9I RELEASE 1 (9.0.1) 新特性......................................................................................2 2.3 ORACLE 8I RELEASEE 8.1.5 新特性.........................................................................................3 2.4 ORACLE 8I RELEASE 8.1.4 新特性 ...........................................................................................3 2.5 ORACLE 8I RELEASE 8.1.3 新特性 ...........................................................................................3 3 概述 .................................................................................................................................................3 3.1 什么是ORACLE预编译程序? ................................................................................................3 3.2 为什么使用ORACLE预编译程序? ........................................................................................4 3.3 为什么使用SQL?..................................................................................................................4 3.4 为什么使用PL/SQL?............................................................................................................5 3.5 PRO*C预编译的优点..............................................................................................................5 3.6 常见的问题 .............................................................................................................................6 3.6.1 我怎么编译链接应用程序? .........................................................................................6 3.6.2 什么是varchar?...............................................................................................................8 3.6.3 在什么情况下不 使用Pro*C/C++和SQLLIB库函数?...............................................8 3.6.4 能在Pro*C/C++程序中调用存储过程吗?..................................................................8 3.6.5 我能在 SQL 语句的任意位置使用绑定变量(也可理解为用户自定义变量或输入 宿主变量)吗?..............................................................................................................................8 3.6.6 对Pro*C/C++字符类型变量的困惑?..........................................................................9 3.6.7 关于字符串指针变量的应用有特殊需要注意的么? .................................................9 3.6.8 为什么SPOOL不能用在Pro*C程序中? ........................................................................9 3.6.9 Pro*C/C++支持结构作为宿主变量么?..........................................................................9 3.6.10 可以在递归函数中嵌入SQL么? ..................................................................................10 3.6.11 我可以在任意版本的Oracle中使用任意版本的预编译器么?.................................10 3.6.12 1405 错误(Fetch column values is null)可避免么?...............................................10 4 预编译介绍....................................................................................................................................10 4.1 嵌入式SQL编程概念介绍....................................................................................................10 4.1.1 可嵌入Pro*C/C++的SQL语句 ....................................................................................10 4.1.2 嵌入SQL语句的语法格式............................................................................................ 11 4.1.3 静态和动态SQL语句....................................................................................................12 4.1.4 嵌入的PL/SQL语句块 ..................................................................................................12 4.1.5 宿主变量和指示变量...................................................................................................12 4.1.6 Oracle数据类型................................................................................................................13 4.1.7 数组...............................................................................................................................13 4.1.8 数据类型转换...............................................................................................................13 4.1.9 私有SQL工作区、游标和记录集................................................................................13 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 4.1.10 事务...............................................................................................................................13 4.1.11 错误和警告...................................................................................................................14 4.2 开发嵌入式PRO*C/C++程序过程........................................................................................14 4.3 程序编写规范 .......................................................................................................................15 4.3.1 注释...............................................................................................................................15 4.3.2 常数表示方法...............................................................................................................15 4.3.3 变量声明段...................................................................................................................16 4.3.4 定界符...........................................................................................................................17 4.3.5 文件长度限制...............................................................................................................17 4.3.6 预编译参数对函数声明的影响 ...................................................................................17 4.3.7 宿主变量命名规则.......................................................................................................18 4.3.8 超长源代码换行...........................................................................................................18 4.3.9 源代码单行最大长度...................................................................................................18 4.3.10 MAXLITERAL ...............................................................................................................18 4.3.11 操作符...........................................................................................................................19 4.3.12 语句结束符号...............................................................................................................19 4.4 根据条件进行预编译 ...........................................................................................................19 4.5 样例表 ...................................................................................................................................20 4.5.1 样例数据.......................................................................................................................21 4.6 样例程序 ...............................................................................................................................21 4.6.1 编译运行方法...............................................................................................................24 5 数据库操作描述............................................................................................................................24 5.1 连接到数据库 .......................................................................................................................25 5.1.1 使用ALTER AUTHORIZATION选项改变用户密码....................................................25 5.1.2 自动连接.......................................................................................................................25 5.1.3 连接权限.......................................................................................................................26 5.1.4 通常连接失败的原因...................................................................................................26 5.2 高级连接选项 .......................................................................................................................26 5.2.1 预备知识.......................................................................................................................26 5.2.2 并发连接.......................................................................................................................27 5.2.3 默认的数据库和连接...................................................................................................27 5.2.4 并行直接连接...............................................................................................................27 5.2.5 间接连接.......................................................................................................................28 5.3 事务应用场合 .......................................................................................................................29 5.4 开始和结束事务 ...................................................................................................................29 5.5 使用COMMIT语句...............................................................................................................29 5.6 使用SAVEPOINT语句..........................................................................................................30 5.7 ROLLBACK语句..................................................................................................................31 5.8 SET TRANSACTION语句 ...................................................................................................31 5.9 重置默认锁状态 ...................................................................................................................32 5.9.1 使用FOR UPDATE OF.................................................................................................32 5.9.2 使用LOCK TABLE ........................................................................................................32 5.10 FETCH中应用COMMIT语句 ..............................................................................................33 5.11 分布式事务处理 ...................................................................................................................33 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 5.12 有用的技巧和方针 ...............................................................................................................34 6 数据类型和宿主变量....................................................................................................................34 6.1 ORACLE数据类型..................................................................................................................34 6.1.1 内部数据类型列表.......................................................................................................34 6.1.2 外部数据类型列表.......................................................................................................35 6.2 宿主变量 ...............................................................................................................................36 6.2.1 声明宿主变量...............................................................................................................36 6.2.2 使用宿主变量...............................................................................................................37 6.3 指示变量 ...............................................................................................................................38 6.3.1 INDICATOR关键字 ..........................................................................................................38 6.3.2 指示变量用法实例.......................................................................................................38 6.3.3 指示变量应用规范.......................................................................................................39 6.3.4 限制...............................................................................................................................39 6.4 VARCHAR变量 ....................................................................................................................39 6.4.1 定义VARCHAR变量......................................................................................................39 6.4.2 使用VARCHAR变量......................................................................................................40 6.4.3 空值处理.......................................................................................................................41 6.4.4 VARCHAR变量作为函数参数用法..................................................................................41 6.4.5 例程...............................................................................................................................41 6.5 CURSOR变量 .......................................................................................................................44 6.5.1 定义cursor变量.............................................................................................................44 6.5.2 分配cursor变量.............................................................................................................44 6.5.3 打开cursor变量.............................................................................................................45 6.5.4 关闭和释放cursor变量.................................................................................................46 6.5.5 使用限制.......................................................................................................................46 6.5.6 例程...............................................................................................................................47 6.6 CONTEXT变量.....................................................................................................................49 6.7 通用ROWID..........................................................................................................................50 6.8 结构型宿主变量 ...................................................................................................................51 6.8.1 结构和数组...................................................................................................................51 6.8.2 PL/SQL的RECORD...........................................................................................................52 6.8.3 结构嵌套和联合...........................................................................................................52 6.8.4 结构型指示变量...........................................................................................................52 6.8.5 例程...............................................................................................................................52 6.9 指针变量 ...............................................................................................................................54 6.9.1 声明指针变量...............................................................................................................54 6.9.2 使用指针变量...............................................................................................................55 7 静态SQL........................................................................................................................................55 7.1 基本SQL语句........................................................................................................................55 7.1.1 SELECT语句.....................................................................................................................56 7.1.2 INSERT语句......................................................................................................................60 7.1.3 UPDATE语句....................................................................................................................62 7.1.4 DELETE语句 ....................................................................................................................64 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 7.1.5 WHERE子句......................................................................................................................65 7.2 DML返回子句 ......................................................................................................................65 7.3 普通顺序游标操作 ...............................................................................................................66 7.3.1 DECLARE语句..................................................................................................................66 7.3.2 OPEN语句.........................................................................................................................67 7.3.3 FETCH语句 ......................................................................................................................67 7.3.4 CLOSE语句.......................................................................................................................68 7.4 滚动游标 ...............................................................................................................................68 7.4.1 使用滚动游标...............................................................................................................68 7.4.2 使用CLOSE_ON_COMMIT预编译选项......................................................................69 7.5 优化提示 ...............................................................................................................................69 7.6 CURRENT OF子句...............................................................................................................69 7.7 游标控制 ...............................................................................................................................70 7.8 一个普通游标范例 ...............................................................................................................70 7.9 一个滚动游标应用范例 .......................................................................................................72 8 ORACLE动态SQL.......................................................................................................................74 8.1 动态SQL含义........................................................................................................................74 8.2 动态SQL的优缺点................................................................................................................74 8.3 动态SQL适用环境................................................................................................................74 8.4 动态SQL执行必须条件........................................................................................................75 8.5 动态语句执行过程 ...............................................................................................................75 8.6 使用动态SQL的情况和方法................................................................................................75 8.6.1 METHOD 1 .......................................................................................................................76 8.6.2 METHOD 2 .......................................................................................................................78 8.6.3 METHOD 3 .......................................................................................................................80 8.6.4 METHOD 4 .......................................................................................................................83 8.6.4.1 需要SQLDA............................................................................................................................83 8.6.4.2 DESCRIBE语句......................................................................................................................83 8.6.4.3 什么是SQLDA?....................................................................................................................84 8.6.4.4 处理过程描述 .........................................................................................................................84 8.6.4.5 使用SQLDA变量....................................................................................................................85 8.6.4.6 一些预备知识 .........................................................................................................................87 8.6.4.7 基本步骤.................................................................................................................................89 8.6.4.7.1 定义一个宿主变量select_stmt..........................................................................................91 8.6.4.7.2 定义SQLDA变量描述字 ..................................................................................................91 8.6.4.7.3 为描述字分配空间............................................................................................................91 8.6.4.7.4 设置描述字中数组的最大成员个数................................................................................92 8.6.4.7.5 给select_stmt宿主变量赋值..............................................................................................93 8.6.4.7.6 PREPARE语句 ...................................................................................................................93 8.6.4.7.7 DECLARE游标..................................................................................................................94 8.6.4.7.8 DESCRIBE绑定变量 .........................................................................................................94 8.6.4.7.9 重置占位符数目................................................................................................................95 8.6.4.7.10 得到宿主变量值并分配存储空间..................................................................................95 8.6.4.7.11 打开游标..........................................................................................................................96 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 8.6.4.7.12 DESCRIBE select-list.......................................................................................................96 8.6.4.7.13 重置select-list列数目 ......................................................................................................97 8.6.4.7.14 重置select-list中列的长度和数据类型...........................................................................98 8.6.4.7.15 FETCH结果集..................................................................................................................99 8.6.4.7.16 释放资源 .......................................................................................................................100 8.6.4.7.17 关闭游标 .......................................................................................................................101 8.6.4.7.18 使用数组宿主变量........................................................................................................101 8.6.4.7.19 宿主数组变量动态插入数据范例................................................................................101 8.6.4.8 METHOD 4 实现sqlplus命令的例子 ..................................................................................103 8.6.4.9 METHOD 4 滚动游标的例子 ............................................................................................. 112 9 标准动态SQL..............................................................................................................................117 9.1 标准动态SQL基础..............................................................................................................117 9.1.1 预编译选项................................................................................................................. 118 9.2 标准动态SQL概述..............................................................................................................118 9.2.1 简单标准动态SQL例程..............................................................................................120 9.3 ORACLE所作的扩展............................................................................................................121 9.3.1 应用宿主变量地址.....................................................................................................121 9.3.2 数组操作.....................................................................................................................122 9.4 预编译器参数对标准动态SQL和ORACLE SQL的影响表.................................................124 9.5 标准动态SQL完全语法......................................................................................................124 9.5.1 ALLOCATE DESCRIPTOR .............................................................................................124 9.5.2 DEALLOCATE DESCRIPTOR........................................................................................124 9.5.3 GET DESCRIPTOR.........................................................................................................125 9.5.4 SET DESCRIPTOR..........................................................................................................126 9.5.5 Use of PREPARE .............................................................................................................127 9.5.6 DESCRIBE INPUT..........................................................................................................127 9.5.7 DESCRIBE OUTPUT......................................................................................................127 9.5.8 EXECUTE........................................................................................................................128 9.5.9 Use of EXECUTE IMMEDIATE......................................................................................128 9.5.10 Use of DYNAMIC DECLARE CURSOR......................................................................128 9.5.11 OPEN Cursor ..............................................................................................................128 9.5.12 FETCH ........................................................................................................................129 9.5.13 CLOSE a Dynamic Cursor ..........................................................................................129 9.6 样例程序 .............................................................................................................................129 9.6.1 标准SQL数据类型实现sqlplus命令的例子...............................................................129 9.6.2 Oracle外部数据类型数组模式实现sqlplus命令的例子 ...............................................135 10 附录 .........................................................................................................................................144 10.1 参考文档 .............................................................................................................................144 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 1 页 1 前言 1.1 读者范围 基本要求: ※ 有一定的 C/C++语言基础,熟悉 C 语言编程的基本语句和思路 ※ 有一定的 SQL 基础,熟悉标准 SQL 语句书写方法和含义 适用范围: ※ 在 Oracle 环境下设计和开发应用程序 ※ 将已经存在于其他数据库环境的应用程序转换到 Oracle 环境下 ※ Oracle 应用软件开发技术规范的管理者 1.2 内容组织 首先声明,本资料为内部培训教材,不是入门教程,在整个文档中不会对 C 语言及其 函数做任何解释,不会对教程中出现的 SQL 语言做任何说明,除非它是 Oracle 特有的 SQL 而不遵循 ANSI SQL 的规定。为了描述方便,书中范例采用 SQL 内嵌入 C 语言编写。 Pro*C/C++语言就是在标准 C/C++语言中通过嵌入 SQL 语句完成对数据库操作的一种 语言组织方式,为了编译成可执行程序必须经过 Oracle 预编译程序预编译成.c/.cpp 源程序, 并由系统 c/c++编译器编译成可执行程序。在以后的章节中将多次涉及到预编译器(预编译 程序),如无特别说明,是指 Pro*C 预编译程序${ORACLE_HOME}/bin/proc。 1.3 约定 本小节描述了在此文章中采用的字体格式,以及各种格式代表的含义约定。 字体格式 含义 范例 Bold 术语和关键字 c语言变量同 ub4,sword 或 OCINumber 类型没有直接可转换的 类型 Italics 描述标题和重点强调 要注意用户表尽量不 要建立在 system 表空间中 大写定宽 字体 系统提供的关键字,例如数据类型、RMAN 关键字,SQL*PLUS 关键字等 DBMS_STATS.GENTERATE_STATS 过程的作用 小写定宽 字体 用于自定义的变量名称,目录名,c 语言语 句等 while ( 1) { printf(“%d\n”,nNum); } 小写 italics 代表变量的占位符 SELECT 语法描述: Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 2 页 SELECT column_name FROM table_name SQL 语法和程序语法约定: 格式 含义 范例 [] 包含在此描述符号中的内容为可选 选项 DECIMAL (digits [,precision]) {} 包含在此描述符号中的内容为可选 项列表,只能包含其中的一项 {ENABLE|DISABLE} | 可选项列表分隔符号 {COMPRESS|NOCOMPRESS} … 表示重复内容的省略,主要用于语句 描述 SELECT col1, col2, ... , coln FROM employees; . . . 列表内容的省略 SQL> SELECT NAME FROM V$DATAFILE; NAME ------------------------------------ /fsl/dbs/tbs_01.dbf /fs1/dbs/tbs_02.dbf . . . /fsl/dbs/tbs_09.dbf 9 rows selected. 本文所涉及样例程序在编译时需要替换掉笔者试验用的数据库用户名和密码 “xcl/xclxcl”为用户自己的用户和密码。 2 新特性介绍 2.1 Oracie9i Release 2 (9.2) 新特性 ※ 在 Pro*C/C++支持上下滚动游标 可以使用可滚动定位游标代替以往顺序游标。 ※ 在 Pro*C/C++支持连接池技术 应用此技术可以在应用程序中采用多线程进行数据操作,提高应用程序性能。 2.2 Oracle9i Release 1 (9.0.1) 新特性 ※ UNICODE 支持 由于此版本数据库“可信赖数据类型支持”技术在 Pro*C/C++程序的应用,UTF16 数据能直接存放到 CHAR 和 NCHAR 类型的列中。 ※ 增加了对 V8OCI 的 XA 支持 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 3 页 在 9i 中,支持 XA 和 V8OCI 的互通,并成为了通信标准,用户可以在 Pro*C/C++ 编程中通过 XA 连接到 Oracle,在 SQLLIB 中注册,然后通过 V8OCI 中的函数 SQLEnvGet 和 SQLSvcCtxGet 获得已经建立和使用的连接。 2.3 Oracle 8i Releasee 8.1.5 新特性 ※ Oracle 预编译器能够直接预编译头文件 从此版本起,Oracle 允许用预编译器直接编译头文件(.h),将其生成一个二进制文 件,在真正对程序进行编译时,Oracle 可直接使用此文件而不是重新编译所有 #include 语句指示的头文件,这样在有大量头文件的项目中,可以节省编译时间。 2.4 Oracle 8i Release 8.1.4 新特性 ※增加了对 java 语言书写的存储过程的支持 2.5 Oracle 8i Release 8.1.3 新特性 ※ 嵌入了 SQL LOB 类型处理结构 ※ 增加了对标准动态 SQL 接口的支持 Pro*C/C++从此版本起具备了执行标准动态 SQL 语句的接口,增强了 Oracle 动态 语句接口的功能。标准动态 SQL 语句接口支持所有 Oracle 的数据类型,包括 Objects,结构数组,游标变量和 LOBS。 ※ DML 语句开始支持返回(returning)子句 Pro*C/C++程序新增对 UPDATE、DELETE 和 INSERT 语句返回语句的支持。 ※ 支持通用 ROWID 类型 Pro*C/C++现在通过 ALLOCATE 和 FREE 机制支持通用 ROWID 类型,所分配描 述符号支持物理 ROWID 和逻辑 ROWID ※ 扩展存储过程支持 用 Pro*C/C++书写的存储过程能在 PL/SQL 中直接调用。 ※ 支持 Pre-fetching 技术 Oracle 支持在一个查询过程中,首先预读出一定行数的记录,以对以后的 FETCH 查询语句提高性能。 3 概述 3.1 什么是 Oracle 预编译程序? Oracle 预编译程序是个编程工具,其主要功能是将 C 语言中嵌入的 SQL 语句转换成高 质量的 C 语言源码,在源码中利用 Oracle 数据库所提供的接口函数对数据库进行访问,之 后可应用操作系统的编译器将其编译成可执行的 Oracle 应用程序。下图展示了一个 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 4 页 Pro*C/C++程序的编写、编译流程: 3.2 为什么使用 Oracle 预编译程序? 主要有以下几个原因: ※ 直接在应用程序中书写适当的,可读性强的 SQL 语句,而不是通过直接调用 Oracle 接口函数(SQLLIB)完成对应用的开发,减轻了开发工作量,提高开发效率。 ※ 应用预编译程序的 SQLCHECK=FULL 选项,预编译程序可以检查在程序中嵌入 SQL 的正确性,包括出现在源代码中的数据库表、字段的检查,将程序出错机率 消灭在预编译期间。 ※ 预编译程序可以按照开发人员嵌入的 SQL 语句,组织最合理的 Oracle 接口函数调 用,并进行合适的、适当的优化,提高应用程序的性能。 3.3 为什么使用 SQL? ※ SQL 变成数据库通用操作语言是因为其具有可伸缩、功能强大、容易学习的优点, 其可以在 Oracle sqlplus 工具中直接使用,也可以嵌入到 c/c++语言中,做为 Pro*C/C++源程序的一部分。 ※ 对 Oracle 数据的存取需要 SQL,虽然不是唯一手段,但却是最简单,最直观的方法。 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 5 页 3.4 为什么使用 PL/SQL? PL/SQL 是 SQL 的扩展,其支持逻辑过程的控制、变量定义和强大的错误处理功 能。在 PL/SQL 程序块种,可同时使用 PL/SQL 语法表示和 SQL 语句,用于完成有一 定业务逻辑的数据操作功能。 在 Pro*C/C++中嵌入 PL/SQL 的主要目的是为了提高应用程序的执行效率。同单 条 SQL 语句不同,在 Pro*c/C++中嵌入的 PL/SQL 块可以看作一组包含特定逻辑功能 的 SQL 语句,在向数据库提交时,应用将其作为一块,向数据库服务器只提交一次, 这就减少了网络和 Oracle 进行 SQL 解析的时间,从而提高了程序处理的性能。 3.5 Pro*C 预编译的优点 Pro*C 能完成以下功能: ※ 用 C/C++语言书写 Oracle 数据库应用程序 ※ 通过嵌入和扩充标准 SQL 语言,丰富逻辑处理功能,可看作组合成了一种强大的 高级语言 ※ 通过动态 SQL 编程,增加了程序在运行状态下接受和运行任何有效 SQL 语句的 能力,可设计开发高定制、高性能的引用程序。 ※ 可书写共享服务器类型的应用程序,相比独占模式占用资源更少。 ※ 对 Oracle 内部数据类型和 C/C++程序语言数据类型进行自动转换。 ※ 可解释和预编译在 C/C++语言中嵌入更高效的 PL/SQL 语言块。 ※ 预编译程序(proc)支持很多可选选项,能在编译过程中灵活调整,加快应用程 序开发过程。 ※ 预编译过程可完整检查 SQL、PL/SQL 语法和语义,减少错误的产生。 ※ 通过 SQL 通讯区域(SQLCA)和 WHENEVER DO 语句的结合,可更简单的捕获和 处理数据库错误、警告。 ※ 通过 ORACLE 通讯区(ORACA)的使用,可以更高效的查找程序的问题。 ※ 支持用户自定义的数据库类型。 ※ 支持 LOBs 数据的处理。 ※ 支持数据库的多国国际语言设置和存储。 ※ 支持 OCI(Oracle Call Interface) 函数 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 6 页 3.6 常见的问题 3.6.1 我怎么编译链接应用程序? 编译和链接应用程序是个很平台化的工作,在本文中以 UNIX 为例子,首先需要保证 操作系统级别的 C/C++编译器可用,然后正确的安装 Oracle 数据库服务器和 proc development tools 组件,确认数据库服务器正常,proc 命令可以使用,设置必要的环境变量, 例如在开发用户中加入如下内容: ORACLE_HOME=/home/oracle #设定 Oracle 主目录路径 ORACLE_SID=xingdb #设定 Oracle 数据库实例名 NLS_LANG=AMERICAN_AMERICA.ZHS16GBK #客户端字符集设置 PATH=${ORACLE_HOME}/bin:${PATH}:. #使 Oracle 命令置在 PATH 变量 LD_LIBRARY_PATH=${ORACLE_HOME}/lib:${LD_LIBRARY_PATH}:. #链接库文件 路径 export ORACLE_HOME ORACLE_SID NLS_LANG PATH LD_LIBRARY_PATH 对于特别简单的 Pro*C 应用程序(例如 foo.pc)可以采用如下命令编译: proc iname=foo.pc oname=foo.c cc -I${ORACLE_HOME}/precomp/public foo.c -o foo -L${ORACLE_HOME}/lib –lclntsh 但对于复杂的工程项目用这种方式进行编译将非常效率低下,建议采用书写 Makefile 文件方式结合 make 指令进行编译链接工作,例如:还是编译 foo 项目,其包含三个源文件, Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 7 页 foo1.pc、foo2.pc、foo3.pc 则对应 Makefile 如下: #------定义程序需要的库和头文件目录------ include $(ORACLE_HOME)/rdbms/lib/env_rdbms.mk ORAINC=${ORACLE_HOME}/precomp/public ORALIB=${ORALCE_HOME}/lib #------定义 Proc 命令参数和 C 编译器命令参数----------- PROC=proc CC=cc PROCPPFLAGS= \ userid=xcl/xclxcl \ sqlcheck=semantics \ include=${ORACLE_HOME}/rdbms/public \ include=$(ORAINC) \ lines=ture \ parse=full #CFLAGS=-q64 -I$(ORAINC) #AIX 64 位编译需要 CFLAGS= -I$(ORAINC) -I${ORACLE_HOME}/rdbms/public #------定义文件类型编译倚赖关系------------ .SUFFIXES: .o .c .pc .pc.c: @$(PROC) $(PROCPPFLAGS) iname=$< .c.o: @$(CC) $(CFLAGS) -c $< .pc.o: @$(PROC) $(PROCPPFLAGS) iname=$< @$(CC) $(CFLAGS) -c $(<:.pc=.c) #------定义应用程序特有信息,对不同项目只需变动下面内容既可----------------- OBJ=foo1.o fool2.o fool3.o foo: $(OBJ) @$(CC) $(CFLAGS) $(OBJ) -o foo -L${ORACLE_HOME}/lib $(PROLDLIBS) -lm @rm -f $(OBJ) @rm -f *.lis all: @make sqlvcp 对本文中所提到的所有范例程序只需改动 OBJ=foo1.o fool2.o fool3.o 内容及其以后两 行内容既可。然后运行 unix 系统命令 make foo –f Makefile 或者 make all –f Makefile 既编 译链接出所需要的应用程序。 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 8 页 关于Makefile和cc参数的知识已经超出了本文描述的范围,相关知识请参考《Unix开发 环境编程》书籍或用 www.google.com进行搜索。 3.6.2 什么是 varchar? 下面是 varchar 的一个简单描述表: VARCHAR 描述 VARCHAR2 Oracle 数据库表用的一种列类型,包含可变字节长度的数据。其被称为 Oracle 内部数据类型,只用于指定表中列的类型,不能在 Pro*C 中声明此种类型的 变量。 VARCHAR Oracle 外部数据类型,可变长数据类型,以方便兼容和移植其他数据库类型 varchar[n] Pro*C/C++程序中,对应数据库表中 varchar/varchar2/char 类型而定义的一种 数据类型,用于定义 Pro*C/C++程序中的宿主变量,其内部实现是一个结构: 2 个字节的代表字符长度的变量,n 个字符的字符数组。 3.6.3 在什么情况下不 使用 Pro*C/C++和 SQLLIB 库函数? SQLLIB 主要是保含一系列函数的 Oracle 应用程序运行库,用于应用程序执行或预编译 时调用,其本身并不是 Oracle 标准接口 API,随着 Oracle 版本的变化 SQLLIB 的函数调用 格式、用法、参数等也有可能变化。 如果一个项目中需要一个比较通用的、平台一致性好的、不随数据库版本变化而变化的 一系列封装函数,最好的建议是使用 Oracle Call Interface (OCI) 进行标准 C 语言的编程处 理。 3.6.4 能在 Pro*C/C++程序中调用存储过程吗? 可以,在第 7 章,“静态 SQL”语言中有例子和介绍。 3.6.5 我能在 SQL 语句的任意位置使用绑定变量(也可理解 为用户自定义变量或输入宿主变量)吗? 例如我需要接受用户的输入,删除所输入表名的内容,于是构造如下语句: DELETE FROM :tabname; 这种方法允许么?绑定变量主要用在什么地方? 通常意义上讲,判断一个绑定变量可应用的位置,基本符合以下规律:如果 sql 语句的 特定位置支持表达式运算,则该位置支持绑定变量。 例如,假设表 emp 中有 age 字段,我们需要找到 age>80 的字段,那么可用如下语句 SELECT * FROM emp WHERE age>80; Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 9 页 在 Pro*C/C++程序中 age>80 位置支持表达式“>80”,则可认为该位置支持绑定变量, 比如我们允许选取 age 大于用户录入的值,假设录入变量值保存在宿主变量 nAge 中,则语 句如下: SELECT * FROM emp WHERE age>:nage; 对于上面提到的 DELETE 问题,需要采用动态 SQL 语句完成,请参阅后面关于动态 SQL 语句的章节。 3.6.6 对 Pro*C/C++字符类型变量的困惑? 为什么我的宿主变量没有空格,而插入数据库中存放就有空格了呢? 为什么从主机选择出的内容赋值到变量中会自动加入字符串结束符号’\0’呢?如果不想 让数据库这样操作该如何做? 我如何控制这些字符类型变量登记到数据库和从数据库导出到变量所采取的格式控制 呢? 这些问题将在后面介绍Pro*C宿主变量类型时详细介绍,本部分只粗略提下。Oracle预 编译器提供了好多选项,其中关于字符串控制的选项CHAR_MAP对字符串变量的操作进行 了控制,后续的文章中将详细描述该选项所造成的影响。 3.6.7 关于字符串指针变量的应用有特殊需要注意的么? 由于 Oracle 在对字符串变量进行处理时需要知道其长度,对于 Por*C 程序定义的 char[n] 和 varchar[n]变量,预编译程序(proc)知道其长度,而对于 c 语言的字符串指针类型,虽然可 由 malloc()分配了其大小,但 Oracle 预编译程序并不知道其长度(proc 不可能给你分析哪个 指针用哪个 malloc 分配了多少长度,而且好多情况下 malloc 都是在运行时通过变量分配长 度的)。 在这种情况下,Oracle 认为该指针变量大小是调用 strlen()函数所得到的值,所以在应 用字符指针变量作为输出变量(Oracle 数据库到宿主变量)时,需要先给该指针填充适合长 度的非’\0’值,并用’\0’结尾,对于输入值(宿主变量到 Oracle 数据库),则注意’\0’值是否出 现在非业务要求的位置。 3.6.8 为什么 SPOOL 不能用在 Pro*C 程序中? SPOOL只是 sqlplus 命令中的一个子命令,本身不是 SQL 语句,所以不能用在 Pro*C 程序中。 3.6.9 Pro*C/C++支持结构作为宿主变量么? 不仅仅支持结构,还支持数组,结构数组,数组结构等复杂类型,详细情况参见本文后 续章节。 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 10 页 3.6.10 可以在递归函数中嵌入 SQL 么? 可以,但对于所嵌入的 SQL 应该使用 cursor 类型变量。 3.6.11 我可以在任意版本的 Oracle 中使用任意版本的预编译 器么? 不可以,你可以在新版本的数据库服务器中使用旧版本的 Pro*C/C++编译程序,但不能 在旧版本数据库服务器中使用新版本的 Pro*C/C++编译程序。 3.6.12 1405 错误(Fetch column values is null)可避免么? 可以,采用proc参数UNSAFE_NULL=YES命令行参数,允许应用程序从数据库中检索出 NULL到宿主变量而不产生错误。 4 预编译介绍 本章描述了 Pro*C/C++编程的基本概念,最后列出了一个例子程序和编译方法,以及以 后章节的范例所需要的数据库表结构。 4.1 嵌入式 SQL 编程概念介绍 本节阐述了 Pro*C/C++嵌入式编程涉及到的基本概念,为以后章节做些技术铺垫。 4.1.1 可嵌入 Pro*C/C++的 SQL 语句 本节涉及到了嵌入到 c 或 c++语言的源程序内部的 SQL 语句,Pro*C/C++应用程序主要 应用场合为 Oracle 数据库服务端,这样的 Pro*C/C++应用程序又称为主机应用程序。 为了管理和查询 Oracle 数据库数据,需要用到 INSERT、DELETE、UPDATE 和 SELECT 语句,所以应用程序用户需要有相应的数据库操作权限。 在 Pro*C 程序中可以通过 SET ROLE 语句动态的调整用户的数据库权限。ROLE 的定义 存储在 Oracle 数据字典中,由具有 DBA 权限的用户对应用用户进行赋权操作,引用用户根 据需要也可以将自身所拥有的权限赋值给其他用户,有关权限的控制,请参阅 Oracle 相关 管理书籍。 建议对本文开发用户由 DBA 授予 CONNECT、RESOURCE 角色。 只有 SQL 语句(不包括 sqlplus 命令中特有的子命令)可以嵌入 Pro*C/C++源程序。 嵌入式 SQL 语句从功能上分为两种:可执行的 SQL 语句和指示语句。可执行的 SQL 语句同 ORACLE 数据库进行交互,并将结果返回到 SQLLIB 或用户变量,例如查询、修改、 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 11 页 事务控制等。指示语句不操作 Oracle 数据,不将结果返回到 SQLLIB,用于完成声明变量、 指示预编译环境等工作。 部分可嵌入 SQL 语句列表如下: 指示语句: 指示语句 描述 ARRAYLEN 在 PL/SQL 中使用主机数组变量用 BEGIN DECLARE SECTION 开始声明宿主变量 END DECLARE SECTION 结束声明宿主变量 DECLARE 命名 Oracle schema objects INCLUDE 包含 Oracle Pro*C/C++相关文件 TYPE 定义数据类型 VA R 声明变量类型 WHENEVER 捕获 Oracle 错误,定义错误处理方法 可执行语句: 可执行语句 描述 ALLOCATE 用于定义和控制 Oracle 数据 ALTER 对 Oracle Objects 进行修改 ANALYZE 对 table、index 等进行重新数据分析 DELETE 删除表中的数据 INSERT 向 Oracle 数据库表中插入记录 SELECT 从库表或视图、同义词中选取数据 UPDATE 更新表中特定记录 COMMIT 提交一个事务 ROLLBACK 回滚一个事务 SAVEPOINT 用于分段提交事务的日志保存点 SET TRANSACTION 设置事务属性 DESCRIBE 使用动态 SQL 语句 EXECUTE 执行存储过程或动态 SQL 语句 PREPARE 准备 SQL 游标 ALTER SESSION 修改连接到 Oracle 回话的默认属性 SET ROLE 设置当前用户所用角色 其他未类出的 SQL 语句,比如 CLOSE、DEALLOCATE、FETCH 等等 4.1.2 嵌入 SQL 语句的语法格式 在 Pro*C/C++源程序中可以在 C/C++语言的任何地方使用符合要求的 SQL 语句,并通 过 C 语言变量、结构同 SQL 语句进行数据交互。在需要执行 SQL 语句的部位,只需在 SQL 语句前加入“EXEC SQL”指示符号,在 SQL 语句后面用分号“;”作为结尾既可。C 语言 宿主变量同交互式 SQL 进行数据传输,只需要在 C 变量前加冒号“:”,表示其为外部宿主 变量既可。Pro*C/C++预处理程序将预编译这些语句为 SQLLIB 对应调用的 C/C++语句 例如一个交互式 SQL 语句如下: Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 12 页 SELECT name FROM emp WHERE emp_id=100; 在作为嵌入式 SQL 语句写入到 Pro*C/C++程序中时,我们需要将所选记录 name 字段的 值赋给一个 c 语言变量 sName,则在程序中书写样式如下: EXEC SQL SELECT name INTO :sName FROM emp WHERE emp_id=100; 4.1.3 静态和动态 SQL 语句 在大多数情况下,应用程序开发者所做的开发工作都是基于静态 SQL,也就是在编程 过程中就指定了 SQL 语法、语句,知道了数据库中数据的变更方式,决定了数据库中列值 更新、列名等静态的程序。 有的应用程序需要程序在运行过程中才能决定对何表、什么字段、进行怎样的更新等 操作,在这种情况下,由于静态 SQL 宿主变量不能应用到表名、列名等位置,故提出了动 态 SQL 的需求。Oracle 支持动态 SQL,并保证其执行过程的效率和可操作性。 动态 SQL 语句是 Pro*C/C++编程中一种高级编程技术,其基于数据类型转换在运行时 完成 SQL 语句的建立和执行。 4.1.4 嵌入的 PL/SQL 语句块 Oracle 视同在 Pro*C/C++中嵌入 PL/SQL 语句块和嵌入单条 SQL 语句一样,只要可以嵌 入单条 SQL 语句的地方,都可以嵌入 PL/SQL 语句块。 Pro*C/C++用特定的标识标注 PL/SQL 块的作用范围,其采用 EXEC SQL EXECUTE 关 键字开始到 END-EXEC 关键字结束。 4.1.5 宿主变量和指示变量 宿主变量是在 C/C++语言中声明的变量,用于同 Oracle 数据库进行数据交互。输入变 量用于从 C 程序中向 Oracle 数据库提供数据,又称为绑定变量。输出变量用于从 Oracle 数 据库中读取数据。 宿主变量使用在 SQL 语句中时,前方必须加入冒号“:”,以同 SQL 语句其他关键字做 区分。 可以使用一个包含一定数量成员的结构作为变量,在 SQL 语句通过在结构变量前加入 冒号“:”符号,Pro*C/C++预编译软件将结构的各个结构成员作为宿主变量同 Oracle 进行 数据交互。 指示变量是一种 short integer 类型的特殊变量,宿主变量可以同指示变量选择性的结合 起来应用到 SQL 语句中,指示变量用于标识其所结合的宿主变量的 NULL 状态、被数据库 截断状态的存储。其同宿主变量结合用法是在宿主变量后加“:指示变量名”,例如宿主变量 为 sName,指示变量为 sIntName,则用法如下: SELECT NAME INTO :sName:sIntName FROM emp WHERE empid=100; 语句执行结束后,sIntName 中将存放 sName 变量的状态,具体状态描述请参阅第六章: 数据类型和宿主变量。 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 13 页 4.1.6 Oracle 数据类型 通常 Pro*C/C++应用程序是为了存储数据到 Oracle 数据库或从 Oracle 数据库读取数据 到应用程序,在操作过程中,Oracle 数据库需要知道数据的类型存储长度才能进行相关的存 储和检索工作,这就引入了 Oracle 的数据类型。 Oracle 能识别两种类型的数据类型:内置类型和外部类型。内置数据类型直接标明了 Oracle 按照何种方式存储列数据,Oracle 也使用内部数据类型作为特殊值向应用程序的传 输。 外部数据类型描述了宿主变量数据的存储类型和方式。当应用程序通过 INPUT 变量向 Oracle 数据库写入数据时,如果必要 Oracle 会将按照外部类型的定义方式,将宿主变量的 外部数据类型转换成内部数据类型,反之亦然。 4.1.7 数组 Pro*C/C++允许用户定义宿主数组变量(host arrays)和结构数组,用于在单个的 SQL 语句中使用。在 SELECT 、FETCH、DELETE、INSERT 和 UPDATE 语句中使用宿主数组 变量,能够在 Oracle 数据库引擎解析单条 SQL 的情况下,管理多条数据结果,能够很大的 提高效率。 也可以在一个宿主结构中使用宿主数组。 4.1.8 数据类型转换 Pro*C/C++内置支持基本 C/C++语言类型到 Oracle 内部数据类型的转换,如果用户需要 自定义数据类型,则必须在 Proc*C/C++代码中定义转换规则。 4.1.9 私有 SQL 工作区、游标和记录集 为了执行一个 SQL 语句,Oracle 开辟一个工作区域,命名为:私有 SQL 工作区(private sql area)。在这个 Area 中存储着执行这条 SQL 语句所需的信息。用户可以采用 cursor 指示符 通过此区域得到这条 SQL 语句的相关信息和控制其执行过程。 游标返回的所有记录被称为记录集(active set),其尺寸依赖于 SQL 语句的查询条件。在 移动游标过程中(FETCH),在未到达记录集头尾时每次指向当前记录集的记录有且只有一 条。 4.1.10 事务 事务是一系列逻辑 SQL 语句的集合,Oracle 将这些 SQL 语句视为一个单元,只有这个 这个单元中的每条记录都成功 Oracle 才会认为成功,否则将恢复其中已经操作成功的 SQL 所引起的 Oracle 数据改变。 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 14 页 SQL 语句 COMMIT 用于提交事务,对事务中 SQL 语句对数据库数据的改变进行确认, 在此操作后,数据将不能自动恢复成事务开始前的状态。 SQL 语句 ROLLBACK 用于回滚事务,用于在事务内 SQL 语句单元执行失败情况下, 恢复数据到未改动状态。 SQL 语句 SAVEPOINT 允许将事务进行分块管理,以方便部分成功数据的提交和部分 失败数据的回滚。 4.1.11 错误和警告 对嵌入式 SQL 语句执行后,Oracle 肯定会返回此语句的执行结果,成功或是失败,也 可能执行成功但包含警告。Pro*C/C++提供了两种模式得到 SQL 语句的执行结果,一种是 WHENEVER 语句,另一种是通过 Oracle SQL Communications Area(SQLCA)。 SQLCA 是一个定义在 Oracle 头文件中的数据结构,在 Pro*C/C++应用程序中需要用以 下语句进行引入: EXEC SQL INCLUDE sqlca; 或 #include 通过对 SQLCA 结构的访问,我们可以知道 SQL 语句执行结果,比如执行 DELETE 语 句后目标表记录删除与否?成功删除的记录行数等等信息。 WHENEVER 语句能指定在特殊的情况下,应用程序该如何进行相应的操作,包括:继 续执行下个语句、调用一个函数、跳转到一个标签处、终止程序。 4.2 开发嵌入式 Pro*C/C++程序过程 如下图所示,设计Æ编码Æ预编译Æ[修改 C/C++源码]Æ编译Æ链接 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 15 页 4.3 程序编写规范 4.3.1 注释 可以在嵌入 SQL 语句的任何空白位置使用 C 语言风格的注释(/*…*/),但在关键字 EXEC SQL 之间不能使用注释。也可以在 SQL 语句行后采用 ANSI-SQL 风格的注释(--…) 注释掉从符号“- -”后面的语句。 例如: EXEC SQL SELECT ENAME,SAL /*EMPID*/ INTO :emp_name, :salary -- output host variables FROM EMP WHERE DEPTNO = :dept_number; 如果在 proc 命令行参数中指定 CODE=CPP 参数值,则可以在 Pro*C/C++中采用 C++注 释风格(//),但为了保持移植编译的简单性,不建议采用此种注释风格。 4.3.2 常数表示方法 在 C 语言中,可以在一个常数后面添加 L 或 l 字符表示一个长整形常量,添加 U 或 u Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 16 页 字符表示一个 unsigned 整形的常数,添加一个 F 或 f 字符表示 float 类型的浮点常数。在常 量前添加 0x 或 0X 表明该常量为一个十六进制的常数。 如此之类的数据描述方式,只能用在 C/C++语言中,不(not)允许应用在 SQL 语句中。 4.3.3 变量声明段 变量声明段用于声明宿主变量,格式如下: EXEC SQL BEGIN DECLARE SECTION; /*在此代码段内声明所有宿主变量*/ Char *uid=”xcl/xclxcl”; … EXEC SQL END DECLARE SECTION; 一个声明段必须用下面语句开始: EXEC SQL BEGIN DECLARE SECTION; 并且用下面语句结束: EXEC SQL END DECLARE SECTION; 在变量声明段中,只允许存在下面语句: ‹ 声明宿主变量和指示变量 ‹ EXEC SQL DECLARE 语句 ‹ EXEC SQL INCLUDE 语句 ‹ EXEC SQL VAR 语句 ‹ EXEC SQL TYPE 语句 ‹ EXEC ORACLE 语句 ‹ C/C++语言注释 具体语句如下列表所示: auto, char, const, double, enum, extern, float, int, long, ulong_varchar, OCIBFileLocator OCIBlobLocator, OCIClobLocator, OCIDateTime, OCIExtProcContext, OCIInterval, OCIRowid, OCIDate, OCINumber, OCIRaw, OCIString, register, short, signed, sql_context, sql_cursor, static, struct, typedef, union, unsigned, utext, uvarchar, varchar, void, volatile, a typedef name, a precompiled header, exec oracle, exec oracle begin, exec, exec sql, exec sql begin, exec sql end, exec sql type, exec sql var, exec sql include 当预编译软件 proc 的参数设置有如下值设定时,变量必须声明在变量声明段中,而不 能在 SQL 语句中直接使用 C 语言中声明的变量。 ‹ MODE=ANSI ‹ CODE=CPP (C++ 应用程序) ‹ PARSE=[NONE|PARTIAL] Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 17 页 4.3.4 定界符 在 C 语言中,使用单引号:“’”作为单个字符的定位符号,在单引号中只能有单个字 符,而 Oracle SQL 中使用单引号作为字符串限定字符,标识了字符串的开始和结尾(C 语言 中用双引号“””标识)。例如: EXEC SQL SELECT ename FROM emp WHERE job=’MANAGER’; 在 Oracle 中,双引号“””用在 SQL 中主要是用来区分大小写,而且不用于变量标识, 常用于定界数据库 sengmet 名称,例如: EXEC SQL CREATE TABLE “Emp2”(empno number(4), …); 用这个例子建立的表“Emp2”则包含大小写区分,在对此表进行检索或其他操作时不 能采用 EXEC SQL SELECT empno FROM emp2; 或 EXEC SQL SELECT empno FROM EMP2; 而只能用 EXEC SQL SELECT empno FROM “Emp2”; 语句进行对该表的检索。 4.3.5 文件长度限制 Pro*C/C++预编译程序不能处理无限制大小的 Pro*C/C++单个源程序,其所能够处理的 源文件大小同单个源文件中以下情况有关: ‹ SQL 语句的复杂程度 ‹ SQL 语句绑定变量的多少 ‹ 是否采用数据库名称(例如:采用 AT 子句连接数据库) ‹ 嵌入 SQL 语句的多少 为了避免单个文件超过 proc 处理限制问题的发生,在项目开发过程中应尽量规划源文 件成多个处理单元。 4.3.6 预编译参数对函数声明的影响 标准 C 语言需要在使用函数前对函数原型进行声明,函数声明指明了函数参数个数、 参数类型、返回值类型信息,C 语言编译器在编译程序时可以知道函数使用过程中是否传入 参数丢失、类型不符等判断,并根据声明中返回类型,分配函数返回所需内存空间。 Pro*C/C++预编译工具 proc 的命令行参数 CODE 可指定 Pro*C/C++所生成的 C/C++语言 代码符合何种 C/C++语言规范,其取值如下: Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 18 页 ANSI_C 使用此参数值,预编译器产生完全的函数原型声明,例如: extern void sqlora(long *, void *); KR_C KR 是("Kernighan and Ritchie")的缩写,用此参数所产生的函数声明中函数的参数 将被注释掉,其他同 ANSI_C 一致。 extern void sqlora(/*_ long *,void * _*/); 所以如果操作系统的 C 编译器不支持 ANSI_C 规范,则 CODE 参数必须设置成 KR_C。 CPP 对于 SQL 嵌入 C++应用程序编程,需要设置此参数值为 CPP,生成符合 C++语言 的函数原型声明。 4.3.7 宿主变量命名规则 宿主变量的名称只能包含大写或小写字符、数字和下划线,并且必须以字符开头。变量 长度可以任意,但对 Pro*C/C++预编译器,只有前 31 个字符长度是有意义的。如果开发/生 产系统所用 C 编译器支持的最大变量名称小于 31,请参考其手册,宿主变量名长度以二者 较小的值为准。 为了同 SQL92 标准相兼容,建议开发过程中变量名长度最大为 18 个字符,便于程序移 植到其他数据库系统或操作平台。 4.3.8 超长源代码换行 通常在 Pro*C/C++中书写 SQL 语句可以在一行较长时换行到下一行继续书写,除非上 行代码的末尾同下行代码的开头是一个字符串,在这种情况下,需要使用反斜线“\”作为 行连接符号: EXEC SQL INSERT INTO dept (deptno, dname) VALUES (50, ’PURCHAS\ ING’); 4.3.9 源代码单行最大长度 Pro*C/C++源代码中,预编译器所限定的最大行长度是 1299 个 ASCII 字符,或 324 个 多字节字符。 4.3.10 MAXLITERAL Pro*C/C++预编译工具 proc 命令行参数 MAXLITERAL 指定了其预编译成 C/C++语言源 文件时所转换的最长单行代码长度,默认为 1024 字节,根据系统应用 C/C++编译器所支持 的最大长度,需要对这个参数进行适当的设置,使其能满足系统 C/C++编译器,而不致于预 编译结束后,因为长度问题而使 C/C++编译器报错。 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 19 页 4.3.11 操作符 C 语言中的逻辑操作符同 SQL 语句中的逻辑操作符是不同的,C 语言的逻辑运算操作 符不能用于 SQL 语句中。列表区分如下: SQL 操作符 C 语言操作符 NOT ! AND && OR || = == SQL 语句中也不允许下面的 C 语言操作符: 描述 C 语言操作符 取地址 & 位操作 &, |, ^, ~ 自运算 +=,-=,…等等 条件运算符号 ?: 自加、自减 ++,-- 取指针内容 * 求余 3 左右移位 >>,<< 4.3.12 语句结束符号 任何嵌入的 SQL 语句都以分号“;”结尾。 EXEC SQL DELETE FROM emp WHERE deptno = :dept_number ; 4.4 根据条件进行预编译 类似于 C 语言的#ifdef 系列语句,在 Pro*C/C++中也存在一系列类似语句用于有条件的 对源代码进行预编译,生成符合开发人员要求的源代码,这些语句包括: EXEC ORACLE DEFINE symbol; -- 定义一个符号 EXEC ORACLE IFDEF symbol; -- 如果这个符号被定义了 EXEC ORACLE IFNDEF symbol; -- 如果这个符号没有定义 EXEC ORACLE ELSE; -- 否则 EXEC ORACLE ENDIF; -- 控制预编译条件块结束 例如有如下要求,我们需要通过定义一个应用软件版本标识 UPVER,如果定义此标识 则删除 AUP 表数据,否则删除表 ADOWN,则部分源代码如下: EXEC ORACLE IFDEF UPVER; Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 20 页 EXEC SQL DELETE FROM AUP; EXEC ORACLE ELSE; EXEC SQL DELETE FROM ADOWN; EXEC ORACLE ENDIF; 对于定于符号 UPVER 的方式,有两种,一是修改源代码,加入 EXEC ORACLE DEFINE UPVER; 一种是在 proc 命令行加入参数 DEFINE=UPVER。 用此种有条件预处理编程方式,可以减少产生的 C/C++源代码尺寸,并且逻辑清楚, 容易写适合多种应用环境的程序。 4.5 样例表 本文大部分例子需要两个表:DEPT 和 EMP 表,其建表语句如下: 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) ); Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 21 页 4.5.1 样例数据 4.6 样例程序 本节列出了一个最基本的例子程序 s1.pc 的源代码,作为前面内容的实践和 Pro*C/C++ 编程的入门。 这个例子允许用户循环输入一个empno,然后程序从数据库表EMP中找到所输入empno 对应的记录信息,并回显给用户,然后继续循环,等待用户输入下个 empno。 在这个例子中从数据库中输出的信息,存储到一个结构类型的宿主变量中,同时用另一 个结构类型的指示变量显示对应宿主变量是否存在 NULL 值。 为了编译这个例子,请确认所用 proc 的参数 MODE=ORACLE。 s1.pc 源代码如下: /*! @file: s1.pc @brief: Test Pro*C program sample 1 @date:2006-10-24 @build:make s1 –f Makefile */ #include #include Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 22 页 /* 定义Oracle数据库用户名长度和密码长度 */ #define UNAME_LEN 20 #define PWD_LEN 40 /* 如果MODE=ORACLE,则定义变量不需要BEGIN DECLARE SECTION…等*/ VARCHAR username[UNAME_LEN]; /*存储DB用户名的宿主变量*/ /* VARCHAR是一个Oracle支持的结构类型,可以用小写书写 */ varchar password[PWD_LEN]; /*存储DB密码的宿主变量*/ /*定义一个结构类型的宿主变量,接收Oracle数据到应用程序*/ struct { VARCHAR emp_name[UNAME_LEN]; float salary; float commission; } emprec; /*定义一个结构类型的指示变量,用于为上面的结构类型宿主变量提供状态指示*/ struct { short emp_name_ind; /*指示型变量,类型必须为short*/ short sal_ind; short comm_ind; } emprec_ind; /*定义用于输入的宿主变量*/ int emp_number; int total_queried; /*包含上SQLCA,可用#include 和EXEC SQL INCLUDE sqlca 两种方式*/ #include /*声明错误处理函数*/ void sql_error(); main() { char temp_char[32]; /*给存放用户名和密码的宿主变量赋值,注意是varchar类型,一个c结构*/ strncpy((char *) username.arr, "xcl", UNAME_LEN); username.len = strlen((char *) username.arr); strncpy((char *) password.arr, "xclxcl", PWD_LEN); password.len = strlen((char *) password.arr); Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 23 页 /*注册sql_error函数为数据库操作错误处理函数*/ EXEC SQL WHENEVER SQLERROR DO sql_error("ORACLE error--\n"); /*连接到数据库*/ EXEC SQL CONNECT :username IDENTIFIED BY :password; printf("\nConnected to ORACLE as user: %s\n", username.arr); total_queried = 0; for (;;) { /*无数据找到时跳出循环*/ EXEC SQL WHENEVER NOT FOUND DO break; for (;;) { emp_number = 0; printf("\nEnter employee number (0 to quit): "); gets(temp_char); emp_number = atoi(temp_char); if (emp_number == 0) break; EXEC SQL SELECT ename, sal, NVL(comm, 0) INTO :emprec INDICATOR :emprec_ind FROM EMP WHERE EMPNO = :emp_number; printf("\n\nEmployee\tSalary\t\tCommission\n"); printf("--------\t------\t\t----------\n"); emprec.emp_name.arr[emprec.emp_name.len] = ’\0’; printf("%-8s\t%6.2f\t\t",emprec.emp_name.arr, emprec.salary); if (emprec_ind.comm_ind == -1) printf("NULL\n"); else printf("%6.2f\n", emprec.commission); total_queried++; } if (emp_number == 0) break; printf("\nNot a valid employee number - try again.\n"); } printf("\n\nTotal rows returned was %d.\n", total_queried); printf("\nG’day.\n\n\n"); /* 从数据库断开连接*/ EXEC SQL COMMIT WORK RELEASE; exit(0) Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 24 页 } void sql_error(msg) char *msg; { char err_msg[128]; int buf_len, msg_len; EXEC SQL WHENEVER SQLERROR CONTINUE; printf("\n%s\n", msg); buf_len = sizeof (err_msg); /*得到错误信息的文字描述*/ sqlglm(err_msg, &buf_len, &msg_len); printf("%.*s\n", msg_len, err_msg); EXEC SQL ROLLBACK RELEASE; exit(1); } 4.6.1 编译运行方法 拷贝并修改本文“3.6.1 我怎么编译链接应用程序?”小节列出的 Makefile 文件到 s1.pc 所在目录,在 PROCPPFLAGS 变量值后面加入:MODE=ORACLE,在 all 前面加入如下内 容: S1_OBJ=s1.o s1: $( S1_OBJ) @$(CC) $(CFLAGS) $( S1_OBJ) -o s1 -L${ORACLE_HOME}/lib $(PROLDLIBS) -lm @rm -f $(OBJ) @rm -f *.lis 然后执行 UNIX 指令:make s1 即可得到可执行程序 s1,然后运行./s1 即可。 本文以后所示样例的编译方法类似,以后章节将不再列出编译和运行方法。 5 数据库操作描述 本章描述了数据库操作的基本概念,以及如何通过事务安全的管理数据库数据、如何在 程序中连接和断开数据库、如何设置数据库连接属性等内容。 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 25 页 5.1 连接到数据库 在 Pro*C/C++应用程序对 Oracle 数据库数据进行操作前,必须先连接到数据库,连接语 句描述如下: EXEC SQL CONNECT { :user IDENTIFIED BY :oldpswd | :usr_psw } [[ AT { dbname | :host_variable }] USING :connect_string ] [ {ALTER AUTHORIZATION :newpswd | IN { SYSDBA | SYSOPER } MODE} ] ; 其中各宿主变量含义如下: user :存放Oracle用户数据库名 oldpswd/usr_psw:存放Oracle用户密码 :host_variable:主机名 :connect_string:Oracle NET/8配置的连接字符串 :newpswd:新Oracle用户密码,用于连接数据库并更改用户密码 常用的连接方法有两种,一是将用户名和密码存放在两个宿主变量中,采用 IDENTIFIED BY 子句: EXEC SQL CONNECT :username IDENTIFIED BY :password ; 一是将用户名和密码按照“username/password”方式(在用户名和密码中用反斜线做分 隔的字符串)存放在一个宿主变量中: EXEC SQL CONNECT :usernameandpwd; 也可以采用移植性 不 好的方式:直接在 SQL 语句中书写用户名和密码字符串,连接 数据库,例如: EXEC SQL CONNECT ‘xcl’ IDENTIFIED BY ‘xclxcl’; 或者 EXEC SQL CONNECT ‘xcl/xclxcl’; 5.1.1 使用 ALTER AUTHORIZATION 选项改变用户密码 可以利用ALTER AUTHORIZATION选项在数据连接时改变数据库用户密码,在改动成 功后,应用程序自动用新密码连接到数据库。例如: EXEC SQL CONNECT ‘xcl’ IDENTIFIED BY ‘xclxcl’ ALTER AUTHORIZATION ‘xcl123’; 5.1.2 自动连接 用户能够通过使用用户名 CLUSTER$username 自动连接到 Oracle 数据库,其中 username 是操作系统用户名,CLUSTER$username 是有效的数据库用户名,CLUSTER$真实值在 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 26 页 Oracle 数据库的初始化参数文件 spfile 或 pfile(init.ora)中定义。在这种模式下,用户只需简 单的将用户名和密码写成“/”即可,例如: char *oid=”/”; EXEC SQL CONNECT :oid; 假设上例中操作系统用户名为 xcl,则上例将以 CLUSTER$xcl 数据库用户的身份连接 到 Oracle 数据库。需要注意的是反斜线前后不能有空格,如果 oid=”/ “将连接失败。 在Pro*C/C++预编译器的参数中如果AUTO_CONNECT=true,则可以在源程序中 不显视的连接数据库,在执行第一条SQL语句前,应用程序将采用上述自动连接的方法预 先连接到数据库。 5.1.3 连接权限 在数据库连接时,可用 IN { SYSDBA | SYSOPER } MODE} 子句确定连接所用的数据库 操作权限。前提是应用用户应具有 SYSDBA 或 SYSOPER 权限,否则无用。需注意的是在 下列情况下,此子句不能出现: ‹ 预编译工具 AUTO_CONNECT=YES ‹ CONNECT 中存在 ALTER AUTHORIZATION 关键字 5.1.4 通常连接失败的原因 ‹ 用户名/密码不对 ‹ Oracle 用户没有 connect 权限 ‹ Oracle 用户账户密码过期 ‹ Oracle 用户账户被锁定 解决方法: 通过 DBA 进行相应的赋权、解锁操作即可。 5.2 高级连接选项 本节描述了在 Pro*C/C++应用程序中同时连接多个数据库的方法,并对 Oracle 网络配置 做个介绍,以对后面的章节有所铺垫和准备。 5.2.1 预备知识 在一个网络上的所有 Oracle 数据库称为不同的 node,Oracle 允许通过 Oracle NET 在不 同 node 间进行信息(sql 语句、数据、状态值)的传输。 在本地域中,Oracle NET 简单的使用数据库的 service 名称作为网络标识,用于连接数 据库,对非本域的节点,必须使用全局规范 NET 描述符来查找对应的数据库服务器。 有关 Oracle NET 的配置,请参阅 Oracle DBA 管理相关书籍。 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 27 页 5.2.2 并发连接 Oracle 的 Pro*C/C++应用程序支持同时连接多个数据库和对一个数据库同时使用多个 连接,下面图展示了应用程序同时连接 ORA2、ORA3、ORA4 三个网络数据库,并通过简 单的 CONNECT 语句连接到本地数据库。 5.2.3 默认的数据库和连接 每一个节点都有一个默认的数据库名,如果在 CONNECT 语句中没有指定 AT 子句,则 连接到该节点的默认数据库。 在 Pro*C/C++所在应用程序主机的 Oracle NET 配置文件中,所有的数据库都不应该重 名,但不同的网络名可以指向同一个数据库,所以在一个应用程序中可以根据不同的名称建 立数据库连接,换句话说就是同时对一个数据库进行多重连接。 5.2.4 并行直接连接 通常连接到网络本节点或其他节点的数据库,只需要在配置好 Oracle NET 后,采用 CONNECT 语句即可,在 Pro*C/C++程序中允许同时连接到多个数据库,例如: EXEC SQL CONNECT ‘xcl’ IDENTIFIED BY ‘xclxcl’ AT :db_name1 USING :db_string1; EXEC SQL CONNECT ‘wang’ IDENTIFIED BY ‘wang123’ AT :db_name2 USING :db_string2; Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 28 页 连接成功后用户可以通过 AT 子句指定 db_name1 和 db_name2,分别对不同的数据库进 行操作,例如: 普通 SQL: EXEC SQL AT :db_name1 SELECT … … EXEC SQL AT :db_name1 DELETE … … EXEC SQL AT :db_name1 UPDATE … … EXEC SQL AT :db_name1 INSERT … … 或者 EXEC SQL AT :db_name2 SELECT … … EXEC SQL AT :db_name2 DELETE … … EXEC SQL AT :db_name2 UPDATE … … EXEC SQL AT :db_name2 INSERT … … PL/SQL 块: EXEC SQL AT :db_name1 EXECUTE begin /* PL/SQL block here */ end; END-EXEC; 游标控制: EXEC SQL AT :db_name DECLARE emp_cursor CURSOR FOR ... EXEC SQL OPEN emp_cursor ... EXEC SQL FETCH emp_cursor ... EXEC SQL CLOSE emp_cursor; 声明游标采用 AT 子句,因为一个程序文件中不能重名游标,所以在 OPEN、FETCH、 CLOSE 中不需要采用 AT 子句。 动态 SQL 语句: EXEC SQL AT :db_name DECLARE sql_stmt STATEMENT; EXEC SQL PREPARE sql_stmt FROM :sql_string; EXEC SQL DECLARE emp_cursor CURSOR FOR sql_stmt; EXEC SQL OPEN emp_cursor ... EXEC SQL FETCH emp_cursor INTO ... EXEC SQL CLOSE emp_cursor; 5.2.5 间接连接 间接连接时采用数据库链的方式,对另外节点的数据库数据进行访问,简单操作步骤如 下: 1、 建立 DATABASE LINK EXEC SQL CREATE DATABASE LINK db_link CONNECT TO username IDENTIFIED BY password Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 29 页 USING ’NYNON’; 2、 从 DATABASE LINK 中选取数据 EXEC SQL SELECT ENAME, JOB INTO :emp_name, :job_title FROM emp@db_link WHERE DEPTNO = :dept_number; 5.3 事务应用场合 ‹ 对数据一致性、完整性要求高的应用逻辑场合 ‹ 多个 SQL 语句相互管理的程序代码片段 ‹ 对数据数据非查询操作 5.4 开始和结束事务 在 Pro*C/C++程序中事务开始于第一个非 CONNECT 语句的 SQL 语句。并在事务结束 后自动从下个 SQL 语句开始新的事务。换句话说所有 4.1 节所述的可执行 SQL 语句都是事 务的一部分,而 4.1 节所示的指示 SQL 语句不被事务所操作,因为这些 SQL 语句不可能引 起 Oracle 数据的回滚和提交。 结束一个事务的方法如下: ‹ 用带或不带 RELEASE 子句的 COMMIT 或 ROLLBACK 语句。这种方法明确的操 作数据库对数据进行永久改变或回滚所作的改变。 ‹ 当程序中包含数据定义语句,例如(ALTER、GRANT、CREATE 等),在这些语 句前,Oracle 自动执行 COMMIT 操作。这种方法隐式的对之前修改的数据进行了 提交。 5.5 使用 COMMIT 语句 如果一个应用程序中没有显示的执行 COMMIT 语句,则直到应用程序结束或者程序中 存在数据库定义语言,Oracle 才会提交数据。 直到 COMMIT 语句执行后,其他用户才能访问应用程序中对数据库改变的数据,在事 务运行期间,其他用户看到的数据都是事务开始前的数据。 执行 COMMIT 语句完成以下工作: ‹ 对事务期间改变的数据进行永久提交。 ‹ 将改变的数据让其他用户可见。 ‹ 删除所有的 savepoint(参看下节) ‹ 释放事务中占用的行级和表级锁,但保留 parse 锁 ‹ 在 MODE=ANSI 时,关闭所有事务打开的游标,在 MODE=ORACLE 模式时则不 关闭游标和重置游标当前位置,这样可以更大的提高应用系统效能 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 30 页 ‹ 结束事务 采用 RELEASE 子句将提交事务后断开数据库连接。 EXEC SQL COMMIT WORK RELEASE; 其中 WORK 关键字是为了同标准 SQL 兼容,RELEASE 关键字释放应用程序的所有锁、 游标等数据库资源,断开数据库连接。 5.6 使用 SAVEPOINT 语句 SAVEPOINT 语句在事务中起到一个保存点标识作用,通过该语句,事务可回滚到特定 的 SAVEPOINT 标识处。在一个事务中可有多个 SAVEPOINT 语句。 /*部分测试代码...*/ for (;;) { printf("Customer number? "); gets(temp); cust_number = atoi(temp); if ( cust_number==0 ) break; printf("New status? "); gets(new_status); EXEC SQL UPDATE mail_list SET stat = :new_status WHERE custno = :cust_number; } /* 下面采用 savepoint 语句,让数据做部分回滚 */ EXEC SQL SAVEPOINT start_delete; EXEC SQL DELETE FROM mail_list WHERE stat = ’INACTIVE’; if (sqlca.sqlerrd[2] < 25) /* 检查删除的记录条数,如果大于等于 25 条则回滚删除 */ printf("Number of rows deleted is %d\n", sqlca.sqlerrd[2]); else { printf("Undoing deletion of %d rows\n", sqlca.sqlerrd[2]); EXEC SQL WHENEVER SQLERROR GOTO sql_error; /*出错则跳转到 C 语言特定 LABEL*/ EXEC SQL ROLLBACK TO SAVEPOINT start_delete; } EXEC SQL WHENEVER SQLERROR CONTINUE; EXEC SQL COMMIT WORK RELEASE; exit(0); Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 31 页 sql_error: EXEC SQL WHENEVER SQLERROR CONTINUE; EXEC SQL ROLLBACK WORK RELEASE; printf("Processing error\n"); exit(1); 执行 COMMIT 和 ROLLBACK 语句时,将清除所有 SAVEPOINT,提交或回滚所有数据。 5.7 ROLLBACK 语句 ROLLBACK 语句完成事务中对数据库数据所做修改的回滚(恢复),其主要完成的工 作如下: ‹ 恢复当前事务对数据库所做的所有数据改变 ‹ 删除所有 SAVEPOINT ‹ 结束事务 ‹ 释放事务所占用的行和表级锁,保留 parse 锁 ‹ MODE=ANSI 情况下关闭所有游标,MODE=ORACLE 情况下保留游标打开和当前 记录级状态 ROLLBACK TO SAVEPOINT 语句所完成的工作: ‹ 回滚指定 SAVEPOINT 语句后所作的数据库改变 ‹ 删除所有指定 SAVEPOINT 语句后的 SAVEPOINT ‹ 释放指定 SAVEPOINT 语句后的行、表锁 注意:ROLLBACK TO SAVEPOINT 语句不能同 RELEASE 选项同时使用 RELEASE子句断开数据库连接,结合 ROLLBACK 语句,就是回滚事务,并断开数据 库连接。 EXEC SQL ROLLBACK WORK RELEASE; 5.8 SET TRANSACTION 语句 使用此语句可以开始一个只读事务,必须位于事务开始。 EXEC SQL SET TRANSACTION READ ONLY; 在一个事务中,本语句只能出现一次,在只读事务中,只能进行 SELECT、COMMIT 和 ROLLBACK 语句。 只读事务主要用于:在其他事务进行对目标表数据更新的同时,对目标表进行数据查询。 如果不采用只读事务进行查询,Oracle 会认为事务有可能对目标数据进行更新,所以会根据 编程情况造成锁等待、取数据表锁死等错误。 在只读事务中,其他用户进程可以继续对目标表进行数据维护和查询。 COMMIT、ROLLBACK 和数据定义语句(CREATE、ALTER 等)会结束只读事务,并 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 32 页 从下个 SQL 语句开始一个新的正常事务。 5.9 重置默认锁状态 通常情况下,Oracle 会根据用户程序自动对数据加入适当的锁,有时在用户特殊需要的 情况下,比如在并发程序时保持高度数据一致性,需要通过用户编程代替默认的锁操作。 5.9.1 使用 FOR UPDATE OF 当声明一个用于包含 CURRENT OF 子句的 DELETE、UPDATE 语句的游标时,使用 FOR UPDATE OF 子句给所操作的记录添加独占模式的行级锁,保证在选出数据后,进行真 正 UPDATE 和 DELETE 语句前该行数据没有被修改。 SELECT FOR UPDATE OF 语句表明所选择的数据行将进行更新或删除,所有该活动记 录级的数据行都将被锁定,直到该行被更新、删除或事务结束。 FOR UPDATE OF 是可选子句,例子如下: EXEC SQL DECLARE emp_cursor CURSOR FOR SELECT ename, job, sal FROM emp WHERE deptno = 20 FOR UPDATE OF sal; 用户能够省略掉FOR UPDATE 子句,象下面语句使用: EXEC SQL DECLARE emp_cursor CURSOR FOR SELECT ename, job, sal FROM emp WHERE deptno = 20; 当使用上述游标过程中使用了CURRENT OF子句,则编译器默认会在DECLARE语句后 加入FOR UPDATE子句。 注意: ‹ 如果使用FOR UPDATE子句,不能用在同时对多个表更新的游标上 ‹ 不管是隐式的FOR UPDATE还是显视的FOR UPDATE子句,都需要锁定数据行,但行锁 定不是在FETCH时,而是在OPEN语句时。 ‹ 在COMMIT和ROLLBACK(除非ROLLBACK到一个SAVEPOINT)后释放游标占用的行级 锁。 ‹ 在COMMIT后 不 能继续FETCH一个FOR UPDATE的游标。 5.9.2 使用 LOCK TABLE 可以使用 LOCK TABLE 语句以多种锁定模式锁定一个或多个表。例如下面语句用 row share 模式锁定了 EMP 表。ROW SHARE 锁定模式允许当前进程访问表,避免其他进程用 exclusive 独占模式锁定整表。 EXEC SQL LOCK TABLE EMP IN ROW SHARE MODE NOWAIT; 锁模式决定了是否其他锁可以被安置在已锁定目标表上。比如许多用户可以同时用ROW SHARE模式锁定同一个表,但只有一个用户能用exclusive模式锁定目标表,在对目标表 进行exclusive独占模式锁定后,其他进程/用户不能对该表进行INSERT、UPDATE和 DELETE操作。 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 33 页 NOWAIT关键字指明了不要等待其他用户在目标表上解锁,如果锁定成功则语句成功, 否则立即返回失败。如果不使用此关键字,在不能立刻锁定表的情况下,Oracle将一直等 待锁定目标表成功为止。 在从表中进行数据检索时,Oracle从不锁定目标表,所以多重查询不会引起相互等待, 同理一个UPDATE过程也不会阻塞另外一个查询,只有两个不同的事务试图更新同一表中同 一行时才可能引起锁等待。 任何 LOCK TABLE 语句隐式的关闭所有基于该表打开的游标。 当执行 COMMIT 或 ROLLBACK 语句时,释放加在表上的锁。 5.10 FETCH 中应用 COMMIT 语句 如果想在 FETCH 语句中更新 FETCH 结果集中的数据,并通过 COMMIT 语句分段提交, 则不要使用 Current of 语句,因为该语句引起隐式 FOR UPDATE,并锁定表记录,在 COMMIT 后将释放锁,而锁是在 OPEN 时加在记录集上的,所以需要重新打开游标才能正常使用后 续的 FETCH 语句。取而代之的是选择表的伪列 rowid,通过 rowid 对记录集进行更新,例 如下例: ... EXEC SQL DECLARE emp_cursor CURSOR FOR SELECT ename, sal, ROWID FROM emp WHERE job = ’CLERK’; ... EXEC SQL OPEN emp_cursor; EXEC SQL WHENEVER NOT FOUND GOTO ... for (;;) { EXEC SQL FETCH emp_cursor INTO :emp_name, :salary, :row_id; ... EXEC SQL UPDATE emp SET sal = :new_salary WHERE ROWID = :row_id; EXEC SQL COMMIT; ... } 5.11 分布式事务处理 分布式事务是指在一个事务中同时对多个节点的数据进行操作,在网络正常情况下,事 务同单机事务操作没什么分别,但一旦网络出现异常则有可能造成事务状态在各个节点不一 致状况,在这种情况下,需要具有FORCE TRANSACTION系统权限的用户通过查询 DBA_2PC_PENDING数据字典得到不一致事务的状态,采用类似如下语句 EXEC SQL COMMIT FORCE ’22.31.83’; … EXEC SQL ROLLBACK FORCE ’25.33.86’; Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 34 页 的SQL语句进行数据一致性调整。有关此部分内容,请参见Oracle DBA管理系列书籍。 5.12 有用的技巧和方针 ‹ 合理的规划事务 ‹ 根据应用需要设定锁模式 ‹ 注意 PL/SQL 块中的 COMMIT/ROLLBACK 语句 在一个 PL/SQL 语句块中的 COMMIT 或 ROLLBACK 语句将影响外部整个事务。例如 在下面的例子中,PL/SQL 语句快中的 ROLLBACK 回滚了 UPDATE 和 INSERT 语句对数据 的改变。 EXEC SQL INSERT INTO EMP ... EXEC SQL EXECUTE BEGIN UPDATE emp ... ... EXCEPTION WHEN DUP_VAL_ON_INDEX THEN ROLLBACK; ... END; END-EXEC; ... 6 数据类型和宿主变量 本章描述了书写 Pro*C/C++应用程序的基本知识,并通过几个完整的实例对所述技术进 行了演示。 6.1 Oracle 数据类型 如前面章节所述,Oracle 数据类型分为两大类,一种是内部数据类型,其用于描述 Oracle 数据在数据库内的存储格式。一种是外部数据类型,用于指定应用程序宿主变量在预编译时 的存储格式。 6.1.1 内部数据类型列表 对存储在数据库列中的数据,Oracle 采用下表所列数据类型: 类型 描述 VARCHAR2 变长字符串 <=4000 字节 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 35 页 NVARCHAR2 or NCHAR VARYING 变长区域语言多字节字符串 <=4000 字节 NUMBER 具有精度和长度定义的数字类型 LONG 长字符串变量 <=2**31-1 字节 ROWID 存放二进制值 DATE 存放固定长度的日期+时间值。7 字节长度 RAW 存放变长二进制数据。 <=2000 字节 LONG RAW 存放变长二进纸数据。 <=2**31-1 字节 CHAR 存放固定长度的字符串数据。 <=2000 字节 NCHAR 存放固定长度的国际特征多字节字符串。<=2000 字 节 BFILE 存放外部文件的二进纸数据。 <=4GB BLOB 存放大二进纸数据。<=4GB CLOB 存放大字符数据。<=4GB NCLOB 存放国家特征字符数据。 <=4GB 这些数据类型好多同 C 语言数据类型十分不同。比如 C 语言中没有对应 NUMBER 的 数据类型,尽管 C 语言的 float 和 double 数据类型可以在一定限制条件下同 Oracle NUMBER 类型的数据进行相互转换,但 NUMBER 类型可以精确到小数点后 38 位,这是任何操作系 统 C/C++编译器的 float 和 double 类型所不能支持的,在这种情况下 Oracle 会自动缩小精度 值以将数据库数据值传递给 C 语言变量。 6.1.2 外部数据类型列表 类型 描述 VARCHAR2 变长字符串 <=65535 字节 NUMBER 具有精度和长度定义的数字类型 INTEGER 具有符号位的整数 FLOAT 浮点数字类型 LONG 长字符串变量 <=2**31-1 字节 STRING 用 NULL 结尾的字符串 VA R N U M 类似 NUMBER,但包含描述整个变量长度的信息 VARCHAR 可变长度字符串 <65533 字节 ROWID 存放二进制值,长度依赖于系统 DATE 存放固定长度的日期+时间值。7 字节长度 VARRAW 变长二进纸数据,<=65535 字节 RAW 存放变长二进制数据。 <=65535 字节 LONG RAW 存放变长二进纸数据。 <=2**31-1 字节 UNSIGNED 无符号整数 LONG VARCHAR 变长字符串,<=2**31-5 字节 LONG VARRAW 变长二进纸数据,<=2**31-5 字节 CHAR 存放固定长度的字符串数据。 <=65535 字节 CHARZ 固定长度,NULL 结尾的字符串。<=65534 字节 CHARF 字符串类型转换格式定义,用于 VA R 和 TYPE 语句 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 36 页 6.2 宿主变量 宿主变量是 Pro*C/C++应用程序同 Oracle 数据库进行通讯和数据交互的中间途径,典型 应用是应用程序通过输入宿主变量向 Oracle 数据库存储数据,通过输出变量从 Oracle 数据 库中读取数据。宿主变量可以是 C 语言中任何一种能转换成 Oracle 外部数据类型的变量类 型,并且支持 C 语言的数组和结构作为数组宿主变量和结构宿主变量。 6.2.1 声明宿主变量 如果预编译选项 MODE=ANSI 或 CODE=CPP 或 PARSE=NONE ,或者 PARSE=PARTIAL,必须在 EXEC SQL BEGIN DECLARE SECTION 语句段中声明宿主变量, 否则在 MODE=ORACLE 的情况下可以象声明 C 语言变量那样声明、定义宿主变量。 宿主变量类型只能是以下列表的 C 语言数据类型和 Oracle 提供的 C 语言结构类型。 C 语言类型和预编译器预定义的类型 描述 char 单字节 char[n] N 个字节长度的字符数组(STRING) int 整型 short 短整型 long 长整型 float 单精度浮点类型 double 双精度浮点类型 VARCHAR[n] 可变长度字符串 下表显示了 Oracle 内部数据类型同宿主变量类型的对应和兼容情况 Oracle 内部数据类型 宿主变量类型 VARCHAR2(Y) CHAR(X) char char[n] VARCHAR[n] int short long float double NUMBER int NUMBER(P,S) short int long float double char char[n] VARCHAR[n] Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 37 页 DATE char[n] VARCHAR[n] LONG char[n] VARCHAR[n] RAW(X) unsigned char[n] VARCHAR[n] LONG RAW unsigned char[n] VARCHAR[n] ROWID unsigned char[n] VARCHAR[n] 注意: 1、 X 的范围是 1-2000。1 是默认值。Y 的范围是 1-4000。 2、 P 的范围是 1-38。S 的范围是从-84 到 127 在定义宿主变量时,可以使用auto、static、extern、const关键字,但不能使用register存 储标识。 6.2.2 使用宿主变量 在 Pro*C/C++的 SQL 语句中使用宿主变量,必须在宿主变量前加入冒号“:”指示符, 而在其它地方使用则等同于 C 语言变量,不能加指示符。 char buf[15]; int emp_number; float salary; ... gets(buf); emp_number = atoi(buf); EXEC SQL SELECT sal INTO :salary FROM emp WHERE empno = :emp_number; 虽然可能引起程序比较混乱,但 Pro*C/C++程序支持使用同 SQL 语句中表名、列名相 同的宿主变量名称,例如: int empno; char ename[10]; float sal; ... EXEC SQL SELECT ename, sal INTO :ename, :sal FROM emp WHERE empno = :empno; 宿主变量本身就是 C 语言变量,其命名规则必须符合 C 语言命名规范,遵循在同一个 代码单元不允许同名、大小写等 C 语言关于变量使用要求。 宿主变量必须具有合法的内存地址,在这个意义上将,不能将数字或函数作为宿主变量, 例如下例演示的代码是非法的。 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 38 页 #define MAX_EMP_NUM 9000 ... int get_dept(); ... EXEC SQL INSERT INTO emp (empno, ename, deptno) VALUES (:MAX_EMP_NUM + 10, ’CHEN’, :get_dept()); 6.3 指示变量 用户能够将任何一个宿主变量同一个指示变量进行关联。宿主变量必须被定义为 2 个字 节的整数类型(short),在 SQL 语句中,如果没有指定 INDICATOR 关键字,指示变量必须 紧跟在与其关联的宿主变量后。如果使用 DECLARE SECTION 声明宿主变量,则相关指示 变量也必须采用 DECLARE SECTION 进行声明。 6.3.1 INDICATOR 关键字 为了提高可读性,可以在代码书写时在任何指示变量前加入 INDICATOR 关键字,标明 后面所根的是指示变量,在 SQL 语句中指示变量前也需要加入“:”指示符。 :host_variable INDICATOR :indicator_variable 等效于 :host_variable:indicator_variable 指示变量的值,及其代表的含义如下: 值 含义 0 操作成功 -1 该指示变量对应的宿主变量返回了或插入、更新成了 NULL 值 -2 从数据库存放数据到对应的宿主变量时,数据超长,并且不能推断出截断了多 少字节的长度 >0 在 FETHC 或 SELECT 语句时,因数据超长而被截断存放在了对应的宿主变量 中,指示变量存放对应列的长度 6.3.2 指示变量用法实例 指示变量主要用于判断从数据库中返回的数据是否超长,是否为空,下面程序代码演示 了指示变量在程序中的应用。 EXEC SQL BEGIN DECLARE SECTION; int emp_number; float salary, commission; short comm_ind; /* 指示变量 */ EXEC SQL END DECLARE SECTION; char temp[16]; Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 39 页 float pay; /* not used in a SQL statement */ ... printf("Employee number? "); gets(temp); emp_number = atof(temp); EXEC SQL SELECT SAL, COMM INTO :salary, :commission:ind_comm FROM EMP WHERE EMPNO = :emp_number; if(ind_comm == -1) /* commission变量是NULL */ pay = salary; else pay = salary + commission; 6.3.3 指示变量应用规范 ‹ 必须被明确的声明成 2 字节的整型 ‹ SQL 语句中使用时,必须用冒号“:”前导标识 ‹ 在不使用 INDICATOR 关键字时,应紧根所关联的宿主变量后 6.3.4 限制 如果不用指示变量在SELECT或FETCH语句中将一个NULL 值赋给宿主变量时,Oracle 会报 1405 错误。为了避免这种情况,可以采用两种解决方法: ‹ 使用指示变量 ‹ 调整 Pro*C/C++预编译程序 proc 的命令行参数,设定 MODE=ORACLE, UNSAFE_NULL=YES 6.4 VARCHAR 变量 VARCHAR 类型是个 C 语言结构类型,在对数据库表中的 VARCHAR2 和 LONG 数据 类型进行操作时,VARCHAR 类型要比 C 语言的 char 字符串类型方便的多。 在代码中,VARCHAR 的书写必须是全大写“VARCHAR”或全小写“varchar”,不能 大小写混合编排。 6.4.1 定义 VARCHAR 变量 VARCHAR 类型可以想象成 Pro*C/C++预编译器要解析成的一个结构,例如: VARCHAR username[20]; Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 40 页 则转换后的机构如下: struct { unsigned short len; unsigned char arr[20]; } username; 使用 VARCHAR 类型变量优点之一是通过 FETCH 和 SELECT 语句从 Oracle 数据库中选择 数据到宿主变量时,Oracle 会把数据长度记录到 VARCHAR 变量的 len 成员中,用户可以方 便的得到数据长度,并给数据末尾置‘\0’结束符,如下: username.arr[username.len]=0x00; 或者在 strncpy 或 printf 系列函数中使用长度数值,如下所示: printf(“Username is %.*s\n”,username.len,username.arr); 定义 VARCHAR 变量必须指定其长度,类似“VARCHAR s[]”的使用是不合法的。 可以在一行中定义多个 VARCHAR 变量: VARCHAR a[20],b[25]; 其中 VARCHAR 变量长度可以是宏,也可以是任何在预编译过程中能识别的表达式。 例如: #define MAX_LEN ... VARCHAR name[MAX_LEN]; ... 可以定义一个 VARCHAR 类型的指针指向一个 VARCHAR 变量,具体应用过程参见后 续“6.9 指针变量”章节。 6.4.2 使用 VARCHAR 变量 ... int part_number; VARCHAR part_desc[40]; ... main() { ... EXEC SQL SELECT pdesc INTO :part_desc FROM parts WHERE pnum = :part_number; ... printf(“part_desc is %.*s\n”,part_desc.len,part_desc.arr); } 上面的例子演示了 VARCHAR 宿主变量的使用方法,可以通过变量的 len 成员得到或设 置 arr 成员变量有效长度,arr 成员存放具体数据值。 VARCHAR name[12]; Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 41 页 strcpy(name.arr,”TEST”); name.len=strlen(name.arr); EXEC SQL INSERT INTO emp(name) VALUES :name; ... 6.4.3 空值处理 ‹ 如果 Oracle 返回空值到一个 VARCHAR 宿主变量,Oracle 不改变该宿主变量原 arr 成员存储内容,也不改变 len 成员的长度 ‹ 如果设置 VARCHAR 类型的宿主变量 len 成员值为 0,在通过其对目标数据表对应 列进行 INSERT 和 UPDATE 时,在没有非空约束情况下,Oracle 更新对应数据库 表该列数据为 NULL 6.4.4 VARCHAR 变量作为函数参数用法 VARCHAR 变量在预编译后就是一个 C 语言结构,只要按照结构的调用方法进行函数 的传递即可,下面例子进行了演示: VARCHAR emp_name[20]; ... emp_name.len = 20; SELECT ename INTO :emp_name FROM emp WHERE empno = 7499; ... print_employee_name(&emp_name); /* 通过指针传递 */ ... print_employee_name(name) VARCHAR *name; { ... printf("name is %.*s\n", name->len, name->arr); ... } 6.4.5 例程 对不同的系统,Pro*C/C++源程序经过预编译成C/C++语言源程序后,所生成对应 VARCHAR结构的arr数组大小可能大于Pro*C/C++程序中定义的VARCHAR[n]长度n。如果想 得到arr数组真实长度,需要用到 SQLVarcharGetLength (dvoid *context, unsigned long *datlen, unsigned long *totlen);或 sqlvcp()函数。下面给出了sqlvcp用法的范例。 #include Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 42 页 #include #include /* 仿造一个 VARCHAR 指针类型*/ struct my_vc_ptr { unsigned short len; unsigned char arr[32767]; }; typedef struct my_vc_ptr my_vc_ptr; my_vc_ptr *vc_ptr; EXEC SQL BEGIN DECLARE SECTION; VARCHAR *names; int limit; char *username = "xcl/xclxcl"; EXEC SQL END DECLARE SECTION; void sql_error(); extern void sqlvcp(), sqlgls(); main() { unsigned int vcplen, function_code, padlen, buflen; int i; char stmt_buf[120]; EXEC SQL WHENEVER SQLERROR DO sql_error(); EXEC SQL CONNECT :username; printf("\nConnected.\n"); EXEC SQL SELECT COUNT(*) INTO :limit FROM emp; EXEC SQL DECLARE emp_name_cursor CURSOR FOR SELECT ename FROM emp; EXEC SQL FOR :limit OPEN emp_name_cursor; vcplen = 10; /* 使用 sqlvcp() 函数得到应该 malloc 的长度*/ sqlvcp(&vcplen, &padlen); printf("Actual array length of VARCHAR is %ld\n", padlen); names = (VARCHAR *) malloc((sizeof (short) + (int)padlen) * limit); if (names == 0) Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 43 页 { printf("Memory allocation error.\n"); exit(1); } for (vc_ptr = (my_vc_ptr *) names, i = 0; i < limit; i++) { vc_ptr->len = (short) padlen; vc_ptr = (my_vc_ptr *)((char *) vc_ptr + padlen + sizeof (short)); } EXEC SQL FOR :limit FETCH emp_name_cursor INTO :names; printf("Employee names--\n"); for (vc_ptr = (my_vc_ptr *) names, i = 0; i < limit; i++) { printf("%.*s\t(%d)\n", vc_ptr->len, vc_ptr->arr, vc_ptr->len); vc_ptr = (my_vc_ptr *)((char *) vc_ptr + padlen + sizeof (short)); } buflen = (long) sizeof (stmt_buf); /*sqlgls 函数得到最近执行的 SQL 语句信息*/ sqlgls(stmt_buf, &buflen, &function_code); if (buflen != 0) { printf("The SQL statement was--\n%.*s\n", buflen, stmt_buf); printf("The statement length is %ld\n", buflen); printf("The function code is %ld\n", function_code); EXEC SQL COMMIT RELEASE; exit(0); } else { printf("The SQLGLS function returned an error.\n"); EXEC SQL ROLLBACK RELEASE; exit(1); } } void sql_error() { char err_msg[512]; int buf_len, msg_len; Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 44 页 EXEC SQL WHENEVER SQLERROR CONTINUE; buf_len = sizeof (err_msg); /*sqlglm函数得到错误信息的文字性描述*/ sqlglm(err_msg, &buf_len, &msg_len); printf("%.*s\n", msg_len, err_msg); EXEC SQL ROLLBACK RELEASE; exit(1); } 6.5 CURSOR 变量 CURSOR 类型的变量是指向 Oracle Server 中用 PL/SQL 定义并打开的一个游标。使用 CURSOR 类型变量具有以下优点: ‹ 容易管理 查询过程集中话,在数据库服务器的 PL/SQL 中打开游标变量,如果需要改变查询过程, 只需改变 PL/SQL 语句即可,无需改动每个应用程序。 ‹ 安全 对特殊的应用,只需给应用程序连接到数据库用户执行存储过程的权限,通过具有访问 权限的存储过程访问该数据库用户没有权限访问的表。 6.5.1 定义 cursor 变量 采用 SQL_CURSOR 关键字定义 cursor 变量,SQL_CURSOR 要么全部大写,要么全部 小写。例如: EXEC SQL BEGIN DECLARE SECTION; sql_cursor emp_cursor; SQL_CURSOR dept_cursor; sql_cursor *ecp; EXEC SQL END DECLARE SECTION; ecp=&emp_cursor; SQL_CURSOR类型经过proc预编译处理后,也将转换成C语言的一个结构类型,所以可 以向VARCHAR类型那样进行函数参数传递等操作。 6.5.2 分配 cursor 变量 在可以使用(OPEN/FETCH)一个 CURSOR 变量前,需要用一个 ALLOCATE 语句对该变 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 45 页 量进行资源分配。例如上节中提到的变量 emp_cursor: EXEC SQL ALLOCATE :emp_cursor; ALLOCATE 语句在预编译时给 cursor 变量分配堆内存空间,在 CLOSE 语句或连接关 闭时收回所分配的堆空间。 EXEC SQL CLOSE :emp_cursor; 6.5.3 打开 cursor 变量 打开 cursor 变量有两种模式: ‹ 使用一个命名的 PL/SQL 存储过程 ‹ 在 Pro*C/C++中使用匿名的 PL/SQL 语句块 例如在应用程序外部,通过编写如下存储过程打开 SQL_CURSOR 类型的变量 emp_cursor : CREATE PACKAGE demo_cur_pkg AS TYPE EmpName IS RECORD (name VARCHAR2(10)); TYPE cur_type IS REF CURSOR RETURN EmpName; PROCEDURE open_emp_cur ( curs IN OUT cur_type, dept_num IN NUMBER); END; CREATE PACKAGE BODY demo_cur_pkg AS CREATE PROCEDURE open_emp_cur ( curs IN OUT cur_type, dept_num IN NUMBER) IS BEGIN OPEN curs FOR SELECT ename FROM emp WHERE deptno = dept_num ORDER BY ename ASC; END; END; 在 Oracle 数据库服务器上正确编译和建立了这个包后,就可以在外部 Pro*C/C++应用 程序中调用这个存储过程,打开游标并检索数据,如下所示: ... sql_cursor emp_cursor; char emp_name[11]; ... EXEC SQL ALLOCATE :emp_cursor; ... /* 在服务器端打开 sql_cursor 变量 */ Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 46 页 EXEC SQL EXECUTE begin demo_cur_pkg.open_emp_cur(:emp_cursor, :dept_num); end; ; EXEC SQL WHENEVER NOT FOUND DO break; for (;;) { EXEC SQL FETCH :emp_cursor INTO :emp_name; printf("%s\n", emp_name); } ... 通过 Pro*C/C++源代码中嵌入 PL/SQL 语句打开 SQL_CURSOR 类型 cursor 变量的范例 如下: sql_cursor emp_cursor; int dept_num = 10; ... EXEC SQL EXECUTE BEGIN OPEN :emp_cursor FOR SELECT ename FROM emp WHERE deptno = :dept_num; END; END-EXEC; ... 之后使用和检索数据方法同上面命名 PL/SQL 存储过程打开游标后的处理方法完全一 致。 6.5.4 关闭和释放 cursor 变量 调用“EXEC SQL CLOSE :变量名”语句关闭不需要用的 CURSOR,例如: EXEC SQL CLOSE :emp_cursor; 可以在应用程序中任意多次的 OPEN、FETCH、CLOSE 游标变量,但如果用户断开数 据库连接后重新连接到数据库,则必须调用 ALLOCATE 对游标变量进行重新的资源分配。 释放关闭了的 SQL_CURSOR 变量资源,采用 EXEC SQL FREE 语句,例如: EXEC SQL FREE :emp_cursor; 释放后的游标,如果需要重新使用,需要重新进行 ALLOCATE。 6.5.5 使用限制 ‹ 动态 SQL 语句中不能使用 SQL_CURSOR 类型变量 ‹ 只能对 SQL_CURSOR 变量使用 ALLOCATE、FETCH、FREE 和 CLOSE Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 47 页 ‹ DELCARE CURSOR 命令并不提供 SQL_CURSOR 类型的变量 ‹ 不能对一个 CLOSE/FREE 的 SQL_CURSOR 变量进行 FETCH ‹ 如果预编译器参数 MODE=ANSI,则 CLOSE 一个已经 CLOSE 的游标将引发一个 错误产生 ‹ 对 ALLOCATE 语句不能采用 AT 子句,同理根这个 SQL_CURSOR 变量关联的 FETCH 和 CLOSE 语句也不能采用 AT 子句 ‹ SQL_CURSOR 变量不能保存到数据库表中 6.5.6 例程 首先用应用程序数据库用户在 sqlplus 命令中执行下面的 SQL,建立命名存储过程,用 于打开和定义 Cursor 变量的查询语句。 CREATE OR REPLACE PACKAGE emp_demo_pkg as TYPE emp_cur_type IS REF CURSOR RETURN emp%ROWTYPE; PROCEDURE open_cur(curs IN OUT emp_cur_type, dno IN NUMBER); END emp_demo_pkg; CREATE OR REPLACE PACKAGE BODY emp_demo_pkg AS PROCEDURE open_cur(curs IN OUT emp_cur_type, dno IN NUMBER) IS BEGIN OPEN curs FOR SELECT * FROM emp WHERE deptno = dno ORDER BY ename ASC; END; END emp_demo_pkg; 下面的 s2.pc 应用程序演示了 SQL_CURSOR 变量的用法: #include #include #include #include #include void sql_error(msg) char *msg; { size_t clen, fc; char cbuf[128]; clen = sizeof (cbuf); sqlgls((char *)cbuf, (size_t *)&clen, (size_t *)&fc); printf("\n%s\n", msg); printf("Statement is--\n%s\n", cbuf); printf("Function code is %ld\n\n", fc); sqlglm((char *)cbuf, (size_t *) &clen, (size_t *) &clen); Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 48 页 printf ("\n%.*s\n", clen, cbuf); EXEC SQL WHENEVER SQLERROR CONTINUE; EXEC SQL ROLLBACK WORK RELEASE; exit(-1); } void main() { char temp[32]; EXEC SQL BEGIN DECLARE SECTION; char *uid = "xcl/xclxcl"; SQL_CURSOR emp_cursor; int dept_num; struct { int emp_num; char emp_name[11]; char job[10]; int manager; char hire_date[10]; float salary; float commission; int dept_num; }emp_info; struct { short emp_num_ind; short emp_name_ind; short job_ind; short manager_ind; short hire_date_ind; short salary_ind; short commission_ind; short dept_num_ind; }emp_info_ind; EXEC SQL END DECLARE SECTION; EXEC SQL WHENEVER SQLERROR do sql_error("Oracle error"); EXEC SQL CONNECT :uid; EXEC SQL ALLOCATE :emp_cursor; EXEC SQL WHENEVER NOT FOUND DO break; for (;;) Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 49 页 { printf("\nEnter department number (0 to exit): "); gets(temp); dept_num = atoi(temp); if (dept_num <= 0) break; EXEC SQL EXECUTE begin emp_demo_pkg.open_cur(:emp_cursor, :dept_num); end; END-EXEC; printf("\nFor department %d--\n", dept_num); printf("ENAME SAL COMM\n"); printf("----- --- ----\n"); for (;;) { EXEC SQL FETCH :emp_cursor INTO :emp_info INDICATOR :emp_info_ind; printf("%s ", emp_info.emp_name); printf("%8.2f ", emp_info.salary); if (emp_info_ind.commission_ind != 0) printf(" NULL\n"); else printf("%8.2f\n", emp_info.commission); } } EXEC SQL WHENEVER SQLERROR CONTINUE; EXEC SQL CLOSE :emp_cursor; EXEC SQL ROLLBACK WORK RELEASE; exit(0); } 6.6 CONTEXT 变量 CONTEXT 是一个客户内存区域,可以包括一个或多个连接,一个或多个 CURSOR, 包含了 MODE、HOLD_CURSOR 等一些状态信息。 其使用流程是: 1. 通过 SQL_CONTEXT 类型定义 context 变量 2. 调用 CONTEXT ALLOCATE 语句分配资源 3. 调用 EXEC SQL CONTEXT USE {:context | DEFAULT} 语句使用该环境 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 50 页 4. 调用 CONTEXT FREE 语句释放该变量 从 CONTEXT USE 语句开始,所有执行的语句都在该变量定义环境下执行,比如有以 下应用要求,数据库程序要同时连接两个用户 system 和 xcl,根据一定的业务逻辑对 xcl 用 户的表和 system 用户的表中数据进行管理。以往做法是根据不同用户每次在需要时重新连 接数据库,效率很低下,而应用 CONTEXT 类型的变量后,则可以定义个 SQL_CONTEXT 环境变量,用于存放一个用户的连接状态。范例如下: #include #include main() { sql_context ctx1; char *usr1 = "xcl/xclxcl"; char *usr2 = "system/manager"; /* 用 xcl 用户连接数据库,作为全局连接 */ EXEC SQL CONNECT :usr1; /*用 SYSTEM 连接,用作 ctx1 环境*/ EXEC SQL CONTEXT ALLOCATE :ctx1; EXEC SQL CONTEXT USE :ctx1; EXEC SQL CONNECT :usr2; EXEC SQL ISNERT INTO system.test(a) VALUES('1234'); EXEC SQL CONTEXT USE DEFAULT; EXEC SQL INSERT INTO xcl.emp (empno, ename) VALUES (1234, ’WALKER’); ... } 6.7 通用 ROWID 对于 Oracle 数据库表分为 heap 表和 index-organized 表,其中默认是 heap 表,两种表存 放 ROWID 的方式不同,heap 表是物理 ROWID,而 index-oranized 是逻辑 ROWID,以下方 法通用的处理了两种表的 ROWID,让用户可以不加区分的对两种表通过 ROWID 对数据进 行操作。(有关 Oracle 表 ROWID 的概念请参阅 Oracle DBA 管理书籍) int n=4001 ; char my_urowid_char[n] ; ... EXEC SQL ALLOCATE :my_urowid_char ; EXEC SQL SELECT rowid INTO :my_urowid_char FROM my_table WHERE ... ; EXEC ORACLE OPTION(CHAR_MAP=STRING); EXEC SQL UPDATE my_table SET ... WHERE rowid = :my_urowid_char ; EXEC SQL FREE :my_urowid_char ; Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 51 页 6.8 结构型宿主变量 6.8.1 结构和数组 用户可以用一个 C 语言结构包含宿主变量。任何合法的宿主变量类型都可以作为结构 的成员。 结构型宿主变量对应于目标表的各列,在用 SELECT、INSERT、FETCH 语句时,简化 代码书写,只需用此结构作为 Oracle 数据库到应用程序数据传输的载体即可。 下面代码演示了用结构作为输入变量从应用程序向 Oracle 数据库传递数据的方法。 typedef struct { char emp_name[11]; int emp_number; int dept_number; float salary; } emp_record; ... emp_record new_employee; strcpy(new_employee.emp_name, "CHEN"); new_employee.emp_number = 9876; new_employee.dept_number = 20; new_employee.salary = 4250.00; EXEC SQL INSERT INTO emp (ename, empno, deptno, sal) VALUES (:new_employee); 需要注意的是,结构中成员的顺序和类型必须同要操作数据库表的列顺序、类型一致。 结构中可包含绑定数组,用于一次提交 SQL 语句,对多条记录进行操作,从而提高应 用程序执行效率范例如下: struct { char emp_name[3][10]; int emp_number[3]; int dept_number[3]; } emp_rec; ... strcpy(emp_rec.emp_name[0], "ANQUETIL"); strcpy(emp_rec.emp_name[1], "MERCKX"); strcpy(emp_rec.emp_name[2], "HINAULT"); emp_rec.emp_number[0] = 1964; emp_rec.dept_number[0] = 5; emp_rec.emp_number[1] = 1974; emp_rec.dept_number[1] = 5; Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 52 页 emp_rec.emp_number[2] = 1985; emp_rec.dept_number[2] = 5; EXEC SQL INSERT INTO emp (ename, empno, deptno) VALUES (:emp_rec); 6.8.2 PL/SQL 的 RECORD 不能绑定一个结构到嵌入式 PL/SQL 语句的 RECORD。 6.8.3 结构嵌套和联合 不能在嵌入 SQL 语句中直接使用嵌套的结构和联合(UNION)类型的变量。 6.8.4 结构型指示变量 结构型指示变量的定义方法同结构型宿主变量,指示其成员类型必须为 2 字节整数型 (short),使用方法同普通指示变量,范例如下: struct { char s_name[32]; int s_id; char grad_date[9]; } student_record; struct { short s_name_ind; short s_id_ind; short grad_date_ind; } student_record_ind; EXEC SQL SELECT student_name, student_idno, graduation_date INTO :student_record INDICATOR :student_record_ind FROM college_enrollment WHERE student_idno = 7200; 指示变量成员将按照顺序关联到对应的结构宿主变量成员,所以要求指示结构变量的 成员个数必须同宿主结构的成员个数相同。 6.8.5 例程 本例 s3.pc 显示的用一个游标选择库表数据到一个结构类型的宿主变量。并显示了自定 义数据类型的技巧。 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 53 页 #include #include #define UNAME_LEN 20 #define PWD_LEN 40 typedef char asciiz[PWD_LEN]; EXEC SQL TYPE asciiz IS STRING(PWD_LEN) REFERENCE; asciiz username; asciiz password; struct emp_info { asciiz emp_name; float salary; float commission; }; void sql_error(); main() { struct emp_info *emp_rec_ptr; if ((emp_rec_ptr = (struct emp_info *) malloc(sizeof(struct emp_info))) == 0) { fprintf(stderr, "Memory allocation error.\n"); exit(1); } strcpy(username, "xcl"); strcpy(password, "xclxcl"); EXEC SQL WHENEVER SQLERROR DO sql_error("ORACLE error--"); EXEC SQL CONNECT :username IDENTIFIED BY :password; printf("\nConnected to ORACLE as user: %s\n", username); EXEC SQL DECLARE salespeople CURSOR FOR SELECT ENAME, SAL, COMM FROM EMP WHERE JOB LIKE ’SALES%’; EXEC SQL OPEN salespeople; printf("\n\nThe company’s salespeople are--\n\n"); Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 54 页 printf("Salesperson Salary Commission\n"); printf("----------- ------ ----------\n"); EXEC SQL WHENEVER NOT FOUND DO break; for (;;) { EXEC SQL FETCH salespeople INTO :emp_rec_ptr; printf("%-11s%9.2f%13.2f\n", emp_rec_ptr->emp_name, emp_rec_ptr->salary, emp_rec_ptr->commission); } EXEC SQL CLOSE salespeople; printf("\nArrivederci.\n\n"); EXEC SQL COMMIT WORK RELEASE; exit(0); } void sql_error(msg) char *msg; { char err_msg[512]; int buf_len, msg_len; EXEC SQL WHENEVER SQLERROR CONTINUE; printf("\n%s\n", msg); buf_len = sizeof (err_msg); sqlglm(err_msg, &buf_len, &msg_len); printf("%.*s\n", msg_len, err_msg); EXEC SQL ROLLBACK RELEASE; exit(1); } 6.9 指针变量 Pro*C/C++程序支持在嵌入的 SQL 语句中应用指针变量,预编译器会根据类型自动进行 处理。 6.9.1 声明指针变量 定义指针变量的方法同普通 C 语言定义指针变量方法一样,例如: int *int_ptr; char *char_ptr; Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 55 页 6.9.2 使用指针变量 在嵌入的 SQL 语句中使用 C 语言指针变量同使用其他变量一致,只需加入前导标识, 冒号“:”即可,例如: char *char_ptr; ... EXEC SQL SELECT intcol INTO :int_ptr FROM ... C 语言指针使用要点、内存分配方式等请参阅相关 C 语言编程书籍。 一个比较详细的范例如下: struct EMP_REC { int emp_number; float salary; }; char *name = "HINAULT"; ... struct EMP_REC *sal_rec; sal_rec = (struct EMP_REC *) malloc(sizeof (struct EMP_REC)); ... EXEC SQL SELECT empno, sal INTO :sal_rec FROM emp WHERE ename = :name; printf("Employee number and salary for %s: ", name); printf("%d, %g\n", sal_rec->emp_number, sal_rec->salary); 7 静态 SQL 本章描述了 Pro*C/C++编程的基本 SQL 知识,并根据所述内容给出了具体的应用例程。 7.1 基本 SQL 语句 执行 SQL 语句能够让用户查询、管理数据库数据,更改数据库表、索引、视图等组成 Oracle 数据库的 OBJECTS。本小节只介绍对 Oracle 数据库中数据进行维护管理的语句 DML, 数据定义语句 DDL 主要用于 DBA 通过 Oracle 工具进行,很少有应用需要执行这些语句, 本节不予以介绍,在本文前几章的范例中曾出现过在 Pro*C/C++中调用 DDL 语句的例子, 读者可通过翻阅进行了解。 执行任何 SQL 语句后,Oracle 都会向 SQLCA 结构置相应的执行情况值,用户可以通 过判断 SQLCA 结构得到对应 SQL 语句执行信息,判断得出执行是否成功,如果执行失败, 还可以得到失败原因。 处理 SQL 语句执行失败的方法一般有两种途径: Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 56 页 ‹ 通过 WHENEVER 语句隐式的进行检查 ‹ 显示的判断 sqlca 结构的成员值,根据不同情况进行不同处理 当执行 SELECT 语句时,无论如何也应该判断以下三种情况: ‹ 没有符合条件的记录,没有数据行返回 ‹ 单条 SELECT 应只返回一行 ‹ 单条 SELECT 语句返回多行 ‹ 下面的语句让你在 Pro*C/C++程序中查询和管理 Oracle 数据: 语句 描述 SELECT 从一个或多个表中检索一条记录 INSERT 增加新数据行到一个数据库表中 UPDATE 更新目标表中的数据 DELETE 删除目标表中的数据 下面语句可以在 Pro*C/C++中显视的操作游标: 语句 描述 DECLARE 声明游标并将游标同查询语句相关联 OPEN 执行游标查询并激活活动结果集 FETCH 在活动结果集上一条条遍历当前记录 CLOSE 删除游标,置结果集为未定义状态 7.1.1 SELECT 语句 从数据库表中检索数据,通过 INTO 子句,传递数据到应用程序宿主变量。 EXEC SQL SELECT ename, job, sal + 2000 INTO :emp_name, :job_title, :salary FROM emp WHERE empno = :emp_number; 去掉 INTO 子句的语法图 (详细说明请参考 Oracle SQL 教程): Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 57 页 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 58 页 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 59 页 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 60 页 7.1.2 INSERT 语句 INSERT 语句用于向数据库表中添加记录,可以用宿主变量方式传递数据: EXEC SQL INSERT INTO emp (empno, ename, sal, deptno) Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 61 页 VALUES (:emp_number, :emp_name, :salary, :dept_number); 也可以用 SQL 子查询从本表或其他表中选择数据插入目标表。 Oracle SQL 子查询是 SELECT 语句的嵌套,其查询结果可以被另个 SQL 语句所用。子 查询可以应用的位置是: ‹ SELECT、DELETE、UPDATE 语句的 WHERE 子句、HAVING 子句和 START WITH 子句。 ‹ 子查询返回的记录集合被 CREATE TABLE 或 INSERT 语句使用 ‹ 返回结果在 UPDATE 的 SET 子句作为 VA L U E 使用 EXEC SQL INSERT INTO emp2 (empno, ename, sal, deptno) SELECT empno, ename, sal, deptno FROM emp WHERE job= :job_title ; INSERT 语句的语法图: Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 62 页 7.1.3 UPDATE 语句 UPDATE 语句用于更新表中特定列的值,例如 emp 表中 empno 为 emp_number 的记录, Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 63 页 sal 和 comm 的值为宿主变量 salary 和 commission 的值: EXEC SQL UPDATE emp SET sal=:salary, comm=:commission WHERE empno=:emp_number; WHERE 子句标明了符合更新记录的条件。 SET 子句的值可以是一个子查询语句返回的记录: EXEC SQL UPDATE emp SET sal = (SELECT AVG(sal)*1.1 FROM emp WHERE deptno = 20) WHERE empno = :emp_number; UPDATE 语句的语法结构图如下: Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 64 页 7.1.4 DELETE 语句 DELETE 语句用于删除数据库表中符合条件的记录。 DELETE FROM emp WHERE emp_no=:emp_number; DELETE 语法结构图如下: Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 65 页 7.1.5 WHERE 子句 在 SELECT、UPDATE 和 DELETE 语句中可以使用 WHERE 子句限定操作数据记录的 范围。在 WHERE 子句中支持宿主变量、子查询、数组宿主变量、系统函数和用户自定义 的存储过程。 7.2 DML 返回子句 UPDATE、INSERT 和 DELETE 语句都有一个 RETURNING 子句,该语句可以返回列 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 66 页 表达式的值到宿主变量 hv 和指示变量 iv。 {RETURNING | RETURN} {expr [,expr]} INTO {:hv [[INDICATOR]:iv] [, :hv [[INDICATOR]:iv]]} 表达式的个数必须同宿主变量的个数相同。这种用法消除了在 INSERT、UPDATE 后重 新通过 SELECT 语句选择新记录信息的要求,RETURNING 返回的值为新记录的值。同时 也消除了在 DELETE 语句前通过 SELECT 语句记录所要删除记录的信息的做法,直接用 DELETE 的 RETURNING 子句可以将待删除记录信息记录到宿主变量中。 由于 RETURNING 消除了重复 SELECT 的需求,减少了 SQL 语句执行的数量,减少了 网络开销,提高了效率。 例如我们对表 emp 中 empno 的记录为 8 的记录信息做如下操作: ... EXEC SQL UPDATE emp SET empno=19 WHERE empno=8 RETURNING empno INTO :emp_number; printf(“After UPDATE empno=[%d]\n”,emp_number) /* emp_number 为 19 */ EXEC SQL DELETE emp WHERE empno=19 RETURNING empno INTO :emp_number2; printf(“Before DELETE empno=[%d]\n”,emp_number2) /* emp_number2 为 19*/ ... 7.3 普通顺序游标操作 当一个查询返回多条记录时需要一个游标,取得第一行返回信息,并顺序得到后续的信 息。或者你可以使用一个数组型宿主变量。 一个游标指向查询语句返回的记录的当前行,允许你一次处理一条返回记录。使用下面 语句定义和管理游标: ‹ DECLARE CURSOR ‹ OPEN ‹ FETCH ‹ CLOSE 首先通过 DELCARE CURSOR 语句声明游标,将其与一个查询语句关联起来。 OPEN 语句执行游标关联的查询语句,查询返回记录称为活动记录集。 FETCH 语句从活动记录集中一条条的指向集合中当前记录,供用户存储到宿主变量中 进行处理。 工作完成后,用 CLOSE 语句关闭游标,释放活动记录集。 7.3.1 DECLARE 语句 声明一个同查询语句关联的游标,如下例: EXEC SQL DECLARE emp_cursor CURSOR FOR SELECT ename, empno, sal FROM emp Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 67 页 WHERE deptno = :dept_number; 其中 emp_cursor 称为游标名称,是指向游标的一个标识,通过游标名进行 OPEN、 FETCH、CLOSE 操作。 游标名不是宿主变量,只是 Pro*C/C++源代码中的一个标识,不能在 DECLARE SECTION 声明段中进行声明,其可以任意长度,但只有前 31 个字符有意义。为了同标准 SQL 兼容,游标名称最好不要超过 18 个字符。 在关联的 SELECT 查询中不能包含 INTO 子句,而应该在 FETCH 语句中使用 INTO 子 句将查询结果赋值给宿主变量供应用程序使用。 由于游标名不是物理宿主变量,只是一个标识,所以使用该游标名称的 OPEN、FETCH、 CLOSE 语句必须同 DELCARE CURSOR 语句放置在同一源文件内。并且在一个源代码文件 中游标名不能重复。 如果在一个应用程序中,需要同时使用大量游标,应该调整 proc 命令行参数 MAXOPENCURSORS。 7.3.2 OPEN 语句 OPEN 语句打开游标,执行关联查询语句,得到查询结果集合,例如打开上节声明的游 标: EXEC SQL OPEN emp_cursor; 一旦 OPEN 了游标,同游标关联查询的输入宿主变量的值的改变将对查询不起作用, 只有重新打开游标才能应用新的宿主变量的值。 通常重新打开游标先需要关闭游标,但如果 proc 参数 MODE=ORACLE,则重新打开游 标不需要预先执行 CLOSE 语句进行关闭,这能提高应用程序性能。 应用程序同时打开游标的数量同 proc 预编译程序的下列参数有关: ‹ HOLD_CURSOR ‹ RELEASE_CURSOR ‹ MAXOPENCURSORS. 7.3.3 FETCH 语句 FETCH 语句通过 INTO 子句将查询结果集中的数据赋值到宿主变量中,例如: EXEC SQL FETCH emp_cursor INTO :emp_name, :emp_number, :salary; FETCH 语句必须位于 DECLARE CURSOR 和 OPEN 语句后。首次执行 FETCH 时返回 查询结果集的第一条记录,以后每次执行 FETCH 语句将顺序返回查询结果集的下条记录。 如果 FETCH 到了结果集的末尾,下条记录不存在,FETCH 语句将执行失败,置 SQLCA 结 构的 sqlcode 为 1403,(No Data Found)错误,使用显示的 if ( sqlca.sqlcode == 1403 ) 语句可以显示的捕获到 FETCH 记录集结束的情况,也可以使用隐式的 WHENEVER NOT FOUND do something() 语句捕获这种情况。 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 68 页 INTO 子句后面的宿主变量的个数必须同 SELECT 语句选择出的列数相同,类型必须 兼容。 EXEC SQL DECLARE emp_cursor CURSOR FOR SELECT ename, sal FROM emp WHERE deptno = 20; ... EXEC SQL OPEN emp_cursor; EXEC SQL WHENEVER NOT FOUND GOTO ... for (;;) { EXEC SQL FETCH emp_cursor INTO :emp_name1, :salary1; EXEC SQL FETCH emp_cursor INTO :emp_name2, :salary2; EXEC SQL FETCH emp_cursor INTO :emp_name3, :salary3; ... } 7.3.4 CLOSE 语句 CLOSE 语句释放游标占用的资源,置记录集合为非定义状态,语句如下: EXEC SQL CLOSE emp_cursor; 如果 MODE=ORACLE ,COMMIT 语句和 ROLLBACK 语句关闭可使用 CURRENT OF 子句的游标(一般用于 UPDATE,声明时显视的加入 FOR UPDATE 子句,或通过使用 CURRENT OF 隐式的加入 FOR UPDATE),而对普通查询游标没有影响。如果 MODE=ANSI 则 COMMIT 和 ROLLBACK 语句关闭当前事务中所有打开的游标。 7.4 滚动游标 滚动游标是一个存放 Oracle SQL 语句和执行过程中产生信息的工作区域。 当一个游标被执行后,查询结果被放在一个结果集中,结果集能被顺序的读取,也可以被非 顺序的读取,对于读取可非顺序读取结果集,称为可滚动的游标。 7.4.1 使用滚动游标 下面的语句让用户可以使用滚动游标。 DECLARE SCROLL CURSOR:可以使用 DECLARE <游标名> SCROLL CURSOR 命名 一个滚动游标,并将之与查询语句关联起来。 OPEN:打开游标,使用方法同普通顺序游标。 FETCH:能有使用 FETCH 语句随机的访问一行,或向上、下遍历游标。也可以精确定 位到结果集合的首尾记录。可选子句如下: FETCH FIRST:取结果集的第一条记录。 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 69 页 FETCH PRIOR:取当前记录的前一条记录。 FETCH NEXT:取当前记录的后一条记录。 FETCH LAST:取记录集的最后一条记录。 FETCH CURRENT:取当前记录。 FETCH RELATIVE n:取相对于当前记录的前或后第 n 条记录,n 是偏移量。 FETCH ABSOLUTE n:取从第一行记录开始的第 n 条记录,n 是便宜量。 下例描述了如何取记录集合的最后一条记录: EXEC SQL DECLARE emp_cursor SCROLL CURSOR FOR SELECT ename, sal FROM emp WHERE deptno=20; ... EXEC SQL OPEN emp_cursor; EXEC SQL FETCH LAST emp_cursor INTO :emp_name, :sal; EXEC SQL CLOSE emp_cursor; 7.4.2 使用 CLOSE_ON_COMMIT 预编译选项 CLOSE_ON_COMMIT 预编译选项决定了是否在 COMMIT 语句时自动关闭事务中的游 标,当 MODE=ansi 时,CLOSE_ON_COMMIT 默认为 yes。明确的指定 CLOSE_ON_COMMIT 为 no,则 COMMIT 时不关闭游标,游标不需要重新打开,可以提升应用程序性能。 7.5 优化提示 在 SELECT、DELETE、UPDATE 语句中可以使用明确的优化选项,以指示数据库服务 器按照用户的选择进行适当的算法优化,格式是在“/*+ ...*/”中加入优化方式,例如: EXEC SQL SELECT /*+ ALL_ROWS (cost-based) */ empno, ename, sal, job INTO :emp_rec FROM emp WHERE deptno = :dept_number; 具体含义和写法参阅本系列文档之“Oracle 系列参考教程--Oracle 性能调整”。 7.6 CURRENT OF 子句 在 DELCARE CURSOR 语句时,FOR UPDATE 子句是可选的,主要用于 UPDATE 或 DELETE 当前游标所对应的查询结果记录时对记录进行加锁。在程序代码中如果用到了 CURRENT OF 子句对当前记录进行 UPDATE 或 DELETE,则预编译器会根据情况自动在 DECLARE 语句中加入 FOR UPDATE 子句。 CURRENT OF 子句代表当前记录,例程如下: EXEC SQL DECLARE emp_cursor CURSOR FOR SELECT ename, sal FROM emp WHERE job = ’CLERK’ FOR UPDATE OF sal; Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 70 页 ... EXEC SQL OPEN emp_cursor; EXEC SQL WHENEVER NOT FOUND GOTO ... for (;;) { EXEC SQL FETCH emp_cursor INTO :emp_name, :salary; ... EXEC SQL UPDATE emp SET sal = :new_salary WHERE CURRENT OF emp_cursor; } 需要注意的是在索引组织的表中不能使用 CURRENT OF 子句,而应该选择出 ROWID 通过 ROWID 进行更新。 7.7 游标控制 下面显示了一个典型顺序游标操作的样例代码: ... /* 定义一个游标 */ EXEC SQL DECLARE emp_cursor CURSOR FOR SELECT ename, job FROM emp WHERE empno = :emp_number FOR UPDATE OF job; /*打开游标确定活动结果记录集 */ EXEC SQL OPEN emp_cursor; /*如果没有记录则break循环 */ EXEC SQL WHENEVER NOT FOUND DO break; /*在循环中读取符合检索条件的记录*/ for (;;) { EXEC SQL FETCH emp_cursor INTO :emp_name, :job_title; EXEC SQL UPDATE emp SET job = :new_job_title WHERE CURRENT OF emp_cursor; } 7.8 一个普通游标范例 下面是一个完整的普通顺序游标的样例程序 s4.pc: Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 71 页 #include char userid[12] = "xcl/xclxcl"; char emp_name[10]; int emp_number; int dept_number; char temp[32]; void sql_error(); #include main() { emp_number = 7499; EXEC SQL WHENEVER SQLERROR do sql_error("Oracle error"); EXEC SQL CONNECT :userid; printf("Connected.\n"); EXEC SQL DECLARE emp_cursor CURSOR FOR SELECT ename FROM emp WHERE deptno = :dept_number; printf("Department number? "); gets(temp); dept_number = atoi(temp); EXEC SQL OPEN emp_cursor; printf("Employee Name\n"); printf("-------------\n"); EXEC SQL WHENEVER NOT FOUND DO break; while (1) { EXEC SQL FETCH emp_cursor INTO :emp_name; printf("%s\n", emp_name); } EXEC SQL CLOSE emp_cursor; EXEC SQL COMMIT WORK RELEASE; exit(0); Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 72 页 } void sql_error(msg) char *msg; { char buf[500]; int buflen, msglen; EXEC SQL WHENEVER SQLERROR CONTINUE; EXEC SQL ROLLBACK WORK RELEASE; buflen = sizeof (buf); sqlglm(buf, &buflen, &msglen); printf("%s\n", msg); printf("%*.s\n", msglen, buf); exit(1); } 7.9 一个滚动游标应用范例 s5.pc: #include char userid[12]="xcl/xclxcl"; char emp_name[10]; void sql_error(); #include main() { EXEC SQL WHENEVER SQLERROR do sql_error("Oracle error"); EXEC SQL CONNECT :userid; printf("Connected.\n"); EXEC SQL DECLARE emp_cursor SCROLL CURSOR FOR SELECT ename FROM emp; EXEC SQL OPEN emp_cursor; /*FETCH 最后一行记录 */ EXEC SQL FETCH LAST emp_cursor INTO :emp_name; Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 73 页 /*FETCH 第 5 行记录 */ EXEC SQL FETCH ABSOLUTE 5 emp_cursor INTO :emp_name; /*FETCH 第 10 行记录 */ EXEC SQL FETCH RELATIVE 5 emp_cursor INTO :emp_name; /*FETCH 第 7 行记录*/ EXEC SQl FETCH RELATIVE -3 emp_cursor INTO :emp_name; /*FETCH 第 1 行记录 */ EXEC SQL FETCH FIRST emp_cursor INTO :emp_name; /*FETCH 第 2 行记录,FETCH 不带任何子句时同 FETCH NEXT*/ EXEC SQL FETCH my_cursor INTO :emp_name; /*FETCH 第 3 行记录 */ EXEC SQL FETCH NEXT my_cursor INTO :emp_name; /*FETCH 当前行也就是第 3 行记录 */ EXEC SQL FETCH CURRENT my_cursor INTO :emp_name; /*FETCH 第 2 行记录*/ EXEC SQL FETCH PRIOR my_cursor INTO :emp_name; } void sql_error(msg) char *msg; { char buf[500]; int buflen , msglen; EXEC SQL WHENEVER SQLERROR CONTINUE; EXEC SQL ROLLBACK TRANSACTION; buflen = sizeof (buf); sqlglm(buf, &buflen, &mesglen); printf("%s\n",msg); printf("%*.s\n",msglen,buf); exit(1); } Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 74 页 8 Oracle 动态 SQL 动态 SQL 编程是 Pro*C/C++中一种比较高级的编程方法,它增强了应用程序的灵活性 和可扩展性,运行应用程序在运行状态时动态组织 SQL 语句并执行和处理执行结果。 Oracle 支持标准动态 SQL 语句和 Oracle 高级动态 SQL 语句,Oracle 动态 SQL 不支持 以下列表的数据类型: ‹ CURSOR 类型 ‹ 结构数组(arrays of struct) ‹ DML 语句的返回子句( returning) ‹ Unicode 变量 ‹ LOBS 类型 如果需要在游标中使用上述类型数据,请使用标准动态 SQL 编写,标准动态 SQL 语句 不作为 Pro*C/C++编程特例,不再本文中进行阐述。 8.1 动态 SQL 含义 在一些应用程序中需要根据业务逻辑特点接收应用程序外部传输过来的变量,在运行时 决定执行什么结构的 SQL 语句,比如:根据用户输入的表名,删除该表对应的数据,我们 知道宿主变量在静态 SQL 语句时不能应用到 DELETE 表名位置的,所以我们没有办法在执 行过程中通过静态 SQL 语句完成这样的功能。而动态 SQL 语句则可以接受用户输入,动态 的组织 SQL 语句,完成上述要求。简言之,动态 SQL 就是在应用程序执行期间组织、准备、 执行的 SQL 语句的方法和技术。 8.2 动态 SQL 的优缺点 优点: ‹ 增强应用程序灵活性 ‹ 增强应用程序扩展性 ‹ 完成静态 SQL 所不能完成的工作 缺点: ‹ 编码相对复杂 ‹ 执行效率没有静态 SQL 好 8.3 动态 SQL 适用环境 在应用程序执行时,如果以下元素在编写代码时不确定就需要使用动态 SQL: ‹ SQL 语句书写方式 ‹ 宿主变量数目 ‹ 宿主变量数据类型 ‹ 未知 Oracle 数据库 Objects(表、试图、索引、列等)属性 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 75 页 8.4 动态 SQL 执行必须条件 无论如何执行动态 SQL 语句,都需要一个包含有效的 SQL 语句(不包括 EXEC SQL 语 句)、或下面嵌入式 SQL 指令的字符串: ‹ ALLOCATE ‹ CLOSE ‹ DECLARE ‹ DESCRIBE ‹ EXECUTE ‹ FETCH ‹ FREE ‹ GET ‹ INCLUDE ‹ OPEN ‹ PREPARE ‹ SET ‹ WHENEVER 在这个字符串中,通常包含假的宿主变量,用这些假的宿主变量只为告诉预处理器在 该位置需要进行变量替换,所以这些假宿主变量不需要声明,可以采用任意命名方式,例如 下面两个语句对动态SQL来说是完全一样的,尽管有“:mgr_number、:job_title”和“:m、:j” 在样式上的区别,这些假宿主变量通常称为占位符号。 “DELETE FROM EMP WHERE MGR = :mgr_number AND JOB = :job_title” “DELETE FROM EMP WHERE MGR = :m AND JOB = :j” 8.5 动态语句执行过程 通常一个应用程序提示用户输入SQL语句组成要素到宿主变量中,应用程序根据宿主变 量值组织SQL语句,并交Oracle数据库服务器进行语法检查。 然后Oracle绑定(bind)宿主变量到准备好的SQL语句中,此时Oracle得到宿主变量的地 址,以能对变量内容进行读写。 接下来Oracle执行(execute)准备好的SQL语句。 随着宿主变量的变化,Oracle可以重复的执行这条SQL语句。 8.6 使用动态 SQL 的情况和方法 有下面四种执行动态 SQL 的情况和方法: 方法名 SQL 语句类型 METHOD 1 无宿主变量的非查询语句 METHOD 2 已知输入变量个数的非查询语句 METHOD 3 已知 SELECT 语句选取列和输入宿主变量个数的查询语句 METHOD 4 未知 SELECT 语句选取列或输入宿主变量个数的查询语句,也适用 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 76 页 于未知列名列表和输入宿主变量的其他 DML 语句,只是其他语句处 理比 SELECT 查询定义游标要简单,本章将举出操作 insert 的例子, 但内容仍以查询为主线进行 8.6.1 METHOD 1 在这种情况下,应用程序接收外界输入构造 SQL 语句到一个字符串,然后调用 EXECUTE IMMEDIATE 命令执行这个 SQL 语句。这种 SQL 语句不能为 SELECT 语句,不 能包含输入变量的占位符。 例如: ’DELETE FROM EMP WHERE DEPTNO = 20’ ’GRANT SELECT ON EMP TO scott’ 对 METHOD 1 情况来说,每执行一次 SQL 语句,数据库都需要解析一遍 SQL。所以 这种方法只适合运行一次或少数几次,否则严重影响应用程序效率。 EXECUTE IMMEDIATE 命令的语法为: EXEC SQL EXECUTE IMMEDIATE { :host_string | string_literal }; 下面例子显示了 METHOD 1 这种情况的用法,并展示了 oraca 结构的一些常见使用方 法。 s6.pc: #include #include #include /*下面两行使用 oraca 结构,程序中要用其得到一些额外运行信息*/ #include EXEC ORACLE OPTION (ORACA=YES); /*设置 RELEASE_CURSOR 选项,使 Oracle 在执行完 CURSOR 后释放 parse 锁*/ EXEC ORACLE OPTION (RELEASE_CURSOR=YES); void dyn_error(); main() { char *username = "xcl"; char *password = "xclxcl"; char *dynstmt1; char dynstmt2[10]; VARCHAR dynstmt3[80]; EXEC SQL WHENEVER SQLERROR DO dyn_error("Oracle error:"); Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 77 页 /*在发生错误时,将出错的 SQL 语句存储在 oraca 结构中 */ oraca.orastxtf = ORASTFERR; EXEC SQL CONNECT :username IDENTIFIED BY :password; puts("\nConnected to ORACLE.\n"); puts("CREATE TABLE dyn1 (col1 VARCHAR2(4))"); EXEC SQL EXECUTE IMMEDIATE "CREATE TABLE dyn1 (col1 VARCHAR2(4))"; dynstmt1 = "INSERT INTO DYN1 values (’TEST’)"; puts(dynstmt1); EXEC SQL EXECUTE IMMEDIATE :dynstmt1; strncpy(dynstmt2, "COMMIT ", 10); printf("%.10s\n", dynstmt2); EXEC SQL EXECUTE IMMEDIATE :dynstmt2; strcpy(dynstmt3.arr, "DROP TABLE DYN1"); dynstmt3.len = strlen(dynstmt3.arr); puts((char *) dynstmt3.arr); EXEC SQL EXECUTE IMMEDIATE :dynstmt3; EXEC SQL COMMIT RELEASE; puts("\nHave a good day!\n"); return 0; } void dyn_error(msg) char *msg; { printf("\n%.*s\n",sqlca.sqlerrm.sqlerrml, sqlca.sqlerrm.sqlerrmc); printf("in \"%.*s...\’\n", oraca.orastxt.orastxtl, oraca.orastxt.orastxtc); printf("on line %d of %.*s.\n\n",oraca.oraslnr, oraca.orasfnm.orasfnml, oraca.orasfnm.orasfnmc); EXEC SQL WHENEVER SQLERROR CONTINUE; EXEC SQL ROLLBACK RELEASE; exit(1); } Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 78 页 8.6.2 METHOD 2 这种情况下,应用程序接受或组建一个 SQL 语句,并通过 PREPARE 和 EXECUTE 语 句执行这个 SQL。不能是查询语句。为输入宿主变量准备的占位符数目和输入宿主变量的 类型必须已知,例如: ’INSERT INTO EMP (ENAME, JOB) VALUES (:emp_name, :job_title)’ ’DELETE FROM EMP WHERE EMPNO = :emp_number’ 对 METHOD 2 来说,SQL 语句只在 PREPARE 时解析一次,以后可以执行多次,并随 输入宿主变量的不同而执行结果不同。 对于数据定义语句(DDL SQL)语句,例如 CREATE 和 GRANT,随 PREPARE 语句执行。 PREPARE 语法格式如下: EXEC SQL PREPARE statement_name FROM { :host_string | string_literal }; PREPARE 语句解析 host_string 宿主变量对应的 SQL 语句,并指定一个语句名称 statement_name,语句名称不是一个宿主变量,不需要声明,只是一个编译标识。 EXECUTE 语法格式如下: EXEC SQL EXECUTE statement_name [USING host_variable_list]; 其中 host_variable_list 格式如下: :host_variable1[:indicator1] [, host_variable2[:indicator2], ...] EXECUTE 通过 USING 子句读取输入的宿主变量,执行经过 PREPARE 解析过的 SQL 语句 statement_name。 下面的例子,通过 USING 子句,让输入宿主变量 emp_number 代替使用占位符号 n: ... int emp_number INTEGER; char delete_stmt[120], search_cond[40];; ... strcpy(delete_stmt, "DELETE FROM EMP WHERE EMPNO = :n AND "); printf("Complete the following statement’s search condition--\n"); printf("%s\n", delete_stmt); gets(search_cond); strcat(delete_stmt, search_cond); EXEC SQL PREPARE sql_stmt FROM :delete_stmt; for (;;) { printf("Enter employee number: "); gets(temp); emp_number = atoi(temp); if (emp_number == 0) break; EXEC SQL EXECUTE sql_stmt USING :emp_number; } Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 79 页 ... 需要说明的是 USING 子句中如果一个是数组宿主变量,USING 子句中的宿主变量必须 全是数组类型。 一个完整的样例程序 s7.pc 如下: #include #include #define USERNAME "xcl" #define PASSWORD "xclxcl" #include #include EXEC ORACLE OPTION (ORACA=YES); char *username = USERNAME; char *password = PASSWORD; VARCHAR dynstmt[80]; int empno = 1234; int deptno1 = 97; int deptno2 = 99; void dyn_error(); main() { EXEC SQL WHENEVER SQLERROR DO dyn_error("Oracle error"); oraca.orastxtf = ORASTFERR; EXEC SQL CONNECT :username IDENTIFIED BY :password; puts("\nConnected to Oracle.\n"); strcpy(dynstmt.arr,"INSERT INTO EMP (EMPNO, DEPTNO) VALUES (:v1, :v2)"); dynstmt.len = strlen(dynstmt.arr); puts((char *) dynstmt.arr); printf(" v1 = %d, v2 = %d\n", empno, deptno1); EXEC SQL PREPARE S FROM :dynstmt; EXEC SQL EXECUTE S USING :empno, :deptno1; empno++; printf(" v1 = %d, v2 = %d\n", empno, deptno2); EXEC SQL EXECUTE S USING :empno, :deptno2; Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 80 页 strcpy(dynstmt.arr, "DELETE FROM EMP WHERE DEPTNO = :v1 OR DEPTNO = :v2"); dynstmt.len = strlen(dynstmt.arr); puts((char *) dynstmt.arr); printf(" v1 = %d, v2 = %d\n", deptno1, deptno2); /*语句改变,必须重新 PREPARE*/ EXEC SQL PREPARE S FROM :dynstmt; EXEC SQL EXECUTE S USING :deptno1, :deptno2; EXEC SQL COMMIT RELEASE; puts("\nComplete!\n"); exit(0); } void dyn_error(msg) char *msg; { printf("\n%s", msg); printf("\n%.*s\n", sqlca.sqlerrm.sqlerrml, sqlca.sqlerrm.sqlerrmc); printf("in \"%.*s...\"\n", oraca.orastxt.orastxtl, oraca.orastxt.orastxtc); printf("on line %d of %.*s.\n\n", oraca.oraslnr, oraca.orasfnm.orasfnml, oraca.orasfnm.orasfnmc); EXEC SQL WHENEVER SQLERROR CONTINUE; EXEC SQL ROLLBACK RELEASE; exit(1); } 8.6.3 METHOD 3 本小节描述了构造动态查询语句的方式,在这种情况下通过接受或构建 SQL 语句,用 DELCARE、PREPARE 和 OPEN、FETCH、CLOSE 语句执行动态查询 SQL 语句,并访问查 询结构。 适用于已知 SELECT 查询语句查询的各列、列类型、输入宿主变量数目、输入宿主变 量类型的情况,例如: ’SELECT DEPTNO, MIN(SAL), MAX(SAL) FROM EMP GROUP BY DEPTNO’ ’SELECT ENAME, EMPNO FROM EMP WHERE DEPTNO = :dept_number’ Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 81 页 METHOD 3 同 METHOD 2 比较相像,只是 PREPARE 关联的语句需要管理和定义游标。 无乱如何在这种情况下必须知道表/视图名、列名,而不能通过占位符号用宿主变量代 替。 对顺序游标动态查询处理逻辑用语句描述如下: PREPARE statement_name FROM { :host_string | string_literal }; DECLARE cursor_name CURSOR FOR statement_name; OPEN cursor_name [USING host_variable_list]; FETCH cursor_name INTO host_variable_list; CLOSE cursor_name; 对滚动游标动态查询处理逻辑用语句描述如下: PREPARE statement_name FROM { :host_string | string_literal }; DECLARE cursor_name SCROLL CURSOR FOR statement_name; OPEN cursor_name [USING host_variable_list]; FETCH [ FIRST| PRIOR|NEXT|LAST|CURRENT | RELATIVE fetch_offset |ABSOLUTE fetch_offset ] cursor_name INTO host_variable_list; CLOSE cursor_name; 一个完整的例程 s8.pc 如下: #include #include #define USERNAME "xcl" #define PASSWORD "xclxcl" #include #include EXEC ORACLE OPTION (ORACA=YES); char *username = USERNAME; char *password = PASSWORD; VARCHAR dynstmt[80]; VARCHAR ename[10]; int deptno = 10; void dyn_error(); main() { EXEC SQL WHENEVER SQLERROR DO dyn_error("Oracle error"); oraca.orastxtf = ORASTFERR; EXEC SQL CONNECT :username IDENTIFIED BY :password; puts("\nConnected to Oracle.\n"); strcpy(dynstmt.arr, "SELECT ename FROM emp WHERE deptno = :v1"); dynstmt.len = strlen(dynstmt.arr); Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 82 页 puts((char *) dynstmt.arr); printf(" v1 = %d\n", deptno); printf("\nEmployee\n"); printf("--------\n"); EXEC SQL PREPARE S FROM :dynstmt; EXEC SQL DECLARE C CURSOR FOR S; EXEC SQL OPEN C USING :deptno; EXEC SQL WHENEVER NOT FOUND DO break; for (;;) { EXEC SQL FETCH C INTO :ename; ename.arr[ename.len] = ’\0’; puts((char *) ename.arr); } /*注意此处用 sqlca.sqlerrd 返回记录行数,sqlca.sqlerrd[2]是个 很有用的 sqlca 成员,指明 SQL 语句影响的记录条数,例如执行 DELETE 语句后,此变量存放删除的记录条数*/ printf("\nQuery returned %d row%s.\n\n", sqlca.sqlerrd[2], (sqlca.sqlerrd[2] == 1) ? "" : "s"); EXEC SQL CLOSE C; EXEC SQL COMMIT RELEASE; puts("Sayonara.\n"); exit(0); } void dyn_error(msg) char *msg; { printf("\n%s", msg); sqlca.sqlerrm.sqlerrmc[sqlca.sqlerrm.sqlerrml] = ’\0’; oraca.orastxt.orastxtc[oraca.orastxt.orastxtl] = ’\0’; oraca.orasfnm.orasfnmc[oraca.orasfnm.orasfnml] = ’\0’; printf("\n%s\n", sqlca.sqlerrm.sqlerrmc); printf("in \"%s...\"\n", oraca.orastxt.orastxtc); printf("on line %d of %s.\n\n", oraca.oraslnr, oraca.orasfnm.orasfnmc); EXEC SQL WHENEVER SQLERROR CONTINUE; Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 83 页 EXEC SQL CLOSE C; EXEC SQL ROLLBACK RELEASE; exit(1); } 8.6.4 METHOD 4 在用户不知道 SELECT 语句选择的列内容或不明确输入宿主变量个数、类型时,需要 用描述字(descriptors)进行处理,例如下面描述的语句: ’INSERT INTO EMP () VALUES ()’ ’SELECT FROM EMP WHERE DEPTNO = 20’ 这种处理方法不支持 Object 类型、LOBS 类型、结构数组、结果集。 一个描述字(descriptor)是一块描述用于在应用程序和 Oracle 数据库间执行动态 SQL 所 需变量信息的内存区域。 8.6.4.1 需要 SQLDA 为了满足这中情况下的动态查询,应用程序必须使用DESCRIBE SELECT LIST命 令,并且需要声明一个SQL Decriptor Area(SQLDA)类型的数据结构。因为这个结构用来保 存SELECT选择的列信息,所以这个结构也被称作检索描述字(SELECT DESCRIPTOR)。 同样的,如果动态SQL语句包含一个未知的输入宿主变量列表(host-variable list),则 也不能使用USING子句完成宿主变量值的传入。在这种情况下,应用程序必须执行 DESCRIBE BIND VARIABLES语句,并声明被称为绑定描述字(BIND DESCRIPTOR)的 另 一SQLDA种类变量,用于保存输入宿主变量所需占位符信息。(用于向Oracle输入数据的宿 主变量也常被称做绑定变量(bind variables))。 如果应用程序同时有多个活动的SQL语句,每一个活动的SQL语句必须有它自己的 SQLDA。 8.6.4.2 DESCRIBE 语句 DESCRIBE 语句初始化一个描述字,用于保存 select-list 列信息或输入宿主变量信息。 如果你指定了一个检索描述字,DESCRIBE SELECT LIST 语句检查 PREPARE 语句中 的动态查询语句的 SELECT-LIST 列信息,得到列的名称、列的类型、约束、长度、精度信 息,并存储这些信息到检索描述字。 如果你指定了一个绑定描述字,DESCRIBE BIND VARIABLES 检查 PREPARE 语句中 的占位符号,将信息存储在绑定描述字中供后续程序使用。 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 84 页 8.6.4.3 什么是 SQLDA? SQLDA 是用来保存 SELECT-LIST 列信息或输入宿主变量信息的数据结构。 SQLDA 变量不能在 DECLARE SECTION 段中进行定义。 SQLDA 变量包含下面 SELECT-LIST 列信息: ‹ 能被 DESCRIBE 的列的最大数量 ‹ 能被 DESCRIBE 的列的真实数量 ‹ 存储列值缓冲区的地址 ‹ 列值的长度 ‹ 列值的数据类型 ‹ 指示变量值的地址 ‹ 存储列名的缓冲区的地址 ‹ 存储列名的缓冲区的长度 ‹ 列名称的当前长度 SQLDA 变量包含在一个动态 SQL 语句中所需要的输入宿主变量的信息: ‹ 能被 DESCRIBE 的占位符最大数量 ‹ 能被 DESCRIBE 的占位符的真实数量 ‹ 输入宿主变量的地址 ‹ 输入宿主变量的长度 ‹ 输入宿主变量的数据类型 ‹ 指示变量的地址 ‹ 存储占位符名称的缓冲区的地址 ‹ 存储占位符名称的缓冲区的长度 ‹ 当前占位符名称的长度 ‹ 存储指示变量名的缓冲区地址 ‹ 存储指示变量名的缓冲区的长度 ‹ 当前指变量名的长度 8.6.4.4 处理过程描述 EXEC SQL PREPARE statement_name FROM { :host_string | string_literal }; EXEC SQL DECLARE cursor_name CURSOR FOR statement_name; EXEC SQL DESCRIBE BIND VARIABLES FOR statement_name INTO bind_descriptor_name; EXEC SQL OPEN cursor_name [USING DESCRIPTOR bind_descriptor_name]; EXEC SQL DESCRIBE [SELECT LIST FOR] statement_name INTO select_descriptor_name; EXEC SQL FETCH cursor_name Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 85 页 USING DESCRIPTOR select_descriptor_name; EXEC SQL CLOSE cursor_name; 对于已知 SELECT-LIST 和 BIND-VARIABLES 单方面信息,可以采用 METHOD 3 的方 法: 如果知道 SELECT-LIST 列信息,而不知道输入变量信息,则采用: EXEC SQL FETCH emp_cursor INTO host_variable_list; 反之,如果只知道输入的宿主变量(绑定变量)信息,则可以直接使用: EXEC SQL OPEN cursor_name [USING host_variable_list]; 8.6.4.5 使用 SQLDA 变量 SQLDA 结构定义在 sqlda.h 头文件中,通常应用程序需要通过指针应用 SQLDA 变量, 对 METHOD 4 的情况至少需要定义一个指向检索描述字(SELECT DESCRIPTOR)的指针或 一个指向绑定描述字(BIND DESCRIPTOR)的指针。 #include ... SQLDA *bind_dp; SQLDA *select_dp; 然后通过使用 SQLSQLDAAlloc 函数(Oracle 8 以前的 sqlaldt 函数)进行所需空间的分配。 bind_dp = SQLSQLDAAlloc(runtime_context, size, name_length, ind_name_length); 当应用程序在非多线程时,runtime_context采用SQL_SINGLE_RCTX代替((dvoid*)0)。 SQLSQLDAAlloc函数的参数含义如下: SQLSQLDAAlloc (runtime_context, max_vars, max_name, max_ind_name); 参数 描述 runtime_context 指向运行环境的指针 max_vars 最大能DESCRIBE 的SELECT-LIST列和占位符号的数目 max_name 占位符和SELECT-LIST列名称的最大长度 max_ind_name 最大指示变量长度,这个参数只用于分配绑定描述字(BIND DESCRIPTOR)空间,对分配检索描述字(SELECT DESCRIPTOR) 空间时,赋0即可 SQLDA 的结构如下: struct SQLDA { long N; /* Descriptor size in number of entries */ char **V; Ptr to Arr of addresses of main variables */ long *L; /* Ptr to Arr of lengths of buffers */ short *T; /* Ptr to Arr of types of buffers */ short **I; * Ptr to Arr of addresses of indicator vars */ long F; /* Number of variables found by DESCRIBE */ Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 86 页 char **S; /* Ptr to Arr of variable name pointers */ short *M; /* Ptr to Arr of max lengths of var. names */ short *C; * Ptr to Arr of current lengths of var. names */ char **X; /* Ptr to Arr of ind. var. name pointers */ short *Y; /* Ptr to Arr of max lengths of ind. var. names */ short *Z; /* Ptr to Arr of cur lengths of ind. var. names */ }; SQLDA结构成员作用描述: 名称 描述 N SELECT-LIST列或占位符的最大数量,执行DESCRIBE前必须执行 SQLSQLDAAlloc()函数给N进行赋值,设定描述数组的维数,其决定了描述字结构成 员数组的最大元素个数。在DESCRIBE命令后,必须将存储在F中的变量真实个数赋 值给N。 V 是一个指向一个地址数组的指针,在该数组中存储SELECT-LIST列值或绑定变 量的值。当使用SQLSQLDAAlloc函数时,系统给V[0]到V[n-]赋值为0。在使用select descriptors时,必须在FETCH语句前,给V指针数组分配内存空间,应为下列语句要 用到两种描述字具体的值: EXEC SQL FETCH ... USING ‘select descriptors’ 对于bind descriptors,必须在OPEN语句前分配空间 EXEC SQL OPEN ... USING ‘bind descriptors’ L L是一个指向存放select-list或bind-variable变量值长度数组的指针。对于select descriptors,DESCRIBE SELECT LIST语句设置数组中各元素的值,对不在select-list 的对应长度设置成该类型的可的定义的最大长度。在FETCH语句前,用户可能需要 重新修改变量长度,比如为了显示原因,需要将NUMBER类型数据存储到C语言char[] 类型,就需要通过SQLNumberPrecV6()函数得到NUMBER长度,然后转换成字符串 后的真实长度。 对于bind descriptors,用户必须在OPEN语句前,给数组成员赋对应V变量成员长 度的值。 因为Oracle间接的通过使用存储在V[i]变量的地址访问数据区域,如果不指定L[i] 变量,就不能得到V[i]的存储数据的长度,也就不能得到或给V[i]指定的数据区域赋 值。如果用户需要更改V[i]指定数据区域的长度,只需要简单更改L[i]的值即可。 T T时指向一个数组的指针,在这个数组中存放这select-list或者bind-variable的数据 类型。这个成员决定了Oracle数据存储到V数组中时如何进行转换。 对于select descriptors,DESCRIBE SELECT LIST语句设置数据类型数组值为 Oracle内部数据类型(char ,number或date等)。在进行FETCH前,用户可能需要重新 根改此数组某元素的值,因为Oracle内部数据类型很难被C语言所采用。通常为了对 select-list对应的select descriptors数据进行显示,需要将数据类型转换成varchar2或 STRING类型。T[i]的高位用来指示其对应的select-list列值的“NULL/NOT NULL”状 态。在使用OPEN或FETCH语句前,需要调用SQLColumnNullCheck()接受T[i]数据类 型值,并清空高位NULL/NOT NULL状态。 对于bind descriptors,DESCRIBE BIND VARIABLES设置数据类型数组的各元素 值为0。用户必须在OPEN命令前设置各输入宿主变量的数据类型。变量类型值采用 Oracle外部数据类型描述。通常,绑定变量值存储在字符串数组中,其对应的T[i]就 设置为1(Varchar2)或者5(STRING)。 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 87 页 I 这是一个指向存储指示变量值数组的指针。 对于select descriptors,在FETCH语句前必须设置I数组各元素所指向的地址。 EXEC SQL FETCH ... USING ‘select descriptor’ 如果第i个select-list对应列值为空,则I[i]值为-1 否则是个>=0的整数。 对于bind descriptors,必须在OPEN语句前设置I数组各变量的值。 EXEC SQL OPEN ... USING ‘bind descriptor’ F 存放通过DESCRIBE语句得到的select-list或占位符的真实数目,如果经过 DESCRIBE语句后F小于零表示DESCRIBE语句发现的select-list数目或占位符数目比 分配描述符时指定的最大数据数目N大,例如,如果设置N=10但DESCRIBE发现 select-list的数目是11个,那么F将被设置成-11,这允许用户根据此值重新调用 SQLSQLDAAlloc函数分配大的描述符存储区域。 S 指向一个数组的指针,该数组存储了select-list或占位符的名字。 M 指向一个数组的指针,该数组存储了select-list或占位符名字的最大长度。 C 指向一个数组的指针,该数组存储了select-list或占位符名字的当前长度。 X 指向一个存储指示变量名字数组的指针。 Y 指向一个存储指示变量名字最大长度数组的指针。 Z 指向一个存储指示变量名称当前长度数组的指针。 8.6.4.6 一些预备知识 „ 转换数据类型 在非 METHOD 4 类型的应用程序中,在预编译时对 Oracle 内部和外部数据类型进 行自动转换。在 DECLARE SECTION 变量声明语句段,预编译程序自动的将宿主变量 类型转换成等值的 Oracle 外部数据类型,例如,自动将 int 类型的宿主变量转换成 Oracle 外部数据类型 NUMBER。 而在 METHOD 4 中,用户可能需要人为的控制数据类型转换,然后将转换后的类 型设置给 SQLDA 的 T 数组变量。 Oracle 内部数据类型指定了数据在 Oracle 数据库中的存储格式,当使用 DESCRIBE SELECT LIST 命令时,Oracle 返回 select-list 各列的内部数据类型存储到 SQLDA 的 T 变量,例如第 n 个 select-list 成员的数据类型被存储到 T[n]。下表显示了内部数据类型 对应的 CODE 值: 内部数据类型 CODE VARCHAR2 1 NUMBER 2 LONG 8 ROWID 11 DATE 12 RAW 23 LONG RAW 24 CHAR 96 Oracle 外部数据类型指定了宿主变量存储数据的格式。DESCRIBE BIND Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 88 页 VARIABLES 命令设置 bind descriptors 的 T 变量各元素值为 0。用户必须在 OPEN 前通 过改变 T 数组的值指定对应绑定变量类型,也就是对第 i 个宿主变量,设置 T[i]为所需 的 Oracle 外部数据类型。下表显示了 Oracle 外部数据类型和对应的 CODE 值和对应的 C 语言数据类型: 外部类型 CODE C 语言类型 VARCHAR2 1 char[n] NUMBER 2 char[n] ( n <= 22) INTEGER 3 int FLOAT 4 float STRING 5 char[n+1] VARNUM 6 char[n] (n <= 22) DECIMAL 7 float LONG 8 char[n] VARCHAR 9 char[n+2] ROWID 11 char[n] DATE 12 char[n] VARRAW 15 char[n] RAW 23 unsigned char[n] LONG RAW 24 unsigned char[n] UNSIGNED 68 unsigned int DISPLAY 91 char[n] LONG VARCHAR 94 char[n+4] LONG VARRAW 95 unsigned char[n+4] CHAR 96 char[n] CHARF 96 char[n] CHARZ 97 char[n+1] „ NUMBER 类型的特殊点 对NUMBER类型的数据,可以通过SQLNumberPrecV6()函数(以前是sqlprc函数)得到 其精度,然后通过计算得到需要设置SQLDA成员L数组对应的值。 SQLNumberPrecV6(dvoid *runtime_context, int *length, int *precision,int *scale); 其中: length指针,存储NUMBER列值的长度,对应SQLDA的L[i]数组值。 precision指针,存储返回的NUMBER列值的精度。 scale指针,存储返回的NUMBER列值的刻度。 如果scale是负数,则计算NUMBER类型长度时,需要加上scale的绝对值。 sqlda *select_des; int prec; int scal; extern void SQLNumberPrecV6(); SQLNumberPrecV6(SQL_SINGLE_RCTX, &(select_des->L[i]), &prec, &scal); /*置NUMBER类型最大长度 */ Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 89 页 if (prec == 0) prec = 38; /* 置实际长度 */ select_des->L[i] = prec + 2; if (scal < 0) select_des->L[i] += -scal; „ 判断列是否存在 NOT NULL 约束 对每一个 select-list 列,DESCRIBE SELECT LIST 语句通过 select descriptor 的 SQLDA 结构中 T 数组返回是否存在 NOT NULL 约束,如果存在,则置 T[i]的高位置空。 在使用 OPEN 或 FETCH 语句前,如果 T[i]的高位被置值,请清空之,在任何情况 下都不要人为的给 T[i]的高位赋值,其值只应该由 DESCRIBE SELECT LIST 语句赋予。 通过使用 SQLColumnNullCheck()函数(以前称为 sqlnul()函数)检查一个列是否可以 为空,该函数同时清空 T[i]的高位。 SQLColumnNullCheck(dvoid *context, unsigned short *value_type, unsigned short *type_code, int *null_status); 其中: value_type:为 T[i] type_code:存储返回的 T[i]的高位 null_status:列 not null 约束状态,1、允许空 0、不允许空 sqlda *select_des; /* pointer to select descriptor */ unsigned short dtype; /* datatype without null bit */ int nullok; /* 1 = null, 0 = not null */ extern void SQLColumnNullCheck(); /* Declare library function. */ SQLColumnNUllCheck(SQL_SINGLE_RCTX, (unsigned short )&(select_des->T[i]), &dtype, &nullok); if (nullok) { /* 允许空值*/ ... /* 清除T[i]的高位 */ SQLColumnNullCheck(SQL_SINGLE_RCTX, &(select_des->T[i]), &(select_des->T[i]),&nullok); } 8.6.4.7 基本步骤 本节描述了使用 METHOD 4 方式使用动态游标的基本步骤: 1. 在 DECLARE SECITON 段声明一个存放动态 SQL 语句的字符串变量 2. 定义用于 selcet-list 和 bind-varibales 的 SQLDA 变量描述字 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 90 页 3. 为检索描述字(select descriptor)和绑定描述字(bind descriptor)分配空间 4. 设置描述字中 select-list 列和 bind variables 的最大数量 5. 给第一步定义的字符串宿主变量赋值 SQL 语句 6. 根据存放 SQL 语句的字符串宿主变量进行 PREPARE 7. 为查询 DECLARE 一个游标 8. DESCRIBE 绑定变量到绑定描述符 9. 重置绑定游标中最大占位符数目为 DESCRIBE 实际得到的占位符数目 10. 得到绑定变量值,并给 DESCRIBE 发现的绑定变量分配存储空间 11. 通过 USING 绑定描述符调用 OPEN 打开游标 12. DESCRIBE select-list 到检索描述字(select descriptor) 13. 重置检索描述字中 select-list 实际选取的列数 14. 因为显示原因,重置描述字中对应 select-list 变量的类型和长度 15. 调用 FETCH,从数据中读取数据到检索描述字指向的内存区域 16. 继续 FETCH 到记录集结束 17. 释放空间 18. 关闭游标 套用上述步骤,对普通顺序游标采用 SQL 语句描述如下: EXEC SQL PREPARE statement_name FROM { :host_string | string_literal }; EXEC SQL DECLARE cursor_name CURSOR FOR statement_name; EXEC SQL DESCRIBE BIND VARIABLES FOR statement_name INTO bind_descriptor_name; EXEC SQL OPEN cursor_name [USING DESCRIPTOR bind_descriptor_name]; EXEC SQL DESCRIBE [SELECT LIST FOR] statement_name INTO select_descriptor_name; EXEC SQL FETCH cursor_name USING DESCRIPTOR select_descriptor_name; EXEC SQL CLOSE cursor_name; 对动态可滚动游标描述如下: EXEC SQL PREPARE statement_name FROM { :host_string | string_literal }; EXEC SQL DECLARE cursor_name SCROLL CURSOR FOR statement_name; EXEC SQL DESCRIBE BIND VARIABLES FOR statement_name INTO bind_descriptor_name; EXEC SQL OPEN cusor_name [ USING DESCRIPTOR bind_descriptor_name]; EXEC SQL DESCRIBE [ SELECT LIST FOR] statement_name INTO select_descriptor_name; EXEC SQL FETCH [ FIRST| PRIOR|NEXT|LAST|CURRENT | RELATIVE fetch_offset |ABSOLUTE fetch_offset ] cursor_name USING DESCRIPTOR select_descriptor_name; EXEC SQL CLOSE cursor_name; 如果查询语句中 SELECT 后的列(select-list)情况是已知的,用户可以省略 DESCRIBE Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 91 页 SELECT LIST 语句,直接使用 METHOD 3 中列出的 FETCH 方法: EXEC SQL FETCH cursor_name INTO host_variable_list; 同理,如果输入宿主变量占位符的数目是已知的,则可以省略 DESCRIBE BIND VARIABLES,采用 METHOD 3 中的 OPEN 方法: EXEC SQL OPEN cursor_name [USING host_variable_list]; 下面个小节将通过一个假设例子详细说明各个步骤的含义,假设如下限制: ‹ 可变绑定变量最大 3 个,所操作列数最大也是 3 个 ‹ 各类名称长度最长为 10 个字符 ‹ 所操作各列值和所输入各绑定变量值的长度最大 10 个字符 8.6.4.7.1 定义一个宿主变量 select_stmt 定义一个用于存放动态查询 SQL 字符串型宿主变量。 EXEC SQL BEGIN DECLARE SECTION; VARCHAR select_stmt[120]; EXEC SQL END DECLARE SECTION; 8.6.4.7.2 定义 SQLDA 变量描述字 可以直接包含 sqlda.h,使用默认的 sqlda 结构。 #include ... sqlda *select_des; /*检索描述字 select descriptor*/ sqlda *bind_des; /*绑定描述字 bind descriptor*/ 8.6.4.7.3 为描述字分配空间 调用 SQLDA *SQLSQLDAAlloc 函数给描述字分配空间。对函数的返回值:如果>0,则 标识返回 SQLDA 结构的指针地址,为 0 则表示函数调用失败。 函数原型: SQLDA *SQLSQLDAAlloc(dvoid *context, unsigned int max_vars, unsigned int max_name, unsigned int max_ind_name); 例子: select_des = SQLSQLDAAlloc(SQL_SINGLE_RCTX, 3, (size_t) 5, (size_t) 0); bind_des = SQLSQLDAAlloc(SQL_SINGLE_RCTX, 3, (size_t) 5, (size_t) 4); 对于 select_des,总是设置 max_ind_name 为 0,也就是指定 select_des 的 X 数组不分配 空间。 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 92 页 8.6.4.7.4 设置描述字中数组的最大成员个数 根据我们限定的条件,最多列数和绑定变量数为 3 个,所以设定长度如下: select_des->N = 3; bind_des->N = 3; select_des 成员属性描述: bind_des 成员属性描述: Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 93 页 8.6.4.7.5 给 select_stmt 宿主变量赋值 例如接受用户输入的 SQL 语句,并赋值给 select_stmt 绑定变量。 printf("\n\nEnter SQL statement: "); gets(select_stmt.arr); select_stmt.len = strlen(select_stmt.arr); 我们假设用户输入了以下 SQL 语句: "SELECT ename, empno, comm FROM emp WHERE comm < :bonus" 8.6.4.7.6 PREPARE 语句 同其他 Prepare 语句: Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 94 页 EXEC SQL PREPARE sql_stmt FROM :select_stmt; 8.6.4.7.7 DECLARE 游标 此处也同其它情况下声明游标方法相同,直接从 PREPARE 的 sql_stmt 中进行声明。 EXEC SQL DECLARE emp_cursor CURSOR FOR sql_stmt; 8.6.4.7.8 DESCRIBE 绑定变量 DESCRIBE BIND VARIABLES 将 SQL 语句中占位符信息写入绑定描述符指向的各数 组变量中。在我们的例子中,DESCRIBE 象下面语句所描述的将信息存储到 bind_des: EXEC SQL DESCRIBE BIND VARIABLES FOR sql_stmt INTO bind_des; DESCRIBE BIND VARIABLES 语句必须在 PREPARE 语句后,并且在 OPEN 语句前。 在这个语句后,bind_des 成员属性情况: Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 95 页 8.6.4.7.9 重置占位符数目 在 DESCRIBE BIND VARIABLES 语句后,必须将得到的实际占位符数目赋值给最大占 位符数目变量,如下: bind_des->N = bind_des->F; 8.6.4.7.10 得到宿主变量值并分配存储空间 在我们的例子中,假设让用户输入绑定变量的值,则代码如下: for (i = 0; i < bind_des->F; i++) { printf("\nEnter value of bind variable %.*s:\n? ", (int) bind_des->C[i], bind_des->S[i]); gets(hostval); /* 设置所输入变量的长度 */ bind_des->L[i] = strlen(hostval); /*为变量值和末尾NULL字符分配存储空间*/ bind_des->V[i] = malloc(bind_des->L[i] + 1); /* 为指示变量值分配存储空间*/ bind_des->I[i] = (unsigned short *) malloc(sizeof(short)); /* 在绑定描述字指定变量内存储所输入的宿主变量值*/ strcpy(bind_des->V[i], hostval); /*设置宿主变量的值*/ *(bind_des->I[i]) = 0; /*如果为空,设置为-1 */ /* 设置数据类型为oracle外部类型为String */ bind_des->T[i] = 5; } 因为只有一个占位符号bonus,所以以上语句只循环一次,假设用户为这个占位符输入的 宿主变量值为625,用null结尾,则输入后,bind描述字bind_des指向内存区域各属性如下: 宿主变量赋值后的bind_des: Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 96 页 8.6.4.7.11 打开游标 同普通打开游标相仿,指示附加 USING 关键字,用来关联绑定变量描述字。 EXEC SQL OPEN emp_cursor USING DESCRIPTOR bind_des; 8.6.4.7.12 DESCRIBE select-list 如果动态语句是查询语句,DESCRIBE SELECT LIST 语句必须位于 OPEN 语句后 FETCH 语句前。 DESCRIBE SELECT LIST 语句将 select-list 列信息解析到 select descriptor 中,本例是 select_des: EXEC SQL DESCRIBE SELECT LIST FOR sql_stmt INTO select_des; 通过查询 Oracle 数据字典,DESCRIBE SELECT LIST 语句设置各 select-list 列的长度和 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 97 页 类型信息。如果动态 SQL 是查询语句,则置 select descriptor 的 F 变量值为 SELECT 出的真 实列数,否则置其为 0。 同时注意如前所述,NUMBER 类型的长度必须 SQLNumberPrecV6 函数进一步处理得 到并需要重置。 经过 DESCRIBE SELECT LIST 语句后,select_des 内容: 8.6.4.7.13 重置 select-list 列数目 select_des->N = select_des->F; Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 98 页 8.6.4.7.14 重置 select-list 中列的长度和数据类型 在 FETCH 语句前,为了我们能更容易的用 C 语言接受变量,需对 select-list 列通过 DESCRIBE 得到的数据类型和长度进行重置。 for (i=0; iF; i++) { /*清除NULL标识位 */ SQLColumnNullCheck(SQL_SINGLE_RCTX, (unsigned short *)&(select_des->T[i]),(unsigned short *)&(select_des->T[i]), &nullok); /*根据不同的数据类型重置必要的长度*/ switch(select_des->T[i]) { case 1: break; case 2: SQLNumberPrecV6(SQL_SINGLE_RCTX, (unsigned long *) &(select_des->L[i]), &prec, &scal); if (prec == 0) prec = 40; select_des->L[i] = prec + 2; if (scal < 0) select_des->L[i] += -scal; break; case 8: select_des->L[i] = 240; break; case 11: select_des->L[i] = 18; break; case 12: select_des->L[i] = 9; break; case 23: break; case 24: select_des->L[i] = 240; break; } /* 为select-list得到的值分配存储空间 */ select_des->V[i] = malloc(select_des->L[i]+1); /*为指示变量的值分配存储空间 */ select_des->I[i] = (short *)malloc(sizeof(short *)); /*处理 LONG RAW数据类型,强制转换其他数据类型为STRING. */ if (select_des->T[i] != 24) select_des->T[i] = 5; } 在经过此操作后,select_des 指向内存区域的内容如下: Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 99 页 8.6.4.7.15 FETCH 结果集 操作方法同普通游标类似,只是需要通过USING子句,指定需要参考的select descriptor。 EXEC SQL FETCH emp_cursor USING DESCRIPTOR select_des; 经过 FETCH 后,ORACLE 存储符合查询条件的数据到 select_des 的 descriptor 中。 select_des 内容如下: Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 100 页 8.6.4.7.16 释放资源 在得到所需要的数据后,必须通过 free 函数释放之前通过 malloc 函数分配的空间,在 我们的例子中,释放空间代码如下: for (i = 0; i < select_des->F; i++) { free(select_des->V[i]); free(select_des->I[i]); } for (i = 0; i < bind_des->F; i++) { free(bind_des->V[i]); free(bind_des->I[i]); } 通过SQLSQLDAFree函数对描述字指针的空间进行释放: SQLSQLDAFree(SQL_SINGLE_RCTX, select_des); SQLSQLDAFree(SQL_SINGLE_RCTX, bind_des); Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 101 页 8.6.4.7.17 关闭游标 同正常普通游标关闭方式: EXEC SQL CLOSE emp_cursor; 8.6.4.7.18 使用数组宿主变量 在 METHOD 4 环境下,使用数组宿主变量,必须使用 FOR 可选子句通知 Oracle 数据 库应用数组的大小。必须将数组的地址赋值给 SQLDA 的 V[i]变量,并给 SQLDA 的 L[i]变 量赋予对应数组元素的长度。 然后通过 FOR 子句在执行 FETCH 和 EXECUTE 语句时通知 Oracle 数据库待操作的数 组元素数量。 下一小节详细描述了一个用数组宿主变量在 METHOD 4 的情况下插入数据库的完整例 子。 8.6.4.7.19 宿主数组变量动态插入数据范例 s9.pc: #include #include #include #include #define NAME_SIZE 10 #define INAME_SIZE 10 #define ARRAY_SIZE 5 char *username = "xcl/xclxcl"; char *sql_stmt ="INSERT INTO emp (empno, ename, deptno) VALUES (:e, :n, :d)"; int array_size = ARRAY_SIZE; SQLDA *binda; char names[ARRAY_SIZE][NAME_SIZE]; int numbers[ARRAY_SIZE], depts[ARRAY_SIZE]; short ind_empno[ARRAY_SIZE] = {0,0,0,0,0}; short ind_dept[ARRAY_SIZE] = {0,0,0,0,0}; main() { EXEC SQL WHENEVER SQLERROR GOTO sql_error; EXEC SQL CONNECT :username; Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 102 页 printf("Connected.\n"); binda = SQLSQLDAAlloc(SQL_SINGLE_RCTX, 3, NAME_SIZE, INAME_SIZE); binda->N = 3; EXEC SQL PREPARE stmt FROM :sql_stmt; EXEC SQL DESCRIBE BIND VARIABLES FOR stmt INTO binda; binda->V[0] = (char *) numbers; binda->L[0] = (long) sizeof (int); binda->T[0] = 3; binda->I[0] = ind_empno; binda->V[1] = (char *) names; binda->L[1] = (long) NAME_SIZE; binda->T[1] = 1; binda->I[1] = (short *)0; binda->V[2] = (char *) depts; binda->L[2] = (long) sizeof (int); binda->T[2] = 3; binda->I[2] = ind_dept; strcpy(&names[0] [0], "ALLISON"); numbers[0] = 1014; depts[0] = 30; strcpy(&names[1] [0], "TRUSDALE"); numbers[1] = 1015; depts[1] = 30; strcpy(&names[2] [0], "FRAZIER"); numbers[2] = 1016; depts[2] = 30; strcpy(&names[3] [0], "CARUSO"); numbers[3] = 1017; ind_dept[3] = -1; /* 为了插入一个 NULL,设置指示变量为-1 */ depts[3] = 30; /* 由于上述指示变量原因 depts[3]被忽略 */ strcpy(&names[4] [0], "WESTON"); numbers[4] = 1018; depts[4] = 30; printf("Adding to the Sales force...\n"); EXEC SQL FOR :array_size EXECUTE stmt USING DESCRIPTOR binda; Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 103 页 printf("%d rows inserted.\n\n", sqlca.sqlerrd[2]); EXEC SQL COMMIT RELEASE; exit(0); sql_error: /* 显示 Oracle 错误信息提示 */ printf("\n%.70s", sqlca.sqlerrm.sqlerrmc); EXEC SQL WHENEVER SQLERROR CONTINUE; EXEC SQL ROLLBACK RELEASE; exit(1); } 8.6.4.8 METHOD 4 实现 sqlplus 命令的例子 s10.pc: /*! @file s10.pc @brief 模仿 Oracle SQLPLUS 工具 @date 2006-10-28 */ #include #include #include #include #include #include /* 定义选择列和绑定变量的最大个数 */ #define MAX_ITEMS 40 /* 定义列名称、占位符、指示变量的最大长度*/ #define MAX_VNAME_LEN 30 #define MAX_INAME_LEN 30 #ifndef NULL #define NULL 0 #endif #if defined(__STDC__) void sql_error(void); int oracle_connect(void); int alloc_descriptors(int, int, int); int get_dyn_statement(void); Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 104 页 void set_bind_variables(void); void process_select_list(void); void help(void); #else void sql_error(/*_ void _*/); int oracle_connect(/*_ void _*/); int alloc_descriptors(/*_ int, int, int _*/); int get_dyn_statement(/* void _*/); void set_bind_variables(/*_ void -*/); void process_select_list(/*_ void _*/); void help(/*_ void _*/); #endif char *dml_commands[] = {"SELECT", "select", "INSERT", "insert", "UPDATE", "update", "DELETE", "delete"}; EXEC SQL INCLUDE sqlda; EXEC SQL INCLUDE sqlca; EXEC SQL BEGIN DECLARE SECTION; char dyn_statement[1024]; EXEC SQL VAR dyn_statement IS STRING(1024); EXEC SQL END DECLARE SECTION; SQLDA *bind_dp; SQLDA *select_dp; /*定义一个保存 long jump 状态信息的缓冲区*/ jmp_buf jmp_continue; int parse_flag = 0; int main() { int i; if (oracle_connect() != 0) exit(1); /* 为 select descriptor 和 bind_descriptor 分配资源 */ if (alloc_descriptors(MAX_ITEMS, MAX_VNAME_LEN, MAX_INAME_LEN) != 0) exit(1); /* 执行 SQL 语句*/ for (;;) { Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 105 页 (void) setjmp(jmp_continue); /* 取得输入的动态语句*/ if (get_dyn_statement() != 0) break; EXEC SQL WHENEVER SQLERROR DO sql_error(); parse_flag = 1; EXEC SQL PREPARE S FROM :dyn_statement; parse_flag = 0; EXEC SQL DECLARE C CURSOR FOR S; /* 为占位符指定绑定变量值. */ set_bind_variables(); /*打开游标执行动态 SQL,如果 SQl 不是 SELECT 查询语句,则 OPEN 将执行该 SQL */ EXEC SQL OPEN C USING DESCRIPTOR bind_dp; /* 处理 select list,如果动态 SQL 不是 SELECT 查询,则函数直接返回 */ process_select_list(); /* 显示执行 SQL 影响的记录行数. */ for (i = 0; i < 8; i++) { if (strncmp(dyn_statement, dml_commands[i], 6) == 0) { printf("\n\n%d row%c processed.\n", sqlca.sqlerrd[2], sqlca.sqlerrd[2] == 1 ? '\0' : 's'); break; } } } /* 结束 FOR 循环 */ /*释放资源*/ for (i = 0; i < MAX_ITEMS; i++) { if (bind_dp->V[i] != (char *) 0) free(bind_dp->V[i]); free(bind_dp->I[i]); Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 106 页 if (select_dp->V[i] != (char *) 0) free(select_dp->V[i]); free(select_dp->I[i]); } SQLSQLDAFree(0,bind_dp); SQLSQLDAFree(0,select_dp); EXEC SQL WHENEVER SQLERROR CONTINUE; EXEC SQL CLOSE C; EXEC SQL COMMIT WORK RELEASE; puts("\nProgram complete normal!\n"); EXEC SQL WHENEVER SQLERROR DO sql_error(); return; } int oracle_connect() { EXEC SQL BEGIN DECLARE SECTION; VARCHAR username[128]; VARCHAR password[32]; EXEC SQL END DECLARE SECTION; printf("\nusername: "); fgets((char *) username.arr, sizeof username.arr, stdin); username.arr[strlen((char *) username.arr)-1] = '\0'; username.len = (unsigned short)strlen((char *) username.arr); printf("password: "); fgets((char *) password.arr, sizeof password.arr, stdin); password.arr[strlen((char *) password.arr) - 1] = '\0'; password.len = (unsigned short)strlen((char *) password.arr); EXEC SQL WHENEVER SQLERROR GOTO connect_error; EXEC SQL CONNECT :username IDENTIFIED BY :password; printf("\nConnected to ORACLE as user %s.\n", username.arr); return 0; connect_error: fprintf(stderr, "Cannot connect to ORACLE as user %s\n", username.arr); return -1; } int alloc_descriptors(size, max_vname_len, max_iname_len) int size; int max_vname_len; Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 107 页 int max_iname_len; { int i; if ((bind_dp=SQLSQLDAAlloc(0, size, max_vname_len, max_iname_len)) ==(SQLDA *) 0) { fprintf(stderr, "Cannot allocate memory for bind descriptor."); return -1; } if ((select_dp=SQLSQLDAAlloc (0, size, max_vname_len, max_iname_len))==(SQLDA *) 0) { fprintf(stderr,"Cannot allocate memory for select descriptor."); return -1; } select_dp->N = MAX_ITEMS; for (i = 0; i < MAX_ITEMS; i++) { bind_dp->I[i] = (short *) malloc(sizeof (short)); select_dp->I[i] = (short *) malloc(sizeof(short)); bind_dp->V[i] = (char *) malloc(1); select_dp->V[i] = (char *) malloc(1); } return 0; } int get_dyn_statement() { char *cp,linebuf[256]; int iter, plsql; for (plsql = 0, iter = 1; ;) { if (iter == 1) { printf("\nSQL> "); dyn_statement[0] = '\0'; } fgets(linebuf, sizeof linebuf, stdin); cp = strrchr(linebuf, '\n'); if (cp && cp != linebuf) *cp = ' '; else if (cp == linebuf) continue; Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 108 页 if ((strncmp(linebuf, "EXIT", 4) == 0) || (strncmp(linebuf, "exit", 4) == 0)) { return -1; } else if (linebuf[0] == '?' || (strncmp(linebuf, "HELP", 4) == 0) || (strncmp(linebuf, "help", 4) == 0)) { help(); iter = 1; continue; } if (strstr(linebuf, "BEGIN") || (strstr(linebuf, "begin"))) { plsql = 1; } strcat(dyn_statement, linebuf); if ((plsql && (cp = strrchr(dyn_statement, '/'))) || (!plsql && (cp = strrchr(dyn_statement, ';')))) { *cp = '\0'; break; } else { iter++; printf("%3d ", iter); } } return 0; } void set_bind_variables() { int i, n; char bind_var[64]; EXEC SQL WHENEVER SQLERROR DO sql_error(); bind_dp->N = MAX_ITEMS; Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 109 页 EXEC SQL DESCRIBE BIND VARIABLES FOR S INTO bind_dp; if (bind_dp->F < 0) { printf ("\nToo many bind variables (%d), maximum is %d\n.",-bind_dp->F, MAX_ITEMS); return; } bind_dp->N = bind_dp->F; for (i = 0; i < bind_dp->F; i++) { printf ("\nEnter value for bind variable %.*s: ", (int)bind_dp->C[i], bind_dp->S[i]); fgets(bind_var, sizeof bind_var, stdin); n = strlen(bind_var) - 1; bind_dp->L[i] = n; bind_dp->V[i] = (char *) realloc(bind_dp->V[i],(bind_dp->L[i] + 1)); strncpy(bind_dp->V[i], bind_var, n); /*设置指示变量的值*/ if ((strncmp(bind_dp->V[i], "NULL", 4) == 0) || (strncmp(bind_dp->V[i], "null", 4) == 0)) *bind_dp->I[i] = -1; else *bind_dp->I[i] = 0; bind_dp->T[i] = 1; } return; } void process_select_list() { int i, null_ok, precision, scale; if ((strncmp(dyn_statement, "SELECT", 6) != 0) && (strncmp(dyn_statement, "select", 6) != 0)) { select_dp->F = 0; return; Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 110 页 } select_dp->N = MAX_ITEMS; EXEC SQL DESCRIBE SELECT LIST FOR S INTO select_dp; if (select_dp->F < 0) { printf ("\nToo many select-list items (%d), maximum is %d\n",-(select_dp->F), MAX_ITEMS); return; } select_dp->N = select_dp->F; printf ("\n"); for (i = 0; i < select_dp->F; i++) { char title[MAX_VNAME_LEN]; SQLColumnNullCheck (0,(unsigned short *)&(select_dp->T[i]),(unsigned short *)&(select_dp->T[i]), &null_ok); switch (select_dp->T[i]) { case 1 : break; case 2 : /*NUMBER 类型*/ SQLNumberPrecV6(0,(unsigned long *)&(select_dp->L[i]), &precision, &scale); if (precision == 0) precision = 40; if (scale > 0) select_dp->L[i] = sizeof(float); else select_dp->L[i] = sizeof(int); break; case 8 : /* LONG 类型*/ select_dp->L[i] = 240; break; case 11 : /* ROWID 类型 */ select_dp->L[i] = 18; break; case 12 : /* DATE 类型*/ select_dp->L[i] = 9; break; case 23 : /* RAW 类型 */ break; case 24 : /* LONG RAW 类型 */ Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 111 页 select_dp->L[i] = 240; break; } if (select_dp->T[i] != 2) select_dp->V[i] = (char *) realloc(select_dp->V[i],select_dp->L[i] + 1); else select_dp->V[i] = (char *) realloc(select_dp->V[i],select_dp->L[i]); memset(title, ' ', MAX_VNAME_LEN); strncpy(title, select_dp->S[i], select_dp->C[i]); if (select_dp->T[i] == 2) if (scale > 0) printf ("%.*s ", select_dp->L[i]+3, title); else printf ("%.*s ", select_dp->L[i], title); else printf("%-.*s ", select_dp->L[i], title); if (select_dp->T[i] != 24 && select_dp->T[i] != 2) select_dp->T[i] = 1; if (select_dp->T[i] == 2) if (scale > 0) select_dp->T[i] = 4; /* float 类型 */ else select_dp->T[i] = 3; /* int类型 */ } printf ("\n\n"); EXEC SQL WHENEVER NOT FOUND GOTO end_select_loop; for (;;) { EXEC SQL FETCH C USING DESCRIPTOR select_dp; for (i = 0; i < select_dp->F; i++) { if (*select_dp->I[i] < 0) if (select_dp->T[i] == 4) printf ("%-*c ",(int)select_dp->L[i]+3, ' '); else printf ("%-*c ",(int)select_dp->L[i], ' '); else if (select_dp->T[i] == 3) /* int datatype */ printf ("%*d ", (int)select_dp->L[i],*(int *)select_dp->V[i]); else if (select_dp->T[i] == 4) /* float datatype */ printf ("%*.2f ", (int)select_dp->L[i],*(float *)select_dp->V[i]); Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 112 页 else /* character string */ printf ("%-*.*s ", (int)select_dp->L[i],(int)select_dp->L[i], select_dp->V[i]); } printf ("\n"); } end_select_loop: return; } void help() { puts("\n\nEnter a SQL statement or a PL/SQL block at the SQL> prompt."); puts("Statements can be continued over several lines, except"); puts("within string literals."); puts("Terminate a SQL statement with a semicolon."); puts("Terminate a PL/SQL block (which can contain embedded semicolons)"); puts("with a slash (/)."); puts("Typing \"exit\" (no semicolon needed) exits the program."); puts("You typed \"?\" or \"help\" to get this message.\n\n"); } void sql_error() { printf ("\n\n%.70s\n",sqlca.sqlerrm.sqlerrmc); if (parse_flag) printf("Parse error at character offset %d in SQL statement.\n",sqlca.sqlerrd[4]); EXEC SQL WHENEVER SQLERROR CONTINUE; EXEC SQL ROLLBACK WORK; longjmp(jmp_continue, 1); /*远程跳到 main 开始继续执行*/ } 8.6.4.9 METHOD 4 滚动游标的例子 相比 s10.pc,这个例子就简单多了,不再进行代码注释了。 s11.pc: #include #include #include #include #include #include Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 113 页 #define MAX_SELECT_ITEMS 200 #define MAX_CHARS 500 #define MAX_NAME_SIZE 50 SQLDA *selda; SQLDA *bind_des; jmp_buf beginEnv; jmp_buf loopEnv; char c_data[MAX_SELECT_ITEMS][MAX_CHARS]; char username[60]; char stmt[500]; void sql_error() { char msgbuf[512]; size_t msgbuf_len, msg_len; msgbuf_len = sizeof(msgbuf); sqlglm(msgbuf, &msgbuf_len, &msg_len); printf ("\n\n%.*s\n", msg_len, msgbuf); EXEC SQL WHENEVER SQLERROR CONTINUE; EXEC SQL ROLLBACK WORK RELEASE; exit(EXIT_FAILURE); } void sql_loop_error() { char msgbuf[512]; size_t msgbuf_len, msg_len; int code = sqlca.sqlcode; msgbuf_len = sizeof(msgbuf); sqlglm(msgbuf, &msgbuf_len, &msg_len); printf ("\n%.*s\n", msg_len, msgbuf); printf("The error code is %d\n", sqlca.sqlcode); if (code==-900 || code == -942 || code == -904) longjmp(beginEnv, 1); longjmp(loopEnv, 1); } void no_data_found() { printf("\nNo Data available at the specified offset\n"); longjmp(loopEnv, 1); Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 114 页 } int main(int argc, char *argv[]) { int i, n; int sli; /* select-list item */ int offset; int contFlag; char bindVar[20]; char *u, temp[3]; char choice; EXEC SQL WHENEVER SQLERROR DO sql_error(); if (argc == 1) { printf("Logging in as default user xcl\n"); strcpy(username, "xcl/xclxcl"); } else strcpy(username, argv[1]); EXEC SQL CONNECT :username; u = username; while(*++u != '/'); *u = '\0'; EXEC SQL WHENEVER SQLERROR DO sql_loop_error(); for (;;) { setjmp(beginEnv); printf("[%s] SQL > ", username); gets(stmt); if (!strlen(stmt)) continue; if (!strcmp(tolower(stmt), "exit")) break; selda = sqlald(MAX_SELECT_ITEMS, MAX_NAME_SIZE, 0); bind_des = sqlald(MAX_SELECT_ITEMS, MAX_NAME_SIZE, 30); EXEC SQL PREPARE S FROM :stmt; EXEC SQL DECLARE C SCROLL CURSOR FOR S; Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 115 页 for (i=0; iI[i] = (short *) malloc(sizeof (short)); bind_des->V[i] = (char *) malloc(1); } bind_des->N = MAX_SELECT_ITEMS; EXEC SQL DESCRIBE BIND VARIABLES FOR S INTO bind_des; if (bind_des->F < 0) { printf("Bind descriptor, value exceeds the limit\n"); exit(-1); } bind_des->N = bind_des->F; for (i=0; iF; i++) { printf("Enter the value for bind variable %.*s: ",(int)bind_des->C[i], bind_des->S[i]); fgets(bindVar, sizeof(bindVar), stdin); n = strlen(bindVar) - 1; bind_des->L[i] = n; bind_des->V[i] = (char *) realloc(bind_des->V[i], (bind_des->L[i] +1)); strncpy(bind_des->V[i], bindVar, n); if ((strncmp(bind_des->V[i], "NULL", 4) == 0) || (strncmp(bind_des->V[i], "null", 4) == 0)) *bind_des ->I[i] = -1; else *bind_des ->I[i] = 0; bind_des->T[i] = 1; } EXEC SQL OPEN C USING DESCRIPTOR bind_des; EXEC SQL DESCRIBE SELECT LIST FOR S INTO selda; if (selda->F < 0) { printf("Select descriptor, value exceeds the limit\n"); exit(-1); } selda->N = selda->F; Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 116 页 for (sli = 0; sli < selda->N; sli++) { selda->V[sli] = c_data[sli]; selda->T[sli] = 1; selda->L[sli] = MAX_CHARS; } while(1) { printf("\n\nEnter the row number to be fetched \n"); printf("1.ABSOLUTE\n"); printf("2.RELATIVE\n"); printf("3.FIRST \n"); printf("4.NEXT \n"); printf("5.PREVIOUS \n"); printf("6.LAST \n"); printf("7.CURRENT \n"); printf("Enter your choice --> "); scanf("%c",&choice); EXEC SQL WHENEVER NOT FOUND DO no_data_found(); switch(choice) { case '1': printf("\nEnter Offset :"); scanf("%d",&offset); EXEC SQL FETCH ABSOLUTE :offset C USING DESCRIPTOR selda; break; case '2': printf("\nEnter Offset :"); scanf("%d",&offset); EXEC SQL FETCH RELATIVE :offset C USING DESCRIPTOR selda; break; case '3': EXEC SQL FETCH FIRST C USING DESCRIPTOR selda; break; case '4': EXEC SQL FETCH NEXT C USING DESCRIPTOR selda; break; case '5': EXEC SQL FETCH PRIOR C USING DESCRIPTOR selda; break; case '6': EXEC SQL FETCH LAST C USING DESCRIPTOR selda; break; case '7': EXEC SQL FETCH CURRENT C USING DESCRIPTOR selda; break; default : printf("Invalid choice\n"); continue; } Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 117 页 for (sli=0; sliN; sli++) printf("%.10s ", c_data[sli]); puts(""); setjmp(loopEnv); contFlag = 'x'; while(contFlag != 'Y' && contFlag != 'N') { printf("\nContinue with the current fetch? [y/n] : "); contFlag = toupper(getchar()); } if (contFlag != 'Y') break; } EXEC SQL CLOSE C; } EXEC SQL ROLLBACK RELEASE; exit(EXIT_SUCCESS); } 9 标准动态 SQL 本章描述了标准动态 SQL(SQL92 动态 SQL 语句),主要用于增强上章 METHOD 4 的内 容。在第八章关于 METHOD 4 的实现办法不支持 Object、LOB、cursor、结构数组、DML returning 字句数据类型,但标准动态 SQL 支持 Oracle 所有数据类型。 9.1 标准动态 SQL 基础 基于下面的 SQL 语句: SELECT ename, empno FROM emp WHERE deptno = :deptno_data 使用标准动态 SQL 语句的步骤如下: ‹ 声明一个字符串变量 X,用于存放需要动态执行的 SQL 语句 ‹ 为输入变量和输出变量分配描述字 ‹ Prepare 动态执行的语句 X ‹ Describe 输入和输出描述字 ‹ 设置输入描述字的值(在我们的例子中只有一个输入宿主变量 deptno_data) ‹ 声明并打开一个动态游标 ‹ 设置输出描述字(在我们的例子中包含两个输出宿主变量 ename 和 empno) ‹ 重复的 fetch 数据,使用 GET DESCRIPTOR 接受输出变量(enmae,empno)的值 ‹ 关闭动态游标,释放输入输出描述字 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 118 页 9.1.1 预编译选项 设置预编译选项 DYNAMIC 值为 ANSI,或者设置 MODE 为 ANSI,可以使与编译器按 照标准动态 SQL 语法对源程序进行预编译,否则将按照 ORACLE 模式。 如果为了兼容其他支持标准动态 SQL 的数据库,设置 TYPE_CODE 为 ANSI。 9.2 标准动态 SQL 概述 为了使用标准动态 SQL,首先需要分配描述字,语法如下: EXEC SQL ALLOCATE DESCRIPTOR [GLOBAL | LOCAL] {:desc_nam | string_literal} [WITH MAX {:occurrences | numeric_literal}]; 一个全局(GLOBAL)描述字对所有源程序单元有效,一个本地(LOCAL)描述字只对当前 所声明的源代码单元有效。默认情况下,声明的描述字为本地描述字。 描述字名字 desc_nam 可以是一个用单引号括起来的字符串,也可以是存放在一个字符 串中的宿主变量。 occurrences 必须是一个描述最大绑定变量和选择列数目的数值,必须为一个数字,默 认值为 100。 不在用到描述字时可通过 deallocate 语句显视的释放描述字所占用的内存,也可以通过 断开数据库连接自动的进行释放。 deallocate 的语法为: EXEC SQL DEALLOCATE DESCRIPTOR [GLOBAL | LOCAL] {:desc_nam | string_literal}; 用 DESCRIBE 语句可以从已经 Prepare 的动态 SQL 语句中得到输入和输出信息。用 DESCRIBE INPUT 语句得到输入绑定变量的信息,用 DESCRIBE OUTPUT 得到需要输出列 的类型、长度、名称信息。语法如下: EXEC SQL DESCRIBE [INPUT | OUTPUT] sql_statement USING [SQL] DESCRIPTOR [GLOBAL | LOCAL] {:desc_nam | string_literal}; 如果需要执行的 SQL 语句有需要输入和输出的值,则必须定义输入、输出两个描述字, 如果没有输入变量,例如: SELECT ename,empno FROM emp; 则输入描述字不是必须的。 使用 SET DESCRIPTOR 语句对 INSERT,UPDATE,DELETE 和 WHERE 子句的输入 变量的个数、值、类型进行指定。 对个数赋值(储存到 COUNT 中): EXEC SQL SET DESCRIPTOR [GLOBAL | LOCAL] {:desc_nam | string_literal} COUNT = {:kount | numeric_literal}; 其中 kount 可以是个宿主变量也可以是个整数,例如 5。 对输入变量进行赋值: EXEC SQL SET DESCRIPTOR [GLOBAL | LOCAL] {:desc_nam | string_literal} VALUE item_number DATA = :hv3; 也可以通过使用 SET DESCRIPTOR 语句设置输入变量的类型和长度。 注意:当预编译器参数类型 TYPE_CODE=ORACLE 时,如果不设置参数类型和长度, Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 119 页 则 Oracle 根据输入变量的值自动决定采用何种类型,如果 TYPE_CODE=ANSI,则必须指定 输入变量(绑定变量)的类型和长度,其类型为标准 SQL 数据类型(参见:标准 SQL 数据 类型列表)。 EXEC SQL SET DESCRIPTOR [GLOBAL | LOCAL] {:desc_nam | string_literal} VALUE item_number TYPE = :hv1, LENGTH = :hv2, DATA = :hv3; item_number 为输入变量在 SQL 语句中的位置,hv1,hv2,hv3 描述符为宿主变量。 标准 SQL 数据类型表: 标准 SQL 数据类型 类型值 CHARACTER 1 CHARACTER VARYING 12 DATE 9 DECIMAL 3 DOUBLE PRECISION 8 FLOAT 6 INTEGER 4 NUMERIC 2 REAL 7 SMALLINT 5 DATA 时输入的值。 也可以通过 SET DESCRIPTOR 语句设置输入变量其他信息,比如指示变量、数据精度。 除了知识变量必须声明成 short int 类型外,对 NUMERIC 数据类型的变量在 C 语言中 必须声明成 int 或 short int。 在下面程序片段中描述了如何接受 empno 的值,其中因为 empno 位于动态 SQL 语句输 出列的第二列,所以 VALUE=2,宿主变量 empno_typ 设置为 3,(Oracle 数据类型 Integer), emp_len 存放 Integer 类型的 empno 的长度,设置成 4,是接受 empno 数据的宿主变量的尺 寸。DATA 为 empno_data,表示其用于存放从数据库表 emp 中得到 empno 的数值。程序片 段如下: ... char *dyn_statement = "SELECT ename, empno FROM emp WHERE deptno = :deptno_number" ; int empno_data ; int empno_typ = 3 ; int empno_len = 4 ; ... EXEC SQL SET DESCRIPTOR ’out’ VALUE 2 TYPE = :empno_typ, LENGTH = :empno_len, DATA = :empno_data ; 在设置输入变量值后,通过使用输入描述字打开需要执行的语句游标。如果有输出列, 需要在 FETCH 语句前对输出描述字进行设置。如果已经执行了 DESCRIBE OUTPUT 语句, 需要测试宿主变量的实际长度,因为 DESCRIBE 返回的 Oracle 内部数据类型同宿主变量采 用的 Oracle 扩展数据类型可能不符。 在 FETCH 语句后,通过 GET DESCRIPTOR 语句得到返回的列值。下面列出简明语法: EXEC SQL GET DESCRIPTOR [GLOBAL | LOCAL] {:desc_nam | string_literal} VALUE item_number :hv1 = DATA, :hv2 = INDICATOR, :hv3 = RETURNED_LENGTH ; Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 120 页 desc_nam 和 item_number 可以是字符描述也可以是宿主变量。例如 desc_nam 可以是字 符串 out,item_number 可以是数字 2。hv1,hv2,hv3 只能是宿主变量。除了指示变量(hv2)必 须在 C 程序定义为 short int,其他数字型变量可以定义为 long,int 或 short。 9.2.1 简单标准动态 SQL 例程 下面程序片段描述了使用标准动态 SQL 的用法,其分配了输入描述字“in”和输出描 述字“out”,通过 GET DESCRIPTOR 语句从数据空中得到数据到宿主变量。 ... char* dyn_statement = "SELECT ename, empno FROM emp WHERE deptno = :deptno_data"; int deptno_type = 3, deptno_len = 2, deptno_data = 10 ; int ename_type = 97, ename_len = 30 ; char ename_data[31] ; int empno_type = 3, empno_len = 4 ; int empno_data ; long SQLCODE = 0 ; ... main () { /*此处放置连接数据库代码和初始化变量代码 */ ... EXEC SQL ALLOCATE DESCRIPTOR 'in' ; EXEC SQL ALLOCATE DESCRIPTOR 'out' ; EXEC SQL PREPARE s FROM :dyn_statement ; EXEC SQL DESCRIBE INPUT s USING DESCRIPTOR 'in' ; EXEC SQL SET DESCRIPTOR 'in' VALUE 1 TYPE = :deptno_type, LENGTH = :deptno_len, DATA = :deptno_data ; EXEC SQL DECLARE c CURSOR FOR s ; EXEC SQL OPEN c USING DESCRIPTOR 'in' ; EXEC SQL DESCRIBE OUTPUT s USING DESCRIPTOR 'out' ; EXEC SQL SET DESCRIPTOR 'out' VALUE 1 TYPE = :ename_type, LENGTH = :ename_len, DATA = :ename_data ; EXEC SQL SET DESCRIPTOR 'out' VALUE 2 TYPE = :empno_type, LENGTH = :empno_len, DATA = :empno_data ; EXEC SQL WHENEVER NOT FOUND DO BREAK ; while (SQLCODE == 0) { EXEC SQL FETCH c INTO DESCRIPTOR 'out' ; EXEC SQL GET DESCRIPTOR 'out' VALUE 1 :ename_data = DATA ; EXEC SQL GET DESCRIPTOR 'out' VALUE 2 :empno_data = DATA ; Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 121 页 printf("\nEname = %s Empno = %s", ename_data, empno_data) ; } EXEC SQL CLOSE c ; EXEC SQL DEALLOCATE DESCRIPTOR 'in' ; EXEC SQL DEALLOCATE DESCRIPTOR 'out' ; ... } 在上例中也可以使用滚动游标,只需在 DECLARE 语句中采用 SCROLL 关键字按照滚 动游标熟悉规范书写即可。 9.3 Oracle 所作的扩展 Oracle 对动态 SQL 语句所作的扩展描述如下: ‹ 在 SET 语句中,支持宿主变量地址引用 ‹ 数组操作 用 Oracle 扩展的表中动态 SQL 语句,需要设置预编译器 MODE=ORACLE, DYNAMIC=ANSI。 9.3.1 应用宿主变量地址 标准动态 SQL 的 SET 语句对变量赋值采用拷贝宿主变量值的方式,为了提高性能, Oracle 允许直接在 SET 语句中使用变量地址,避免了内存拷贝,提高大数据量的处理性能。 通过在 DATA 关键字前使用 REF 关键字,可实现对宿主变量地址的访问,避免内存拷 贝: EXEC SQL SET DESCRIPTOR ’out’ VALUE 1 TYPE = :ename_type, LENGTH = :ename_len, REF DATA = :ename_data ; EXEC SQL SET DESCRIPTOR ’out’ VALUE 2 TYPE = :empno_type, LENGTH = :empno_len, REF DATA = :empno_data ; 通过上面的操作后,在 FETCH 语句后,不需要使用 GET DESCRIPTOR 语句得到列值, 因为其值直接存放在 ename_data 变量中。 REF 关键字,只允许应用在 DATA、INDICATOR 和 RETURNED_LENGTH 关键字前, 例如下面的程序片段: int indi, returnLen ; ... EXEC SQL SET DESCRIPTOR ’out’ VALUE 1 TYPE = :ename_type, LENGTH = :ename_len, REF DATA = :ename_data, REF INDICATOR = :indi, REF RETURNED_LENGTH = :returnLen ; 通过上面语句的设置,在 FETCH 语句执行后,returnLen 变量保存 ename 列值的长度。 ename_len 变量不保存列值长度,执行 FETCH 语句后其值不变。 REF 关键字不仅仅用于 SELECT 语句,还可以用在其他场合。REF 关键字指定的宿主 变量的值不能在 SET 语句时得到,而是在动态 SQL 语句执行期间得到。 如下所示: Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 122 页 int x = 1 ; EXEC SQL SET DESCRIPTOR ’value’ VALUE 1 DATA = :x ; EXEC SQL SET DESCRIPTOR ’reference’ VALUE 1 REF DATA = :x ; x = 2 ; EXEC SQL EXECUTE s USING DESCRIPTOR ’value’ ; /* x = 1 */ EXEC SQL EXECUTE s USING DESCRIPTOR ’reference’ ; /* x = 2 */ 9.3.2 数组操作 Oracle 扩展了标准动态 SQL,可以使用 FOR 关键字指定输入数组大小和接收数据行数。 在 ALLOCATE 语句中使用 FOR 子句,可以指定输出描述字接受行数的大小。例如: EXEC SQL FOR 100 ALLOCATE DESCRIPTOR ’out’ ; 或者: int array_size = 100 ; ... EXEC SQL FOR :array_size ALLOCATE DESCRIPTOR ’out’ ; 对上面语句分配的输出描述符,在使用 FETCH 语句时也必须采用 FOR 子句,其指定 的大小必须小于或等于 ALLOCATE 中 FOR 子句指定的大小。 EXEC SQL FOR 20 FETCH c1 USING DESCRIPTOR ’out’ ; 使用 GET 语句接收上述 FETCH 语句返回的数据时,必须采用 FETCH 中 FOR 子句指 定的同样大小的数组,在数组定义时可大于指定尺寸,但 FOR 子句中指定的大小必须等于 FETCH 语句中 FOR 子句指定的大小: int val_data[30] ; short val_indi[20] ; ... EXEC SQL FOR 20 GET DESCRIPTOR ’out’ VALUE 1 :val_data = DATA, :val_indi = INDICATOR ; 当 GET 语句接收返回数据的长度和类型时,不能使用 FOR 子句,而应该应用以下语句: int cnt, len ; ... EXEC SQL GET DESCRIPTOR ’out’ :cnt = COUNT ; EXEC SQL GET DESCRIPTOR ’out’ VALUE 1 :len = LENGTH ; 同理对 SET 语句使用上述分配的描述字时,也必须采用 FOR 子句: int ref_data[20] ; short ref_indi[20] ; ... EXEC SQL FOR 20 SET DESCRIPTOR ’out’ VALUE 1 REF DATA = :ref_data, REF INDICATOR = :ref_indi ; 对于输入描述符,在应用批量插入和删除、更新等语句时,在 ALLOCATE、OPEN、 和 EXECUTE 语句时也要用到 FOR 子句。 但 FOR 子句在 DEALLOCATE 或 PREPARE 语句中不能使用。 下面程序片段描述了通过数组模式批量插入 emp 表的例子,用到了两个存放数据的数 组 ename_arr 和 empno_arr,都包含 3 条记录,并采用 ename_ind 指示变量,置 ename_ind[1] Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 123 页 的值为-1,这就保证了插入数据库时,第二条记录的名字用 NULL 值代替“Dick”。 ... char* dyn_statement = "INSERT INTO emp (ename,empno) VALUES (:ename_arr,:empno_arr)" ; char ename_arr[3][6] = {Tom","Dick","Harry"} ; short ename_ind[3] = {0,-1,0} ; int ename_len = 6, ename_type = 97, cnt = 2 ; int empno_arr[3] = {8001, 8002, 8003} ; int empno_len = 4 ; int empno_type = 3 ; int array_size = 3 ; EXEC SQL FOR :array_size ALLOCATE DESCRIPTOR ’in’ ; EXEC SQL SET DESCRIPTOR ’in’ COUNT = :cnt ; EXEC SQL SET DESCRIPTOR ’in’ VALUE 1 TYPE = :ename_type, LENGTH = :ename_len ; EXEC SQL SET DESCRIPTOR ’in’ VALUE 2 TYPE = :empno_type, LENGTH = :empno_len ; EXEC SQL FOR :array_size SET DESCRIPTOR ’in’ VALUE 1 DATA = :ename_arr, INDICATOR = :ename_ind ; EXEC SQL FOR :array_size SET DESCRIPTOR ’in’ VALUE 2 DATA = :empno_arr ; EXEC SQL PREPARE s FROM :dyn_statement ; EXEC SQL FOR :array_size EXECUTE s USING DESCRIPTOR ’in’ ; ... 执行上述代码后,将在数据库表 emp 中插入 3 条记录,如下: EMPNO ENAME ---------- ----------- 8001 Tom 8002 8003 Harry Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 124 页 9.4 预编译器参数对标准动态 SQL 和 Oracle SQL 的影响表 9.5 标准动态 SQL 完全语法 9.5.1 ALLOCATE DESCRIPTOR 分配输入或输出描述符,指定宿主变量数组大小,指定输入变量和输出列数量。 语法: EXEC SQL [FOR [:]array_size] ALLOCATE DESCRIPTOR [GLOBAL | LOCAL] {:desc_nam | string_literal} [WITH MAX occurrences] ; 范例: EXEC SQL ALLOCATE DESCRIPTOR ’SELDES’ WITH MAX 50 ; EXEC SQL FOR :batch ALLOCATE DESCRIPTOR GLOBAL :binddes WITH MAX 25 ; 9.5.2 DEALLOCATE DESCRIPTOR 释放描述字所占用的资源。 语法: EXEC SQL DEALLOCATE DESCRIPTOR [GLOBAL | LOCAL] {:desc_nam | Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 125 页 string_literal} ; 范例: EXEC SQL DEALLOCATE DESCRIPTOR GLOBAL ’SELDES’ ; EXEC SQL DEALLOCATE DESCRIPTOR :binddes ; 9.5.3 GET DESCRIPTOR 得到 SQL 描述字的信息。 语法: EXEC SQL [FOR [:]array_size] GET DESCRIPTOR [GLOBAL | LOCAL] {:desc_nam | string_literal} { :hv0 = COUNT | VALUE item_number :hv1 = item_name1 [ {, :hvN = item_nameN}] } ; Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 126 页 范例: EXEC SQL GET DESCRIPTOR :binddes :n = COUNT ; EXEC SQL GET DESCRIPTOR ’SELDES’ VALUE 1 :t = TYPE, :l = LENGTH ; EXEC SQL FOR :batch GET DESCRIPTOR LOCAL ’SELDES’ VALUE :sel_item_no :i = INDICATOR, :v = DATA ; 9.5.4 SET DESCRIPTOR 设置描述字中宿主变量和列信息。 语法: EXEC SQL [FOR array_size] SET DESCRIPTOR [GLOBAL | LOCAL] {:desc_nam | string_literal} {COUNT = :hv0 | VALUE item_number [REF] item_name1 = :hv1 [{, [REF] item_nameN = :hvN}]} ; 其中 Descriptor Item Name 的取值含义如下: TYPE:如果没有相应的标准 SQL 类型,使用 Oracle 可接受的类型 LENGTH:数据列的最大长度 INDICATOR:指示变量的值 Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 127 页 DATA:存放数据对应的宿主变量 CHARACTER_SET_NAME:对应列的字符集设置 范例: int bindno = 2 ; short indi = -1 ; char data = "ignore" ; int batch = 1 ; EXEC SQL FOR :batch ALLOCATE DESCRIPTOR ’binddes’ ; EXEC SQL SET DESCRIPTOR GLOBAL :binddes COUNT = 3 ; EXEC SQL FOR :batch SET DESCRIPTOR :bindes VALUE :bindno INDICATOR = :indi, DATA = :data ; 9.5.5 Use of PREPARE 语法: EXEC SQL PREPARE statement_id FROM :sql_statement ; 范例: char* statement = "SELECT ENAME FROM emp WHERE deptno = :d" ; EXEC SQL PREPARE S1 FROM :statement ; 9.5.6 DESCRIBE INPUT 得到绑定变量的信息。 语法: EXEC SQL DESCRIBE INPUT statement_id USING [SQL] DESCRIPTOR [GLOBAL | LOCAL] {:desc_nam | string_literal} ; 范例: EXEC SQL DESCRIBE INPUT S1 USING SQL DESCRIPTOR GLOBAL :binddes ; EXEC SQL DESCRIBE INPUT S2 USING DESCRIPTOR ’input’ ; 9.5.7 DESCRIBE OUTPUT 得到输出变量的信息。 语法: EXEC SQL DESCRIBE [OUTPUT] statement_id USING [SQL] DESCRIPTOR [GLOBAL | LOCAL] {:desc_nam | string_literal} ; 范例: char* desname = "SELDES" ; EXEC SQL DESCRIBE S1 USING SQL DESCRIPTOR ’SELDES’ ; /* Or, */ Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 128 页 EXEC SQL DESCRIBE OUTPUT S1 USING DESCRIPTOR :desname ; 9.5.8 EXECUTE 匹配输入输出变量,执行 SQL 语句。 语法: EXEC SQL [FOR :array_size] EXECUTE statement_id [USING [SQL] DESCRIPTOR [GLOBAL | LOCAL] {:desc_nam | string_literal}] [INTO [SQL] DESCRIPTOR [GLOBAL | LOCAL] {:desc_nam | string_literal}] ; 其中 INTO 子句对应 DML 语句的 Returning 子句。 范例: EXEC SQL EXECUTE S1 USING SQL DESCRIPTOR GLOBAL :binddes ; EXEC SQL EXECUTE S2 USING DESCRIPTOR :bv1 INTO DESCRIPTOR ’SELDES’ ; 9.5.9 Use of EXECUTE IMMEDIATE 直接执行一个未 Prepare 的 SQL 语句。 语法: EXEC SQL EXECUTE IMMEDIATE {:sql_statement | string_literal} 范例: EXEC SQL EXECUTE IMMEDIATE :statement ; 9.5.10 Use of DYNAMIC DECLARE CURSOR 声明一个用于查询的游标。 语法: EXEC SQL DECLARE cursor_name CURSOR FOR statement_id; 范例: EXEC SQL DECLARE C1 CURSOR FOR S1 ; 9.5.11 OPEN Cursor 关联输入参数,打开游标。 语法: EXEC SQL [FOR :array_size] OPEN dyn_cursor [[USING [SQL] DESCRIPTOR [GLOBAL | LOCAL] {:desc_nam1 | string_literal}] [INTO [SQL] DESCRIPTOR [GLOBAL | LOCAL] {:desc_nam2 | string_literal}]] ; Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 129 页 范例: EXEC SQL OPEN C1 USING SQL DESCRIPTOR :binddes ; EXEC SQL FOR :limit OPEN C2 USING DESCRIPTOR :b1, :b2 INTO SQL DESCRIPTOR :seldes ; 9.5.12 FETCH 语法: EXEC SQL [FOR :array_size] FETCH cursor INTO [SQL] DESCRIPTOR [GLOBAL | LOCAL] {:desc_nam | string_literal} ; 范例: EXEC SQL FETCH FROM C1 INTO DESCRIPTOR ’SELDES’ ; EXEC SQL FOR :arsz FETCH C2 INTO DESCRIPTOR :desc ; 9.5.13 CLOSE a Dynamic Cursor 语法: EXEC SQL CLOSE cursor ; 范例: EXEC SQL CLOSE C1 ; 9.6 样例程序 9.6.1 标准 SQL 数据类型实现 sqlplus 命令的例子 s12.pc (MODE=ansi) #include #include #include #include #include #define MAX_OCCURENCES 40 #define MAX_VAR_LEN 255 #define MAX_NAME_LEN 31 #ifndef NULL #define NULL 0 #endif Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 130 页 /* Prototypes */ #if defined(__STDC__) void sql_error(void); int oracle_connect(void); int get_dyn_statement(void); int process_input(void); int process_output(void); void help(void); #else void sql_error(/*_ void _*/); int oracle_connect(/*_ void _*/); int get_dyn_statement(/* void _*/); int process_input(/*_ void _*/); int process_output(/*_ void _*/); void help(/*_ void _*/); #endif EXEC SQL INCLUDE sqlca; char SQLSTATE[6]; EXEC SQL BEGIN DECLARE SECTION; char dyn_statement[1024]; char SQLSTATE[6]; EXEC SQL END DECLARE SECTION; jmp_buf jmp_continue; int parse_flag = 0; int select_found; int main() { if (oracle_connect() != 0) exit(1); EXEC SQL WHENEVER SQLERROR DO sql_error(); EXEC SQL ALLOCATE DESCRIPTOR 'input_descriptor'; EXEC SQL ALLOCATE DESCRIPTOR 'output_descriptor'; for (;;) { Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 131 页 (void) setjmp(jmp_continue); if (get_dyn_statement() != 0) break; parse_flag = 1; EXEC SQL PREPARE S FROM :dyn_statement; parse_flag = 0; EXEC SQL DECLARE C CURSOR FOR S; if (process_input()) exit(1); EXEC SQL OPEN C USING DESCRIPTOR 'input_descriptor'; if (process_output()) exit(1); EXEC SQL CLOSE C; } EXEC SQL DEALLOCATE DESCRIPTOR 'input_descriptor'; EXEC SQL DEALLOCATE DESCRIPTOR 'output_descriptor'; EXEC SQL WHENEVER SQLERROR CONTINUE; EXEC SQL COMMIT WORK; puts("\nHave a good day!\n"); EXEC SQL WHENEVER SQLERROR DO sql_error(); return; } int get_dyn_statement() { char *cp, linebuf[256]; int iter, plsql; for (plsql = 0, iter = 1; ;) { if (iter == 1) { printf("\nSQL> "); dyn_statement[0] = '\0'; select_found = 0; } fgets(linebuf, sizeof linebuf, stdin); cp = strrchr(linebuf, '\n'); if (cp && cp != linebuf) Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 132 页 *cp = ' '; else if (cp == linebuf) continue; if ((strncmp(linebuf, "SELECT", 6) == 0) || (strncmp(linebuf, "select", 6) == 0)) { select_found=1;; } if ((strncmp(linebuf, "EXIT", 4) == 0) || (strncmp(linebuf, "exit", 4) == 0)) { return -1; } else if (linebuf[0] == '?' || (strncmp(linebuf, "HELP", 4) == 0) || (strncmp(linebuf, "help", 4) == 0)) { help(); iter = 1; continue; } if (strstr(linebuf, "BEGIN") || (strstr(linebuf, "begin"))) { plsql = 1; } strcat(dyn_statement, linebuf); if ((plsql && (cp = strrchr(dyn_statement, '/'))) || (!plsql && (cp = strrchr(dyn_statement, ';')))) { *cp = '\0'; break; } else { iter++; printf("%3d ", iter); } } return 0; } int process_input() { Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 133 页 int i; EXEC SQL BEGIN DECLARE SECTION; char name[31]; int input_count, input_len, occurs, ANSI_varchar_type; char input_buf[MAX_VAR_LEN]; EXEC SQL END DECLARE SECTION; EXEC SQL DESCRIBE INPUT S USING DESCRIPTOR 'input_descriptor'; EXEC SQL GET DESCRIPTOR 'input_descriptor' :input_count = COUNT; ANSI_varchar_type=12; for (i=0; i < input_count; i++) { occurs = i +1; /* occurence is 1 based */ EXEC SQL GET DESCRIPTOR 'input_descriptor' VALUE :occurs :name = NAME; printf ("\nEnter value for input variable %*.*s: ", 10,31, name); fgets(input_buf, sizeof(input_buf), stdin); input_len = strlen(input_buf) - 1; /* get rid of new line */ input_buf[input_len] = '\0'; /* null terminate */ EXEC SQL SET DESCRIPTOR 'input_descriptor' VALUE :occurs TYPE = :ANSI_varchar_type, LENGTH = :input_len, DATA = :input_buf; } return(sqlca.sqlcode); } int process_output() { int i, j; EXEC SQL BEGIN DECLARE SECTION; int output_count, occurs, type, len, col_len; short indi; char data[MAX_VAR_LEN], name[MAX_NAME_LEN]; EXEC SQL END DECLARE SECTION; if (!select_found) return(0); EXEC SQL DESCRIBE OUTPUT S USING DESCRIPTOR 'output_descriptor'; EXEC SQL GET DESCRIPTOR 'output_descriptor' :output_count = COUNT; printf ("\n"); type = 12; /* ANSI VARYING character type */ len = MAX_VAR_LEN; /* use the max allocated length */ Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 134 页 for (i = 0; i < output_count; i++) { occurs = i + 1; EXEC SQL GET DESCRIPTOR 'output_descriptor' VALUE :occurs :name = NAME; printf("%-*.*s ", 9,9, name); EXEC SQL SET DESCRIPTOR 'output_descriptor' VALUE :occurs TYPE = :type, LENGTH = :len; } printf("\n"); /* FETCH each row selected and print the column values. */ EXEC SQL WHENEVER NOT FOUND GOTO end_select_loop; for (;;) { EXEC SQL FETCH C INTO DESCRIPTOR 'output_descriptor'; for (i=0; i < output_count; i++) { occurs = i + 1; EXEC SQL GET DESCRIPTOR 'output_descriptor' VALUE :occurs :data = DATA, :indi = INDICATOR; if (indi == -1) printf("%-*.*s ", 9,9, "NULL"); else printf("%-*.*s ", 9,9, data); /* simplified output formatting */ } printf ("\n"); } end_select_loop: return(0); } void help() { puts("\n\nEnter a SQL statement or a PL/SQL block at the SQL> prompt."); puts("Statements can be continued over several lines, except"); puts("within string literals."); puts("Terminate a SQL statement with a semicolon."); puts("Terminate a PL/SQL block (which can contain embedded semicolons)"); puts("with a slash (/)."); puts("Typing \"exit\" (no semicolon needed) exits the program."); puts("You typed \"?\" or \"help\" to get this message.\n\n"); } Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 135 页 void sql_error() { /* ORACLE error handler */ printf("\n\nANSI sqlstate: %s: ", SQLSTATE); printf ("\n\n%.70s\n",sqlca.sqlerrm.sqlerrmc); if (parse_flag) printf("Parse error at character offset %d in SQL statement.\n",sqlca.sqlerrd[4]); EXEC SQL WHENEVER SQLERROR CONTINUE; EXEC SQL ROLLBACK WORK; longjmp(jmp_continue, 1); } int oracle_connect() { EXEC SQL BEGIN DECLARE SECTION; VARCHAR username[128]; VARCHAR password[32]; EXEC SQL END DECLARE SECTION; printf("\nusername: "); fgets((char *) username.arr, sizeof username.arr, stdin); username.arr[strlen((char *) username.arr)-1] = '\0'; username.len = (unsigned short)strlen((char *) username.arr); printf("password: "); fgets((char *) password.arr, sizeof password.arr, stdin); password.arr[strlen((char *) password.arr) - 1] = '\0'; password.len = (unsigned short)strlen((char *) password.arr); EXEC SQL WHENEVER SQLERROR GOTO connect_error; EXEC SQL CONNECT :username IDENTIFIED BY :password; printf("\nConnected to ORACLE as user %s.\n", username.arr); return 0; connect_error: fprintf(stderr, "Cannot connect to ORACLE as user %s\n", username.arr); return -1; } 9.6.2 Oracle 外部数据类型数组模式实现 sqlplus 命令的例子 s13.pc(dynamic = ansi) #include #include Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 136 页 #include #include #include #define MAX_OCCURENCES 40 #define MAX_ARRSZ 100 #define MAX_VAR_LEN 255 #define MAX_NAME_LEN 31 #ifndef NULL #define NULL 0 #endif /* Prototypes */ #if defined(__STDC__) void sql_error(void); int oracle_connect(void); int get_dyn_statement(void); int process_input(void); int process_output(void); void rows_processed(void); void help(void); #else void sql_error(/*_ void _*/); int oracle_connect(/*_ void _*/); int get_dyn_statement(/* void _*/); int process_input(/*_ void _*/); int process_output(/*_ void _*/); void rows_processed(/*_ void _*/); void help(/*_ void _*/); #endif EXEC SQL INCLUDE sqlca; char dyn_statement[1024]; /* statement variable EXEC SQL VAR dyn_statement IS STRING(1024); */ char indesc[]="input_descriptor"; char outdesc[]="output_descriptor"; char input[MAX_OCCURENCES][MAX_ARRSZ][MAX_VAR_LEN +1 ],output[MAX_OCCURENCES][MAX_ARRSZ][MAX_VAR_LEN + 1]; short outindi[MAX_OCCURENCES][MAX_ARRSZ]; short *iptr; int in_array_size; int out_array_size; Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 137 页 int max_array_size=MAX_ARRSZ; char *dml_commands[] = {"SELECT", "select", "INSERT", "insert", "UPDATE", "update", "DELETE", "delete"}; int select_found, cursor_open = 0; /* Define a buffer to hold longjmp state info. */ jmp_buf jmp_continue; /* A global flag for the error routine. */ int parse_flag = 0; int main() { /* Connect to the database. */ if (oracle_connect() != 0) exit(1); EXEC SQL WHENEVER SQLERROR DO sql_error(); /* Allocate the input and output descriptors. */ EXEC SQL FOR :max_array_size ALLOCATE DESCRIPTOR GLOBAL :indesc; EXEC SQL FOR :max_array_size ALLOCATE DESCRIPTOR GLOBAL :outdesc; /* Process SQL statements. */ for (;;) { (void) setjmp(jmp_continue); /* Get the statement. Break on "exit". */ if (get_dyn_statement() != 0) break; /* Prepare the statement and declare a cursor. */ parse_flag = 1; /* Set a flag for sql_error(). */ EXEC SQL PREPARE S FROM :dyn_statement; parse_flag = 0; /* Unset the flag. */ EXEC SQL DECLARE C CURSOR FOR S; /* Call the function that processes the input. */ if (process_input()) exit(1); /* Open the cursor and execute the statement. */ EXEC SQL FOR :in_array_size OPEN C USING DESCRIPTOR GLOBAL :indesc; cursor_open = 1; /* Call the function that processes the output. */ Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 138 页 if (process_output()) exit(1); /* Tell user how many rows were processed. */ rows_processed(); } /* end of for(;;) statement-processing loop */ /* Close the cursor. */ if (cursor_open) EXEC SQL CLOSE C; /* Deallocate the descriptors */ EXEC SQL DEALLOCATE DESCRIPTOR GLOBAL :indesc; EXEC SQL DEALLOCATE DESCRIPTOR GLOBAL :outdesc; EXEC SQL WHENEVER SQLERROR CONTINUE; EXEC SQL COMMIT WORK RELEASE; puts("\nHave a good day!\n"); EXEC SQL WHENEVER SQLERROR DO sql_error(); return; } int get_dyn_statement() { char *cp, linebuf[256]; int iter, plsql; for (plsql = 0, iter = 1; ;) { if (iter == 1) { printf("\nSQL> "); dyn_statement[0] = '\0'; select_found = 0; } fgets(linebuf, sizeof linebuf, stdin); cp = strrchr(linebuf, '\n'); if (cp && cp != linebuf) *cp = ' '; else if (cp == linebuf) continue; if ((strncmp(linebuf, "SELECT", 6) == 0) || (strncmp(linebuf, "select", 6) == 0)) { select_found=1;; } if ((strncmp(linebuf, "EXIT", 4) == 0) || (strncmp(linebuf, "exit", 4) == 0)) { return -1; Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 139 页 } else if (linebuf[0] == '?' || (strncmp(linebuf, "HELP", 4) == 0) || (strncmp(linebuf, "help", 4) == 0)) { help(); iter = 1; continue; } if (strstr(linebuf, "BEGIN") || (strstr(linebuf, "begin"))) { plsql = 1; } strcat(dyn_statement, linebuf); if ((plsql && (cp = strrchr(dyn_statement, '/'))) || (!plsql && (cp = strrchr(dyn_statement, ';')))) { *cp = '\0'; break; } else { iter++; printf("%3d ", iter); } } return 0; } int process_input() { int i, j; char name[31]; int input_count, input_len= MAX_VAR_LEN; int occurs, string_type = 5; int string_len; char arr_size[3]; EXEC SQL DESCRIBE INPUT S USING DESCRIPTOR GLOBAL :indesc; EXEC SQL GET DESCRIPTOR GLOBAL :indesc :input_count = COUNT; if (input_count > 0 && !select_found ) { /* get input array size */ printf ("\nEnter value for input array size (max is %d) : ", max_array_size); fgets(arr_size, 4, stdin); Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 140 页 in_array_size = atoi(arr_size); } else { in_array_size = 1; } for (i=0; i < input_count; i++) { occurs = i +1; /* occurence is 1 based */ EXEC SQL GET DESCRIPTOR GLOBAL :indesc VALUE :occurs :name = NAME; for (j=0; j < in_array_size; j++) { if (in_array_size == 1) printf ("\nEnter value for input variable %*.*s: ",10,31, name); else printf ("\nEnter %d%s value for input variable %*.*s: ", j +1, ((j==0) ? "st" : (j==1) ? "nd" : (j==2) ? "rd" :"th"), 10,31, name); fgets(input[i][j], sizeof(input[i][j]), stdin); string_len = strlen(input[i][j]); input[i][j][string_len - 1 ] = '\0'; /* change \n to \0 */ } EXEC SQL SET DESCRIPTOR GLOBAL :indesc VALUE :occurs TYPE = :string_type, LENGTH = :input_len; EXEC SQL FOR :in_array_size SET DESCRIPTOR GLOBAL :indesc VALUE :occurs REF DATA = :input[i]; } return(sqlca.sqlcode); } int process_output() { int i, j; int output_count, occurs; int type, output_len= MAX_VAR_LEN; char name[MAX_OCCURENCES][MAX_NAME_LEN]; int rows_this_fetch=0, cumulative_rows=0; char arr_size[3]; if (!select_found) return(0); EXEC SQL DESCRIBE OUTPUT S USING DESCRIPTOR GLOBAL :outdesc; Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 141 页 EXEC SQL GET DESCRIPTOR GLOBAL :outdesc :output_count = COUNT; if (output_count > 0 ) { printf ("\nEnter value for output array size (max is %d) : ", max_array_size); fgets(arr_size, 4, stdin); out_array_size = atoi(arr_size); } if (out_array_size < 1) /* must have at least one */ out_array_size = 1; printf ("\n"); for (i = 0; i < output_count; i++) { occurs = i + 1; EXEC SQL GET DESCRIPTOR GLOBAL :outdesc VALUE :occurs :type = TYPE, :name[i] = NAME; occurs = i + 1; /* occurence is one based */ type = 5; /* force all data to be null terminated character */ EXEC SQL SET DESCRIPTOR GLOBAL :outdesc VALUE :occurs TYPE = :type, LENGTH = :output_len; iptr = (short *)&outindi[i]; /* no mult-dimension non-char host vars */ EXEC SQL FOR :out_array_size SET DESCRIPTOR GLOBAL :outdesc VALUE :occurs REF DATA = :output[i], REF INDICATOR = :iptr; } EXEC SQL WHENEVER NOT FOUND GOTO end_select_loop; /* print the column headings */ for (j=0; j < out_array_size; j++) for (i=0; i < output_count; i++) printf("%-*.*s ", 9,9, name[i]); printf("\n"); /* FETCH each row selected and print the column values. */ for (;;) { EXEC SQL FOR :out_array_size FETCH C INTO DESCRIPTOR GLOBAL :outdesc; rows_this_fetch = sqlca.sqlerrd[2] - cumulative_rows; cumulative_rows = sqlca.sqlerrd[2]; if (rows_this_fetch) for (j=0; j < out_array_size && j < rows_this_fetch; j++) { /* output by columns using simplified formatting */ for (i=0; i < output_count; i++) { if (outindi[i][j] == -1) Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 142 页 printf("%-*.*s ", 9, 9, "NULL"); else printf("%-*.*s ", 9, 9, output[i][j]); /* simplified */ /* output formatting may cause truncation */ /* but columns will line up */ } } printf ("\n"); } end_select_loop: /* print any unprinted rows */ rows_this_fetch = sqlca.sqlerrd[2] - cumulative_rows; cumulative_rows = sqlca.sqlerrd[2]; if (rows_this_fetch) for (j=0; j < out_array_size && j < rows_this_fetch; j++) { /* output by columns using simplified formatting */ for (i=0; i < output_count; i++) { if (outindi[i][j] == -1) printf("%-*.*s ",9, 9, "NULL"); else printf("%-*.*s ", 9, 9, output[i][j]); } } return(0); } void rows_processed() { int i; for (i = 0; i < 8; i++) { if (strncmp(dyn_statement, dml_commands[i], 6) == 0) { printf("\n\n%d row%c processed.\n", sqlca.sqlerrd[2], sqlca.sqlerrd[2] == 1 ? ' ' : 's'); break; } } return; } void help() Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 143 页 { puts("\n\nEnter a SQL statement or a PL/SQL block at the SQL> prompt."); puts("Statements can be continued over several lines, except"); puts("within string literals."); puts("Terminate a SQL statement with a semicolon."); puts("Terminate a PL/SQL block (which can contain embedded semicolons)"); puts("with a slash (/)."); puts("Typing \"exit\" (no semicolon needed) exits the program."); puts("You typed \"?\" or \"help\" to get this message.\n\n"); } void sql_error() { /* ORACLE error handler */ printf ("\n\n%.70s\n",sqlca.sqlerrm.sqlerrmc); if (parse_flag) printf ("Parse error at character offset %d in SQL statement.\n", sqlca.sqlerrd[4]); EXEC SQL WHENEVER SQLERROR CONTINUE; EXEC SQL ROLLBACK WORK; longjmp(jmp_continue, 1); } int oracle_connect() { EXEC SQL BEGIN DECLARE SECTION; VARCHAR username[128]; VARCHAR password[32]; EXEC SQL END DECLARE SECTION; printf("\nusername: "); fgets((char *) username.arr, sizeof username.arr, stdin); username.arr[strlen((char *) username.arr)-1] = '\0'; username.len = (unsigned short)strlen((char *) username.arr); printf("password: "); fgets((char *) password.arr, sizeof password.arr, stdin); password.arr[strlen((char *) password.arr) - 1] = '\0'; password.len = (unsigned short)strlen((char *) password.arr); EXEC SQL WHENEVER SQLERROR GOTO connect_error; EXEC SQL CONNECT :username IDENTIFIED BY :password; printf("\nConnected to ORACLE as user %s.\n", username.arr); return 0; connect_error: fprintf(stderr, "Cannot connect to ORACLE as user %s\n", username.arr); return -1; Oracle 系列参考教程 Pro*C 程序开发 Create/Modify Email:xingchengli@gmail.com Date:2006/10/23 第 144 页 } 10 附录 10.1 参考文档 1. Oracle 《Pro*c/C++ Programmer’s Guide》
还剩149页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享pdf获得金币 ] 1 人已下载

下载pdf

pdf贡献者

DanielWang

贡献于2013-07-18

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf