Oracle函數中對於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;--將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是一樣的,很好理解。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM