C语言开发过程中,需要对数据库进行操作,使用 Oracle 数据库一般会采用Oracle Pro*C/C++ 预编译器。
本文章参考Oracle官方文档和自我实践,实验环境为 RHEL7 和 Oracle19c,详细介绍 Oracle Pro*C/C++ 预编译器,了解它在开发操作 Oracle 数据的应用程序中的作用,并了解它使您的应用程序能够做什么,具体如使用。
Oracle数据类型
Oracle 内部数据类型指定 Oracle 如何在数据库表中存储列值,以及用于表示伪列值(如 NULL、SYSDATE、USER 等)的格式。
对于存储在数据库列中的值,Oracle 使用如图所示的内部数据类型:
| Name | Description |
|---|---|
| VARCHAR2 | 变长字符串,<= 4000 字节 |
| NVARCHAR2 or NCHAR VARYING | 可变长度的单字节或国家字符串,<= 4000 字节 |
| NUMBER | 具有精度和小数位数的数值,以 base-100 格式表示 |
| LONG | 变长字符串 <=2**31-1 字节 |
| BINARY_FLOAT | 32 位浮点数,4 字节 |
| BINARY_DOUBLE | 64 位浮点数,8 字节 |
| TIMESTAMP | 日期的年、月和日值,以及时间的时、分和秒值,7 或 11 个字节 |
| DATE | 定长日期+时间值,7 个字节 |
| INTERVAL YEAR | 以年和月为单位存储一段时间,5 个字节 |
| INTERVAL DAY | 以天、小时、分钟和秒为单位存储一段时间,共 11 个字节 |
| RAW | 可变长度的二进制数据,<=2000 字节 |
| LONG RAW | 变长二进制数据,<=2**31-1 字节] |
| ROWID | 二进制值 |
| UROWID | 二进制值,<=4000 字节 |
| CHAR | 定长字符串,<=2000 字节 |
| NCHAR | 定长单字节或国家字符串,<= 2000 字节 |
| CLOB | 字符数据,<= 4 GB |
| NCLOB | 国家字符集数据,<= 4 GB |
| BLOB | 二进制数据,<= 4 GB |
| BFILE | 外部文件二进制数据,<= 4 GB |
这些内部数据类型可能与 C 数据类型完全不同。例如,C 没有与 Oracle NUMBER 数据类型等效的数据类型。但是,NUMBER 可以在 C 数据类型(例如float和double )之间转换,但有一些限制。例如,Oracle NUMBER 数据类型允许多达 38 位十进制数字的精度,而当前的 C 实现无法表示具有这种精度的double。
Oracle NUMBER 数据类型精确地表示值(在精度限制内),而浮点格式不能精确地表示诸如 10.0 之类的值。
使用 LOB 数据类型存储非结构化数据(文本、图形图像、视频剪辑或声音波形)。BFILE 数据存储在数据库外的操作系统文件中。LOB 类型存储指定数据位置的定位符。
NCHAR 和 NVARCHAR2 用于存储多字节字符数据。
C语言数据类型
可以根据 C 编程语言的规则声明宿主变量,指定 Oracle 程序接口支持的 C 数据类型。
C 数据类型必须与源或目标数据库列的数据类型兼容。
如图显示了在声明宿主变量时可以使用的 C 数据类型和伪类型。只有这些数据类型可用于宿主变量, 主机变量的 C 数据类型:
| C 数据类型或伪类型 | 描述 |
|---|---|
| char | 单个字符 |
| char[n] | n 字符数组(字符串) |
| int | 整数 |
| short | 小整数 |
| long | 大整数 |
| long long | 非常大的(8 字节)整数 |
| float | 浮点数(通常是单精度) |
| double | 浮点数(总是双精度) |
| VARCHAR[n] | 变长字符串 |
宿主变量
宿主变量是宿主程序和 Oracle 之间通信的关键。通常,预编译器程序将数据从宿主变量输入到 Oracle,然后 Oracle 将数据输出到程序中的宿主变量。Oracle 将输入数据存储在数据库列中,并将输出数据存储在程序主变量中。
宿主变量可以是任何解析为标量类型的任意 C 表达式。但是,宿主变量也必须是左值。还支持大多数主机变量的主机数组。
如图显示了兼容的 Oracle 内部数据类型:
| Internal Type | C Type | Description |
|---|---|---|
| VARCHAR2(Y) (Note 1) | char | 单个字符 |
| CHAR(X) (Note 1) | char[n] VARCHAR[n] int short long long long float double | n 字节字符数组 n 字节变长字符数组 整数 小整数 大整数 非常大的(8 字节)整数 浮点数 双精度浮点数 数字 |
| NUMBER | int | 整数 |
| NUMBER(P,S) (Note 2) | short int long float double char char[n] VARCHAR[n] | 小整数 整数 大整数 浮点数 双精度浮点数 数字 单个字符 n 字节字符数组 n 字节变长字符数组 |
| DATE | char[n] VARCHAR[n] | n 字节字符数组 n 字节变长字符数组 |
| LONG | char[n] VARCHAR[n] | n 字节字符数组 n 字节变长字符数组 |
| RAW(X) (Note 1) | unsigned char[n] VARCHAR[n] | n 字节字符数组 n 字节变长字符数组 |
| LONG RAW | unsigned char[n] VARCHAR[n] | n 字节字符数组 n 字节变长字符数组 |
| ROWID | unsigned char[n] VARCHAR[n] | n 字节字符数组 n 字节变长字符数组 |
| Notes: 1. X 的范围是 1 到 2000。1 是默认值。Y 的范围是 1 到 4000。 2. P 范围从 1 到 38。S 范围从 -84 到 127。 |
简单 C 类型的一维数组也可以用作宿主变量。对于 char[n] 和 VARCHAR[n],n指定最大字符串长度,而不是数组中的字符串数。二维数组只允许用于 char[m][n] 和 VARCHAR[m][n],其中m指定数组中的字符串数,n指定最大字符串长度。
支持指向简单 C 类型的指针。指向 char[n] 和 VARCHAR[n] 变量的指针应该声明为指向 char 或 VARCHAR 的指针(没有长度说明)。但是,不支持指针数组。
宿主结构
可以使用 C 结构来包含主变量。您在 SELECT 或 FETCH 语句的 INTO 子句以及 INSERT 语句的 VALUES 列表中引用包含主变量的结构。宿主结构的每个组件都必须是合法的 Pro*C/C++ 宿主变量,如主机变量的 C 数据类型所定义。
当结构体用作宿主变量时,SQL 语句中仅使用结构体的名称。但是,该结构的每个成员都会向 Oracle 发送数据,或者在查询时从 Oracle 接收数据。以下示例显示了用于将员工添加到 EMP 表的主机结构:
typedef struct
{
char emp_name[11]; /* one greater than column length */
int emp_number;
int dept_number;
float salary;
} emp_record;
...
/* define a new structure of type "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 语句中出现的顺序相匹配,如果省略了 INSERT 语句中的列列表,则在数据库表中出现的顺序必须匹配。
宿主结构和数组
一个阵列是相关的数据项,被称为的集合的元素,具有单个变量名相关联。当声明为宿主变量时,该数组称为宿主数组。同样,声明为数组的指标变量称为指标数组。指标数组可以与任何主机数组相关联。
主机数组可以让使用单个 SQL 语句操作整个数据项集合,从而提高性能。除了少数例外,可以在任何允许标量宿主变量的地方使用宿主数组。此外,可以将指标数组与任何主机数组相关联。
可以使用主机数组作为主机结构的组件。在以下示例中,使用包含数组的结构向 EMP 表中插入三个新条目:
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;
emp_rec.emp_number[2] = 1985; emp_rec.dept_number[2] = 5;
EXEC SQL INSERT INTO emp (ename, empno, deptno)
VALUES (:emp_rec);
...