【原創】Oracle函數中對於NO_DATA_FOUND異常處理的研究

一直以來有一個困惑,一直沒解決,昨天一哥們問我這個問題,決心弄清楚,終於獲得了答案。
先看下面這個函數: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

對於【異常老是會拋出,只是客戶端(調用者)對其處理方式不同】,能夠這樣理解:
當用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語句調用函數的過程,若是出現了異常,在報異常的地方函數就此中止運行,再也不往下執行。
驗證: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是同樣的,很好理解。

相關文章
相關標籤/搜索