本節講述內容:
1.嵌入式SQL 語言概述
2.變量聲明與數據庫連接
3.數據集與游標
4.可滾動游標與數據庫的增刪改
5.狀態捕捉以及錯誤處理機制
(一)嵌入式SQL語言
之前我們所學的都是交互式SQL 語言: select .. from .. where..
嵌入式SQL語言 表示 將SQL語言嵌入到 某一種高級語言中使用, 比如C++ ,Java, powerbuilder等
它們也稱為宿主語言(host language).
復雜的檢索不能用一條SQL語句完成, 需要結合高級語言中的順序\分支\循環結構幫助處理.
if [conditon] then SQL_query else SQL_query end if
do while [condition] SQL_query end do
還有在SQL語句的檢索結果基礎上,再進行處理的
SQL_query1 for ... do process the record next SQL_query 2 if .. then else end if
交互式SQL: select sname, sage from student where sname='xy';
嵌入式SQL: 以宿主語言C語言為例,
exec sql select sname, sage into :vsname, :vsage from student where sname='xy';
主要區別:
(1) exex sql 是一個引導詞, 它引導sql 語句, 將SQL語句預編譯成C編譯器可識別的語句.
(2) 增加 into 子句, 用於把SQL 語句的檢索結果賦給高級語言的程序變量
(3) 用冒號開頭 表示高級語言的程序變量 :vsname , :vsage
冒號很重要, 用於區分是程序變量 還是 表的字段!! .... 還有很多特點之后在詳細介紹
為啥要學嵌入式SQL , 用來解決啥問題?

下面逐個解決上述8個問題
(一) 數據庫的連接(問題1)
在嵌入式SQL 操作之前, 首先需要與數據庫進行連接
不同的DMBS 的語句是有差別的,

在嵌入式SQL程序執行之后, 需要斷開數據庫

SQL 執行的提交與撤銷
SQL語句在執行過程中, 必須要有提交, 撤銷語句
提交: exec sql commit work;
撤銷: exec sql rollback work;
很多DBMS 都設計了捆綁 提交\撤銷 與斷開連接在一起的語句, 以保證在斷開連接之前
使用戶確認提交或 撤銷先前的工作, Oracle 中就是這樣:
exec sql commit release;
exec sql rollback release;
為什么需要提交和撤銷呢? 這個設計到數據庫中的'' 事務 ''處理
什么是事務? 從應用程序員角度來看, 事物是一個存取或者改變數據庫內容的程序的一次執行,
或者說是一條或者多條SQL 語句的一次執行被看做是一個事務
事務 一般由應用程序員提出, 因此有開始和結束, 結束前需要提交或者撤銷
begin transaction exec sql... exec sql... exec sql commit work | exec sql rollback work --提交或者撤銷 end transaction
注意: 提交表示這一系列操作對數據庫的更新是有效的, 撤銷則表示無效
其實從 任何一個SQL語句執行 就表示了一個事務的開始, 到了 commit 或 rollback 則結束一個事務,
因此上述的 begin end 可以省略.
事務的ACID 特性
A : atomicity 原子性, DBMS保證表示事務中的一組操作是不可分的,要么全做,要么一條也不做
C : consistency 一致性,例如兩個人同時在買車票,會不會買到同一張車票
I: isolation 隔離性 兩個事務操作互不干擾
D: durability 已提交事務的影響是持久的, 被撤銷的事務影響可以恢復
事務處理技術是DBMS的核心處理技術!!
(二) 變量聲明(問題2)
exec sql select sname, sage into :vsname, :vsage from student where sname=:specname;
加了冒號表示高級語言的程序變量, 這些變量需要聲明
exec sql begin declare section; --開始聲明 char vsname[10], specname[10] ='xy' ; int vsage; exec sql end declare section; -- 結束聲明
注: 宿主程序的字符串變量長度要比字符型字段多1, 因為宿主程序的字符串尾部多一個終止符'\0' .
-- 變量的聲明與使用
exec sql begin declare section; char vsname[10], specname[10] ='xy' ; int vsage; exec sql end declare section; -- 用戶在此處 可以基於鍵盤輸入給specname 賦值 exec sql select sname, sage into :vsname, :vsage from student where sname=:specname;
實例: 數據庫連接+變量定義
#include<stdio.h> #include"prompt.h" exec sql include sqlca; --sqlca 表示SQL的通信區, communication area char cid_prompt[]="please enter customer id:"; int main() { exec sql begin declare section; --下面聲明變量 char cust_id[5], cust_name[14]; float cust_discnt; exec sql end declare section; exec sql whenever sqlerror goto report_error;-- 錯誤捕獲 exec sql whenever not found goto notfound; -- 記錄沒有找到 strcpy(user_name,"poneilsql");-- 字符串賦值 strcpy(user_pwd,"123456"); exec sql connect :user_name identified by :user_pwd; -- 連接數據庫 while((prompt(cid_prompt,1,cust_id,4))>=0){ exec sql select cname,discnt into :cust_name,:cust_discnt from customers where cid=:cust_id; -- 根據輸入的客戶id 找到名字和折扣 exec sql commit work;-- 提交 printf("customer's name is %s and discount is %.1f\n",cust_name, cust_discnt); continue; -- 接着循環,再輸入客戶id notfound:printf("can't find customer %s, continuing\n", cust_id);} exec sql commit release; -- 斷開數據庫的連接 return 0; report_error: -- 前面報錯的執行 print_dberror(); exec sql rollback release; -- 斷開連接 return 1; }
(三) 數據集與游標(問題3 4 5)
問題3: SQL 語句如何執行?
問題4: 如何將SQL 檢索到的結果傳遞回宿主程序進行處理?
問題5: 如何將靜態SQL , SQL語句中的常量更換為變量?
如何讀取單行數據和多行數據, 單行結果處理與多行結果處理的差異: into 子句 和 游標 cursor
1. 檢索單行結果, 可以將結果直接傳送到宿主主程序的變量中, select ... into ...
exec sql select sname, sage into :vsname, :vsage from student where sname=:specname;
2. 如果是多行結果, 則需要使用游標cursor
游標是指向某個檢索記錄的指針, 通過這個指針, 每次讀一行, 處理一行,
接着再讀一行...,直到全部處理完畢 fetch..into... (一次一行)
需要先定義一個cursor-->再打開-->接着一條一條處理-->最后關閉
exec sql delcare cur_student cursor for --游標名 select sno, sname, sclass from student where sclass='0315'; -- 定義游標 exec sql open cur_student; --打開游標 exec sql fetch cur_student into :vsno, :vsname, :vsclass; --取數據
... exec sql close cur_student; --關閉游標
具體實例:
已知表orders(cid, aid, product, dollars) 客戶id, 代理人id, 產品, 金額
游標: 給定一個客戶id, 選出該客戶下的所有代理商 和 金額(多行數據)
#define True 1 #include<stdio.h> #include"prompt.h" exec sql include sqlca; --sqlca 表示SQL的通信區, communication area exec sql begin declare section; --聲明變量 char cust_id[5], agent_id[14]; double dollar_sum; exec sql end declare section; int main() { char cid_prompt[]="please enter customer id:"; -- 定義提示字符串 exec sql declare agent_dollars cursor for -- 定義游標 select aid,sum(dollars) from orders where cid=:cust_id group by aid; exec sql whenever sqlerror goto report_error;-- 錯誤捕獲 exec sql connect to testdbl; --連接數據庫 exec sql whenever not found goto finish; -- 記錄沒有找到 while((prompt(cid_prompt,1,cust_id,4))>=0){ exec sql open agent_dollars; -- 打開游標 while(True){ -- 打印每一條記錄 exec sql fetch agent_dollars into :agent_id,:dollar_sum; printf("%s %11.2f\n",agent_id, dollar_sum) }; finish: exec sql close agent_dollars; -- 關閉游標 exec sql commit work; -- 提交 exec sql disconnect current;--斷開連接 return 0; report_error: -- 前面報錯的執行 print_dberror(); exec sql rollback;-- 撤銷 exec sql disconnect current; --斷開連接 return 1; }
總結游標:
exec sql delcare cur_student cursor for --游標名 select sno, sname, sclass from student where sclass=:vclass; -- 定義游標 order by sno for read only; --只讀, 不可更新
cursor 數據讀取 fetch : exec sql fetch cursor_name into host_variable
exec sql delcare cur_student cursor for --游標名 select sno, sname, sclass from student where sclass=:vclass; -- 定義游標 order by sno for read only; --只讀, 不可更新 exec sql open cur_student; -- 打開 exec sql fetch cur_student into :vsno, :vsname, :vsage; -- 使用 exec sql close cur_student; -- 關閉
可滾動游標與數據庫的增刪改
標注的游標 始終是自開始到結束方向移動的, 每fetch 一次,向結束方向移動一次,
每一條記錄只能被訪問一次, 再次訪問該記錄只能關閉游標后重新打開
可不可以實現游標的向上移動呢? ODBC (open database connectivity) 是一種跨DBMS
的DB 操作平台, 它在應用程序與實際的DBMS之間提供了一種通用的接口,
很多DBMS不支持可滾動游標, 但是通過ODBC 可實現該功能
定義中增加了 scroll
使用如下:

可滾動游標移動時需要判斷 是否到了結束位置, 或者到了起始位置,
EOF表示最后一條記錄的后面位置
BOF表示起始位置的前面
如果不需要區分最上 最下, 則可以用whenever not found 進行檢測
用游標進行數據庫的增刪改
1. 查找刪除(與交互式delete 語句相同)
exec sql delete from customers c where c.city='harbin' and not exists (select * from orders o where o.cid=c.cid) -- 刪除 城市是哈爾濱 且在訂單 orders表里面沒有記錄的.
2. 定位刪除
exec sql declare delcust cursor for select cid from customers c where c.city='harbin' and not exists (select * from orders o where o.cid=c.cid) for update of cid; exec sql open delcust while(True){ exec sql fetch delcust into :cust_id; exec sql delete from customers where current of delcust;}
1. 查找更新
exec sql update student s set scalss='0315' where s.sclass='0314';
2.定位更新
exec sql declare stud cursor for select * from student s where s.sclass='0314' and for update of sclass; exec sql open stud while(True){ exec sql fetch stud into :vsno, :vsname,:vsclass; exec sql update student set sclass='0315' where current of stud;}
插入語句
exec sql insert into student(sno,sname,sclass) values ('031501','xy','0315'); exec sql insert into master_stud(sno,sname,sclass) select sno,sname,sclass from student;
綜合實例: 求數據庫中某一列位於中值的那一行
--已知表 orders(cid,aid,product,dollars) -- 尋找數據庫中某一列位於中值的那一行 #include<stdio.h> #include"prompt.h" exec sql include sqlca; --sqlca 表示SQL的通信區, communication area char cid_prompt[]="please enter customer id:"; -- 定義提示字符串 int main() { exec sql begin declare section; --聲明變量 char cid[5], user_name[20], user_pwd[10]; double dollars; int ocount; exec sql end declare section; exec sql declare dollars_cursor cursor for -- 定義游標 select dollars from orders where cid=:cid and dollars is not null order by dollars; exec sql whenever sqlerror goto report_error;-- 錯誤捕獲 strcpy(user_name,"poneilsql");-- 字符串賦值 strcpy(user_pwd,"123456"); exec sql connect :user_name identified by :user_pwd; -- 連接數據庫 --exec sql whenever not found goto finish; -- 記錄沒有找到 while((prompt(cid_prompt,1,cust_id,4))>=0){ exec sql select count(dollars) into :ocount from orders where cid=:cid; if(ocount==0) {printf("no record reviewed for cid value %s\n",cid); continue;} exec sql open dollars_cursor; for (i=0;i<(ocount+1)/2;i++) exec sql fetch dollars_cursor into :dollars ; exec sql close dollars_cursor; exec sql commit work; -- 提交 printf("median dollar amount=%f\n",dollars); }
