Proc *C/C++入門之動態SQL
基本概念
在有些情況下, 在編碼時 SQL 語句還不能完整地寫出來, 而是在程式執行時才能
構造出來,這種在程式執行臨時生成的 SQL 語句叫動態 SQL 語句. 利用動態 SQL 來
編寫 Pro*C 程式的方法叫動態 SQL 技術!
- 目的:加強應用程式的功能和靈活
- 靜態SQL —- 在編寫應用程式時,使用EXEC SQL關鍵字直接嵌入的SQL語句;在proc編譯應用程式生成c語言的時,都已經確定
- 動態SQL —- 在執行應用程式時,由使用者動態輸入的SQL語句。
在下列情況之一不知道時, 使用動態 SQL 技術:
- SQL 語句的文字.
- 宿主變數的個數。
- 宿主變數的資料型別
- 引用的資料庫物件, 如列, 索引, 序列, 表, 使用者名稱和檢視.
Oracle 中動態 SQL 可用以下兩種方法實現:一個是 Oracle 自己的方法, 一個是 ANSI 的方法. 一般建議使用 Oracle 的方法,
但對於較複雜的應用, 可以使用 ANSI 的方法, 因為這樣可以使自己的程式簡化。
動態SQL1
- 語法:
EXEC SQL EXECUTE IMMEDIATE :host_string
host_string 字串
- 限制
- 不能執行select語言,僅適用於非select語句
- 在sqlplus上執行的命令,都可以拿過來來執行
- 語句中不包含輸入宿主變數–一個宿主指標變數指向一塊記憶體空間(存有使用者輸入的SQL語句)
- 常用於僅執行一次的動態語句
- 對重複執行多次的動態SQL語句,降低執行效率
nt main01()
{
int ret = 0;
int i = 0;
char choosechar;
memset(mySql, 0, sizeof(mySql));
pSql = NULL;
EXEC SQL WHENEVER SQLERROR DO sqlerr02();
connet();
pSql = mySql;
//迴圈處理sql語言
for(;;)
{
printf ("\nplease enter sql(not select ): ");
gets(mySql);
//scanf("%s", mySql); --空格截斷
printf("mysql:%s\n", mySql);
printf("任意鍵繼續....\n");
getchar();
EXEC SQL EXECUTE IMMEDIATE :pSql;
EXEC SQL COMMIT;
printf("繼續執行嗎?\n");
scanf("%c", &choosechar);
fflush(stdin);
if (choosechar=='n' || choosechar=='N')
{
break;
}
}
EXEC SQL COMMIT WORK RELEASE;
printf("return ok...\n");
return ret ;
}
動態SQL2
- 使用PREPARE命令準備SQL語句
EXEC SQL PREPARE statement_name FROM :host_string;
statement_name: 識別符號,
host_string:含SQL語句的字串
使用EXECUTE命令執行SQL語句
EXEC SQL EXECUTE statement_name [USING :host_variable]
如果SQL語句要通過宿主變數賦值,輸入SQL語句時要用佔位符
- 僅適用於非select語句
- 可包含虛擬輸入宿主變數和指示器變數,其個數和型別在預編譯時已知
int main02()
{
int ret = 0;
int i = 0;
char choosechar;
memset(mySql, 0, sizeof(mySql));
pSql = NULL;
EXEC SQL WHENEVER SQLERROR DO sqlerr02();
connet();
pSql = mySql;
//迴圈處理sql語言
for(;;)
{
printf("\n請輸入要更新部門編號 ");
scanf("%d", &deptno);
printf("\n請輸入要新loc值 ");
scanf("%s", loc);
//準備動態sql
EXEC SQL PREPARE my_pre_sql FROM 'update dept set loc = :a where deptno = :b';
//執行動態sql
EXEC SQL EXECUTE my_pre_sql USING :loc, :deptno;
EXEC SQL COMMIT;
printf("\n 按任意鍵繼續? ");
getchar();
printf("\n退出鍵入n, 其他繼續? ");
scanf("%c", &choosechar);
fflush(stdin);
if (choosechar=='n' || choosechar=='N')
{
break;
}
}
EXEC SQL COMMIT WORK RELEASE;
printf("return ok...\n");
return ret ;
}
動態SQL3
- 使用PREPARE命令準備SQL語句
EXEC SQL PREPARE statement_name FROM :host_string;
statement_name: 識別符號,
host_string:含SQL語句的字串
- 使用DECLARE命令定義遊標,可以結合遊標一塊使用
EXEC SQL DECLARE cursor_name CURSOR FOR statement_name;
EXEC SQL OPEN cursor_name [using host_variable_list]
EXEC SQL FETCH cursor_name INTO host_variable_list
EXEC SQL CLOSE cursor_name
- 查詢部門號大於10的所有部門資訊
- 動態sql3 處理選擇列表項(select查詢出來的結果列數固定) 和 輸入宿主變數個數一定
- 本質:
- 輸入宿主變數個數固定 查詢條件固定
- 輸出宿主變數個數固定 返回結果固定
//可以結合遊標一塊使用
int main()
{
int ret = 0;
int i = 0;
char choosechar;
memset(mySql, 0, sizeof(mySql));
pSql = NULL;
EXEC SQL WHENEVER SQLERROR DO sqlerr02();
connet();
//EXEC SQL WHENEVER NOT FOUND DO nodata();
//迴圈處理sql語言
for(;;)
{
printf("\n請輸入部門編號 ");
scanf("%d", &deptno);
//準備動態sql
EXEC SQL PREPARE my_pre_sql3 FROM 'select deptno, dname, loc from dept where deptno > :a';
//定義遊標
EXEC SQL DECLARE c1 CURSOR FOR my_pre_sql3;
//開啟遊標
EXEC SQL OPEN c1 USING :deptno;
//提取資料
EXEC SQL WHENEVER NOT FOUND DO break;
for (;;)
{
EXEC SQL FETCH c1 INTO :deptno, :dname,:loc:loc_ind;
printf("%d\t %s\t %s \n", deptno, dname, loc);
}
EXEC SQL CLOSE c1;
EXEC SQL COMMIT;
printf("\n 按任意鍵繼續? ");
getchar();
printf("\n鍵入 n 退出, 其他繼續? ");
scanf("%c", &choosechar);
fflush(stdin);
if (choosechar=='n' || choosechar=='N')
{
break;
}
}
EXEC SQL COMMIT WORK RELEASE;
printf("return ok...\n");
return ret ;
}
動態SQL4
- 既適用於SELECT語句,也適用於非SELECT語句、
- 工程中主要使用該模式
與前面的方法相比有兩個突出的不同點:
- 不但包含選擇表項或虛擬輸入宿主變數,而且它們的個數或資料型別在編譯時還不知道
- 在其它方法中,ORACLE和C之間的資料型別轉換是自動實現的。而在方法4中,由於動態語句中的宿主變數個數和型別在編譯時還不知道,因此不能實現自動轉換,必須由程式來控制資料型別之間的轉換
主要資訊:
- 選擇表項和實輸入宿主變數的個數
- 每一個選擇表項和實輸入宿主變數的成年高度
- 每一個選擇表項和實輸入宿主變數的資料型別
- 每一個輸出宿主變數和實輸入宿主變數的記憶體單元地址
ANSI模式
/* 包含C標頭檔案 */
#include <stdio.h>
#include <string.h>
/* 包含SQLCA標頭檔案 */
#include "sqlca.h"
/* 定義繫結變數值和選擇列表項值的最大長度 */
#define MAX_VAR_LEN 30
/* 定義選擇列表項名的最大長度 */
#define MAX_NAME_LEN 31
/* 定義宿主變數 */
exec sql begin declare section;
char *usrname = "scott";
char *passwd = "tiger";
char *serverid = "orcl";
char sql_stat[100];
char current_date[20];
exec sql end declare section;
void sql_error();
void connet();
void process_input();
void process_output();
int main()
{
/* 安裝錯誤處理控制代碼 */
exec sql whenever sqlerror do sql_error();
/* 連線到資料庫 */
connet();
/* 分配輸入描述區和輸出描述區 */
exec sql allocate descriptor 'input_descriptor';
exec sql allocate descriptor 'output_descriptor';
for( ; ; )
{
printf("\n請輸入動態SQL語句(EXIT:退出):\n");
gets(sql_stat);
/* EXIT(exit)->退出 */
if(0 == strncmp(sql_stat , "EXIT" , 4) || 0 == strncmp(sql_stat , "exit" , 4))
break;
/* 準備動態SQL語句 */
exec sql prepare s from :sql_stat;
/* 定義遊標 */
exec sql declare c cursor for s;
/* 處理繫結變數 */
process_input();
/* 開啟遊標
* select語句:處理查詢結果
* 其他SQL語句:執行
*/
exec sql open c using descriptor 'input_descriptor';
if(0 == strncmp(sql_stat , "SELECT" , 6) , 0 == strncmp(sql_stat , "select" , 6))
{
process_output();
}
/* 關閉遊標 */
exec sql close c;
}
/* 釋放輸入描述區和輸出描述區 */
exec sql deallocate descriptor 'input_descriptor';
exec sql deallocate descriptor 'output_descriptor';
/* 提交事務,斷開連線 */
exec sql commit work release;
puts("謝謝使用ANSI動態SQL!\n");
return 0;
}
void sql_error()
{
/* 顯示SQL錯誤 */
printf("%.*s\n" , sqlca.sqlerrm.sqlerrml , sqlca.sqlerrm.sqlerrmc);
exit(0);
}
void process_input()
{
int i;
/* 定義宿主變數 */
exec sql begin declare section;
int input_count;
int input_type ;
int input_len;
char input_buffer[MAX_VAR_LEN];
char name[MAX_NAME_LEN];
int occurs;
exec sql end declare section;
/* 繫結變數->輸入描述區 */
exec sql describe input s using descriptor 'input_descriptor';
/* 取得繫結變數個數 */
exec sql get descriptor 'input_descriptor' :input_count = count;
/* 迴圈處理繫結變數名 */
for(i = 0 ; i != input_count ; ++i)
{
occurs = i + 1;
/* 取得繫結變數名 */
exec sql get descriptor 'input_descriptor' value :occurs :name = name;
printf("請輸入%s的值:" , name);
gets(input_buffer);
/* 以NULL結尾 */
input_len = strlen(input_buffer);
input_buffer[input_len] = '\0';
/* 設定繫結變數型別、長度和值 */
input_type = 1;
exec sql set descriptor 'input_descriptor' value :occurs
type = :input_type , length = :input_len , data = :input_buffer;
}
}
void process_output()
{
int i;
// 定義宿主變數
EXEC SQL BEGIN DECLARE SECTION ;
int output_count;
int output_type;
int output_len;
char output_buffer[MAX_VAR_LEN];
short output_indicator;
char name[MAX_NAME_LEN];
int occurs;
EXEC SQL END DECLARE SECTION ;
// 選擇列表項, 輸出描述區
exec sql describe output s using descriptor 'output_descriptor';
//取得選擇列表項個數
exec sql get descriptor 'output_descriptor' :output_count = count;
//迴圈處理選擇列表項
output_type = 12; //note //設定型別為變長字串
//output_type = 1; //note
for(i = 0 ; i != output_count ; ++i)
{
occurs = i + 1;
output_len = MAX_VAR_LEN;
//printf("22222222222222:%d \n", i);
// 設定選擇列表項的型別和長度
exec sql set descriptor 'output_descriptor' value :occurs
type = :output_type , length = :output_len;
//取得選擇列表項的名稱並輸出
exec sql get descriptor 'output_descriptor' value :occurs :name = name;
//顯示選擇列表項名稱
printf("\t%s" , name);
}
printf("\n");
// 提取資料完畢->退出迴圈
exec sql whenever not found do break;
// 迴圈處理選擇列表項資料
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
:output_buffer = DATA , :output_indicator = INDICATOR;
//輸出列資料
if(-1 == output_indicator)
printf("\t%s", " ");
else
printf("\t%s" , output_buffer);
}
printf("\n");
}
}
void connet()
{
int ret = 0;
//連線資料庫
EXEC SQL CONNECT:usrname IDENTIFIED BY:passwd USING:serverid ;
if (sqlca.sqlcode != 0)
{
ret = sqlca.sqlcode;
printf("sqlca.sqlcode: err:%d \n", sqlca.sqlcode);
return ;
}
else
{
printf("connect ok...\n");
}
}
ORACLE模式
關於SQLDA
為了儲存執行動態SQL語句所需要的資訊,系統提供一個稱之為SQL描述區的程式資料結構。把ORACLE所需要的全部有關選擇表項或虛擬輸入宿主變數的資訊,除了值和名字外,都儲存在SQLDA中SQLDA中所包含的資訊主要通過以下三種方法寫入:
- sqlald()函式:在分配描述區和緩衝區的同時,還把SLI或P的名字在緩衝區中的地址和長度寫入SQLDA中
- 應用程式:通過程式把SLI或BV值的緩衝區地址、長度既資料型別寫入SQLDA
- DESCRIBE語句:檢查每一個選擇表項,確定它的名字、資料型別、約束、長度、定標和精度,然後把這方面的資訊儲存在選擇SQLDA和輸出緩衝區中,以供使用者使用
說明的方式有如下三種:
- 直接把商標中所示的程式碼編寫到程式中
- 用INCLUDE語句:
EXEC SQL INCLUDE sqlda;
- 使用指標:
EXEC SQL INCLUDE sqlda; sqlda *bind_dp;sqlda *select_dp;bind_dp=sqlald(…);select_dp=sqlald(…);
sqlald(max_vars,max_name,max_ind_name)
為SQLDA分配控制元件,並分配相應的緩衝區,並把緩衝區的地址等填入SQLDA中
/* 包含C標頭檔案 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <setjmp.h>
#include <sqlcpr.h>
/* 包含SQLDA和SQLCA結構 */
#include <sqlda.h>
#include <sqlca.h>
/* 定義繫結變數和選擇列表項的最大個數 */
#define MAX_ITEMS 40
/* 定義繫結變數和選擇列表項名稱的最大長度 */
#define MAX_VNAME_LEN 30
/* 定義指示變數名稱的最大長度 */
#define MAX_INAME_LEN 30
void connect();
void sql_error();
void alloc_descriptors(int , int , int);
void dealloc_descriptors();
void set_bind_variables();
void process_select_list();
/* 定義繫結描述區和選擇描述區 */
SQLDA* bind_dp;
SQLDA* select_dp;
/* 定義輸入宿主變數:存放動態SQL語句 */
char sql_stat[100];
char current_date[20];
int main()
{
/* 安裝錯誤處理控制代碼 */
exec sql whenever sqlerror do sql_error();
/* 連線到資料庫 */
connect2();
/* 分配繫結描述區和選擇描述區 */
alloc_descriptors(MAX_ITEMS , MAX_VNAME_LEN , MAX_INAME_LEN);
for( ; ; )
{
printf("請輸入動態SQL語句(exit:退出):");
gets(sql_stat);
/* EXIT(exit):退出 */
if(0 == strncmp(sql_stat , "exit" , 4) || 0 == strncmp(sql_stat , "EXIT" , 4))
break;
/* 準備動態SQL語句 */
exec sql prepare s from :sql_stat;
/* 定義遊標 */
exec sql declare c cursor for s;
/* 出錯,繼續下一迴圈 */
if(0 != sqlca.sqlcode)
continue;
/* 設定繫結變數 */
set_bind_variables();
/*
* 開啟遊標
* select語句:生成結果集
* 其他SQL語句:執行語句
*/
exec sql open c using descriptor bind_dp;
/*
* select語句
*/
if(0 == strncmp(sql_stat , "select" , 6) || 0 == strncmp(sql_stat , "SELECT" , 6))
{
process_select_list();
}
/* 關閉遊標 */
exec sql close c;
}
/* 釋放選擇描述區和選擇描述區 */
dealloc_descriptors();
/* 提交事務,斷開連線 */
exec sql commit work release;
puts("謝謝使用Oracle動態SQL方法四!\n");
return 0;
}
void connect2()
{
/* 定義宿主變數 */
char username[20] , password[20] , server[20];
/* 輸入使用者名稱、口令和網路服務名 */
printf("輸入使用者名稱:");
gets(username);
printf("輸入口令:");
gets(password);
printf("輸入網路服務名:");
gets(server);
/* 連線到資料庫 */
EXEC SQL CONNECT :username identified by :password using :server;
}
void sql_error()
{
/* 顯示SQL錯誤資訊 */
printf("%.*s\n" , sqlca.sqlerrm.sqlerrml , sqlca.sqlerrm.sqlerrmc);
}
void alloc_descriptors(int size , int max_vname_len , int max_iname_len)
{
int i;
/* 分配繫結描述區和選擇描述區 */
bind_dp = SQLSQLDAAlloc(0 , size , MAX_VNAME_LEN , MAX_INAME_LEN);
select_dp = SQLSQLDAAlloc(0 , size , MAX_VNAME_LEN , MAX_INAME_LEN);
/* 為指示變數、繫結變數和選擇列表項分配記憶體 */
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);
}
}
void dealloc_descriptors()
{
int i;
/* 釋放指示變數、繫結變數和選擇列表項佔用的記憶體 */
for(i = 0 ; i != MAX_ITEMS ; ++i)
{
if(bind_dp->V[i] != (char*)0)
free(bind_dp->V[i]);
free(bind_dp->I[i]);
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);
}
void set_bind_variables()
{
int i;
char bind_var[64];
/* 設定繫結變數最大個數 */
bind_dp->N = MAX_ITEMS;
/* 繫結變數名稱: 繫結描述區 */
exec sql describe bind variables for s into bind_dp;
/* 設定繫結變數實際個數 */
bind_dp->N = bind_dp->F;
/* 迴圈處理繫結變數 */
for(i = 0 ; i != bind_dp->F ; ++i)
{
/* 顯示繫結變數名 */
printf("請輸入繫結變數%.*s的值:" , (int)bind_dp->C[i] , bind_dp->S[i]);
/* 輸入繫結變數的值 */
gets(bind_var);
/* 設定繫結變數的長度成員 */
bind_dp->L[i] = strlen(bind_var);
/* 為繫結變數資料緩衝區重新分配記憶體(多一位,留給'\0') */
bind_dp->V[i] = (char*)realloc(bind_dp->V[i] , bind_dp->L[i] + 1);
/* 繫結變數資料: 資料緩衝區 */
strcpy(bind_dp->V[i] , bind_var);
/* 設定指示變數,處理NULL */
if(0 == strncmp(bind_var , "NULL" , 4) || 0 == strncmp(bind_var , "null" , 4))
*bind_dp->I[i] = -1;
else
*bind_dp->I[i] = 0;
/* 設定資料緩衝區資料型別程式碼->char */
bind_dp->T[i] = 1;
}
}
void process_select_list()
{
int i , null_ok , precision , scale;
char title[MAX_VNAME_LEN];
/* 設定選擇列表項的最大個數 */
select_dp->N = MAX_ITEMS;
/* 選擇列表項: 選擇描述區 */
exec sql describe select list for s into select_dp;
/* 設定選擇列表項的實際個數 */
select_dp->N = select_dp->F;
/* 迴圈處理選擇列表項 */
for(i = 0 ; i != select_dp->F ; ++i)
{
/* 清除select_dp->T[i]的高位->null */
SQLColumnNullCheck(0 , (unsigned short*)&select_dp->T[i]
, (unsigned short*)&select_dp->T[i] , &null_ok);
/* 根據內部資料型別確定外部型別資料長度(顯示長度) */
switch(select_dp->T[i])
{
case 2:
/* number型別,取得精度與標度 */
//SQLNumberPrecV6(0 , (unsigned short*)&select_dp->T[i] , &precision , &scale);
SQLNumberPrecV6(0 , (unsigned long *)&select_dp->L[i] , &precision , &scale); //wangbaoming modify 201409
if(scale > 0)
/* 實數: 顯示長度:float */
select_dp->L[i] = sizeof(float);
else
/* 整數: 顯示長度 int */
select_dp->L[i] = sizeof(int);
break;
case 12:
/* DATA資料型別(DD-MON-YY) */
select_dp->L[i] = 9;
break;
}
/* 根據變數長度,重新為選擇列表項資料緩衝區分配記憶體 */
if(2 != select_dp->T[i])
/* 其他型別 */
select_dp->V[i] = (char*)realloc(select_dp->V[i] , select_dp->L[i] + 1);
else
/* number型別 */
select_dp->V[i] = (char*)realloc(select_dp->V[i] , select_dp->L[i]);
/* 初始化title */
memset(title , ' ' , MAX_VNAME_LEN);
/* 選擇列表項名稱: title */
strncpy(title , select_dp->S[i] , select_dp->C[i]);
/* 顯示列名 */
if(2 == select_dp->T[i])
if(scale > 0)
printf("\t%.*s" , select_dp->L[i] + 3, title);
else
printf("\t%.*s" , select_dp->L[i] , title);
else
printf("\t%-.*s" , select_dp->L[i] , title);
/* 根據Oracle內部型別確定外部資料型別(顯示型別) */
if( 2 == select_dp->T[i])
{
/* number 型別*/
if(scale > 0)
/* float */
select_dp->T[i] = 4;
else
/* int */
select_dp->T[i] = 3;
}
else
/* char */
select_dp->T[i] = 1;
}
printf("\n");
/* 提取資料完畢->結束迴圈 */
exec sql whenever not found do break;
/* 迴圈處理選擇列表資料 */
for( ; ; )
{
/* 資料->選擇描述區 */
exec sql fetch c using descriptor select_dp;
/* 顯示資料 */
for( i = 0 ; i != select_dp->F ; ++i)
{
if(*select_dp->I[i] < 0){
/* 處理NULL */
printf("\tNULL");
}else{
if(3 == select_dp->T[i]){
/* int */
printf("\t%d" , *(int*)select_dp->V[i]);
}else if(4 == select_dp->T[i]){
/* float */
printf("\t%8.2f" , *(float*)select_dp->V[i]);
}else{
/* char */
printf("\t%.*s" , select_dp->L[i] , select_dp->V[i]);
}
}
}
printf("\n");
}
}