一直以來有一個困惑,一直沒解決,昨天一哥們問我這個問題,決心弄清楚,終於得到了答案。
先看下面這個函數:
create or replace function fn_test(c_xm varchar) return varchar2 as V_P1 varchar(10); begin select name into V_p1 from t1 where 1 = 2;--將name查出賦值給v_p1 return 'test' || c_xm; end;
這個函數很簡單,是我寫的一個測試函數,沒什么意義,“select name into V_p1 from t1 where 1 = 2;”這句話有經驗的人一看就知道它會報錯,因為這個查詢返回的結果集是空,會報一個錯,將其賦值時,pl/sql引擎會認為它沒有數據,是一個null,這很類似於java中的空指針異常。當我們調試該函數的時候,到這一句,立刻會報ORA-1403錯誤:沒有數據。
但是如果在sql中調用該函數呢?執行以下查詢:
select fn_test('1') from dual;
結果是返回一個空記錄,沒有任何報錯。這是為什么呢?難道遇到了bug?如果是存儲過程呢?無論如何調試還是直接調用,此處都會報錯,有興趣的可以驗證一下,我就不驗證了,因為我之前碰到過許多次了,所以一般在select into時,如果沒有把握這個結果集一定有,都會select count一下然后再into。
這究竟是怎么回事呢?
下面是我的猜測:
對於查不到結果集來說,這不是什么很嚴重的錯誤,沒有就沒有了,不用報錯吧?如我們執行一條sql,select * from t1 where 1=2;如果這個sql沒有查到數據,難道就非得報個錯?基於這個考慮,在sql調用函數時,如果這種NO__DATA_FOUND的異常,可能sql解析器就直接處理了,不用再報錯了.因為它並不像諸如找不到表、找不到字段、沒有權限等等的錯誤嚴重。
這僅僅是猜測,但是究竟為什么呢?
最后我在asktom的網站上找到了答案:
http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::p11_question_id:10321465390114
從1樓這哥們的提問來看,他也是很郁悶,他甚至和我提出了一樣的疑問:“為什么過程就沒有這種問題?”
我看完了后,總結了一下,原因大致為:
當select a into b 時,如果沒有命中結果集,則當前的查詢為空,在sql語句的查詢中,解析器僅僅是認為“沒有數據(no data found)”而已,而不是將它作為一個錯誤,然后返回一個null,函數就此停止不再往下執行,tom的解釋也比較詼諧:
Under the covers, SQL is raising back to the client application "hey buddy -- no_data_found". The client in this case says "ah hah, no data found means 'end of data'" and stops.
但是在pl/sql中卻不是,pl/sql的處理方式卻是將它認為是一個錯誤,
Under the covers, PLSQL is raising back to the client application "hey -- no_data_found. The client in this case says "uh-oh, wasn't expecting that from PLSQL -- sql sure, but not PLSQL. Lets print out the text that goes with this exceptional condition and continue on"
NO_DATA_FOUND並不是一個錯誤,而且一個意外的情況,這類似於空指針異常,而這個意外情況只是沒找到數據而已,當調用者不同時,對其的處理也不同,當sql查詢調用時,遇到這個異常就認為是沒有數據,然后返回一個Null,但是當PL/sql調用時,會認為這是一個不好的情況,轉由異常處理塊處理。
歸根結底一句話,NO_DATA_FOUND都會由調用者捕獲,只是調用者對這個異常的處理方式不一樣而已。如果想在sql調用時報錯怎么辦?其實很簡單,捕獲這個NO_DATA_FOUND異常,然后raise即可:
create or replace function fn_test(c_xm varchar) return varchar2 as V_P1 varchar(10); begin select name into V_p1 from t1 where 1 = 2; return 'test' || c_xm; exception when no_data_found then /*RAISE_APPLICATION_ERROR(-20000, 'no data found');*/--拋出自定義的異常也行 raise program_error; end;
這樣,當再執行到該句時,立刻轉到異常處理塊,拋出一個非NO_DATA_FOUND異常,調用者不認識,認為是一個錯誤或者很嚴重的異常,只能報錯給客戶端了。
當用pl/sql調試時,運行到1403異常處,pl/sql調試器的處理方式就是立刻彈出一個錯誤信息;而sql調用時,這地方異常也會拋出,但是sql查詢器會認為,哦,沒有數據,查詢器選擇了用一個null值應對這個異常,而作為執行sql的我們,所看到的就是一個空值,而沒有報錯。
這類似於我們寫java程序對異常的處理,有的異常我們會直接拋給用戶,讓用戶知道出錯了,而有的異常被我們吃掉,然后選擇了別的處理方法,用戶看到的是另外一個情形,他根本不知道后台有異常發生。
這也就是對於【異常存在,只是怎么應對】的解釋。
有個結論:如果在function中,如果某行報了NO_DATA_FOUND,也沒有處理塊,那么不好意思,pl/sql語句就此就不在執行,這和普通的java程序是一樣的,什么地方拋出異常,程序在此就停止運行,要么轉到異常處理部分,要么就此stop,如果在sql查詢語句中調用這個fn_test函數:
select fn_test('1') from dual;
執行函數調用的過程用偽代碼表示如下:
begin: select fn_test('1') from dual;--開始解析sql查詢語句 call fn_test;--發現值來自於函數,開始調用fn_test var result;--定義臨時變量接收結果 try{ result=fn_test('1'); }catch(NO_DATA_FOUND){--如果是NO_DATA_FOUND異常則null處理 result=null; }catch(OTHERS){--如果其他異常則拋出 throw others; } select result from dual; end;
以上過程模擬了select語句調用函數的過程,如果出現了異常,在報異常的地方函數就此停止運行,不再往下執行。
驗證:
create or replace function fn_test(c_xm varchar) return varchar2 as V_P1 varchar(10); begin select name into V_p1 from t1 where 1 = 2;--NO_DATA_FOUND,也會報錯,但是sql解析器會以null返回 select 1/0 into v_p1 from dual;--除數為0,會報錯 return 'test' || c_xm; end;
當再次執行selectu語句的時候,並沒有報除數為0的錯誤,因為查詢在第一條語句就停止了,不再往下執行,如果去掉第一條語句:
create or replace function fn_test(c_xm varchar) return varchar2 as V_P1 varchar(10); begin --select name into V_p1 from t1 where 1 = 2;--NO_DATA_FOUND,注釋掉該行 select 1/0 into v_p1 from dual;--除數為0,會報錯 return 'test' || c_xm; end;
執行查詢,立刻報錯:ORA-01476:除數為0。如下圖:
當執行了異常處理時,若發生了異常,則會立即跳轉到異常塊中,這和java是一樣的,可以選擇捕獲NO_DATA_FOUND異常然后外拋。
create or replace function fn_test(c_xm varchar) return varchar2 as V_P1 varchar(10); begin select name into V_p1 from t1 where 1 = 2;--NO_DATA_FOUND,會立即跳轉到exception塊,不再繼續執行 select 1 / 0 into v_p1 from dual; --除數為0,會報錯,但是這句沒有機會執行了 return 'test' || c_xm; exception when NO_DATA_FOUND then raise_application_error('-20000', '沒找到數據');--異常外拋給調用者,直接報錯 end;
如下圖:
也可以在異常中返回一個有意義的提示,告訴調用者一個有意義的信息,如:
create or replace function fn_test(c_xm varchar) return varchar2 as V_P1 varchar(10); begin select name into V_p1 from t1 where 1 = 2;--NO_DATA_FOUND,會立即跳轉到exception塊,不再繼續執行 select 1 / 0 into v_p1 from dual; --除數為0,會報錯,但是這句沒有機會執行了 return 'test' || c_xm; exception when NO_DATA_FOUND then return '沒有找到數據!'; end;
結果如下圖:
這個結論適用於其他情況,無論是在loop中,還是單一查詢,只要報了NO_DATA_FOUND異常,都會立即stop,要么跳轉到exception,要么返回null,不再繼續執行,其實原理很簡單,和java是一樣的,很好理解。