SQL入門(4): 嵌入式SQL語言


本節講述內容: 

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); }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM