oracle儲存過程詳解--遊標 實現增、刪、改、查

langgufu314發表於2011-09-23

自行建立,表複製的語句為

    CREATE TABLE former_emp AS SELECT * FROM EMP;

 

   

 遊標(CURSOR)是ORACLE系統在記憶體中開闢的一個工作區,在其中存放SELECT語句返回的查詢結果.  
    這個查詢結果既可以是零記錄,單條記錄,也可以是多條記錄.在遊標所定義的工作區中,存在著一個指標(POINTER),  
    在初始狀態它指向查詢結果的首記錄.   
    
    SQL是用於訪問ORACLE資料庫的語言,PL/SQL擴充套件和加強了SQL的功能,它同時引入了更強的程式邏輯。    
    PL/SQL支援DML命令和SQL的事務控制語句。DDL在PL/SQL中不被支援,這就意味作在PL/SQL程式塊中不能建立表或其他任何物件。  
    較好的PL/SQL程式設計是在PL/SQL塊中使用象DBMS_SQL這樣的內建包或執行EXECUTE   IMMEDIATE命令建立動態SQL來執行DDL命令,  
    PL/SQL編譯器保證物件引用以及使用者的許可權。   
   
    下面我們將討論各種用於訪問ORACLE資料庫的DML和TCL語句。   
   
    查詢   
   
    SELECT語句用於從資料庫中查詢資料,當在PL/SQL中使用SELECT語句時,要與INTO子句一起使用,查詢的返回值被賦予INTO子句中的變數,變數的宣告是在DELCARE中。SELECT INTO語法如下:  
   
  SELECT   [DISTICT|ALL]{*|column[,column,...]}  
  INTO   (variable[,variable,...]   |record)  
  FROM   {table|(sub-query)}[alias]  
  WHERE............     
   
    PL/SQL中SELECT語句只返回一行資料。如果超過一行資料,那麼就要使用顯式遊標(對遊標的討論我們將在後面進行),  
    INTO子句中要有與SELECT子句中相同列數量的變數。INTO子句中也可以是記錄變數。  
   
    %TYPE屬性  
   
    在PL/SQL中可以將變數和常量宣告為內建或使用者定義的資料型別,以引用一個列名,同時繼承他的資料型別和大小。  
  這種動態賦值方法是非常有用的,比如變數引用的列的資料型別和大小改變了,如果使用了%TYPE,那麼使用者就不必修改程式碼,  
  否則就必須修改程式碼。  
   
   例:  
   
  v_empno   SCOTT.EMP.EMPNO%TYPE;  
  v_salary   EMP.SALARY%TYPE;      
   
    不但列名可以使用%TYPE,而且變數、遊標、記錄,或宣告的常量都可以使用%TYPE。這對於定義相同資料型別的變數非常有用。  
   
DECLARE
  V_A NUMBER(5) := 10;
  V_B V_A%TYPE := 15;
  V_C V_A%TYPE;
BEGIN
  DBMS_OUTPUT.PUT_LINE('V_A=' || V_A || 'V_B=' || V_B || 'V_C=' || V_C);
END;
   
  SQL>/  
  V_A=10   V_B=15   V_C=  
  PL/SQL   procedure   successfully   completed.  
   
  SQL>    
   
    其他DML語句  
   
    其它運算元據的DML語句是:INSERT、UPDATE、DELETE和LOCK   TABLE,這些語句在PL/SQL中的語法與在SQL中的語法相同。  
  我們在前面已經討論過DML語句的使用這裡就不再重複了。在DML語句中可以使用任何在DECLARE部分宣告的變數,如果是巢狀塊,  
  那麼要注意變數的作用範圍。  
   
    例:  
   
CREATE OR REPLACE PROCEDURE FIRE_EMPLOYEE(p_empno in number) AS
begin
  declare
    v_ename scott.EMP.ENAME%TYPE;
  BEGIN
    SELECT ename INTO v_ename FROM emp WHERE empno = p_empno;
    INSERT INTO FORMER_EMP (EMPNO, ENAME) VALUES (p_empno, v_ename);
    DELETE FROM emp WHERE empno = p_empno;
    UPDATE former_emp SET HIREDATE = SYSDATE WHERE empno = p_empno;
 
  EXCEPTION
    WHEN NO_DATA_FOUND THEN
      DBMS_OUTPUT.PUT_LINE('Employee   Number   Not   Found!');
   
  END;
end FIRE_EMPLOYEE;

 

    
    DML語句的結果  
   
    當執行一條DML語句後,DML語句的結果儲存在四個遊標屬性中,這些屬性用於控制程式流程或者瞭解程式的狀態。  
  當執行DML語句時,PL/SQL開啟一個內建遊標並處理結果,遊標是維護查詢結果的記憶體中的一個區域,遊標在執行DML語句時開啟,完成後關閉。  
  隱式遊標只使用SQL%FOUND,SQL%NOTFOUND,SQL%ROWCOUNT三個屬性.SQL%FOUND,SQL%NOTFOUND是布林值,SQL%ROWCOUNT是整數值。  
   
    SQL%FOUND和SQL%NOTFOUND  
   
    在執行任何DML語句前SQL%FOUND和SQL%NOTFOUND的值都是NULL,在執行DML語句後,SQL%FOUND的屬性值將是:  
   
    .   TRUE   :INSERT  
   
    .   TRUE   :DELETE和UPDATE,至少有一行被DELETE或UPDATE.  
   
    .   TRUE   :SELECT   INTO至少返回一行  
   
    當SQL%FOUND為TRUE時,SQL%NOTFOUND為FALSE。  
   
    SQL%ROWCOUNT    
   
    在執行任何DML語句之前,SQL%ROWCOUNT的值都是NULL,對於SELECT   INTO語句,如果執行成功,SQL%ROWCOUNT的值為1,如果沒有成功,  
  SQL%ROWCOUNT的值為0,同時產生一個異常NO_DATA_FOUND.  
   
    SQL%ISOPEN  
   
    df  
   
    事務控制語句  
   
    事務是一個工作的邏輯單元可以包括一個或多個DML語句,事物控制幫助使用者保證資料的一致性。  
  如果事務控制邏輯單元中的任何一個DML語句失敗,那麼整個事務都將回滾,  
  在PL/SQL中使用者可以明確地使用COMMIT、ROLLBACK、SAVEPOINT以及SET   TRANSACTION語句。  
   
    COMMIT語句終止事務,永久儲存資料庫的變化,同時釋放所有LOCK,ROLLBACK終止現行事務釋放所有LOCK,  
  但不儲存資料庫的任何變化,SAVEPOINT用於設定中間點,當事務呼叫過多的資料庫操作時,中間點是非常有用的,  
  SET   TRANSACTION用於設定事務屬性,比如read-write和隔離級等。  
   
    顯式遊標  
   
    當查詢返回結果超過一行時,就需要一個顯式遊標,此時使用者不能使用select   into語句。PL/SQL管理隱式遊標,  
  當查詢開始時隱式遊標開啟,查詢結束時隱式遊標自動關閉。顯式遊標在PL/SQL塊的宣告部分宣告,  
  在執行部分或異常處理部分開啟,取資料,關閉。下表顯示了顯式遊標和隱式遊標的差別:  
  使用遊標  
   
   
    這裡要做一個宣告,我們所說的遊標通常是指顯式遊標,因此從現在起沒有特別指明的情況,我們所說的遊標都是指顯式遊標。  
  要在程式中使用遊標,必須首先宣告遊標。  
   
    宣告遊標  
   
    語法:  
   
  CURSOR   cursor_name   IS   select_statement;    
   
    在PL/SQL中游標名是一個未宣告變數,不能給遊標名賦值或用於表示式中。  
   
    例:  
   
  DELCARE  
  CURSOR   C_EMP   IS   SELECT   empno,ename,salary  
  FROM   emp  
  WHERE   salary>2000  
  ORDER   BY   ename;  
  ........  
  BEGIN    
   
    在遊標定義中SELECT語句中不一定非要表可以是檢視,也可以從多個表或檢視中選擇的列,甚至可以使用*來選擇所有的列   。  



開啟遊標  
   
    使用遊標中的值之前應該首先開啟遊標,開啟遊標初始化查詢處理。開啟遊標的語法是:  
   
  OPEN   cursor_name    
   
    cursor_name是在宣告部分定義的遊標名。  
   
    例:  
   
  OPEN   C_EMP;    
   
    關閉遊標  
   
    語法:  
   
  CLOSE   cursor_name    
   
    例:  
   
  CLOSE   C_EMP;    
   
    從遊標提取資料  
   
    從遊標得到一行資料使用FETCH命令。每一次提取資料後,遊標都指向結果集的下一行。語法如下:  
   
  FETCH   cursor_name   INTO   variable[,variable,...]    
   
    對於SELECT定義的遊標的每一列,FETCH變數列表都應該有一個變數與之相對應,變數的型別也要相同。  
   
    例:

DECLARE
  v_ename  EMP.ENAME%TYPE;
  v_salary EMP.SAL%TYPE;
  CURSOR c_emp IS
    SELECT ename, SAL FROM emp;
BEGIN
  OPEN c_emp;
  FETCH c_emp
    INTO v_ename, v_salary;
  DBMS_OUTPUT.PUT_LINE('Salary   of   Employee' || v_ename || 'is' ||
                       v_salary);
  FETCH c_emp
    INTO v_ename, v_salary;
  DBMS_OUTPUT.PUT_LINE('Salary   of   Employee' || v_ename || 'is' ||
                       v_salary);
  FETCH c_emp
    INTO v_ename, v_salary;
  DBMS_OUTPUT.PUT_LINE('Salary   of   Employee' || v_ename || 'is' ||
                       v_salary);
  CLOSE c_emp;
END;    
   
    這段程式碼無疑是非常麻煩的,如果有多行返回結果,可以使用迴圈並用遊標屬性為結束迴圈的條件,以這種方式提取資料,  
  程式的可讀性和簡潔性都大為提高,下面我們使用迴圈重新寫上面的程式:  
   
DECLARE
  v_ename  EMP.ENAME%TYPE;
  v_salary EMP.SAL%TYPE;
  CURSOR c_emp IS
    SELECT ename, SAL FROM emp;
BEGIN
  OPEN c_emp;
  LOOP
    FETCH c_emp
      INTO v_ename, v_salary;
    EXIT WHEN c_emp%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE('Salary   of   Employee' || v_ename || 'is' ||
                         v_salary);
  END LOOP;
END;    
   
    記錄變數  
   
    定義一個記錄變數使用TYPE命令和%ROWTYPE,關於%ROWsTYPE的更多資訊請參閱相關資料。  
   
    記錄變數用於從遊標中提取資料行,當遊標選擇很多列的時候,那麼使用記錄比為每列宣告一個變數要方便得多。  
   
    當在表上使用%ROWTYPE並將從遊標中取出的值放入記錄中時,如果要選擇表中所有列,  
  那麼在SELECT子句中使用*比將所有列名列出來要安全得多。  
   
    例:  
   
 DECLARE
  R_emp EMP%ROWTYPE;
  CURSOR c_emp IS
    SELECT * FROM emp;
BEGIN
  OPEN c_emp;
  LOOP
    FETCH c_emp
      INTO r_emp;
    EXIT WHEN c_emp%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE('Salary   of   Employee' || r_emp.ename || 'is' ||
                          r_emp.sal);
  END LOOP;
  CLOSE c_emp;
END;
   
   
    %ROWTYPE也可以用遊標名來定義,這樣的話就必須要首先宣告遊標:  
   
DECLARE
  CURSOR c_emp IS
    SELECT ename, sal FROM emp;
  R_emp c_emp%ROWTYPE;
BEGIN
  OPEN c_emp;
  LOOP
    FETCH c_emp
      INTO r_emp;
    EXIT WHEN c_emp%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE('Sal   of   Employee' || r_emp.ename || 'is' ||
                         r_emp.sal);
  END LOOP;
  CLOSE c_emp;
END;
      
   
    帶引數的遊標  
   
    與儲存過程和函式相似,可以將引數傳遞給遊標並在查詢中使用。這對於處理在某種條件下開啟遊標的情況非常有用。它的語法如下:  
   
  CURSOR   cursor_name[(parameter[,parameter],...)]   IS   select_statement;    
   
    定義引數的語法如下:  
   
  Parameter_name   [IN]   data_type[{:=|DEFAULT}   value]    
   
    與儲存過程不同的是,遊標只能接受傳遞的值,而不能返回值。引數只定義資料型別,沒有大小。    
   
    另外可以給引數設定一個預設值,當沒有引數值傳遞給遊標時,就使用預設值。遊標中定義的引數只是一個佔位符,  
  在別處引用該引數不一定可靠。  
   
    在開啟遊標時給引數賦值,語法如下:  
   
  OPEN   cursor_name[value[,value]....];    
   
    引數值可以是文字或變數。  
   
    例:

DECLARE

  CURSOR c_dept IS
    SELECT * FROM dept ORDER BY deptno;
  CURSOR c_emp(p_dept emp.deptno%type) IS
    SELECT ename, sal FROM emp WHERE deptno = p_dept ORDER BY ename;
  r_dept       DEPT%ROWTYPE;
  v_ename      EMP.ENAME%TYPE;
  v_salary     EMP.SAL%TYPE;
  v_tot_salary EMP.SAL%TYPE;

BEGIN

  OPEN c_dept;
  LOOP
    FETCH c_dept
      INTO r_dept;
    EXIT WHEN c_dept%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE('Department:' || r_dept.deptno || '-' ||
                         r_dept.dname);
    v_tot_salary := 0;
    OPEN c_emp(r_dept.deptno);
    LOOP
      FETCH c_emp
        INTO v_ename, v_salary;
      EXIT WHEN c_emp%NOTFOUND;
      DBMS_OUTPUT.PUT_LINE('Name:' || v_ename || '   sal:' || v_salary);
      v_tot_salary := v_tot_salary + v_salary;
    END LOOP;
    CLOSE c_emp;
    DBMS_OUTPUT.PUT_LINE('Toltal   Sal   for   dept:' || v_tot_salary);
  END LOOP;
  CLOSE c_dept;
END;
   
    
   遊標FOR迴圈  
   
    在大多數時候我們在設計程式的時候都遵循下面的步驟:  
   
    1、開啟遊標  
   
    2、開始迴圈  
   
    3、從遊標中取值  
   
    4、檢查那一行被返回  
   
    5、處理  
   
    6、關閉迴圈  
   
    7、關閉遊標  
   
    可以簡單的把這一類程式碼稱為遊標用於迴圈。但還有一種迴圈與這種型別不相同,這就是FOR迴圈,  
  用於FOR迴圈的遊標按照正常的宣告方式宣告,它的優點在於不需要顯式的開啟、關閉、取資料,測試資料的存在、定義存放資料的變數等等  
  。遊標FOR   迴圈的語法如下:  
   
  FOR   record_name   IN  
  (corsor_name[(parameter[,parameter]...)]  
  |   (query_difinition)  
  LOOP  
  statements  
  END   LOOP;    
   
    下面我們用for迴圈重寫上面的例子:  
   
DECLARE

  CURSOR c_dept IS
    SELECT deptno, dname FROM dept ORDER BY deptno;
  CURSOR c_emp(p_dept emp.deptno%type) IS
    SELECT ename, sal FROM emp WHERE deptno = p_dept ORDER BY ename;

  v_tot_salary EMP.SAL%TYPE;

BEGIN

  FOR r_dept IN c_dept LOOP
    DBMS_OUTPUT.PUT_LINE('Department:' || r_dept.deptno || '-' ||
                         r_dept.dname);
    v_tot_salary := 0;
    FOR r_emp IN c_emp(r_dept.deptno) LOOP
      DBMS_OUTPUT.PUT_LINE('Name:' || r_emp.ename || '   sal:' ||
                           r_emp.sal);
      v_tot_salary := v_tot_salary + r_emp.sal;
    END LOOP;
    DBMS_OUTPUT.PUT_LINE('Toltal   Sal   for   dept:' || v_tot_salary);
  END LOOP;

END;
    
    在遊標FOR迴圈中使用查詢  
   
    在遊標FOR迴圈中可以定義查詢,由於沒有顯式宣告所以遊標沒有名字,記錄名通過遊標查詢來定義。  
   
DECLARE
  v_tot_salary EMP.SAL%TYPE;
BEGIN
  FOR r_dept IN (SELECT deptno, dname FROM dept ORDER BY deptno) LOOP
    DBMS_OUTPUT.PUT_LINE('Department:' || r_dept.deptno || '-' ||
                         r_dept.dname);
    v_tot_salary := 0;
    FOR r_emp IN (SELECT ename, sal
                    FROM emp
                   WHERE deptno = r_dept.deptno
                   ORDER BY ename) LOOP
      DBMS_OUTPUT.PUT_LINE('Name:' || r_emp.ename || '   salary:' ||
                           r_emp.sal);
      v_tot_salary := v_tot_salary + r_emp.sal;
    END LOOP;
    DBMS_OUTPUT.PUT_LINE('Toltal   Salary   for   dept:' || v_tot_salary);
  END LOOP;
END;
      
   
    遊標中的子查詢  
   
    語法如下:  
   
  CURSOR   C1   IS   SELECT   *   FROM   emp  
  WHERE   deptno   NOT   IN   (SELECT   deptno  
  FROM   dept  
  WHERE   dname!='ACCOUNTING');      
   
    可以看出與SQL中的子查詢沒有什麼區別。  
   
    遊標中的更新和刪除**************************************************************************POINT  
   
    在PL/SQL中依然可以使用UPDATE和DELETE語句更新或刪除資料行。顯式遊標只有在需要獲得多行資料的情況下使用。  
  PL/SQL提供了僅僅使用遊標就可以執行刪除或更新記錄的方法。  
   
    UPDATE或DELETE語句中的WHERE   CURRENT   OF子串專門處理要執行UPDATE或DELETE操作的表中取出的最近的資料。  
  要使用這個方法,在宣告遊標時必須使用FOR   UPDATE子串,當對話使用FOR   UPDATE子串開啟一個遊標時,  
  所有返回集中的資料行都將處於行級(ROW-LEVEL)獨佔式鎖定,其他物件只能查詢這些資料行,  
  不能進行UPDATE、DELETE或SELECT...FOR   UPDATE操作。  
   
    語法:  
   
  FOR   UPDATE   [OF   [schema.]table.column[,[schema.]table.column]..  
  [nowait]    
   
    在多表查詢中,使用OF子句來鎖定特定的表,如果忽略了OF子句,那麼所有表中選擇的資料行都將被鎖定。  
  如果這些資料行已經被其他會話鎖定,那麼正常情況下ORACLE將等待,直到資料行解鎖。  
   
    在UPDATE和DELETE中使用WHERE   CURRENT   OF子串的語法如下:  
   
  WHERE{CURRENT   OF   cursor_name|search_condition}    
   
    例:  
   
DECLARE

  CURSOR c1 IS
    SELECT empno, sal FROM test_emp WHERE comm IS NULL FOR UPDATE OF comm;

  v_comm NUMBER(10, 2);

BEGIN

  FOR r1 IN c1 LOOP
 
    IF r1.sal < 500 THEN
      v_comm := r1.sal * 0.25;
    ELSIF r1.sal < 1000 THEN
      v_comm := r1.sal * 0.20;
    ELSIF r1.sal < 3000 THEN
      v_comm := r1.sal * 0.15;
    ELSE
      v_comm := r1.sal * 0.12;
    END IF;
 
    UPDATE test_emp SET comm = v_comm WHERE CURRENT OF c1;
 
  END LOOP;
END;

相關文章