Oracle/PLSQL儲存過程詳解
一.在plsql中建立一個儲存過程
開啟plsql,右鍵procedures,新建。
如果新建毫無反應
直接檔案-新建-程式視窗-空白,新建一個程式視窗:
儲存過程建立語法:
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方法。
五:注意事項:
- 儲存過程引數不帶取值範圍,in表示傳入,out表示輸出
- 變數帶取值範圍,後面接分號
- 在判斷語句前最好先用count(*)函式判斷是否存在該條操作記錄
- 用select … into … 給變數賦值
- 在程式碼中拋異常用 raise+異常名
已命名的異常:
命名的系統異常 | 產生原因 |
---|---|
ACCESS_INTO_NULL | 未定義物件 |
CASE_NOT_FOUND | CASE 中若未包含相應的 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_DENIED | PL/SQL 應用程式連線到 oracle 資料庫時,提供了不正確的使用者名稱或密碼 |
NOT_LOGGED_ON | PL/SQL 應用程式在沒有連線 oralce 資料庫的情況下訪問資料 |
PROGRAM_ERROR | PL/SQL 內部問題,可能需要重灌資料字典& pl./SQL系統包 |
ROWTYPE_MISMATCH | 宿主遊標變數與 PL/SQL 遊標變數的返回型別不相容 |
SELF_IS_NULL | 使用物件型別時,在 null 物件上呼叫物件方法 |
STORAGE_ERROR | 執行 PL/SQL 時,超出記憶體空間 |
SYS_INVALID_ID | 無效的 ROWID 字串 |
TIMEOUT_ON_RESOURCE | Oracle 在等待資源時超時 |
六:基本語法
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了。