天天看點

Oracle資料庫-03 PL/SQL程式設計、遊标、存儲過程

一、PL/SQL基礎知識

1、PL/SQL簡介

  • PL/SQL概述

    PL/SQL(Procedural Language)是Oracle在關系資料庫結構化查詢語言SQL 基礎上擴充得到的一種過程化查詢語言。

  • SQL與PL/SQL

    SQL與程式設計語言之間的不同在于,SQL沒有變量,沒有流程控制(分支、循環)。而PL/SQL是結構化和過程化的結合體,而且最為重要的是,在使用者執行多條SQL語句時,每條SQL語句都是逐一的發送給資料庫,而PL/SQL可以一次性将多條SQL語句一起發送給資料庫,減少網絡流量。

  • PL/SQL的工作原理

    (1)PL/SQL 引擎接收PL/SQL塊并對其進行編譯執行

    (2)該引擎執行所有過程語句

    (3)将SQL語句發送給Oracle的SQL語句執行器

    Oracle資料庫-03 PL/SQL程式設計、遊标、存儲過程
  • PL/SQL優點

(1)與SQL緊密內建,簡化資料處理

  • 支援SQL,在PL/SQL中可以使用:資料操縱指令、事務控制指令、遊标控制、SQL函數和SQL運算符。
  • 支援所有SQL資料類型
  • 支援NULL值
  • 支援 %TYPE 和 %ROWTYPE 屬性類型

(2)更佳的性能,PL/SQL經過編譯執行

(3)支援面向程式設計(OOP)

2、PL/SQL塊

文法結構:

DECLARE
     --聲明部分:如定義變量、遊标和自定義異常
BEGIN
     --程式編寫,包含SQL和PL/SQL語句的可執行部分
     
EXCEPTION /*異常處理語句*/
  
     --指定出現錯誤時,需要執行的操作
END;
 /
           

說明:

  • 聲明部分(DECLARE):包含變量定義、使用者定義的PL/SQL類型、遊标、引用的函數或過程。
  • 執行部分(BEGIN):包含變量指派、對象初始化、條件結構、疊代結構、嵌套的PL/SQL匿名塊,或是對局部或存儲PL/SQL命名塊的調用。
  • 異常部分(EXCEPTION):包含錯誤處理語句,該語句可以像執行部分一樣使用所有項。
  • 結束部分(END):程式執行到END表示結束,分号用于結束匿名塊,而正斜杠 (/) 執行塊程式。

PL/SQL塊範例:

DECLARE
        qty_on_hand NUMBER(5);
BEGIN
        SELECT quantity INTO qty_on_hand
           FROM Products
        WHERE product = '玩具熊';
        IF qty_on_hand > 0 THEN
             UPDATE Products SET quantity = quantity + 1
              WHERE product = '玩具熊';
             INSERT INTO purchase_record
                     VALUES ('已購買玩具熊', SYSDATE);
        END IF;
        COMMIT;
EXCEPTION  /* 異常處理語句 */
        WHEN OTHERS THEN
                 DBMS_OUTPUT.PUT_LINE('出錯:'|| SQLERRM);  
END;


           

3、PL/SQL變量

聲明變量文法:

-- 聲明變量文法結構
DECLARE variable_name [CONSTANT] type [NOT NULL] [:=value];
           

說明:

  • 使用 DECLARE 關鍵字進行聲明
  • variable_name: 表示變量名稱
  • CONSTANT:表示是否是常量
  • type:表示變量的資料類型
  • NOT NULL :是否為空
  • :=value

    :初始化變量
  • [ ]方括号表示可選項

變量命名規則:

  • 變量名首字母必須是英文字母
  • 其後可以是字母、數字或者特殊符号$、#和下劃線
  • 變量名長度不超過30個字元
  • 變量名中不能有空格
  • 不能是SQL保留字
    Oracle資料庫-03 PL/SQL程式設計、遊标、存儲過程

PL/SQL支援的内置資料類型如圖:

Oracle資料庫-03 PL/SQL程式設計、遊标、存儲過程

4、表達式和運算符

(1)表達式的分類

  • 數值型
  • 字元型
  • 日期型
  • 布爾型

(2)運算符分類

  • 算術運算符
  • 關系運算符
  • 邏輯運算符
  • 其他運算符

5、變量的指派及注釋的使用

/*
|| 在注釋開始的首行隻放斜線星号,标志注釋開始,
|| 然後注釋塊的每一行以雙垂直線開頭,突出注釋後面
|| 的内容是注釋部分,最後,将星号斜線單置于一行
*/
DECLARE
       v_ename VARCHAR2(20);
       v_rate NUMBER(7,2);
       c_rate_incr CONSTANT NUMBER(7,2):=1.10;
BEGIN
       --方法一:通過SELECT INTO給變量指派
       SELECT ename, sal* c_rate_incr  
             INTO   v_ename, v_rate
          FROM   employee 
       WHERE empno='7788';
      --方法二:通過指派操作符“:=”給變量指派
      v_ename:='SCOTT';
END;

           

6、控制語句

(1)條件控制

  • IF語句
-- 例1
DECLARE
  v_str varchar2(10):='b';
BEGIN
  IF(v_str='a') THEN dbms_output.put_line('if');
  ELSIF(v_str='b') THEN  dbms_output.put_line('elsif');
  ELSE dbms_output.put_line('else');
  END IF;
END;


-- 例2
DECLARE
   i constant number:=5;
BEGIN
  IF(i=4) THEN null; --如果什麼都不做的話,那麼補null來保證語句的完整性
  ELSIF(i=4) THEN dbms_output.put_line(i);
  ELSE dbms_output.put_line('else');
  END IF;
END;


-- 例3:驗證如果不給字元變量指派時預設是空字元串還是為空
DECLARE
  i varchar2(10);	--字元類型
BEGIN
  IF(i='') THEN dbms_output.put_line('空字元串');
  ELSIF(i is null) THEN dbms_output.put_line('null');
  ELSIF(i='a') THEN dbms_output.put_line('a');
  ELSE dbms_output.put_line('b');
  END IF;
END;



-- 例4
DECLARE
  i number;	-- 數值類型
BEGIN
  IF(i='') THEN dbms_output.put_line('空字元串');
  ELSIF(i is null) THEN dbms_output.put_line('null');
  ELSIF(i='a') THEN dbms_output.put_line('a');
  ELSE dbms_output.put_line('b');
  END IF;
END;

           
  • CASE語句
-- 例1:CASE語句
--方法一
DECLARE
       grade char:='A';
       remark varchar2(20);
BEGIN
       CASE grade
            WHEN 'A' THEN remark:='is Excellent';
            WHEN 'B' THEN remark:='is Good';
            WHEN 'C' THEN remark:='is Normal';
            WHEN 'D' THEN remark:='is Bad';
            ELSE remark:='big Problem';
       END CASE;
       dbms_output.put_line(remark);
END;



--例2:CASE語句
--方法二
DECLARE
       grade NUMBER:=70;
       remark varchar2(20);
BEGIN
       CASE
            WHEN grade=100 THEN remark:='is Excellent';
            WHEN grade>=80  THEN remark:='is Good';
            WHEN grade>=60  THEN remark:='is Normal';
            WHEN grade>=50 THEN remark:='is Bad';
            ELSE remark:='big Problem';
       END CASE;
        dbms_output.put_line(remark);
END;

           

(2)循環控制

  • LOOP循環
- 例1:簡單循環loop
declare
   i number:=0;
begin
  loop
    i:=i+1; --循環為i+1
    dbms_output.put_line('i='||i);          --輸出i的值
    if(i=30) then exit;       --i的值到30,則退出
    end if;
   end loop;
end;


-- 例2:簡單循環loop
declare
   i number:=0;
begin
  loop
    i:=i+1;
    dbms_output.put_line('i='||i);
    exit when (i=30);		--使用when關鍵字
    end loop;
end;


-- 例3:嵌套循環
declare
   i number:=0;
   b number:=0;
begin
   loop
     i:=i+1;
     dbms_output.put_line('i>>>>>>='||i);
     exit when i=5;
     b:=0;
     --嵌套循環
     loop
       b:=b+1;
       dbms_output.put_line('b='||b);
       exit when b=2;
       end loop;
     end loop;
end;

           
  • WHILE循環
-- while循環
declare
  a constant number:=1;
  b constant number:=10;
  i number:=0;
begin
  while (b>a) loop
    i:=i+1;
    dbms_output.put_line('i='||i);
    exit when i=5;
    end loop;
end;

           
  • FOR 循環
-- 例1:for循環
declare
  a constant number:=4;
  b constant number:=10;
begin
  for i in a ..b loop
      dbms_output.put_line('i:'||i);
   end loop;
end;


-- 例2:for循環
declare
  a constant number:=1;
  b constant number:=10;
begin
  for i in reverse a ..b loop		--使用reverse關鍵字實作倒序周遊
    dbms_output.put_line('i+>>>>>'||i);
    end loop;
end;

           

7、編碼規則

  • 利用縮進排列展現邏輯結構
  • 利用大小寫增強可讀性
  • 格式化單獨語句
  • 格式化SQL語句
  • 規範注釋
    Oracle資料庫-03 PL/SQL程式設計、遊标、存儲過程

二、PL/SQL異常處理

1、異常的分類

  • 在運作程式時出現的錯誤稱為異常。
  • 發生異常後,語句将停止執行,控制權轉移到PL/SQL塊的異常處理部分。
  • 異常有兩種類型:

    (1)預定義異常:當PL/SQL程式違反Oracle規則或超越系統限制時隐式引發。

    (2)使用者定義異常:使用者可以在PL/SQL塊的聲明部分定義異常,自定義的異常通過RAISE語句顯式引發。

2、如何使用

PL/SQL預定義異常文法:

--預定義異常
BEGIN
        --過程及SQL語句;
EXCEPTION
        WHEN --異常名稱THEN
                -- 過程及SQL語句;
        WHEN OTHERS THEN
                --過程及SQL語句;
END;
           

預定義異常分類如圖:

Oracle資料庫-03 PL/SQL程式設計、遊标、存儲過程

預定義異常範例:

declare
    v_ename char(1);
begin
      select ename into v_ename
      from emp
      where empno=7788;
      dbms_output.put_line('員工姓名為:'||v_ename);
      Exception
         when no_data_found then    --處理沒有資料的異常
              dbms_output.put_line('沒有該員工');
         when value_error then
              dbms_output.put_line('該員工名稱過長');
         when others then
               dbms_output.put_line('異常編碼:'||sqlcode||',異常資訊:'||sqlerrm);         
end;

           

PL/SQL中的使用者自定義異常處理:

  • 使用 RAISE 關鍵字引發異常
  • 使用 EXCEPTION 關鍵字處理異常
    Oracle資料庫-03 PL/SQL程式設計、遊标、存儲過程

使用者自定義異常範例:

-- 示範自定義異常
declare
   v_eno Scott.emp.empno%type:=7839;
   v_deptno  Scott.emp.deptno%type;
   v_sal  Scott.emp.sal%type;
   e_money_big exception;
begin
   --根據員工編号查詢員工所屬部門和工資
   select deptno,sal into v_deptno,v_sal
   from Scott.emp
   where empno=v_eno;
   --判斷,如果部門編号為10
     if v_deptno=10 then
         --判斷工資是否低于10000
         if v_sal<10000 then
              update Scott.emp set sal=10000 where empno=v_eno;
              commit;
         else
              raise e_money_big;
         end if;
     end if;
     
     --異常處理
     exception
       when e_money_big then
           dbms_output.put_line('工資不低于10000元');
       when others then
           dbms_output.put_line('異常編碼:'||sqlcode||',異常資訊:'||sqlerrm);     
end; 

           

三、遊标

1、遊标概述

在使用SQL編寫查詢語句時,所有的查詢結果會直接顯示給使用者,但是在很多情況下,使用者需要對傳回結果中的每一條資料分别進行操作,則這個時候普通的查詢語句就無法使用了,那麼就可以通過結果集(由查詢語句傳回完整的行集合叫做結果集)來接收,之後就可以利用遊标來進行操作。

2、遊标分類

在Oracle資料庫之中,遊标分為以下兩種類型:

(1)靜态遊标: 結果集已經存在(靜态定義)的遊标。分為隐式遊标和顯式遊标。

  • 隐式遊标:所有DML語句為隐式遊标,通過隐式遊标屬性可以擷取SQL語句資訊。
  • 顯式遊标:使用者顯式聲明的遊标,即指定結果集。當查詢傳回結果超過一行時,就需要一個顯式遊标。

(2)REF遊标: 動态關聯結果集的臨時對象。

遊标的屬性如圖:

Oracle資料庫-03 PL/SQL程式設計、遊标、存儲過程

3、顯式遊智語法和特點

遊标流程:

  • 定義遊标
  • 打開遊标
  • 提取資料
  • 關閉遊标

(1)遊标的聲明:

-- 遊标的聲明
CURSOR cursor_name [ ( parameter [ , parameter]……)]
[ RETURN return_type ] IS selectsql
           

說明:

  • CURSOR:聲明遊标關鍵字
  • cursor_name :遊标名稱
  • parameter:可選參數,用于指定參數類型、模式等
  • return:可選,指定遊标的傳回類型
  • selectsql:需要處理的select語句,不能包含 into 子句。

(2)打開遊标:

--使用open語句開啟一個遊标
	open cursor_name;
           

(3)提取遊标

  • 使用FETCH語句實作對遊标内容的讀取
  • variable_list 必須從遊标提取的結果集類型相同
-- 提取遊标
	FETCH cursor_name INTO variable_list
           

(4)關閉遊标

  • 使用close語句關閉遊标
  • 關閉遊标後,所有資源都将被釋放
-- 關閉遊标
	close cursor_name
           

遊标範例: 使用fetch提取遊标的資料到變量中 必須手動打開關閉遊标

declare
   	    --聲明遊标 查詢所有員工,将結果集存放到cursor_emp變量中
        cursor cursor_emp is select * from Scott.emp;
        
        --定義變量,類型為cursor_emp遊标中的資料的行類型
         v_emp cursor_emp%rowtype;
     begin
        --判斷遊标是否打開,如果沒有則打開
        if cursor_emp%isopen=false then
           open cursor_emp;  --打開遊标
        end if;
        --提取遊标中的資料
        loop
        	 --逐行提取遊标中每行的資料到v_emp變量中
           fetch cursor_emp into v_emp;   
           --判斷如果沒有資料則退出
           exit when cursor_emp%notfound;  
           --輸出目前資料
           dbms_output.put_line('員工編号:'||v_emp.empno||'員工姓名:'||v_emp.ename||'工資:'||v_emp.sal);
        end loop;
        --關閉遊标
        close cursor_emp;
     end;
           

(5)允許使用遊标删除或更新活動集中的行

  • 聲明遊标時必須使用select …for update語句
CURSOR <cursor_name> IS SELECT statement FOR UPDATE;
           
  • 使用以下文法更新行
UPDATE <table_name> SET <set_clause> WHERE CURRENT OF <cursor_name>
           
  • 使用以下文法删除行
DELETE FROM <table_name> WHERE CURRENT OF <cursor_name>
           

遊标範例: 使用foreach提取遊标的資料到變量中 自動打開關閉遊标

declare
  	--聲明遊标
    cursor cursor_emp1 
    is select * from Scott.emp for update;
begin
    --提取遊标中的資料
    for v_emp in cursor_emp1 loop
          delete Scott.emp  where current of cursor_emp1;
         --輸出目前資料
          dbms_output.put_line('員工編号:'||v_emp.empno||'員工姓名:'||v_emp.ename||'工資:'||v_emp.sal);
    end loop;
end;

           

四、存儲過程

1、子程式

  • 子程式:命名的PL/SQL塊,編譯并存儲在資料庫中
  • 子程式的各個部分

    (1)聲明部分

    (2)可執行部分

    (3)異常處理部分(可選)

  • 子程式的分類

    (1)過程:執行某些操作

    (2)函數:執行操作并傳回值

2、存儲過程的用法

(1)建立存儲過程

文法:

CREATE [OR REPLACE] PROCEDURE 	 --使用PROCEDURE關鍵字建立存儲過程
   <procedure name> [(<parameter list>)]
IS|AS 							 -- 建立存儲過程,可指定運作過程中需傳遞的參數
   <local variable declaration>
BEGIN
   <executable statements>		 --包括在存儲過程中要執行的語句
[EXCEPTION
   <exception handlers>]		 --處理異常
END;

           

補充:

存儲過程的參數模式:

  • IN:用于接收調用程式的值,相當于方法中的形參(預設的參數模式)。
  • OUT:用于向調用程式傳回值,相當于return。
  • IN OUT:用于接收調用程式的值,并向調用程式傳回更新的值。

建立存儲過程範例:

-- 例1:建立一個根據empno查詢指定使用者資訊的存儲過程
 create or replace procedure pro_getEmpByNo(
       v_empno number,
       v_emp  out scott.emp%rowtype    
 )
 as
 begin
      select * into v_emp from scott.emp where empno=v_empno;
 end;


-- 例2:建立擷取所有員工的薪水的存儲過程
 create or replace procedure get_sals(
 	   --存放所有員工薪水的遊标;注意此處使用OUT關鍵字,該參數作為輸出參數
       cur_salary OUT SYS_REFCURSOR,  
       on_Flag OUT number,       --執行狀态
       os_Msg OUT VARCHAR2       --提示資訊
    ) 
    as
    begin
       open cur_salary for
           select empno,sal from Scott.emp;
       on_Flag:=1;
       os_Msg:='成功';  
    
    exception
       when others then
      on_Flag:=-1;
      os_Msg:='其他錯誤,與管理者聯系。';
    end;
   
           

(2)調用存儲過程

文法:

EXEC[UTE] procedure_name (param_list);
           

說明:

  • 使用EXEC或 EXECUTE關鍵字調用
  • procedure_name :表示調用的存儲過程的名稱
  • param_list:參數清單

執行存儲過程範例:

-- 調用例2中的get_sals存儲過程
 declare
        v_empno  scott.emp.empno%type;
        v_sal scott.emp.sal%type;
        emp_salary1 SYS_REFCURSOR;
        on_Flag1 number(1);     --執行狀态
        os_Msg1 VARCHAR2(200);  --提示資訊 
   
     begin
         --執行存儲過程
         get_sals(emp_salary1,on_Flag1,os_Msg1);
         if on_flag=1 then
          loop         --由于存儲過程中已經打開遊标,是以不可以使用foreach
             fetch emp_salary into v_empno,v_sal;
             exit when emp_salary%notfound;
             dbms_output.put_line(v_empno||'的薪水是' ||v_sal);
          end loop;
         else
           dbms_output.put_line(os_Msg);
         end if;
         if emp_salary%ISOPEN THEN
            close emp_salary;
          end if;
     end;
           

補充:參數傳遞的三種方式

  • 按位置傳遞參數
-- 按位置傳遞參數
declare
        v_emp scott.emp%rowtype;
begin
        pro_showAllEmp(7788,v_emp);
        dbms_output.put_line(v_emp.ename);
end;
           
  • 按名稱傳遞參數
--按名稱傳遞參數
   declare
       v_emp1 scott.emp%rowtype;
   begin
   	   -- 被調存儲過程的參數指向變量或值
       pro_showAllEmp(v_emp=>v_emp1,v_empno => 7788);
       dbms_output.put_line(v_emp1.ename);
   end;
           
  • 混合方式傳遞參數

(3)将過程的執行權限授予/撤銷其他使用者

-- 将權限授予指定使用者
	SQL> GRANT EXECUTE ON find_emp TO MARTIN;
	
	--将權限授予所有使用者
	SQL> GRANT EXECUTE ON swap TO PUBLIC;
	
	 --撤銷權限
     revoke execute on 存儲過程名 to 使用者名/public;
           

(4)删除存儲過程文法

DROP PROCEDURE  procedure_name;
           

3、存儲過程使用規則

  • 存儲過程中不可以直接使用DDL語句,可以通過動态SQL實作。但不建議頻繁的使用DDL語句。
  • 存儲過程中必須有相應的出錯處理功能。
  • 存儲過程變量使用%type和%rowtype類型。
  • 必須在存儲過程中做異常捕獲,并将異常資訊通過os_Msg變量輸出。
  • -1~-19999的異常為Oracle定義的異常代碼。
  • 存儲過程必須包含兩個輸出參數分别用于辨別過程的執行狀态及過程提示資訊。
  • “WHEN OTHERS”語句必須放置在異常處理代碼的最後面,作為預設處理器處理沒有顯式異常的處理。
關注微信公衆号【程式媛琬淇】,專注分享Java幹貨,給你意想不到的收獲。
Oracle資料庫-03 PL/SQL程式設計、遊标、存儲過程