一.在plsql中創建一個存儲過程
本文轉自 https://blog.csdn.net/zezezuiaiya/article/details/79557621
打開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了。