1. 程式人生 > >Oracle/PLSQL儲存過程詳解

Oracle/PLSQL儲存過程詳解

一.在plsql中建立一個儲存過程

開啟plsql,右鍵procedures,新建。
新建
如果新建毫無反應
直接檔案-新建-程式視窗-空白,新建一個程式視窗: 
新建2


儲存過程建立語法:

    create [or replace] procedure 儲存過程名(param1 in type,param2 out type)
as
變數1 型別(值範圍);
變數2 型別(值範圍);
Begin
    Select count(*) into 變數1 from 表A where列名=param1;

    If (判斷條件) then
       Select 列名 into 變數2 from 表A where
列名=param1; Dbms_output。Put_line(‘列印資訊’);
Elsif (判斷條件) then Dbms_output。Put_line(‘列印資訊’); Else Raise 異常名(NO_DATA_FOUND); End if; Exception When others then Rollback; End;

二:例項:寫儲存過程(例子)

--建立一個名為p_contract_purchase_import的儲存過程
create or replace procedure p_contract_purchase_import(
  --以下寫儲存過程的外部引數(傳入的引數)
  --格式為:引數名  in
引數型別 --注意,這裡的varchar不標註大小 V_IN_SUBCOMPANYID in VARCHAR2, --專業分公司 V_IN_PURCONTRACTMONEY in NUMBER, --採購合同金額 V_IN_PARTYBNAME in VARCHAR2, --供應商名稱 --設定一個返回值 v_o_ret out number --返回結果0:成功;1:失敗; 4:查不到供應商; 5
:新增關聯失敗;6:新增採購合同失敗 ) --以下寫內部引數 --格式為:引數名稱 引數型別 --注意,這裡的varchar需要標註大小 as V_SUPPLIERID INTEGER;
--供應商編號 V_PARTYBACCOUNT VARCHAR2(100);--收款賬號 V_SQLERRM VARCHAR2(4000);--錯誤詳情 --儲存過程開始 begin --為某些變數賦初值 --格式為 變數名 := 值 v_o_ret := 1; V_SUPPLIERID := ''; V_PARTYBACCOUNT := ''; --寫具體的操作語句(sql) --if語句 if(V_IN_PARTYBNAME is not null) then begin select t.SUPPLIERID,t.PARTYBACCOUNT,t.PARTYBBANK ,t.PARTYBNAME into V_SUPPLIERID,V_PARTYBACCOUNT,V_PARTYBBANK,V_PARTYBNAME from T_SUPPLIER t where t.PARTYBNAME=trim(V_IN_PARTYBNAME) and t.SUBCOMPANYID=trim(V_IN_SUBCOMPANYID); --拋異常 exception when others then v_o_ret := 4 ; --找不到該供應商 V_PARTYBNAME := V_IN_PARTYBNAME; -- 將異常原因寫入儲存過程日誌表 V_SQLERRM := SQLERRM; INSERT INTO T_LOG_DBERR (ERRTIME, ERRMODEL, ERRDESC) VALUES (SYSDATE, 'PROCEDURES', 'p_contract_purchase_import:ret=' || v_o_ret ||','|| V_SQLERRM); COMMIT; end ; end if; ······ end ; commit; v_o_ret :=0 ; return; EXCEPTION WHEN OTHERS THEN ROLLBACK; -- 插入異常原因 V_SQLERRM := SQLERRM; INSERT INTO T_LOG_DBERR (ERRTIME, ERRMODEL, ERRDESC) VALUES (SYSDATE, 'PROCEDURES', 'p_contract_purchase_import:ret=' || v_o_ret ||','|| V_SQLERRM); COMMIT; --儲存過程結束 end p_contract_purchase_import;

1.注意begin 和 end成對 

2.務必將錯誤內容打印出來。否則,有的時候,就算除錯看出來時哪一個語句的錯誤,也鬧不明白錯在哪裡

三.儲存過程除錯

1.按F8或是選單欄第三行第二個執行按鍵編譯儲存過程。此時,如果有語法上的明顯錯誤,plsql會給予提示。 
2.在procedures中找到要除錯的儲存過程,右鍵,選測試。注意,要記得勾選新增除錯資訊啊。 
開啟除錯 
3.開啟除錯視窗,填寫輸入引數 
4.點測試視窗的除錯按鍵(如圖中畫圈的位置)開始除錯 
填引數並開啟除錯
5.逐步除錯,如下圖: 
除錯
圖中1:點選單步除錯,主要就是使用這個來進行除錯。執行到的程式碼會高亮顯示,此外,注意圖中4的位置,可以在這裡輸入變數的名字,來檢視變數的當前值。 
圖中2/3:分別為跳出單步執行和全部執行,除錯時不推薦使用。

單步除錯過程中,如果執行到哪一步直接跳轉到了exception結束,那麼,就是這一步出來問題,可以記住這個位置,再次除錯,通過檢視這附近變數的值,以及檢視錯誤日誌記錄的錯誤詳情,來確認出錯的具體原因並進行修改。

四.在java中呼叫

儲存過程除錯好之後,就可以在java中呼叫該儲存過程。

logger.info("呼叫儲存過程p_contract_purchase_import");
//儲存過程名稱。有多少個傳入引數打幾個問號(包括v_o_ret)
String procName="{Call p_contract_purchase_import(?,?,?,?) }";
DataSource ds = SessionFactoryUtils.getDataSource(this.getHibernateTemplate().getSessionFactory());
                    Connection conn = null;
                    CallableStatement call = null;
                    //ResultSet rs =null;

                    try
                    {
                        //建立連線
                        conn = ds.getConnection();
                        call = conn.prepareCall(procName);
                        //傳入資料
                        call.setString(1, (importList.get(0)).trim());
                        if(ratio_amount==null) {
                            call.setString(2,null);
                        } else {
                            call.setLong(2, ratio_amount);
                        }
                        call.setString(3, (importList.get(2)).trim());

                        //第四個引數是作為返回值存在的
                        call.registerOutParameter(4, Types.BIGINT);
                        //執行儲存過程
                        call.executeUpdate();
                        //獲取返回的結果
                        ret = call.getInt(17) ;
                        logger.info("ret:"+ret); ;//call.getInt(6));

                        try
                        {
                            //關閉連線
                            call.close();
                            call = null ;
                            conn.close();
                            conn = null ;
                        } catch (SQLException e) {
                            // TODO Auto-generated catch block
                        }

                    }catch (SQLException e){
                        logger.error("開啟儲存過程錯誤:",e);
                    }
                    finally{
                        try {
                            if (call != null) {
                                call.close();
                            }
                            if (conn != null) {
                                conn.close();
                            }
                        } catch (SQLException e) {
                            // TODO Auto-generated catch block
                            conn = null;
                        }
                    }

需要注意的是,call.setLong()不可以傳入空值。如果內容有可能為空的話,set之前需要判斷是否為空,不為空才能使用setLong()方法,否則,要使用setString方法。


五:注意事項:

  1. 儲存過程引數不帶取值範圍,in表示傳入,out表示輸出
  2. 變數帶取值範圍,後面接分號
  3. 在判斷語句前最好先用count(*)函式判斷是否存在該條操作記錄
  4. 用select … into … 給變數賦值
  5. 在程式碼中拋異常用 raise+異常名

已命名的異常:

命名的系統異常產生原因
ACCESS_INTO_NULL未定義物件
CASE_NOT_FOUNDCASE 中若未包含相應的 WHEN ,並且沒有設定ELSE 時
COLLECTION_IS_NULL集合元素未初始化
CURSER_ALREADY_OPEN遊標已經開啟
DUP_VAL_ON_INDEX唯一索引對應的列上有重複的值
INVALID_CURSOR在不合法的遊標上進行操作
INVALID_NUMBER內嵌的 SQL 語句不能將字元轉換為數字
NO_DATA_FOUND使用 select into 未返回行,或應用索引表未初始化的
TOO_MANY_ROWS執行 select into 時,結果集超過一行
ZERO_DIVIDE除數為 0
SUBSCRIPT_BEYOND_COUNT元素下標超過巢狀表或 VARRAY 的最大值
SUBSCRIPT_OUTSIDE_LIMIT使用巢狀表或 VARRAY 時,將下標指定為負數
VALUE_ERROR賦值時,變數長度不足以容納實際資料
LOGIN_DENIEDPL/SQL 應用程式連線到 oracle 資料庫時,提供了不正確的使用者名稱或密碼
NOT_LOGGED_ONPL/SQL 應用程式在沒有連線 oralce 資料庫的情況下訪問資料
PROGRAM_ERRORPL/SQL 內部問題,可能需要重灌資料字典& pl./SQL系統包
ROWTYPE_MISMATCH宿主遊標變數與 PL/SQL 遊標變數的返回型別不相容
SELF_IS_NULL使用物件型別時,在 null 物件上呼叫物件方法
STORAGE_ERROR執行 PL/SQL 時,超出記憶體空間
SYS_INVALID_ID無效的 ROWID 字串
TIMEOUT_ON_RESOURCEOracle 在等待資源時超時

六:基本語法

1. 基本結構

CREATE OR REPLACE PROCEDURE 儲存過程名字
(
    引數1 IN NUMBER,
    引數2 IN NUMBER
) IS
變數1 INTEGER :=0;
變數2 DATE;
BEGIN
    --執行體
END 儲存過程名字;

2. SELECT INTO STATEMENT

將select查詢的結果存入到變數中,可以同時將多個列儲存多個變數中,必須有一條記錄,否則丟擲異常(如果沒有記錄丟擲NO_DATA_FOUND) 
例子:

  BEGIN
  SELECT col1,col2 into 變數1,變數2 FROM typestruct where xxx;
  EXCEPTION
  WHEN NO_DATA_FOUND THEN
      xxxx;
  END;

3. IF 判斷

 IF V_TEST=1 THEN
    BEGIN 
       do something
    END;
  END IF;

4. while 迴圈

  WHILE V_TEST=1 LOOP
  BEGIN
    XXXX
  END;
  END LOOP;

5. 變數賦值

 V_TEST := 123;

6. 用for in 使用cursor

  IS
  CURSOR cur IS SELECT * FROM xxx;
  BEGIN
 FOR cur_result in cur LOOP
  BEGIN
   V_SUM :=cur_result.列名1+cur_result.列名2
  END;
 END LOOP;
  END;

7. 帶引數的cursor

  CURSOR C_USER(C_ID NUMBER) IS SELECT NAME FROM USER WHERE TYPEID=C_ID;
  OPEN C_USER(變數值);
  LOOP
 FETCH C_USER INTO V_NAME;
 EXIT FETCH C_USER%NOTFOUND;
    do something
  END LOOP;
  CLOSE C_USER;

8. 用pl/sql developer debug

連線資料庫後建立一個Test WINDOW,在視窗輸入呼叫SP的程式碼,F9開始debug,CTRL+N單步除錯

八:關於oracle儲存過程的若干問題備忘

1.在oracle中,資料表別名不能加as,如:

select a.appname from appinfo a;-- 正確
select a.appname from appinfo as a;-- 錯誤

也許,是怕和oracle中的儲存過程中的關鍵字as衝突的問題吧

2.在儲存過程中,select某一欄位時,後面必須緊跟into,如果select整個記錄,利用遊標的話就另當別論了。

select af.keynode into kn from APPFOUNDATION af 
   where af.appid=aid and af.foundationid=fid;-- 有into,正確編譯
select af.keynode from APPFOUNDATION af 
 where af.appid=aid and af.foundationid=fid;-- 沒有into,編譯報錯,提示:Compilation 

Error: PLS-00428: an INTO clause is expected in this SELECT statement

3.在利用select…into…語法時,必須先確保資料庫中有該條記錄,否則會報出”no data found”異常。

可以在該語法之前,先利用select count(*) from 檢視資料庫中是否存在該記錄,如果存在,再利用select…into…

4.在儲存過程中,別名不能和欄位名稱相同,否則雖然編譯可以通過,但在執行階段會報錯

 --正確
select keynode into kn from APPFOUNDATION where appid=aid and foundationid=fid;

--錯誤
select af.keynode into kn from APPFOUNDATION af 
 where af.appid=appid and af.foundationid=foundationid;
-- 執行階段報錯,提示ORA-01422:exact fetch returns more than requested number of rows

5.在儲存過程中,關於出現null的問題

假設有一個表A,定義如下:

create table A(
id varchar2(50) primary key not null,
vcount number(8) not null,
bid varchar2(50) not null -- 外來鍵 
);

如果在儲存過程中,使用如下語句:

select sum(vcount) into fcount from A where bid='xxxxxx';

如果A表中不存在bid=”xxxxxx”的記錄,則fcount=null(即使fcount定義時設定了預設值,如:fcount number(8):=0依然無效,fcount還是會變成null),這樣以後使用fcount時就可能有問題,所以在這裡最好先判斷一下:

if fcount is null then
    fcount:=0;
end if;

這樣就一切ok了。