一直以來有一個困惑,一直沒解決,昨天一哥們問我這個問題,決心弄清楚,終於獲得了答案。
先看下面這個函數:java
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的網站上找到了答案:sql
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的解釋也比較詼諧:oracle
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的處理方式倒是將它認爲是一個錯誤,app
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異常,調用者不認識,認爲是一個錯誤或者很嚴重的異常,只能報錯給客戶端了。oop
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語句調用函數的過程,若是出現了異常,在報異常的地方函數就此中止運行,再也不往下執行。
驗證:post
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異常而後外拋。this
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是同樣的,很好理解。