Pro*C 編程的精髓在於嵌入式 SQL 與 C 宿主語言的無縫融合。本文聚焦數組批量操作、動態 SQL 方法 2/3/4、存儲過程調用、編譯構建流程四大進階主題,提供可直接落地的代碼模式。
#include#include // SQL 通信區 EXEC SQL INCLUDE SQLCA; // 預編譯器展開 sqlca 結構 // 全局預編譯選項(也可放 Makefile) EXEC ORACLE OPTION (RELEASE_CURSOR=YES); EXEC ORACLE OPTION (CHAR_MAP=STRING); // 關鍵:char[n] 當作字符串處理 void main() { EXEC SQL BEGIN DECLARE SECTION; char uid[30] = "scott/tiger@orcl"; char ename[21]; // VARCHAR2(20) + \0 short ename_ind; EXEC SQL END DECLARE SECTION; EXEC SQL CONNECT :uid; if (sqlca.sqlcode != 0) { /* 錯誤處理 */ } EXEC SQL SELECT ename INTO :ename:ename_ind FROM emp WHERE empno = 7369; if (sqlca.sqlcode == 1403) { /* 無數據 */ } else if (sqlca.sqlcode < 0) { /* 錯誤 */ } else if (ename_ind == -1) { ename[0] = '\0'; } // NULL 值處理 printf("ENAME = %s\n", ename); EXEC SQL COMMIT WORK RELEASE; }
#define BATCH 500
struct { long empno; char ename[21]; double sal; short empno_i, ename_i, sal_i; } emp_arr[BATCH];
EXEC SQL DECLARE cur CURSOR FOR SELECT empno, ename, sal FROM emp;
EXEC SQL OPEN cur;
for (;;) {
EXEC SQL FOR :BATCH FETCH cur INTO :emp_arr;
int cnt = sqlca.sqlerrd[2]; // 累積行數,需自行計算增量
if (cnt == 0) break;
// 處理 emp_arr[0..cnt-1]
}
EXEC SQL CLOSE cur;
long ids[1000];
char names[1000][21];
for (int i=0; i<1000; i++) { ids[i]=i; sprintf(names[i], "EMP_%d", i); }
EXEC SQL FOR :1000 INSERT INTO temp_emp (id, name) VALUES (:ids, :names);
long del_ids[100] = {7369, 7499, ...};
EXEC SQL FOR :100 DELETE FROM emp WHERE empno = :del_ids;
char *sql = "INSERT INTO log (msg) VALUES (:v)"; EXEC SQL PREPARE stmt FROM :sql; EXEC SQL FOR :100 EXECUTE stmt USING :messages;
struct Emp { long empno; char ename[21]; double sal; } emps[200];
Emp *p = emps;
EXEC SQL FOR :200 SELECT empno, ename, sal INTO :p FROM emp WHERE deptno = 10;
p 指向數組首地址| 方法 | 適用場景 | 核心語句 | 複雜度 |
|---|---|---|---|
| 方法 1 | 無輸入/輸出宿主變量的非查詢 | EXEC SQL EXECUTE IMMEDIATE :sql; | 最低 |
| 方法 2 | 已知輸入宿主變量數/類型的非查詢 | PREPARE -> EXECUTE USING :vars | 低 |
| 方法 3 | 已知輸出宿主變量數/類型的查詢 | PREPARE -> DECLARE CURSOR -> OPEN USING -> FETCH INTO | 中 |
| 方法 4 | 輸入/輸出數量類型運行時才知 | DESCRIBE -> SQLDA -> 動態綁定 | 最高 |
char *sql = "UPDATE emp SET sal = sal * :ratio WHERE deptno = :dno";
float ratio = 1.1; int dno = 20;
EXEC SQL PREPARE stmt FROM :sql;
EXEC SQL EXECUTE stmt USING :ratio, :dno;
printf("Updated %ld rows\n", sqlca.sqlerrd[2]);
EXEC SQL EXECUTE
BEGIN
pkg_emp.update_salary(
:empno, -- IN
:new_sal, -- IN
:old_sal, -- OUT
:status, -- OUT
:err_msg -- OUT
);
END;
END-EXEC;
proc iname=main.pc userid=scott/tiger \
sqlcheck=SEMANTICS parse=FULL char_map=STRING \
release_cursor=YES hold_cursor=NO maxopencursors=100
main.c (或 .cpp)CODE=CPP 選項生成 C++ 兼容代碼gcc -c -I$ORACLE_HOME/precomp/public -I$ORACLE_HOME/rdbms/public main.c
gcc -o myapp main.o -L$ORACLE_HOME/lib -lclntsh
ORACLE_HOME = /opt/oracle/product/19c PROC = $(ORACLE_HOME)/bin/proc PROCFLAGS = userid=app/app parse=FULL sqlcheck=SEMANTICS char_map=STRING release_cursor=YES CC = gcc CFLAGS = -I$(ORACLE_HOME)/precomp/public -I$(ORACLE_HOME)/rdbms/public LIBS = -L$(ORACLE_HOME)/lib -lclntsh -lm -lpthread .SUFFIXES: .pc .c .o .pc.c: $(PROC) $(PROCFLAGS) iname=$< .c.o: $(CC) $(CFLAGS) -c $< app: main.o db.o utils.o $(CC) -o $@ $^ $(LIBS)
| 症狀 | 原因 | 對策 |
|---|---|---|
| ORA-01000: maximum open cursors exceeded | 游標未 CLOSE/RELEASE | RELEASE_CURSOR=YES + 显式 CLOSE |
| 字符串截斷/亂碼 | CHAR_MAP 預設 CHARZ 填充空格 | CHAR_MAP=STRING 或 VARCHAR 等價聲明 |
| NULL 值導致 sqlcode < 0 | 未使用指示變量 | INTO :var:ind (無逗號) |
| PREPARE 重複報錯 | MODE=ANSI 要求重新 PREPARE 前 CLOSE | 嚴格遵循 OPEN→FETCH→CLOSE→PREPARE 循環 |
| 多線程 sqlca 衝突 | 共用 sql_context | ENABLE THREADS + 每線程 CONTEXT ALLOCATE |
Pro*C 進階核心是數組批量化(減少網絡往返)、動態 SQL 方法 2(靈活構建語句)、存儲過程封裝業務邏輯、標準化構建流程。掌握這四大支柱,配合 CHAR_MAP=STRING、RELEASE_CURSOR=YES 兩大鐵律,即可駕馭百萬行級 Pro*C 核心系統。新架構請考慮 OCCI/ODPI-C/微服務化演進路徑。