Oracle學習11:遊標(cursor)--顯式遊標&隱式遊標、遊標四個屬性、循環遍歷

1.概述

上文PLSQL學習中提到的知識,可以發現,基本都可以通過Java等語言實現,而爲了實現程序的可移植性,實際開發工作中我們也是如此做的。
那麼PLSQL的重點是什麼呢?接下來我們來介紹遊標cursor的概念。
之前提到,在不使用顯式遊標的情況下,PLSQL的中select語句只能返回一條記錄,那麼我們如果想要返回多條記錄,或者遍歷整個結果集該如何實現呢。
這裏我們可以通過cursor實現。

1.1 cursor是什麼

cursor是光標,遊標的意思。比如我們的鼠標的光標就是cursor。
那麼在數據庫中cursor是什麼呢?
當運行DML(select,update,insert,delete)語句時,ORACLE會在內存中爲其分配緩衝區(Context Area),PL/SQL打開一個內建遊標並處理結果,遊標是維護查詢結果的內存中的一個區域
遊標在運行DML語句時打開,完成後關閉
通俗的說:
我們知道,select語句會產生一個結果集,而遊標是指在這個結果集上的第一條記錄的一個指針
而我們每次取出(fetch)一條記錄,cursor就會自動指向下一條記錄。
如果學過Java的話,可以這樣理解,cursor類似於Java中的迭代器iterator。

1.2 cursor作用

基於以上的理解,cursor自然就被用於:取出每一條記錄,遍歷結果集。

1.3 cursor的四個屬性

cursor有如下四個屬性:

  • %isopen:布爾類型。判斷遊標是否打開,打開爲true。對於隱式遊標而言,這個值總是false,因爲隱式遊標在DML語句執行時打開,結束時就立即關閉。
  • %found:布爾類型。在執行任何DML語句前SQL%FOUND和SQL%NOTFOUND的值都是NULL。在執行DML語句後,SQL%FOUND的屬性值將是:

      . TRUE :INSERT
      . TRUE :DELETE和UPDATE,至少有一行被DELETE或UPDATE.
      . TRUE :SELECT INTO至少返回一行

  • %notfound:布爾類型。當SQL%FOUND爲TRUE時,SQL%NOTFOUND爲FALSE

  • %rowcount:數值類型。在執行任何DML語句之前,SQL%ROWCOUNT的值都是NULL,對於SELECT INTO語句,如果執行成功,SQL%ROWCOUNT的值爲1,如果沒有成功,SQL%ROWCOUNT的值爲0,同時產生一個異常NO_DATA_FOUND。

注:PLSQL中的布爾類型的值爲null、true、false。

2.顯式遊標和隱式遊標

在遊標聲明之前,我們來看下顯式遊標和隱式遊標。

2.1 隱式遊標

事實上,當我們在PLSQL中進行非查詢(或者返回單條記錄的查詢)語句,如update、delete、insert等時,ORACLE 系統會自動地爲這些操作設置遊標並創建其工作區,並且隱式遊標的名字爲SQL,由ORACLE 系統定義。
對於隱式遊標的操作,如定義、打開、取值及關閉操作,都由ORACLE 系統自動地完成,無需用戶進行處理。
PL/SQL管理隱式遊標,當查詢開始時隱式遊標打開,查詢結束時隱式遊標自動關閉
用戶只能通過隱式遊標的相關屬性,來完成相應的操作。在隱式遊標的工作區中,所存放的數據是與用戶自定義的顯示遊標無關的、最新處理的一條SQL 語句所包含的數據。
簡單實例
對於如下一張表ljb_test,更新每個人的薪水:
這裏寫圖片描述

set serveroutput on;
begin
    update ljb_test set salary = salary + 1;
    dbms_output.put_line('更新了'||SQL%rowcount||'行數據');   --must before commit
    commit;
end;
/

結果如下:
這裏寫圖片描述

這裏需要指出的是:關於隱式遊標的屬性操作,必須在commit之前,可以嘗試把打印輸出放在commit之後,得到的結果是0。這裏之所以結果加了2,是因爲我實驗了兩次。

2.2 顯式遊標

當查詢返回結果超過一行時,就需要一個顯式遊標,此時用戶不能使用select into語句。
顯式遊標在PL/SQL塊的聲明部分聲明,在執行部分或異常處理部分打開,取數據,關閉。
這裏要做一個聲明,我們所說的遊標通常是指顯式遊標,而顯式遊標需要被聲明

2.2.1 cursor的聲明、打開、關閉、從遊標提取數據

聲明遊標

CURSOR cursor_name IS select_statement;

打開遊標

OPEN cursor_name;

關閉遊標

CLOSE cursor_name;

從遊標提取數據
從遊標得到一行數據使用FETCH命令。每一次提取數據後,遊標都指向結果集的下一行。
語法如下:

FETCH cursor_name INTO variable[,variable,...]

如:

set serveroutput on;
declare
    cursor c is select * from ljb_test; --1.聲明遊標的時候Oracle不會從數據庫中取數據
    v_test c%rowtype;
begin
    open c;         --2.打開遊標,此時從數據庫中取數據,並把結果集放在內存中
        fetch c into v_test;    --3.獲取數據,fetch的時候遊標自動往下移動一格
        dbms_output.put_line(v_test.name);

        fetch c into v_test;
        dbms_output.put_line(v_test.name);
    close c;        --4.關閉遊標,清掉內存。成對編程 
end;
/

命令行運行,結果如下:
這裏寫圖片描述

2.2.2 遍歷結果集

如果我們想要遍歷整個測試表,顯然需要通過循環來進行。
正常情況下如果遵循循環遍歷遊標應當遵從以下步驟:
1、打開遊標
2、開始循環
3、從遊標中取值
4、檢查那一行被返回
5、處理
6、關閉循環
7、關閉遊標
事實上,我們確實可以通過該方式實現,即通過while循環和do while循環。
但是,for循環卻不無需這麼複雜,這裏我們重點介紹for循環
上文提到,PLSQL中有三種循環,這裏需要指出的是,使用遊標遍歷時,最簡單最穩定的就是for循環,但另外兩種循環仍然會做簡單介紹。如下:

2.2.2.1 for循環遍歷

FOR循環的遊標按照正常的聲明方式聲明,但是不需要顯式的打開、關閉、取數據,測試數據的存在、定義存放數據的變量等等。
for循環是最簡單也是最不容易出錯的方式,推薦採用for循環遍歷。

set serveroutput on;
declare
    cursor c is select * from ljb_test;
    --無需在此聲明變量v_test
begin
    --無需顯式打開遊標
    for v_test in c loop
    --無需顯式fetch
        dbms_output.put_line(c%rowcount||'--'||v_test.name);
    end loop;
    --無需顯式關閉遊標
end;
/

九條記錄全部被打印,運行結果如下:
這裏寫圖片描述

2.2.2.2 while循環遍歷

declare
    cursor c is select * from ljb_test; --聲明遊標的時候Oracle不會從數據庫中取數據
    v_test c%rowtype;
begin
    open c;         --打開遊標,此時從數據庫中取數據,並把結果集放在內存中

        fetch c into v_test;    --獲取數據,fetch的時候遊標自動往下移動一格
        while c%found loop
            dbms_output.put_line(c%rowcount||'--'||v_test.name);
            fetch c into v_test; 
        end loop;
    close c;        --關閉遊標,清掉內存。成對編程 
end;
/

九條記錄被遍歷,結果如下:
這裏寫圖片描述

如果我們把fetch語句和打印語句調換一下位置,結果會怎樣?
這裏寫圖片描述

可以看到,第一個記錄被跳過取,最後一個打印兩邊。原因是,我們第一次打印前fetch了兩次,而最後一個雖然c無法fetch到數據,但是上一個的c%fetch仍然是true。

所以,採用while循環一定注意fetch和打印的順序。do while類似。

2.2.2.3 do while循環遍歷

declare
    cursor c is select * from ljb_test; --聲明遊標的時候Oracle不會從數據庫中取數據
    v_test c%rowtype;
begin
    open c;
    loop
        fetch c into v_test;
            exit when(c%notfound);
            dbms_output.put_line(c%rowcount||'--'||v_test.name);  --如果順序反了,就會最後一條記錄打印兩次
    end loop;
    close c;       --關閉遊標,清掉內存。成對編程 
end;
/

這裏寫圖片描述

2.2.3 含參遊標

類似於函數,我們可以將參數傳遞給遊標並在查詢中使用。

CURSOR cursor_name[(parameter[,parameter],...)] 
IS select_statement;

參數定義方式爲:

Parameter_name [IN] data_type[{:=|DEFAULT} value]

需要注意的是:遊標只能接受傳遞的值,而不能返回值。參數只定義數據類型,沒有大小

declare
    cursor c(v_dep ljb_test.dep%type, v_salary ljb_test.salary%type) 
        is select * from ljb_test where dep = v_dep and salary = v_salary;
begin
    for v_temp in c(3,4000) loop
        dbms_output.put_line(v_temp.name);
    end loop;
end;
/

結果如下:
這裏寫圖片描述

2.4 可更新遊標

declare 
    cursor c is select * from ljb_test for update;  --添加for update即可
begin
    for v_temp in c loop
        if(v_temp.salary<3500) then
            update ljb_test set salary = salary * 2 where current of c; --更新條件
        elsif(v_temp = 5000) then
            delete from ljb_test where current of c;    --更新條件
        end if;
    end loop;
    commit;
end;
/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章