1. 程式人生 > >Oracle (07)遊標物件.智慧迴圈(FOR) 遍歷遊標.NULL值的比較.異常處理.儲存過程.如何指定引數的模式.function 函式.包 package.觸發器 trigger

Oracle (07)遊標物件.智慧迴圈(FOR) 遍歷遊標.NULL值的比較.異常處理.儲存過程.如何指定引數的模式.function 函式.包 package.觸發器 trigger

遊標物件 熟練

遊標就是查詢結果的容器中游動的標記 !

使用的步驟與語法:

  1. 宣告一個遊標, 並繫結一個select語句

    在宣告區中定義: cursor 遊標變數名稱 is 查詢語句;

  2. 開啟遊標
    在執行區操作:
    open 遊標變數;

  3. 控制遊標向下移動, 提取一行資料
    fetch 遊標變數 into record型別變數;

    如果結果集中存在的資料 只有一列, 可以賦值給基本資料型別(number varchar2…)

  4. 關閉遊標 釋放資源

    因為資料庫的連線控制代碼只有1024個 ,所以在連線完畢後, 需要釋放資源 !

案例

定義一個遊標, 結果集中包含了 s_emp表格中的id,salary,last_name欄位, 然後提取遊標的前3行資料, 並將其列印到控制檯: 

set serveroutput on;
declare
–宣告遊標 , 並關聯查詢語句
cursor myec is select id,salary,last_name from s_emp;
–定義一個與遊標中屬性一致的 record型別變數
ecr myec%rowtype;

begin

--開啟遊標
open myec;

--迴圈取出前三條
for i in 1..3 loop
    --隨著每次迴圈, 遊標向下移動, 並取出行內資料
    fetch myec into ecr;
    --輸出列印這些列的內容
    dbms_output.put_line('員工編號:'||ecr.id||',員工姓名:'||ecr.last_name||',員工月薪:'||ecr.salary);
end loop;

close myec;

end;

通過多表查詢 ,關聯遊標, 並進行列印輸出

查詢 s_dept 和 s_region表格, 並取出s_dept(id,name),s_region(name)

set serveroutput on;
declare
cursor s_dr_cursor is select d.id did,d.name dname,r.name rname from s_dept d,s_region r where d.region_id=r.id;

mydr s_dr_cursor%rowtype;

begin
open s_dr_cursor;

fetch s_dr_cursor into mydr;
dbms_output.put_line('部門編號:'||mydr.did||',部門名稱:'||mydr.dname||',地區名稱:'||mydr.rname);

close s_dr_cursor;

end;

遊標常用屬性: 熟練

  • 遊標%rowtype : 得到與遊標屬性一致的 record型別 !

  • 遊標%found : 判斷上次遊標是否取到了資料 , 取到資料則返回true , 否則返回false;

  • 遊標%notfound:判斷上次遊標是否取到了資料 , 取到資料則返回false , 否則返回true;

遊標%found or 遊標%notfound 在使用時限制:

-   遊標必須是開啟的 , 
-   並且在呼叫此屬性時, 遊標必須是經過下移的! 否則獲取結果為null

練習:

通過迴圈遍歷上題:

通過多表查詢 ,關聯遊標, 並進行列印輸出

查詢 s_dept 和 s_region表格, 並取出s_dept(id,name),s_region(name)

set serveroutput on;
declare
cursor s_dr_cursor is select d.id did,d.name dname,r.name rname from s_dept d,s_region r where d.region_id=r.id;
mydr s_dr_cursor%rowtype;
begin
open s_dr_cursor;

loop
    fetch s_dr_cursor into mydr;

    --如果上次遊標下移, 沒有獲取到資料, 則退出迴圈
    exit when s_dr_cursor%notfound;

    dbms_output.put_line('部門編號:'||mydr.did||',部門名稱:'||mydr.dname||',地區名稱:'||mydr.rname);
end loop;
close s_dr_cursor;

end;

練習

將上述案例, 改造為while迴圈

  1. 什麼是迴圈條件

  2. 什麼是退出迴圈條件

set serveroutput on;
declare
cursor s_dr_cursor is select d.id did,d.name dname,r.name rname from s_dept d,s_region r where d.region_id=r.id;
mydr s_dr_cursor%rowtype;
begin
open s_dr_cursor;
fetch s_dr_cursor into mydr;
while s_dr_cursor%found loop
dbms_output.put_line(‘部門編號:’||mydr.did||’,部門名稱:’||mydr.dname||’,地區名稱:’||mydr.rname);
fetch s_dr_cursor into mydr;
end loop;
close s_dr_cursor;

end;

遊標其他屬性

  • 遊標變數%isopen: 判斷遊標是否開啟, 開啟則返回true , 否則返回false

  • 遊標變數%rowcount: 獲取當前遊標所在資料行的 偏移量 !

set serveroutput on;
declare
cursor s_dr_cursor is select d.id did,d.name dname,r.name rname from s_dept d,s_region r where d.region_id=r.id;
mydr s_dr_cursor%rowtype;
begin
open s_dr_cursor;
fetch s_dr_cursor into mydr;
while s_dr_cursor%found loop
dbms_output.put_line(‘部門編號:’||mydr.did||’,部門名稱:’||mydr.dname||’,地區名稱:’||mydr.rname);
fetch s_dr_cursor into mydr;

    dbms_output.put_line('當前遊標開啟狀態:'||s_dr_cursor%isopen||',當前遊標偏移量:'||s_dr_cursor%rowcount);

end loop;
close s_dr_cursor;

end;

智慧迴圈(FOR) 遍歷遊標 ***

for迴圈遍歷遊標時:

-   自動定義record變數
-   自動開啟遊標
-   自動關閉遊標
-   每次迴圈自動提取一行資料 給到record變數

案例:

set serveroutput on;

declare
cursor s_dr_cursor is select d.id did,d.name dname,r.name rname from s_dept d,s_region r where d.region_id=r.id;
begin
for mydr in s_dr_cursor loop
dbms_output.put_line(‘部門編號:’||mydr.did||’,部門名稱:’||mydr.dname||’,地區名稱:’||mydr.rname);
end loop;
end;

引數遊標

在遊標開啟時, 可以傳遞引數到查詢語句中

步驟格式:

1.  定義遊標時, 在遊標變數名稱後,新增形式引數列表;
    cursor 遊標變數名稱(形式引數列表) is 查詢語句;
        列表中的形式引數, 可以在select語句中使用;

2.  在開啟遊標時, 在遊標變數名稱後, 輸入實際引數列表!
    open 遊標變數名稱(實際引數列表);

案例:

提示使用者輸入要查詢的員工編號. 根據使用者輸入的編號查詢s_emp表格, 並輸出id,last_name,salary資訊

set serveroutput on;
declare
cursor emp_cursor(empid number) is select id,last_name,salary from s_emp where id=empid;
empid number;
myemp emp_cursor%rowtype;
begin
empid := &請輸入要查詢的員工編號;
open emp_cursor(empid);
fetch emp_cursor into myemp;
dbms_output.put_line(‘資訊查詢完畢’);
if emp_cursor%found then
dbms_output.put_line(‘員工編號:’||myemp.id||’,員工姓名:’||myemp.last_name||’,員工月薪:’||myemp.salary);
else
dbms_output.put_line(‘您指定的員工編號不存在!’);
end if;

end;

參考遊標

參考遊標在宣告遊標時, 不用指定查詢語句, 可以在遊標的開啟時 繫結一個語句 !

也就是說, 可以在宣告區不指定查詢語句 來建立遊標, 等到執行區程式碼執行時, 再去繫結查詢語句 進行操作!

語法格式:

  1. 宣告遊標型別
    type 遊標名稱 is ref cursor;

  2. 宣告遊標變數

    變數名 遊標型別名稱;

  3. 開啟遊標 並繫結查詢語句
    open 遊標變數 for ‘查詢語句字串’;

案例:

提示使用者操作, 根據使用者的選擇, 執行不同的SQL語句

提示使用者輸入要查詢的資訊 為 部門|員工, 然後再根據使用者的提示開啟遊標繫結不同的查詢語句!

set serveroutput on;

declare
–定義參考遊標型別
type mycursor is ref cursor;
– 根據上面的型別, 定義一個變數
mc mycursor;
– 字串型別的變數, 用來後期指定查詢語句
select_sql varchar2(500);
– 接收使用者輸入的變數, 用來判斷使用者選擇的操作
user_type number;
– 接收使用者輸入的查詢id
user_id number;
–用來接收查詢的員工薪資
usersalary number;
–用來接收查詢的部門名稱
user_deptname varchar2(500);
begin
dbms_output.put_line(‘歡迎進入公司管理系統:’);
dbms_output.put_line(‘請輸入您要查詢的資訊:’);
dbms_output.put_line(‘1.查詢員工資訊’);
dbms_output.put_line(‘2.查詢部門資訊’);
user_type := &請輸入1員工查詢2部門查詢;

if user_type =1 then
    --使用者要查詢的是員工資訊
    user_id := &請輸入要查詢的員工id;
    select_sql := 'select salary from s_emp where id='||user_id;

    --開啟遊標, 並繫結sql語句
    open mc for select_sql;
    fetch mc into usersalary;
    dbms_output.put_line('您查詢的員工月薪為:'||usersalary);
    close mc;
elsif user_type=2 then
    --使用者要查詢的時部門資訊
    user_id := &請輸入要查詢的部門id;
    select_sql := 'select name from s_dept where id='||user_id;
    --開啟遊標 並繫結查詢語句
    open mc for select_sql;
    fetch mc into user_deptname;
    dbms_output.put_line('查詢的部門名稱為:'||user_deptname);
    close mc;
else
    --使用者輸入錯誤
    dbms_output.put_line('您輸入的選項有誤');
end if;

end;

NULL值的比較***

在PL/SQL中, null值參與比較運算, 不會出現結果!

如果使用null參與if運算, 永遠進不去任何的if段 , 必進else!

面試題: 觀察如下程式碼 ,分析列印的結果為哪句話, 並說出原因!

A. 12345程式執行結束 B. 3程式執行結束
C. 程式執行結束 D. 5程式執行結束 ✔

set serveroutput on;

declare
x number := 10;
y number;
begin
if x>y then
dbms_output.put_line(‘1’);
elsif x=y then
dbms_output.put_line(‘2’);
elsif x!=y then
dbms_output.put_line(‘3’);
elsif x<y then
dbms_output.put_line(‘4’);
else
dbms_output.put_line(‘5’);
end if;
dbms_output.put_line(‘程式執行結束’);
end;
/

異常處理

  1. 非執行時異常 (受檢異常)

  2. 執行時異常 (非受檢異常)

PL/SQL中的執行時異常

set serveroutput on;
declare
m_last_name s_emp.last_name%type;
begin
select last_name into m_last_name from s_emp where id=-100;
dbms_output.put_line(m_last_name);
end;

上述程式碼 出現瞭如下錯誤:

錯誤報告:
ORA-01403: 未找到任何資料
ORA-06512: 在 line 4
01403. 00000 - “no_data_found”

異常處理格式 ***

begin

exception
–當begin中出現執行時異常後, exception塊自動執行
when 異常型別1 then
–異常型別匹配時的處理塊
when 異常型別2 then

...

when 異常型別n then
    --異常型別匹配時的處理塊
when others then
    --當上面所有的異常型別都不匹配時,  或 上面不存在任何的異常型別時 ,由這裡處理異常!

end;

使用上述的異常處理格式, 處理程式碼的異常

set serveroutput on;
declare
m_last_name s_emp.last_name%type;
begin
select last_name into m_last_name from s_emp where id=-100;
dbms_output.put_line(m_last_name);
exception
when others then
dbms_output.put_line(‘程式出現了異常. 哈哈哈哈哈’);
end;

針對異常型別 進行異常處理

set serveroutput on;
declare
m_last_name s_emp.last_name%type;
begin
select last_name into m_last_name from s_emp where id=-100;
dbms_output.put_line(m_last_name);
exception

when no_data_found then
    dbms_output.put_line('程式資料查詢失敗. 哈哈哈哈哈');
when others then
    dbms_output.put_line('程式出現了其他異常. 哈哈哈哈哈');

end;

儲存過程 *****

把一組邏輯相關的SQL語句 或 PL/SQL語句 組織到一起的一個程式碼結構, 我們稱之為過程 !

定義上 與 Java中的方法很像, 但是不是!

儲存過程不存在返回值 !

建立儲存過程的語法格式:

create or replace procedure 過程名稱(形參列表)
is
    /*宣告區*/
begin
    /*程式碼執行區*/
end;

呼叫儲存過程的語法:

在sql> 中通過兩種方式呼叫:
1. call 過程名稱(實參列表);
2. exec 過程名稱(實參列表);

在匿名過程塊中:
3.
begin
過程名稱(實參列表);
end;

刪除儲存過程

drop procedure 儲存過程名稱;

練習

編寫一個儲存過程 , 用來比較兩個引數的大小, 並輸出較大的一個

(建議大家在手生時, 編寫儲存過程時, 先編寫匿名塊, 再改造 )

set serveroutput on;
declare
x number := 1000;
y number := 2000;
begin
if x>y then
dbms_output.put_line(x);
elsif x<y then
dbms_output.put_line(y);
else
dbms_output.put_line(‘兩個數字沒有誰大誰小’);
end if;
end;

將上述的程式碼 改造為儲存過程, 並嘗試呼叫:
create or replace procedure max15(x number,y number)
is
begin
if x>y then
dbms_output.put_line(x);
elsif x<y then
dbms_output.put_line(y);
else
dbms_output.put_line(‘兩個數字沒有誰大誰小’);
end if;
end;

set serveroutput on;
call max15(150,160);
exec max15(150,160);

儲存過程形式引數 指定預設值 ***

案例:

drop procedure max15;
create or replace procedure max15(x number:=100,y number:=200)
is
begin
if x>y then
dbms_output.put_line(x);
elsif x<y then
dbms_output.put_line(y);
else
dbms_output.put_line(‘兩個數字沒有誰大誰小’);
end if;
end;

set serveroutput on;
call max15(150,160);

在匿名塊中呼叫儲存過程 ***

set serveroutput on;
begin
max15(1,10);
end;

查詢儲存過程結構

格式: desc 過程名稱;

案例: desc max15;

查詢的結果如下:

SQL> desc max15;
PROCEDURE max15
引數名稱 型別 輸入/輸出 預設值?


X NUMBER IN DEFAULT
Y NUMBER IN DEFAULT

  • 輸入/輸出:
    描述的是引數的操作模式:
    取值:
    - in: 只讀,預設模式, 表示引數在過程中只讀
    - out:只寫, 引數在過程中不可用來讀取使用 , 只能用來賦值!
    - in out: 可讀可寫, 在過程中 既可以讀取資料, 又可以給引數賦值!

    如果引數的模式為out  或者為in out , 過程在呼叫時, 傳遞的引數必須時變數.
    
  • 預設值? :
    表示引數在過程中, 是否存在預設值, default表示存在預設值!

如何指定引數的模式 ***

在定義儲存過程時, 編寫形式引數列表時 可以指定引數的使用模式

格式:

... 過程名(引數名 模式 引數型別); 模式可選: in/out/in out

案例:

編寫一個儲存過程, 擁有三個number型別的引數, 比較第一個引數和第二個引數 , 將最大的值 儲存在第三個引數中.

create or replace procedure max15_2(x number,y number,z out number)
is
begin
if x>y then
z := x;
elsif y>x then
z := y;
else
z :=x;
end if;
end;

在匿名塊中呼叫:

set serveroutput on;
declare
max1 number;
begin
max15_2(100,200,max1);

dbms_output.put_line('max的值為:'||max1);

end;

根據形式引數的名稱, 傳遞值

語法格式:

在呼叫時,  

儲存過程名稱(引數名稱=>值);

create or replace procedure max15(x number:=100,y number:=200)
is
begin
if x>y then
dbms_output.put_line(x);
elsif x<y then
dbms_output.put_line(y);
else
dbms_output.put_line(‘兩個數字沒有誰大誰小’);
end if;
end;

set serveroutput on;
declare

begin
max15(y=>50);

end;

function 函式 *

函式的概念 基本上可以理解為Java中的方法 .

與儲存過程概念也很相似, 都是把一組邏輯相關的SQL語句 / PLSQL語句 組織到一起的程式碼結構!

函式與儲存過程的區別:

1.  名稱不同 , 函式的關鍵字是function , 儲存過程的關鍵字procedure
2.  函式可以通過return返回資料, 而儲存過程沒有返回值!
3.  呼叫方式不同 , 
    -   儲存過程在呼叫時, 可以是pl/sql中的一部分, 也可以在sql>中直接call
    -   函式在呼叫時, 必須組成表示式, 才可以使用!

宣告函式的語法格式:

create or replace function 函式名(形參列表) return 返回值型別
    is
    /*宣告區*/
    begin
        return 值;
    end;

檢視函式結構:

desc 函式名;

刪除函式

drop function 函式名;

案例:

建立一個函式,  兩個number型別引數 ,返回比較引數的最大值 .

create or replace function mymax15(x numnber,y number) return number

is
begin
    if x>y then
        return x;
    end if;
    return y;
end;

回顧之前呼叫單行函式 和 組函式

select length(‘12345’) from dual;

呼叫剛編寫的函式:

select mymax15(100,300) from dual;

練習:

編寫一個函式 , 傳入兩個值, 返回其中的最大值, 並將兩個引數的和 儲存到第二個引數中, 在程式碼中不允許使用第三方變數:

create or replace function maxsum15(x number,y in out number) return number

is

begin

if x<y then
    y := x+y;
    return y-x;
end if;
    y := x+y;
    return x;

end;

在匿名塊中呼叫 與java很像:

set serveroutput on;

declare
–用來接收返回值
var_max number;
var_x number := 300;
var_y number := 1000;
begin
var_max := maxsum15(var_x,var_y);
dbms_output.put_line(‘兩個數字的和為:’||var_y||’,最大值為:’||var_max);

end;

包 package

把相關的 函式, 過程, 型別 等等放到一起的一個邏輯結構 !

dbms_output: 系統輸出包

函式:

  • put_line(文字); 輸出到控制檯

dbms_random: 隨機數包

函式:

  • value(引數1,引數2)
    引數1. 隨機數字產生的最小範圍 number型別 in模式
    引數2. 隨機數字產生的最大範圍 number型別 in模式

    返回值: number型別的浮點型隨機數

案例:

獲取一個0-10000的隨機數字

select dbms_random.value(0,10000) from dual;

隨機獲取一個0-10000的隨機數 , 並取整

select trunc(dbms_random.value(0,10000)) from dual;

dbms_job 定時任務包

dbms_lob: 與定時任務包很像, 容易混淆, 是用來讀取和寫入大文字和二進位制的包!

常用函式:

  1. submit(引數1,引數2,引數3,引數4);

    • 用來提交定時任務到資料庫管理系統
    • 引數1.job(out模式的binary_integer型別) , 是用來提交定時任務時 接收任務編號的!
    • 引數2.what(varchar2):要呼叫的儲存過程的名稱; 例如:‘過程名();’
    • 引數3.next_date(date): 第一次執行任務的時間
    • 引數4.interval(varchar2) 呼叫的間隔時間
      引數4, 要傳遞的是一個字串, 但是字串中必須是date型別 , 例如: ‘sysdate+1’;
      間隔時間的計算方式 , 引數4-引數3
  2. run(任務編號 binary_integer型別): 執行資料庫管理系統中已提交的某定時任務!

  3. remove(任務編號 binary_integer型別);移除資料庫管理系統中已提交的某定時任務! 無論任務是否在執行都會被移除!

定時任務案例

定時向dongfei表格中, 插入一行資料 10秒插入一次!

  1. 建立一個表格

    drop table dongfei cascade constraints;

    create table dongfei(
    id number constraint dongfei_id_pk primary key,
    name varchar2(20)
    length number
    );

  2. 定義一個序列, 用來插入id欄位
    drop sequence dongfei_id_seq;
    create sequence dongfei_id_seq;

    • 序列中如何獲取當前的值
      序列.currval
    • 序列中如何獲取下一個值:
      序列.nextval
  3. 定義一個儲存過程, 插入dongfei一行資料
    drop procedure xdongfei;
    create or replace procedure xdongfei
    is
    begin
    insert into dongfei values(dongfei_id_seq.nextval,‘董飛鈦合金版’,2);
    insert into dongfei values(dongfei_id_seq.nextval,‘董飛回旋專版’||dongfei_id_seq.currval,5);
    end;

  4. 在匿名塊中 建立定時任務 並提交, 然後啟動

    set serveroutput on;
    declare
    job_id binary_integer;
    begin
    dbms_job.submit(job_id,‘xdongfei();’,sysdate,‘sysdate+(1/(1440*6))’);
    dbms_job.run(job_id);
    dbms_output.put_line(‘本次任務的id:’||job_id);
    end;

  5. 刪除定時任務

    begin
    dbms_job.remove(1);
    end;

觸發器 trigger

概念:

在對資料庫表格 進行 dml操作時 , 我們可以對其新增觸發器 !

新增觸發器後, 當指定的dml操作發生時, 觸發器會自動執行指定的一段過程 !

行級觸發器:
針對每一行資料, 觸發一次邏輯 !

語句級觸發器:
每個dml操作語句 ,無論 修改 了多少條資料 , 都只觸發一次!

建立觸發器的語法格式:

create or replace trigger 名稱 before|after insert|update|delete on 表名 [for each row]
declare

begin
    觸發器執行時的程式碼塊
end;

[for each row]:  在建立語句上新增, 表示將語句級觸發器 修改為 行級觸發器

案例:

偵聽 dongfei 表格的插入操作

create or replace trigger dongfei_insert_trigger after insert on dongfei
declare

begin
    dbms_output.put_line('有人在插入dongfei表格');
end;


插入資料:
set serveroutput on;
 insert into dongfei values(dongfei_id_seq.nextval,'董飛上天版',20);

在觸發器中 獲取原資料 和 新資料

在觸發器的begin 到 end之間, 可以使用兩個已存在的物件, 操作新資料和舊資料 !

:old 舊資料 和 :new 新資料

看作自定義物件 , 通過:new.列名, 即可獲取新資料中 某一列的值!

DML操作分為三種:

1.  insert語句    : 只存在新資料, 沒有舊資料!

2.  update語句    : 存在新資料和舊資料!

3.  delete語句    : 只有舊資料 , 沒有新資料

編寫一個偵聽修改的觸發器, (行級)

create or replace trigger dongfei_update_trigger after update on dongfei for each row
declare

begin
dbms_output.put_line(‘有人在修改董飛:舊董飛的名字:’||:old.name||’,新的董飛名稱:’||:new.name);
end;

set serveroutput on;
update dongfei set name=’***’;

作業

  1. 編寫一個儲存過程, 存在兩個number型別形式引數,

    比較大小, 並將較大的資料, 儲存到第二個引數的位置 !

  2. 編寫一個儲存過程, 存在兩個number型別形式引數,

    比較大小, 並將較大的資料, 儲存到第二個引數的位置 !

    求和, 將和儲存到第一個引數的位置!