- 1.1,遊標的概念
- 1.2,處理顯式遊標
- 1.3,遊標屬性
- 1.4,遊標的 FOR循環
- 1.5,處理隱式遊標
- 1.6,關於 NO_DATA_FOUND 和 %NOTFOUND 的區別
- 1.7,遊標修改和刪除操作
遊標的使用
- 在 PL/SQL 程序中,對於 處理多行記錄的事務經常使用遊標來實現。
- 爲了處理 SQL 語句,ORACLE 必須分配一片叫上下文( context area )的區域來處理所必需的信息,其中包括要處理的行的數目,一個指向語句被分析以後的表示形式的指針以及查詢的活動集(active set)。遊標是一個 指向上下文的句柄( handle) 或指針。通過遊標,PL/SQL 可以控制上下文區和處理語句時上下文區會發生些什麼事情。
- 對於不同的 SQL 語句,遊標的使用情況不同:
顯式遊標處理需四個 PL/SQL 步驟:
- 定義遊標 :就是定義一個遊標名,以及與其相對應的 SELECT 語句。格式:
CURSOR cursor_name[(parameter[, parameter]…)] IS select_statement;
遊標參數只能爲輸入參數,其格式爲:
parameter_name [IN] datatype [{:= | DEFAULT} expression]
在指定數據類型時,不能使用長度約束。如 NUMBER(4)、CHAR(10) 等都是錯誤的。
- 打開遊標 :就是執行遊標所對應的 SELECT 語句,將其查詢結果放入工作區,並且指針指向工作區的首
部,標識遊標結果集合。如果遊標查詢語句中帶有 FOR UPDATE 選項,OPEN 語句還將鎖定數據庫表中
遊標結果集合對應的數據行。格式:
OPEN cursor_name[([parameter =>] value[, [parameter =>] value]…)];
在向遊標傳遞參數時,可以使用與函數參數相同的傳值方法,即位置表示法和名稱表示 法。PL/SQL
程序不能用 OPEN 語句重複打開一個遊標。
- 提取遊標數據 :就是檢索結果集合中的數據行,放入指定的輸出變量中。格式:
FETCH cursor_name INTO {variable_list | record_variable };
對該記錄進行處理;繼續處理,直到活動集合中沒有記錄;
- 關閉遊標 :當提取和處理完遊標結果集合數據後,應及時關閉遊標,以釋放該遊標所佔用的系統資源,
並使該遊標的工作區變成無效,不能再使用 FETCH 語句取其中數據。關閉後的遊標可以使用 OPEN 語
句重新打開。格式:
CLOSE cursor_name;
- 例:查詢前 5 名員工的信息,按工資從低往高排
declare
---定義遊標
cursor emp_cursor(emp_no number default 6) is
select ename, sal from emp where rownum < emp_no order by sal;
v_name emp.ename%type;
v_sal emp.sal%type;
begin
--打開遊標
open emp_cursor(emp_no => 6);
--提取遊標數據
fetch emp_cursor
into v_name, v_sal;
while emp_cursor%found loop
dbms_output.put_line(v_name || '---' || v_sal);
fetch emp_cursor
into v_name, v_sal;
end loop;
--關閉遊標
close emp_cursor;
end;
-
%FOUND 布爾型屬性,當最近一次讀記錄時成功返回,則值爲 TRUE;
-
%NOTFOUND 布爾型屬性,與%FOUND 相反;
-
%ISOPEN 布爾型屬性,當遊標已打開時返回 TRUE;
-
%ROWCOUNT 數字型屬性,返回已從遊標中讀取的記錄數。
-
例:給工資低於 3000 的員工工資加30:
declare
v_empno emp.empno%type;
v_sal emp.sal%type;
cursor emp_cursor is
select empno, sal from emp;
begin
open emp_cursor;
loop
fetch emp_cursor
into v_empno, v_sal;
exit when emp_cursor %notfound;
if v_sal <= 3000 then
update emp set sal = sal + 30
where empno = v_empno;
dbms_output.put_line('員工號' || v_empno || '工資已更新');
end if;
end loop;
dbms_output.put_line('記錄數爲' || emp_cursor %rowcount);
close emp_cursor;
end;
- PL/SQL 語言提供了 遊標 FOR 循環語句,自動執行遊標的 OPEN 、FETCH 、CLOSE 語句和 循環 語句的功能;當進入循環時,遊標 FOR 循環語句 自動 打開遊標,並提取第一行遊標數據,當程序處理完 當前所提取的數據而進入下一次循環時,遊標 FOR 循環語句自動提取下一行數據供程序處理,當提取完結果集合中的所有數據行後結束循環,並自動關閉遊標,格式:
FOR index_variable IN cursor_name[value[, value]…] LOOP
-- 遊標數據處理代碼
END LOOP;
其中:
index_variable 爲遊標 FOR 循環語句隱含聲明的索引變量,該變量爲 記錄變量,其 結構與遊標查詢語句返回的結構集合的結構相同。在程序中可以通過引用該索引記錄變量元素來讀取所提取的遊標數據,index_variable 中各元素的名稱與遊標查詢語句選擇列表中所制定的列名相同。 如果在遊標查詢語句的選擇列表中存在計算列,則必須爲這些計算列指定別名後才能通過遊標 FOR 循環語句中的索引變量來訪問這些列數據
declare
cursor c_emp(dep_id number default 20) is
select empno, sal from emp where deptno = dep_id;
begin
for v_emp in c_emp(30) loop
dbms_output.put_line(v_emp.empno || ',' || v_emp.sal);
end loop;
end;
-
由系統隱含創建的遊標稱爲隱式遊標, 隱式遊標的名字爲 SQL,對於隱式遊標的操作,如定義、打開、取值及關閉操作,都由 ORACLE 系統自動地完成,無需用戶進行處理。 用戶只能通過隱式遊標的相關屬性,來完成相應的操作。
-
隱式遊標屬性
SQL%FOUND 布爾型屬性,當最近一次讀記錄時成功返回,則值爲 TRUE;
SQL%NOTFOUND 布爾型屬性,與%FOUND 相反;
SQL %ROWCOUNT 數字型屬性, 返回已從遊標中讀取得記錄數;
SQL %ISOPEN 布爾型屬性, 取值總是 FALSE。SQL 命令執行完畢立即關閉隱式遊標。 -
例題:更新指定員工信息,如果該員工沒有找到,則打印”查無此人”信息:
declare
v_name emp.ename%type;
v_id emp.empno%type := &v_id;
begin
update emp set ename = v_name where empno = v_id;
if SQL%NOTFOUND then
dbms_output.put_line('查無此人');
end if;
end;
- SELECT … INTO 語句觸發 NO_DATA_FOUND;
- 當一個顯式遊標的 WHERE 子句未找到時觸發%NOTFOUND;
- 當 UPDATE 或 DELETE 語句的 WHERE 子句未找到時觸發 SQL%NOTFOUND;在提取循環中要用 %NOTFOUND
或%FOUND 來確定循環的退出條件,不要用 NO_DATA_FOUND.
- 遊標修改和刪除操作是指在遊標定位下,修改或刪除表中指定的數據行。這時, 要求遊標 查詢語句中必須使用 FOR UPDATE 選項,以便在打開遊標時鎖定遊標結果集合在表中對應數據行的所有列和部分列。
- 爲了對正在處理( 查詢) 的行不被另外的用戶改動,ORACLE 提供一個 FOR UPDATE 子句來對所選擇的行進行鎖住。該需求迫使 ORACLE 鎖定遊標結果集合的行,可以防止其他事務處理更新或刪除相同的行,直到您的事務處理提交或回退爲止。
- 語法:
SELECT . . . FROM … FOR UPDATE [OF column[, column]…] [NOWAIT]
-
如果使用 FOR UPDATE 在 聲明遊標,則可在 DELETE 和 和 UPDATE 用 語句中使用 WHERE CURRENT OF
cursor_name 子句,修改或刪除遊標結果 -
例 :從 EMPLOYEES 表中查詢某部門的員工情況,將其工資最低定爲 1000:
declare
v_dep_id emp.deptno%type := &v_dep_id;
cursor emp_cursor is
select ename, sal from emp where deptno = v_dep_id for update nowait;
begin
for emp_rec in emp_cursor loop
if emp_rec.sal < 1000 then
update emp set sal = 1000 where current of emp_cursor;
end if;
end loop;
end;
異常錯誤處理
- 異常情況處理(EXCEPTION) 是用來處理正常執行過程中未預料的事件, 程序塊的異常處理預定義的錯誤和 自定義錯誤, 由於 PL/SQL 程序塊一旦產 生異常而沒有指出如何處理時, 程序就會自動終止整個程序運行.
- 有三種類型的異常錯誤:
①, 預定義 ( Predefined ) 錯誤
ORACLE 預定義的異常情況大約有 24 個。對這種異常情況的處理,無需在程序中定義,由ORACLE 自動將其引發。
②, 非預定義 ( Predefined )錯誤
即其他標準的 ORACLE 錯誤。對這種異常情況的處理,需要用戶在程序中定義,然後由 ORACLE 自動將其引發。
③, 用戶定義(User_define) 錯誤
程序執行過程中,出現編程人員認爲的非正常情況。對這種異常情況的處理, 需要 用戶在程序中定義,然後顯式地在程序中將其引發。
- 異常處理部分一般放在 PL/SQL 程序體的後半部,結構爲:
EXCEPTION
WHEN first_exception THEN <code to handle first exception >
WHEN second_exception THEN <code to handle second exception >
WHEN OTHERS THEN <code to handle others exception >
END;
- 對這種異常情況的處理,只需在 PL/SQL 塊的異常處理部分,直接引用相應的異常情況名,並對其完成相應的異常錯誤處理即可。
- 例:更新指定員工工資,如工資小於 1000,則加 30;對 NO_DATA_FOUND 異常, TOO_MANY_ROWS 進行處理:
declare
v_empno emp.empno%type := &v_empno;
v_sal emp.sal%type;
begin
select sal into v_sal from emp where empno = v_empno for update;
if v_sal <= 1000 then
update emp set sal = sal + 30 where empno = v_empno;
dbms_output.put_line('編碼爲' || v_empno || '員工工資已更新');
else
dbms_output.put_line('編碼爲' || v_empno || '員工工資不需要更新');
end if;
exception
when NO_DATA_FOUND then
dbms_output.put_line('數據庫中沒有編碼爲' || v_empno || '的員工');
when TOO_MANY_ROWS then
dbms_output.put_line('程序運行錯誤,請使用遊標');
when others then
dbms_output.put_line('其他錯誤');
end;
- 在 PL/SQL 塊的定義部分定義異常情況:
<異常情況> EXCEPTION;
- 將其定義好的異常情況,與標準的 ORACLE 錯誤聯繫起來,使用 PRAGMA EXCEPTION_INIT 語句:
PRAGMA EXCEPTION_INIT(< 異常情況>, < 錯誤代碼>);
- 在 PL/SQL 塊的異常情況處理部分對異常情況做出相應的處理。
- 例 :刪除指定部門的記錄信息,以確保該部門沒有員工:
declare
v_depno emp.deptno%type := &v_depno;
deptno_remaining exception;
-- -2292是違反一致性約束的錯誤代碼
pragma exception_init(deptno_remaining, -2292);
begin
delete from emp where deptno = v_depno;
exception
when deptno_remaining then
dbms_output.put_line('違反數據完整性約束');
when others then
dbms_output.put_line(sqlcode || '--' || sqlerrm);
end;
- 當與一個異常錯誤相關的錯誤出現時,就會隱含觸發該異常錯誤。 用戶定義的異常錯誤是通過顯式使用 RAISE 語句來觸發。當引發一個異常錯誤時,控制就轉向到 EXCEPTION 塊異常錯誤部分,執行錯誤處理代碼。
- 對於這類異常情況的處理,步驟如下:
①, 在 PL/SQL 塊的定義部分定義異常情況:
< 異常情況> EXCEPTION;
②, RAISE < 異常情況>;
③, 在 PL/SQL 塊的異常情況處理部分對異常情況做出相應的處理。
- 例:更新指定員工工資,增加 100;若該員工不存在則拋出用戶自定義異常: no_result
declare
v_empid emp.empno%type := &v_empid;
no_result exception;
begin
update emp set sal = sal + 100 where empno = v_empid;
if sql%notfound then
raise no_result;
end if;
exception
when no_result then
dbms_output.put_line('數據更新失敗');
when others then
dbms_output.put_line('其他異常');
end;
-
SQLCODE 返回錯誤代碼數字
-
SQLERRM 返回錯誤信息
-
如: SQLCODE=-100 ------》 SQLERRM=’no_data_found ‘
SQLCODE=0 -----》 SQLERRM=’normal, successfual completion’ -
例 . 查詢 ORACLE 錯誤代碼:
BEGIN
INSERT INTO emp
(empno, ename, hiredate, deptno)
VALUES
(2222, 'Jerry', SYSDATE, 20);
DBMS_OUTPUT.PUT_LINE('插入數據記錄成功!');
INSERT INTO emp
(empno, ename, hiredate, deptno)
VALUES
(2222, 'Jerry', SYSDATE, 20);
DBMS_OUTPUT.PUT_LINE('插入數據記錄成功!');
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLCODE || '---' || SQLERRM);
END;
存儲函數和過程
- ORACLE 提供可以把 PL/SQL 程序存儲在數據庫中,並可以在任何地方來運行它。這樣就叫存儲過程或函數。過程和函數統稱爲 PL/SQL 子程序,他們是被命名的 PL/SQL 塊,均存儲在數據庫中,並通過輸入、輸出參數或輸入/輸出參數與其調用者交換信息。 過程和函數的唯一區別是函數總向調用者返回數據,而過程則不返回數據。
- 建立內嵌函數
語法如下:
CREATE [OR REPLACE] FUNCTION function_name
[ (argment [ { IN | IN OUT }] Type,
argment [ { IN | OUT | IN OUT } ] Type ]
[ AUTHID DEFINER | CURRENT_USER ]
RETURN return_type
{ IS | AS }
<類型.變量的說明>
BEGIN
FUNCTION_body
EXCEPTION
其它語句
END;
- 不帶參數的函數
create or replace function test_fun return date is
v_date date;
begin
select sysdate into v_date from dual;
dbms_output.put_line('我是函數哈^^');
return v_date;
end;
--執行函數
declare v_date date;
begin
v_date := test_fun(); dbms_output.put_line(v_date);
end;
- 獲取某部門的工資總和:
create or replace function get_salary(dep_id emp.deptno%type,
emp_count out number) return number is
v_sum number;
begin
select sum(salary), count(1)
into v_sum, emp_count
from emp
where deptno = dep_id;
return v_sum;
exception
when no_data_found then
dbms_output.put_line('你需要的數據不存在');
when others then
dbms_output.put_line(sqlcode || ':' || sqlerrm);
end;
- 第一種參數傳遞格式稱爲 位置表示法 ,格式爲:
argument_value1[,argument_value2 …]
declare
v_num number;
v_sum number;
begin
v_sum := get_sal(80, v_num);
dbms_output.put_line('80號部門的工資總和:' || v_sum || ',人數' || v_num);
end;
- 第二種參數傳遞格式稱爲 名稱表示法 (必須與函數定義時所聲明的形式參數名稱相同),格式爲:
argument => parameter [,…]
declare
v_num number;
v_sum number;
begin
v_sum := get_sal(emp_count => v_num,deptno => 80);
dbms_output.put_line('80號部門的工資總和:' || v_sum || ',人數' || v_num);
end;
- 第三種參數傳遞格式稱爲 混合表示法(使用位置表示法所傳遞的參數必須放在名稱表示法所傳遞的參數前面):
declare
v_num number;
v_sum number;
begin
v_sum := get_sal(80, emp_count => v_num);
dbms_output.put_line('80號部門的工資總和:' || v_sum || ',人數' || v_num);
end;
-
論採用哪一種參數傳遞方法,實際參數和形式參數之間的數據傳遞只有兩種方法 :傳址法和傳值法。
-
所謂傳址法是指在調用函數時, 將實際參數的地址指針傳遞給形式參數, 使形式參數和實際參數指向內存中的同一區域,從而實現參數數據的傳遞。這種方法又稱作參照法,即形式參數參照實際參數數據。 輸入參數均採用傳址法傳遞數據。
-
傳值法是指將實際參數的數據拷貝到形式參數,而不是傳遞實際參數的地址。默認時, 輸出參數和輸入/ 輸出參數均採用傳值法。
-
刪除指定員工記錄:
create or replace procedure del_emp(v_empid in emp.empno%type) is
no_result exception;
begin
delete from emp where empno = v_empid;
if sql%notfound then
raise no_result;
end if;
dbms_output.put_line('編號爲' || v_empid || '的員工已被除名');
exception
when no_result then
dbms_output.put_line('你要刪除的數據不存在');
when others then
dbms_output.put_line(sqlcode || '--' || sqlerrm);
end;
- ORACLE 使用 EXECUTE 語句來實現對存儲過程的調用:
EXEC[UTE] Procedure_name( parameter1, parameter2…);
- 查詢指定員工記錄:
create or replace procedure query_map(v_empid emp.empno%type,
v_name out emp.ename%type,
v_sal out emp.sal%type) is
begin
select ename, sal into v_name, v_sal from emp where empno = v_empid;
dbms_output.put_line('員工號爲:' || v_empid || '的員工已經找到');
exception
when no_data_found then
dbms_output.put_line('你要查詢的數據不存在');
when others then
dbms_output.put_line(sqlcode || '--' || sqlerrm);
end;
- 調用
declare
v1 emp.ename%type;
v2 emp.sal%type;
begin
query_map(7499, v1, v2);
dbms_output.put_line('姓名' || v1 || ',工資' || v2);
query_map(201, v1, v2);
dbms_output.put_line('姓名' || v1 || ',工資' || v2);
end;
- 計算指定部門的工資總和,並統計其中的職工數量:
create or replace procedure proc_demo(v_depid emp.deptno%type default 10,
v_salsum out emp.sal%type,
v_empcount out number) is
begin
select sum(sal), count(1)
into v_salsum, v_empcount
from emp
where deptno = v_depid;
exception
when no_data_found then
dbms_output.put_line('你要查詢的數據不存在');
when others then
dbms_output.put_line(sqlcode || '--' || sqlerrm);
end;
- 調用
declare
v_num number;
v_sum number;
begin
proc_demo(v_salsum => v_sum, v_empcount => v_num);
dbms_output.put_line('10號部門的工資總額爲:' || v_sum || ',人數爲:' || v_num);
end;
- 在創建存儲過程時, 可使用 AUTHID CURRENT_USER 或 AUTHID DEFINER 選項,以表明在執行該過程時Oracle 使用的權限
- 如果使用 AUTHID CURRENT_USER 選項創建一個過程, 則 Oracle 用調用該 過程的用戶權限執行該過程. 爲了成功執行該過程, 調用者 必須具有訪問該存儲過程體中引用的所有數據庫對象所必須的權限
- 如果用默認的 AUTHID DEFINER 選項創建過程, 則 Oracle 使用過程所有者的特權執行該過程,爲了成功執行該過程,過程的所有者 必須具有訪問該存儲過程體中引用的所有數據庫對象所必須的 權限, 想要簡化應用程序用戶的特權管理, 在創建存儲過程時, 一般選擇 AUTHID DEFINER選項 –-- 這樣就不必授權給需要調用的此過程的所有用戶了.
DROP PROCEDURE [user.]Procudure_name;
觸發器
- 觸發器是許多關係數據庫系統都提供的一項技術。在 ORACLE 系統裏, 觸發器類似過程和函數,都有聲明,執行和異常處理過程的 PL/SQL 塊。
- ORACLE 可以在 DML 語句進行觸發,可以在 DML 操作前或操作後進行觸發,並且 可以對每個行或語句操作上進行觸發。
觸發器組成:
- 觸發事件:即在何種情況下觸發 TRIGGER; 例如:INSERT, UPDATE, DELETE。
- 觸發時間:即該 TRIGGER 是在觸發事件發生之前(BEFORE)還是之後(AFTER)觸發,也就是觸發事件和該 TRIGGER 的操作順序。
- 觸發器本身:即該 TRIGGER 被觸發之後的目的和意圖,正是觸發器本身要做的事情。 例如:PL/SQL 塊。
- 觸發頻率:說明觸發器內定義的動作被執行的次數。即 語句級(STATEMENT) 觸發器和行級(ROW) 觸發器。
語句級(STATEMENT)觸發器:是指當某觸發事件發生時,該觸發器只執行一次;
行級(ROW)觸發器:是指當某觸發事件發生時,對受到該操作影響的每一行數據,觸發器都單獨執行一次
- 創建觸發器的一般語法是:
CREATE [OR REPLACE] TRIGGER trigger_name
{BEFORE | AFTER }
{INSERT | DELETE | UPDATE [OF column [, column …]]}
ON [schema.] table_name
[FOR EACH ROW ]
[WHEN condition]
trigger_body;
- 其中:
BEFORE 和 AFTER 指出觸發器的觸發時序分別爲前觸發和後觸發方式,前觸發是在執行觸發事件之前觸發當前所創建的觸發器,後觸發是在執行觸發事件之後觸發當前所創建的觸發器。
FOR EACH ROW 選項說明觸發器爲行觸發器。 行觸發器和語句觸發器的區別表現在:行觸發器要求當一個 DML 語句操做影響數據庫中的多行數據時,對於其中的每個數據行,只要它們符合觸發約束條件,均激活一次觸發器;而語句觸發器將整個語句操作作爲觸發事件,當它符合約束條件時,激活一次觸發器。 當省略 FOR EACH ROW 選項時BEFORE 和 AFTER 觸發器爲 語句觸發器,而 INSTEAD OF 觸發器則爲行觸發器。
WHEN 子句說明觸發約束條件。Condition 爲一個邏輯表達時,其中必須包含相關名稱,而不能包含查
詢語句,也不能調用 PL/SQL 函數。WHEN 子句指定的觸發約束條件只能用在 BEFORE 和 AFTER 行觸發器
中,不能用在 INSTEAD OF 行觸發器和其它類型的觸發器中。
- 執行 BEFORE 語句級觸發器;
- 對與受語句影響的每一行:
執行 BEFORE 行級觸發器
執行 DML 語句
執行 AFTER 行級觸發器 - 執行 AFTER 語句級觸發器
-
觸發器名可以和表或過程有相同的名字,但在一個模式中觸發器名不能相同。
-
觸發器的限制
CREATE TRIGGER 語句文本的字符長度不能超過 32KB;
觸發器體內的 SELECT 語句只能爲 SELECT … INTO … 結構,或者爲定義遊標所使用的 SELECT 語句。
觸發器中不能使用數據庫事務控制語句 COMMIT; ROLLBACK, SVAEPOINT 語句;
由觸發器所調用的過程或函數也不能使用數據庫事務控制語句; -
建立一個觸發器,當職工表emp表被刪除一條記錄時,把被刪除記錄寫到職工表刪除日誌表中去
create table emp_his as select * from emp where 1=2
create or replace trigger del_emp_trigger
before delete on emp
for each row
begin
insert into emp_his
(deptno, empno, ename, job, mgr, sal, comm, hiredate)
values
(:old.deptno,
:old.empno,
:old.ename,
:old.job,
:old.mgr,
:old.sal,
:old.comm,
:old.hiredate);
end;
- 刪除觸發器 :
DROP TRIGGER trigger_name;
當刪除其他用戶模式中的觸發器名稱,需要具有 DROP ANY TRIGGER 系統權限,當刪除建立在數據庫上的觸發器時,用戶需要具有 ADMINISTER DATABASE TRIGGER 系統權限。此外,, 當刪除表或視圖時,建立在這些對象上的觸發器也隨之刪除。