懵懂oracle之存儲過程


  作為一個oracle界和廚師界的生手,筆者想給大家分享討論下存儲過程的知識,因為在我接觸的通信行業中,存儲過程的使用還是占據了一小塊的地位。

  存儲過程是什么?不得不拿下百度詞條的解釋來:“存儲過程(Stored Procedure)是在大型數據庫系統中,一組為了完成特定功能的SQL 語句集,存儲在數據庫中, 經過第一次編譯后再次調用不需要再次編譯,用戶通過指定存儲過程的名字並給出參數(如果該存儲過程帶有參數)來執行它。存儲過程是數據庫中的一個重要對象。” 其實就似我們經過一系列的材料准備和烹飪過程准備的一道菜,這樣的菜內部消化是存儲過程,而端出來給別人吃就是函數了(比喻好像不恰當……),后邊也不提函數, 因為懂得了存儲過程,函數也就會了,只要稍微了解下結構就行。

  雖然說用上了存儲過程,也喜歡上存儲過程,但是還是得在分享時提提它的利和弊,因為筆者也被它的弊坑得很慘(要遷移近百個存儲過程?表模型也要改造有大變動? 還以前基本用dblink(這個坑貨更不想提,咱還是忘記它吧!)傳輸?),然后苦逼的“搬磚” 生涯開始了(如果是自己做的菜,怎么着也吃得下,不好吃也很快就能改好菜譜, 但別人的菜別人的菜譜……一言難盡!)。你看,存儲過程用起來是爽,很多時候也提高效率,筆者也是算java開發的,比起用java一次次的從廚房拿材料出來到java代碼里面來搞事情, 在方便的時候還是傾向於在廚房里把菜做好,這樣也減少了網絡傳輸,因為很多時候調來調去處理好數據最終還是寫到表里面去,所以才把整個邏輯直接包在存儲過程中,而且有plsql, 也還算方便調試,特特特便利的是,如果這盤菜按照菜譜做出來不好吃,誒,我們改改菜譜在廚房里立馬就能做新的出來,維護性也很強,如果邏輯在java里面就得部署升級重啟項目。 但是爽歸爽了,在遷移的時候就懵逼了,存儲過程和數據庫綁在一起,庫一旦變了,那得重新把它們綁起來,而且前期開發的時候如果未把這些存儲過程功用記錄好,遷移就沒人知道 它是拿來干嘛的,是不是必須的(不可否認,總會有些臟東西在廚房里面,或多或少會把這些東西也帶到新廚房)。

  閑談這么多,該到一些基礎的語法了(由於筆者看到大寫容易文盲,所以多數字母都是小寫,同時下面內容介紹的比較細比較雜,顯得有些啰嗦,有需要的朋友可將代碼拿走在數據庫中創建,對於注釋部分可折疊或刪除,希望它能成為新手開發存儲過程的一個簡要模板),同時希望大家發現有錯誤的地方在方便的時候能及時進行指正,以免錯誤的知識誤導他人,感激不盡!

 

  1 create or replace procedure sp_hll_test_20170415
  2 /*
  3     創建[或者替換] 存儲過程 存儲過程的名字
  4     所有對象的名稱都一樣限制為最多30個字節……筆者一般習慣以sp_起頭,而且不喜歡把名稱包裹在英文雙引號里面
  5     因此得提下雙引號一些常見的用法:
  6             1.當為oracle特殊關鍵字時(不建議存儲過程啊表啊字段啊等名稱使用oracle關鍵字(可搜索了解)命名)
  7             2.用於一些格式字符串中包裹非法格式
  8              (例如像用於select to_char(sysdate, 'yyyy"年"mm"月"dd"日" hh24"小時"mi"分"ss"秒"') from dual;
  9                但是在select 'yyyy"年"mm"月"dd"日" hh24"小時"mi"分"ss"秒"' from dual;中則雙引號正常輸出。
 10                ps:dual表的用途如果不清楚,還是去網上搜索下吧。
 11  12             3.用於嚴格區分大小寫(在sql語句中大小寫不敏感,但在數據庫中數據值大小寫敏感)
 13              (一般而言我們創建表、字段、存儲過程、等對象,oracle是默認大寫名稱的,所以要是亂加雙引號會導致你找不到你認為建好的表,
 14                用不了你認為你建好的存儲過程,因為你使用它們的時候忘了雙引號區分大小寫。
 15  16     當然使用了英文雙引號包裹存儲過程名稱的話,這兩個引號是不計入長度的。
 17     而且得注意,在oracle中,單個漢字的長度,根據設置不同,占2(ZHS16GBK字符集)~3(AL32UTF8字符集)個字節,
 18     同時也可提下在oracle中,下面這條語句兩個值可是不同的:
 19         select length('abcd啊啵唓嘚'),lengthb('abcd啊啵唓嘚') from dual;
 20         前者把單個漢字算一個長度,后者根據字符集不同算2~3個長度。
 21     (安裝oracle默認是AL32UTF8字符集,字符集修改為ZHS16GBK字符集的方法:
 22         CONNECT SYS_NAME/SYS_PASSWORD AS SYSDBA --根據自己的實際情況登入
 23         SHUTDOWN IMMEDIATE;
 24         STARTUP MOUNT;
 25         ALTER SYSTEM ENABLE RESTRICTED SESSION;
 26         ALTER SYSTEM SET JOB_QUEUE_PROCESSES=0;
 27         ALTER SYSTEM SET AQ_TM_PROCESSES=0;
 28         ALTER DATABASE OPEN;
 29         ALTER DATABASE CHARACTER SET INTERNAL_USE ZHS16GBK;
 30         SHUTDOWN IMMEDIATE;
 31         STARTUP;
 32         ps:不是自己的廚房(數據庫)可別亂動哦!此內容取自網絡,動手前務必了解各項含義!
 33     )。
 34     ps:創建一個存儲過程也就是准備一道菜譜,菜譜的具體內容都在下面一一列出。可千萬要明確好是准備新的菜譜,
 35     不要擅自把別人的菜譜替換為你的菜譜了,這樣做出來的菜可不一定是顧客要吃的。
 36     也就是說不要隨便使用"or replace" ,在新建每個存儲過程的時候,務必去除這兩個詞(這樣的話在新建如果重名就會有提示),
 37     或者確保自己的存儲過程不和已有的存儲過程重名,這邊就可提下如何查看已有存儲過程的方法:
 38             1.在plsql中一個SQL窗口內輸入存儲過程的名稱,通過按住ctrl鍵,然后左鍵單擊存儲過程名字,就可進入存儲過程的內容程序窗口,
 39               要是進不了,恭喜您,該名稱可用於新建新的存儲過程。也可通過右擊存儲過程名稱,通過彈出的右鍵菜單中“查看”、“編輯”進入內容窗口,
 40               在編輯模式下才可實時修改存儲過程內容,然后點擊運行或者按F8進行編譯,如果有問題有錯誤都會及時反饋在窗口下部的矩形區域內。
 41             2.在plsql左側對象資源欄中展開Procedures,然后在搜索框中輸入存儲過程名稱,通過回車進行搜索,找到對應的存儲過程后也可在其名稱上右擊
 42               打開右鍵菜單。
 43             3.最靠譜的語句查詢來了,上面第2點也就相當於查詢all_objects這個表:
 44                 select * from all_objects a where a.object_type = 'PROCEDURE' 
 45                 and a.object_name = '你的存儲過程名稱,記得大寫(當然你建存過時定制化了有小寫的請忽略,不想和你說話……),用plsql可雙擊選中一串信息,然后右擊-選擇-大寫';
 46             ps:正如表名含義所示,該表記錄了所有對象的信息,所以存儲過程、表、視圖、序列、包、函數……一系列的對象你都可以查得到。
 47   */
 48 (
 49  /*
 50     當要創建的存儲過程需要一些外部參數時,就用括號括起來,沒有參數的時候就不用加這對括號了
 51     參數基本格式:param1 [ in | out [nocopy] | in out [nocopy] ] type1 [(default | :=) value1]
 52                   參數名 [ 入參(默認,可省略) | 出參 | 既入又出的參] 參數對應的類型 [默認 值,出參、既入又出參的形參不能有默認值表達式]
 53                   ps:參數的類型不能指定長度
 54                   注意:in {只能夠將實參傳遞給形參,在存儲過程內部只能讀取使用該值,無法修改。可為變量或常量}
 55                         out {不管外部實參是否有值,進入存儲過程內部初始值總是null,在內部可以任意使用修改該參數的值,存儲過程執行完畢后,形參的值會傳遞給實參。必須為變量}
 56                         in out {既可正常接收實參值,又可在結束時傳遞形參值給實參。必須為變量}
 57                         out和in out都可加nocopy,加上nocopy就可能是按引用傳遞(nocopy是一個提示而不是指令,編譯器可以決定是否執行該項),沒加則是按值傳遞。
 58                             某些情況下會有效率提升,在遇到異常時候兩者有不同之處要注意,筆者未使用涉及nocopy,詳情略過不表。
 59                   ps:菜譜要是沒有其它特殊進口材料要用,就不需要進口的包裝袋了
 60   */
 61 
 62  /*
 63     Warning(警告):請不要學習筆者下面的用法,參數、變量等命名為a、b、c、d……務必發揮你英文特長或網絡漢英詞典的優勢,將參數、變量命名的有含義,最好加上注釋注明含義。
 64     同時也不可直接使用對應表的對應字段名稱作為參數名稱或變量名稱,例如:
 65         參數名或變量名為table_name,user_tables表的table_name字段,那么往往會遇到在存儲過程主體中使用如下模式的語句:
 66             select ut.status into v_status from user_tables ut where ut.table_name = table_name;
 67         當然,你的本意可能是想讓ut.table_name = 入參table_name,可是對於sql語句來說,這個table_name就是user_tables里面的table_name列,而對表來說,
 68         它每行的ut.table_name總是等於它的table_name,所以select ut.status 返回的就是整個表的status列的值,你還可以把這個列的值存到你定義的單值變量v_status中嗎?當然不行啦。
 69     因此,參數或變量要有含義,而有字段名稱含義的參數也得加些東西改動。
 70     筆者習慣的參數或變量模式為:i_(入參)、o_(出參)、io_(入出參)、v_(變量)、c_(游標)……
 71 
 72     同時在oracle中,使用" := "進行初始化或賦值的操作,而" = " 則是比較左右兩邊的值是否相等返回true或false的boolean值。
 73   */
 74 
 75  i_a      in number, -- 入參a,in可省略
 76  i_b      date default sysdate, -- 入參b,用default設定默認值
 77  i_c      varchar2 := 'default c value', -- 入參c,用:=設定默認值  ps:默認值就相當於大眾口味,當然你也可以改動它,作為湖(fu)南(lan)人(yin)我表示要特辣
 78  o_d      out number, -- 出參d
 79  i_status user_tables.status%type,
 80  /*
 81     i_status user_tables.status%type
 82     一種實用的設定參數類型的方式,它的類型會隨着user_tables這個表的status的這個字段的類型一起變動保持一致,
 83     當該個參數實質數據來源於某個表某個字段,可仿照上例設置,就不用擔心該個字段的類型有變動,長度有變化之類的,
 84     導致后邊又得來改動這個參數的類型。
 85   */
 86  io_e in out varchar2 -- 既入又出的參f
 87  )
 88 
 89   AUTHID DEFINER
 90 /*
 91     AUTHID DEFINER(可省略,默認為這個) | AUTHID CURRENT_USER
 92     前者表示這個存儲過程使用創建這個存儲過程的用戶的權限來運行--定義者權限。
 93     后者表示這個存儲過程使用當前調用它的用戶的權限來運行--調用者權限。
 94      這樣有什么用呢?一個用戶一般對應一個schema(不知可否對應多個,暫不考慮),該用戶的schema名等於用戶名,並作為該用戶缺省schema。
 95     而這個schema就對應了這個用戶下面的所有數據對象的集合,用戶如果使用自己的東西,當然可以不用打招呼:select * from 我自己的表;
 96     而要是其它用戶的表你也有查詢權限,那用之前還是需要打下招呼:select * from 其它用戶schema名.其它用戶schema下的表;
 97     同理,其它對象如視圖、函數、存儲過程等等都需要這么做。我們總是喜歡偷懶的,所以調用自己的東西總是不會打招呼,因此存儲過程里面查詢
 98     的表總是會沒帶schema。
 99     可要是有這種情況:我們大家都有a、b、c三張表,都想要匯總a、b表信息放到c表中(就是想做的活是一模一樣的),哥們1號就創建了存儲過程來做這個活,
100     但是悲劇的事情發生了,其他哥們包括我,都想用這個存儲過程,誒,都去幫哥們1號干活去了,都去操作匯總哥們1號的表了。想各自都干各自的活怎么辦?
101     難道只能像哥們1號一樣,都各自建各自的存儲過程嗎?因此有AUTHID CURRENT_USER這個設置了,這樣這個存儲過程,誰調用它,它就用誰的schema去查對應
102     的表(不僅僅是表,不帶schema的其它所有對象都一樣)。哈哈,“王美”。
103     ps:用了AUTHID CURRENT_USER的菜譜,那就每個廚師用自己的廚具來干活了。
104   */
105  is
106   -- is | as 這個地方is或者as都可,看喜好。
107   /******************************************************************************
108      NAME:       HLL_TEST_20170415
109      PURPOSE:    懵懂oracle之存儲過程,存儲過程相關知識的分享。
110 
111 
112      REVISIONS:
113      Ver         Date               Author            Description
114      ---------  --------------  ---------------  ------------------------------------
115      1.0           2017-04-15    HE.LILI           創建存儲過程
116      2.0           2017-05-24    HE.LILI           DDL/EXECUTE IMMEDIATE/CLOB/BULK COLLECT/FORALL/CONTINUE/單引號/EXCEPTION_INIT等的添加
117      2.1           2017-06-15    HE.LILI           游標介紹錯誤修復,commit的添加
118      2.2           2017-06-16    HE.LILI           'case'語句使用'else'的缺失補充
119      3.0           2017-06-17    HE.LILI           存儲過程的調用和調試,補充至另一篇《懵懂oracle之存儲過程2》
120      3.1           2017-09-16    HE.LILI           讀TOM書有感,加些警示,學習是漫漫長路
121      3.2           2018-05-10    HE.LILI           修正FORALL與bulk collect into配合處bug
122   ******************************************************************************
123   ******************************************************************************/
124   /*
125     下面這塊屬變量定義區域,類型除了常用的一些字符串類型、數字類型、日期類型、LOB類型等,還可以自主搭配定義類型。
126     同時不同於參數,變量類型如果有長度區分則可自定義長度,具體oracle含哪些基本類型和各個類型的一些默認長度范圍和設定情況
127     可網絡搜索了解。
128     變量基本格式:var1 type1 [(default | :=) value1];
129                   變量1 類型1 [默認 值1];
130     ps:廚房里也有基本佐料如鹽、糖、油、醋等等,也可以自由搭配出你的獨門秘方yo。
131   */
132 
133   v_salt    char := 'Y'; -- 默認長度為1
134   v_sugar   varchar2(50) default 'sugar is sweet,and so are you.'; -- varchar2需指定長度
135   v_oil     date;
136   v_vinegar clob;
137   v_chili   number; -- 默認[10e-130,10e126)
138   v_ginger  user_tables.table_name%type; -- 和參數一樣,我們也可這樣根據對應表對應字段設置類型
139 
140   v_sql varchar2(4000);-- varchar2最大字節長度
141   v_table_batch number := 1000;
142 
143   v_sqlcode number; -- 用來獲取異常code
144   v_sqlerrm varchar2(1024); -- 用來獲取異常errm信息
145 
146   subtype number1 is number(8, 2); -- 定義子類型number1。 注意:number(8,2)表示整數位最多6位小數位最多2位的數值
147   v_paprika  number1; -- 變量 number(8,2)
148   v_capsicum number1; -- 變量 number(8,2)
149   v_pepper   number1; -- 變量 number(8,2)
150   /*
151     subtype又是我們用於偷懶的方法,基本格式:
152         subtype subtype_name is based_type [not null]
153         subtype 子類型名稱 is 基類型 [非空]
154     然后我們就得到個“新”的類型,也就相當於is后邊的類型的一個別名,因為有些時候我們有一批變量都用到這個類型,一旦長度或類型有變化,一一都去改很麻煩,
155     所以就可定義這個子類型,然后那一批變量都用這個子類型,這樣就只要改這個子類型,然后所有用它的變量類型就都改變了。
156     ps:子類型長度限定不一定要有,可以在用這個子類型的時候再加。
157     子類型也可以是根據下面會表述的record類型、table類型等創建的子類型,但是這樣的子類型只繼承大小精度等約束,並不能繼承其他約束,如not null。
158   */
159 
160   v_user_tables user_tables%rowtype;
161   /*
162     單行多列(一個表字段量也就1~1000個)數據。
163     類似於%type,%rowtype表示行類型,%號前面是表則對應這個表的行類型,如果是游標則是游標的行類型。
164   */
165 
166   type tr_redburnedlionhead is record( -- 定義一個叫tr_redburnedlionhead的record類型。
167     v_choppedpork varchar2(100) := 'good marbled meat',
168     v_eggs        user_tables.iot_name%type default 'one egg or two',
169     v_scallion    number1,
170     v_ginger      char(2),
171     v_cookingwine user_tab_columns.data_length%type);
172   r_redburnedlionhead tr_redburnedlionhead; -- 實例化使用叫tr_redburnedlionhead的record類型,定義變量r_redburnedlionhead。
173   /*
174     單行多列數據,當需要用的類型是一個或多個表的某些字段和某些其它類型的集合,我們就能使用record了
175     record基本格式:
176         type record_name is record( -- 定義一個record類型
177         var1  type1 [not null][(default | :=) value1],
178         var2  type2 [not null][(default | :=) value2],
179         var3  type3 [not null][(default | :=) value3]);
180         record_instance record_name; -- 實例化使用這個record類型,這是最基本的使用方法,還可以用於其它需要類型的地方,如后邊會講述的varray或table中,可互相嵌套。
181   */
182 
183   type tv_rice is varray(3) of varchar2(50); -- 定義一個varray類型
184   a_rice tv_rice; -- 實例化使用叫tv_rice的varray類型,定義變量a_rice。
185   /*
186     多行單列數據,varray基本格式:type varray_name is varray(size) of type1 [not null];  -- 定義一個varray類型
187                                   varray_instance varray_name; -- 實例化使用這個varray_name類型,亦可用於table of的類型。
188     varray里面的元素是有序排列的,通過size設定固定正整數位長度,可通過extend(k)方法增加k長度,a_rice.extend 與a_rice.extend(1)同樣增加一個長度。
189   */
190 
191   type tt_rooms is table of number index by varchar2(20);
192   v_rooms tt_rooms;
193   type tt_tables is table of varchar2(200);
194   v_tables tt_tables := tt_tables('101號桌', '102號桌', '103號桌'); -- 基本類型且未設置動態自增的table可如此初始化
195                                                                     -- 或存過主體中使用如:v_tables.extend; v_tables(1) := '101號桌'; 來初始化值。
196   type tt_members is table of user_tables%rowtype index by binary_integer;
197   v_members tt_members;
198   /*
199     多行多列數據,table基本格式:
200         type table_name is table of type1 [not null] [index by binary_integer|pls_integer|varchar2(size)];  -- 定義一個table類型
201         table_instance table_name; -- 實例化使用這個table_name類型。
202     ps:type1-可為基本類型,可為record、varray(它們三者之間的互相嵌套可各自摸索使用),可為游標%rowtype、表%rowtype。
203         index by binary_integer|pls_integer|varchar2(size) : 使用該項配置,則table會動態自增,無需利用extend方法申請擴展長度同時也不可在聲明時初始化值。
204             binary_integer:整型下標,值計算由oracle模擬執行,不會溢出,但執行速度較慢,最為常用;
205             pls_integer:整型下標,值計算由CPU執行,會出現溢出,比oracle模擬快;
206             varchar2(size):字符串下標,size范圍為1~32767。
207         dbms_sql系統包中提供了一些常用的table,如:type number_table is table of number index by binary_integer; 等,在存儲過程中
208         可直接使用dbms_sql.number_table。(更多內容請進入此包查看)
209   */
210 
211   type tr_tables_comp is record(
212     tb_name   user_tables.table_name%type,
213     tbsp_name user_tables.tablespace_name%type);
214   cursor c_tables return tr_tables_comp is
215     select table_name, tablespace_name
216       from user_tables
217      where status = i_status;
218 
219   type trc_tables is ref cursor;
220   vc_tables trc_tables;
221   /*
222     顯示游標基本格式:
223         cursor cursor_name -- 定義一個叫cursor_name的顯示游標,可以用括號設定入參,類似於開頭介紹過的存過入參,但是此處只能為in類型的入參。
224             [(parameter[, parameter]…)]   --定義若干參數,基本格式param1 [in] type1 [(default | :=) value1]
225             [return return_type] -- 比較少添加,return_type定義返回一個記錄類型,指定了游標變量最終返回的查詢結果集類型,需和select_statement返回的類型和列數目一致,
226                                  -- 可以是自定義的記錄類型(最終獲取游標行數據時列名采用記錄內部字段名稱)或%rowtype定義的記錄類型。
227             is select_statement  -- 定義游標對應的select語句,可用上所有cursor定義的參數和存儲過程的in類型參數。
228             [for update          -- 定義for update后,則可在循環游標時,通過where current of cursor_name來更新或刪除當前游標所在行數據,會默認給select語句中所有表加共享鎖。
229                 [of [table1.]column1[, [table2.]column2]…]  -- 定義不同的列,則for update后只給對應的表加共享鎖
230                 [nowait] -- nowait用於指定不等待其它會話的鎖,當遇此情況,會拋出ORA-0054異常並退出當前塊,默認當前會話要一直等待釋放。
231             ];
232     隱示游標基本格式:sql%isopen | sql%%found | sql%notfound | sql%rowcount -- 這不是定義,而是在存儲過程主體中使用這四個屬性的方式。
233         基本上是這四種用法,“PL/SQL為所有SQL數據操作語句(包括返回一行的SELECT)隱式聲明游標,稱為隱式聲明游標的原因是用戶不能直接命名和控制此類游標。
234         當用戶在PL/SQL中使用數據庫操作語言(DML)時,Oracle預定義一個名為SQL的隱式游標,通過檢查隱式游標的屬性可以獲取與最近執行的SQL語句相關的信息。"--摘自百度百科
235 
236     游標變量基本格式:
237         type ref_cursor_name is ref cursor  -- 定義一個游標變量類型
238             [ return return_type]; -- 類似顯示游標處作用,有指定此處return則是強類型定義(限制了后面調用時select需要返回的類型次序),否則是弱類型定義(未限制)。
239         ref_cursor_instance ref_cursor_name; -- 實例化使用這個ref_cursor_name類型
240                                              -- ps:此處實例化游標變量時還可用sys_refcursor這個類型,它是oracle9i以后系統定義的一個ref cursor,
241                                              --     主要用在過程中返回結果集(作為出差)。
242      ps:當sql語句是動態生成的時候,我們就沒辦法按顯示游標基本格式定義游標,所以可以用游標變量,在存過主體中它倆的基本用法也差不多,見后面關於循環的知識點。
243     游標基本屬性:
244         isopen:【顯】當游標已打開時返回 true。【隱】因為隱示游標在執行DML語句前自動隱含式的打開,並在DML執行完后關閉,所以隱示游標的該值總為false。
245         found:【顯】當最新fetch提取游標操作成功則為true,否則為false。【隱】當insert、update、delete語句處理一行或多行,或者是執行select into 語句返回一行(多行或0行都會
246                 直接異常報錯)時,該屬性為true,否則為false。
247         notfound:【顯】【隱】與found屬性相反。
248         rowcount:【顯】返回當前已從游標中讀取到的記錄數。【隱】執行insert、update、delete語句返回的行數(0~n),或執行select into語句時查詢出的行數(0或1,
249                 多行就異常不會再判斷rowcount)。
250   */
251   
252   bulk_error exception;-- 定義一個異常
253   pragma exception_init (bulk_error, -24381);-- 將此定義的異常與oracle的一個錯誤聯系起來
254   continue_error exception;-- 定義一個異常,用於下文實現continue
255   
256   -- pragma autonomous_transaction;
257   /*
258     [  pragma autonomous_transaction; ]
259     自治事務,可選的一項配置,相當於一段獨立出來的子事務,設置后在此存儲過程中可以自由的commit或者rollback,卻不會影響到調用這個存儲過程的主存過主事務中去,
260     當然主事務未commit的數據對我們這個存儲過程來說也是不知道的,主事務的rollback也回滾不了我們子事務已commit的東西。
261     一般專用於用來記錄日志的存儲過程。
262     ps: (2017-9-16)此項還是注釋掉為好!請合理考慮使用邏輯,基本用到的地方只有用於錯誤日志記錄,如果有其它場景需要使用,
263     請考慮是否場景有誤。終歸跳出主事務的東西還是得謹慎使用。
264   */
265 begin
266   -- 存儲過程主體內容的開始 ps:菜譜的材料都准備好了,該來做菜步驟了。
267   select count(1)
268     into v_chili
269     from user_tables
270    where table_name = 'USER_TABLES_TMP'
271      and tablespace_name <> r_redburnedlionhead.v_choppedpork; -- 在增、刪、改、查等語句中,都是可以這樣直接使用變量的
272   /* 
273     一種可在存儲過程中給變量(包括type類的記錄類型)賦值的語句:
274          select value1,value2,value3… into val1,var2,var3…|record_instance|table_instance from table1 where condition1;
275     或者 execute immediate 查詢語句字符串或查詢語句字符串變量 into val1,var2,var3…|record_instance|table_instance;
276     
277     備注:
278     1.在存儲過程中select時,不存在沒有意義的select,平常單條執行select我們可以主動看到查詢的結果,
279       而在存儲過程中,這個結果得拿出來才能使用,所以在存儲過程中,我們的select語句要么跟隨游標一起
280       把結果存入變量,要么則用into存入變量中。
281         
282     2.除了 select語句 的賦值,:= 的賦值,還有update、insert、delete語句也能賦值。
283       當然這個賦值不是必要的,這三個語句與select不同,它們對存過來說本身就是有意義的。
284       所以returning into語句對它們來說是可有可無選項,加上這個后會在這些DML語句成功執行后
285       影響到的行數據的值保存到變量中。
286       returning into子句的用法:
287         insert into 表1(字段1[,字段2,字段3,……]) values (值1[,值2,值3,……]) 
288             returning 字段1[,字段2,字段3,……] into 變量1[,變量2,變量3,……] 或者 記錄1;
289         update 表1 set 字段1=值1[ ,……] where 條件 
290             returning 字段1[,字段2,字段3,……] into 變量1[,變量2,變量3,……] 或者 記錄1;
291         delete 表1 where 條件 
292             returning 字段1[,字段2,字段3,……] into 變量1[,變量2,變量3,……] 或者 記錄1;
293       ps:由於returning into是執行DML后才將影響到的行數據的對於列的值保存到對應變量中。
294           所以1)語句執行異常,到最近的捕獲異常處被處理(沒有則整個存儲過程異常退出)。
295               2)語句更新、刪除成功,影響行數=0,那么相當於沒有returning into選項,不對變量值造成影響。
296               3)語句執行成功,影響行數>0,那么等於執行了insert/update/delete后再select對應的值into變量。
297                      
298     3.對應insert、update、delete這樣的DML語句,在存儲過程中需配合使用rollback(回滾)和commit(提交)來結束
299       它們的事務。如果整個存儲過程是一個整體,需各個插入刪除等語句都成功后才提交,往往在存儲過程末尾才
300       加commit進行提交。
301         
302     4.DML(數據操作語言):select/insert/update/delete/merge/…… --select在某些數據庫中當成DQL
303       DDL(數據定義語言):create/alert/drop --注意:DDL會隱示的進行事務提交
304       DCL(數據控制語言):grant、revoke --授予權限與回收權限語句
305   */
306   
307   if v_chili = 0 then
308     execute immediate 'create table USER_TABLES_TMP as select * from user_tables';
309   end if;
310   for i in 1..v_tables.count loop
311     v_sql := 'declare 
312               flag number; 
313               begin 
314                 select count(1) into flag from user_tables 
315                  where table_name = :1
316                    and tablespace_name <> '''||v_sugar || ''';
317                 if flag =0 then
318                    delete from user_tables_tmp where table_name = :2;
319                 end if;
320               end;';
321       execute immediate v_sql using v_tables(i),v_tables(i);
322   end loop;
323   /*
324     1.DDL在存儲過程中的執行:
325         上面已經提到了DML語句的使用,而DDL語句在存儲過程中是不鼓勵使用的,如果直接用這些語句,
326     那么存儲過程無法編譯通過,所以得用到execute immedate來將DDL“屏蔽”。注意:在存儲過程中這樣執行
327     DDL語句也不是直接就能執行的,需要用戶有執行存儲過程中DDL的權限(如創建表等權限,通過語句給這個
328     用戶授權:grant create table to 用戶名),或者使用之前提過的AUTHID CURRENT_USER項將存儲過程改為
329     調用者權限機制也行,因為之所以沒有權限是因為用戶的角色權限在進入存過里面會被剝離掉,所以要么顯示
330     的授權,這樣系統權限就會帶入進來,要么則改為調用者權限機制。
331           
332     警告:DDL語句在存儲過程中的使用需謹慎處理,因為DDL語句是隱示提交的,所以看的一個DDL語句就得
333     想到一個commit語句,這個會對存儲過程中的事務控制造成影響!如果在存儲過程中無法避免使用到影響事務
334     控制的DDL語句,那么怎么辦呢?沒錯,我們可以使用之前提到的pragma autonomous_transaction;項自治事務。
335           
336     2. EXECUTE IMMEDIATE(后續簡稱為EI)神器:
337     ps: (2017-9-16)此項用起來是爽,還是強調下別濫用了,能用正常SQL的地方請勿用動態拼裝的SQL,
338     數據庫它更喜歡明確的東西,從執行效率上來看這個玩意是肯定比不上正常SQL的。
339         上面第1點的接受我們已經知道execute immedate可以用來執行DDL語句,其實,這個神器可以做的還有很多。
340     基本格式:
341         execute immediate dynamic_sql_string|plsql block|var1(字符串變量或者clob變量)
342       [into {var1[,var2]…| record}]
343       [using [in | out | in out] arg1[,[in | out | in out] arg1]…]
344       [{returning | return} into r_arg1[,r_arg2]…];
345 
346     2.1 dynamic_sql_string|plsql block 指的是動態拼接的SQL語句或者一個PL/SQL塊。
347         因此,EI不止可執行DDL,之前講的DML、DCL甚至一個完整的PL/SQL塊等都可以執行,它替代了以前ORACLE8I中
348         DBMS_SQL包(當然兩者還是有區別差異,但DBMS_SQL用到的情況現在已經很少見了,有興趣的可搜索了解)。
349         后邊接動態拼接(可以利用一些循環)的語句或者塊的字符串,可以用變量--字符串變量或者clob變量(oracle 11g
350         才開始支持clob)存儲拼接,
351        
352         PL/SQL塊簡單介紹:可以看成是一種匿名的存儲過程,可以看成執行時立即調用自己,不會像存儲過程一樣生成對象,
353         待后續的調用使用。
354         基本格式declare …… begin …… end; (如果不需要定義變量,直接使用begin …… end; 塊亦是可行的)
355         類比存儲過程就相當於create or replace procedure sp_hll_test_20170415(參數) as用declare代替。當然一些屬於
356         存儲過程特有的參數、事務自治、定義者或調用者權限等無法使用,其它內部格式和使用與存儲過程基本一致。
357           
358     2.2 into一般用來保存單個select語句查詢返回的結果,類似於select……into……,后邊可接多個單值變量,或者一個記錄類型。
359      
360     2.3 using 需在動態語句中配合  :(冒號)+綁定變量名  來使用,筆者一般使用:1,:2,:3……等方便快捷,或者可以:table_name等
361         用字段具體名稱來標識,都無關緊要,對於using來說,它就從using后接的參數值按從前至后的順序把值自前往后地
362         替換到動態語句中對應的綁定變量中(該變量不可綁定表名、字段名等,只可綁定字段值或者塊里面調用存過函數等
363         參數值,對於表名、字段名等就需直接拼接,而字段值可直接拼接,亦可用using方式來綁定變量)
364         
365         何時需要使用using呢?1)不涉及where的語句;2)OLTP環境;--OLTP與OLAP的含義和區別請搜索了解
366         執行下面代碼可對綁定變量使硬解析轉變為軟解析的情況加以了解(硬軟解析請搜索了解):
367                 select * from all_objects where object_id=20;
368                 select * from all_objects where object_id=30;
369                 select * from all_objects where object_id=40;
370                 select * from all_objects where object_id=50;
371                 begin
372                   for i in 1 .. 4 loop
373                     execute immediate 'select * from all_objects where object_id=:i'
374                       using i;
375                   end loop;
376                 end;
377                 /
378                 select sql_text, s.parse_calls, loads, executions
379                   from v$sql s
380                  where sql_text like 'select * from all_objects where object_id%'
381                  order by 1, 2, 3, 4;
382                    
383     2.4 {returning | return} into 對應的是EI執行之前介紹的returning into子句,同時與using一樣需在動態語句中
384         配合  :(冒號)+綁定變量名 來使用,即returning into子句into后的變量需加:(冒號),同樣與using一樣,內部變量的名稱
385         亦無關緊要,統一都按變量順序一一對應。最后,它的執行賦值結果也同returning into子句一致。
386         示例:
387             execute immediate 'update TEMP_WC_1 t
388                                   set t.接口表名 = ''INTF_LTE_EUTRANCELLTDD''
389                                 where t.接口表字段 = ''related_me'' and rownum = 1
390                                returning 接口表名 into :a'
391             returning into b;
392 
393         備注:EI執行動態語句的優勢在於其靈活性,根據條件不同可拼接成不同的語句,大至整個內容塊,小至表名、字段名、
394             參數值等,使代碼更具適用性更簡潔,利用好也能減少耗費提高性能。腦洞大開的你還能EI嵌套EI嵌套EI……
395             所以濫用EI也會使得代碼更加脆弱,調整優化更加困難。筆者建議在使用EI時,定義一個變量存儲值,如v_sql,然后
396             可以通過dbms_output.put_line(v_sql)或者調試取v_sql的值來了解最終動態拼接成的語句是什么樣的,可以把這個語句
397             放到新的SQL窗口執行來解決錯誤。
398             
399     3. CLOB的使用:由於存儲過程中varchar2最大長度是4000,所以在字段長度會超過varchar2的限制時,我們就會用clob類型,
400         特別在使用EI時,由於是動態拼接的語句,很多情況下都會導致拼接的語句過長。而在使用中,clob類型又是能方便的與
401         varchar2類型進行拼接,如:v_clob := v_clob || '我是一個普通字符串';  所以濫用clob引發性能問題也是不少的。
402         因為在對clob字段進行操作時,會導致大量的邏輯讀和臨時段(在會話退出才清除),影響性能,怎么解決呢?
403         在必須用到clob時,我們需要根據情況,先將短的字符串進行拼接,確保小於4000,然后再拼接到clob變量上去,
404         以盡量減少對clob字段的操作,來降低對性能的影響。
405     
406     4. (單引號--> ' )的使用:
407         1)字符串的標識(由兩個單引號包裹組成);
408         2)轉義(在字符串內部相鄰兩個單引號,前者為轉義符號)。--特別在EI嵌套中需注意使用!
409         示例1:
410             select '''',' '' ','''''',''' '' ''' from dual;
411         示例2(EI嵌套時,每多一層嵌套,代表單引號的引號數目需乘以2):
412             select 'declare begin execute immediate ''declare begin execute immediate ''''select sysdate from dual''''; end;''; end;'
413                 from dual;
414   */
415   
416   /*
417     if判斷基本格式:
418         if condition1 then
419             content1     -- 當滿足條件1的時候,做內容1的事情
420         [elsif condition2 then content2] -- 當滿足條件2的時候,做內容2的事情
421         [elsif condition3 then content3] -- 當滿足條件3的時候,做內容3的事情
422 423         end if; -- 結束if判斷,注意加分號
424         
425     case判斷基本格式:
426         case [var1] when value1|condition1 then -- 有var1時,when后面跟value1~n(對應具體值,進行判斷var1是否等於value1~n);無var1時,when后跟condition1~n,類似於if的用法。
427             content1     -- 當滿足條件1的時候,做內容1的事情
428         [when value2|condition2 then content2] -- 當滿足條件2的時候,做內容2的事情
429         [when value3|condition3 then content3] -- 當滿足條件3的時候,做內容3的事情
430             [else contentn]
431         end case;        -- 結束case判斷,注意加分號
432     ps:使用"case …end case;"則在"then"后接具體的執行語句;
433         使用"case …end"則在"then"后接具體的值,整個case結構代表一個具體固定類型的值,所有then與else后返回的值類型必須統一(或number或varchar2等等,由第一個then后
434         的值類型確定)。
435     ps:判斷格式condition1~n未要求但可使用小括號包裹,也可利用小括號包裹部分形成一個整體用於條件組合。
436   */
437   if (case i_a
438        when 1 then
439         true
440        when 2 then
441         false
442        when 3 then
443         true
444        else
445         false
446      end) or (i_b > sysdate and i_c = 'default c value') then
447     --    a := 2; 這個語句是錯誤的,作為一個in的入參,不可以在存儲過程中用“:=”去重新賦值,只能拿本身的值去使用
448     o_d  := 1; -- 而作為out的出參,則當然可以改動值。
449     io_e := 'changed e value'; -- 既入又出的參,也能改動值,同時還可以接收外界傳的值。
450   end if;
451 
452   case
453     when i_status = '' then -- null = '' --> false
454       /*
455         =''用法是不對的,或者說它總是false,在存儲過程編譯的時候就會有提示信息,告訴你“懷疑有 NULL 進行比較的情況”。
456         在oracle中,'' 就被當做為null了,而mysql則兩者是不同的東西,這個務必要注意,
457         暫發現在oracle中concat、count函數,把null能看成是'',一個空字符串,其它函數中,基本結果都是null,
458         當然如果是一些邏輯運算的話,例如:null = null;  null > '1' 等等,結果都是false。
459       */
460       v_sugar := 'I need more sugar!';
461       dbms_output.put_line('sugar:' || v_sugar);
462       /*
463         dbms_output是oracle的一個系統包,可用於輸出一些調試信息,常用方法:
464             put:把內容寫到內存,無換行符在末尾,遇到put_line或new_line后才會把內容輸出;
465             put_line:輸出內容並換行(輸出一行內容包括前面所有put里的內容);
466             new_line:相當於輸出換行符,另起一行,亦即結束當前行(把所有put里的內容也輸出)。
467         在plsql中的“SQL窗口-輸出”、“測試窗口-DBMS輸出”處都有設置“緩沖區大小”,用dbms_output輸出的內容在此處區域顯示,
468         且put的內存內容字節長或put_line的內容的字節長或所有本次輸出的內容字節總長都不能大於“緩沖區大小”,同時每行最多32767字節長,
469         否則會報錯(在手動執行測試時),在調試存過的時候也會因此異常而中斷存過,無法繼續執行,需要注意使用。
470         ps:其它方法或其它常用系統包根據情況網絡搜索了解,不再細表。
471       */
472 
473     when length(i_status) = 0 then -- null = 0 --> false
474       v_sugar := 'more and more sugar!';
475       dbms_output.put_line('sugar:' || v_sugar);
476     when i_status is null then
477       v_sugar := 'just "sugar_maroon 5"!';
478       dbms_output.put_line('sugar:' || v_sugar);
479     /*else -- 務必加else項,否則在上面都不滿足后,找不到該執行的項而報錯:ORA-06592: 執行 CASE 語句時未找到 CASE
480       v_sugar:='no more sugar!';
481       dbms_output.put_line('sugar:' || v_sugar);*/--注釋后用於調試測試
482   end case;
483 
484   /*
485     因游標與循環關聯比較多,故將游標定義后在存儲過程主體中的用法也匯總在循環格式里。
486         loop循環基本格式:
487             [open cursor_name|ref_cursor_instance [([param1 =>] value1[, [param2 =>] value2]…)] -- 匯總游標的用法:打開一個游標,若為帶參數的游標可在此處傳入參數。
488                 [for select_statement | select_sql_variable;] -- 當open的是一個游標變量時,我們可以在此處for后添加select語句或者一個字符串變量(拼接的一串select語句)。
489                                                               -- ps:用法多多,可以根據條件判斷open不同的語句,也可重復利用同一個游標變量open不同的語句。
490             ];
491             [<<label_name>>] --設定一個標簽名稱,關聯這個loop,可選
492             loop -- 開始循環
493                 [
494                 fetch cursor_name|ref_cursor_instance  -- 匯總游標的用法:loop開始時每fetch取一行或一個集合數據,然后游標自動指向下一行或下個集合數據
495                     [into var1 [, var2]…| record_instance;]-- fetch獲取一行數據后,還得扔到我們定義的一些變量中保存,
496                                                            -- 也可放到record中作為一個整體(使用時“record_instance.具體的屬性名稱”)。
497                     [bulk collect into varray_instance|table_instance  [limit rows];] -- into 和 bulk collect into 只能有一種,要么一次取一行,要么一次取一批
498                     -- bulk collect into提取一批數據放入varray/table的一些實例變量內,limit項可選,
499                     -- 限制每批提取數據的最大的行數,rows為正整數(常量or變量)。
500                     -- ps:limit一般用於分批提取處理,將“for循環limit分批值”放入上面的loop循環游標內部,
501                     --     來循環中分批提取數據,既保證能提取游標中所有數據,又可通過分批提高效率。
502                 exit when cursor_name%notfound|ref_cursor_instance%notfound; -- 游標的話就可在此處exit,判斷fetch是否有取到值。
503                 ]
504                 content1; -- 循環的內容1 -- 可嵌套一個循環處理bulk collect獲取的集合的內容
505                 if condition1 then [content2;] exit [label_name]; | exit [label_name] when condition1; -- 無論是用if…exit還是exit…when都行或者上述游標的exit,請有能退出循環的條件。
506                 content2; -- 循環的內容2
507             end loop [label_name]; -- 結束循環
508             [close cursor_name;] --匯總游標的用法:關閉游標,必須在使用完后及時關閉以釋放該游標所占用的系統資源,並使該游標的工作區變成無效。
509 
510         for循環基本格式:
511             [open cursor_name|ref_cursor_instance [([param1 =>] value1[, [param2 =>] value2]…)] -- 匯總游標的用法:同上。
512                 [for select_statement | select_sql_variable;] -- 同上。
513             ];
514             [
515             fetch cursor_name|ref_cursor_instance
516                 bulk collect into varray_instance|table_instance; -- 將游標中數據一次性全部取出來,然后在下面的for循環中可做相應業務操作。
517             ] -- 初始化fetch值
518             [<<label_name>>] --設定一個標簽名稱,關聯這個for…loop,可選
519             for var1 in -- 設定一個變量,且這個變量無需在前面有定義過
520                 [reverse]     value1 .. value2|cursor_name|(select_statement)
521                 -- 前者設置循環[floor(value1),floor(value2)],此時變量var1為從floor(value1)至floor(value2)(設置了reverse則從floor(value2)至floor(value1))的整數值。
522                 -- ps:匯總游標使用:可用1 .. table_instance.count或者table_instance.first .. table_instance.last來指定遍歷集合數據類型變量里存的數據
523                 --     的下標范圍(從1開始),可通過“table_instance(var1).具體字段名”來使用每行數據內的每列值,var1相當於循環計數器。
524                 -- 中者設置循環游標查詢出來的結果集,for開始時隱含地打開游標,在循環開始隱含地執行了fetch,
525                 -- 在繼續循環前隱含地檢查了notfound值,在循環結束隱含地執行了close,較便利。
526                 -- ps:在for內部var1相當於一個賦了值的record,可通過“var1.具體字段名”來使用每行數據內的每列值
527                 -- 后者設置循環select語句查詢出來的結果集,此時變量var1為循環這個結果集中的每一行數據,
528                 -- 在for內部var1相當於一個賦了值的record,可通過“var1.具體字段名”來使用每行數據內的每列值。
529                 -- ps:這種for循環游標簡潔安全,省卻了定義游標,open游標,fetch值,循環,判斷退出,close游標等等操作,可用的時候推薦使用它。
530             loop -- 開始循環
531                 content1; -- 循環的內容
532             end loop [label_name]; -- 結束循環
533             [close cursor_name;] --匯總游標的用法:同上。
534 
535         while循環基本格式:
536             [open cursor_name|ref_cursor_instance [([param1 =>] value1[, [param2 =>] value2]…)] -- 匯總游標的用法:同上。
537                 [for select_statement | select_sql_variable;] -- 同上。
538             ];
539             [fetch cursor_name|ref_cursor_instance -- 匯總游標的用法:同上
540                 into var1 [, var2]…| record_instance;      -- 同上。
541             ]  -- 初始化fetch值
542             [<<label_name>>] --設定一個標簽名稱,關聯這個while…loop,可選
543             while condition1 loop -- 先判斷是否滿足condition1條件,然后開始循環
544                                   -- 匯總游標的用法:condition1則為cursor_name%found或ref_cursor_instance%found,看是否有數據。
545                 content1; -- 循環的內容
546                 [fetch cursor_name|ref_cursor_instance -- 匯總游標的用法:同上
547                     into var1 [, var2]…| record_instance;      -- 同上。
548                 ] -- 循環fetch值,如果游標的用法,必須在循環最后都重新fetch值。while前的fetch只是一個初始化,只會執行一次。
549             end loop [label_name]; -- 結束循環
550             [close cursor_name;] --匯總游標的用法:同上。
551 
552   */
553   io_e := 'select * from user_tables';
554   open vc_tables for io_e; -- 可使用open … for … using 的用法,類似於之前介紹的EI
555   loop
556     fetch vc_tables bulk collect -- bulk collect增強SQL引擎到PL/SQL引擎的交換
557       into v_members limit v_table_batch;
558     exit when v_members.count = 0; -- 退出fetch前的loop,否則fetch空不會報錯,導致會無限循環下去。
559     for i in 1 .. v_members.count loop
560       for j in 1 .. 1 loop -- 假循環實現contine
561         if i=5 then
562               exit; -- 起continue的作用,退出假循環
563         end if;
564           dbms_output.put_line(v_members(i).table_name);
565       end loop;
566       
567       begin -- 拋異常方式實現continue
568         if i = 5 then
569           raise continue_error; -- 起continue的作用,不再繼續執行后續循環內容,到異常處理
570         end if;
571           dbms_output.put_line(v_members(i).table_name);
572       exception
573         when continue_error then -- 捕獲此異常,並不做任何處理,以進行后邊的循環
574           null;
575       end;
576       /*
577         oracle中continue的實現(由於oracle中沒有continue):
578         1. 如上例,使用一個for j in 1 .. 1 loop   <content>  end loop;這樣的假循環將循環體包裹,
579             這樣在內部exit只會退出當前的假循環。
580         2. 如上例,begin end、raise exception聯合處理來實現異常,比較繁瑣。
581         3. 通過goto和<<標簽>>的配合也可實現continue,但是由於goto會打亂我們的程序邏輯,一般不使用,因此不介紹。
582       */
583     end loop;
584   end loop;
585   close vc_tables; --關閉游標
586   
587   /*
588     BULK COLLECT批量綁定:一次性提取所有行並綁定到記錄變量里面,可增強SQL引擎到PL/SQL引擎的交換。
589     使用:配合into 記錄類型:
590     1)select … bulk collect into …
591     2)fetch … bulk collect into … [limit row_number]
592     3)returning … bulk collect into …
593   */
594     forall j in v_members.first .. v_members.last -- forall增強PL/SQL引擎到SQL引擎的交換
595     save exceptions -- 可選項配置,將產生的異常保存並繼續批處理
596       execute immediate 'delete from user_tables_tmp where table_name = :1' using v_members(j).table_name;
597   /*
598     FORALL批聯編基本格式:
599          forall index_name in { min_index .. max_index 
600                                 | indices of collection_instance [ between min_index and max_index ]
601                                 | values of index_collection
602                               }
603          [ save exceptions ] 
604             DML_SQL|DML_EI;
605     1. index_name:無需事先申明的變量標識符,在循環中作為集合的下標來使用,常命名為i,j,k…等。
606     2. min_index、max_index:數值限定index_name的最大最小值。
607     3. indices of record_instance:當記錄中第i行未初始化賦值,或者被刪除導致沒有值,我們需要在循環中調過這
608         些行的數據,則需使用indices of,以此使下標調過這些無效項,同時也可配合min_index、max_index使用。
609     4. values of index_collection:上述3是自動跳過無效項,而此項values of配置則是手動設置“有效項”,index_collection
610         需為pls_integer/binary_integer組成的集合來配合。
611         示例:1)定義變量:type t_index_collection is table of pls_integer;   
612                            index_collection t_index_collection:=t_index_collection(2,4,6,8,10); --在定義時可初始化
613               2)內部賦值:index_collection:=t_index_collection(1,3,5,7,9);
614     5. save exceptions:配置此項則表明當批量執行中遇到異常仍執行到最后。此時會拋出ORA-24381: error(s) in array DML。
615         可將此異常在變量定義區定義異常來關聯,如本存儲過程定義的bulk_error變量。
616         同時所有異常信息會保存在sql%bulk_exceptions,可在捕獲異常處處理,見本存儲過程最后異常處理部分。
617     6. DML_SQL|DML_EI:此項是批處理的單條DML語句,也可以用EI語句。如果單條DML語句需循環引用集合數據執行,
618         那么采用FORALL會顯著地提供處理性能,它會通過只調用一次SQL,然后將整個循環處理的內容提交上去。
619     7. FORALL一般配合select … bulk collect into … 使用。
620         但當數據量比較大的時候,往往配合fetch … bulk collect into … limit row_number來分批處理可使效率更快。
621         下面提供一個簡單實用的海量數據分批處理模板:
622             declare -- 在處理量多的時候,最好轉成存儲過程然后通過JOB調用來執行,否則手動在plsql執行很容易卡死。
623                 maxrows      number default 5000; -- 可根據情況適當調整每批處理的量
624                 row_id_table dbms_sql.urowid_table;
625                 cursor c_cursor is
626                   select n.rowid from 表 n where 一些限制條件;
627             begin
628                 open c_cursor;
629                 loop
630                   fetch c_cursor bulk collect
631                     into row_id_table limit maxrows;
632                   -- exit when c_cursor%notfound; --2018年5月10日22:24:20 發現有誤,需要調整為下面語句,或把此行移至commit后,否則丟數據
633                   exit when row_id_table.count = 0;
634                   forall i in 1 .. row_id_table.count
635                     update 表 a
636                        set a.字段1 = 值1
637                      where rowid = row_id_table(i);
638                   commit;
639                 end loop;
640                 close c_cursor;
641             end;
642 
643   */
644 
645   commit;
646   /* 
647     COMMIT:
648     ps: (2017-9-16)要不是讀了TOM的書,還不知道commit的使用情況,很多錯誤的理解錯誤的攢了下來,如果不去實踐,
649     或者從其它途徑了解到真實情況,可能永遠都得不到更正了,還是要多讀書多實踐啊,使用commit必須要謹慎,TOM告訴了我,
650     "COMMIT和ROLLBACK一般情況下不應該在PL/SQL中使用,只有PL/SQL存儲過程的調用者才知道事務何時完成。
651     在你開發的PL/SQL過程中執行COMMIT和ROLLBACK是一個不好的編程實踐"
652     
653     如果此存儲過程是自己完成任務而不是給別人調用,那么一般也就在最后顯示的加一個commit來進行提交工作,
654     對於不同的使用工具、環境來說,有可能在正常退出會話時會進行commit操作,
655     也有可能進行rollback操作,這些隱式的行為是不可控的,所以對於整體的事務來看,顯示提交和回滾是正常有效的。
656     
657     如果此存儲過程是提供給別的程序、存儲過程等調用的,那么還是讓別人來操心提交的問題吧,因為這種情況下你只是做了
658     一部分的活,你(不是自治事務的存過)不是一個主要的整體,你會把別人主事務在調用你之前的未提交的內容一起提交!
659     可能需要使用多個提交的情況是單個海量數據操作事務整體時根據操作量來提交,例如每100000條數據提交一次之類等,
660     反正還是得謹慎使用,如果頻繁地提交,通常並不會更快,一般在一個SQL語句中完成工作幾乎總是更快一些。
661     而且用到頻繁提交一般也得配合批處理來進行,總之都會使得一條SQL語句的活變成了一堆復雜的代碼,而且這樣提交,不可避免的,
662     需要考慮到中途異常失敗的處理,異常前用commit提交的內容已經提交了無法回滾了,那么使用到這種提交的必定要是能
663     從失敗的地方解決問題如何接着繼續完成未了的事情,也就是你能夠清楚的知道哪些是處理掉的(有可能是處理表中有字段做標識,
664     有可能是從一個表到另一個表等)
665     
666     因此,取一個網友的話來總結下:
667     "從事務上來說,如果是單獨的存儲過程調用,一個commit,如果是前台語言調用存儲過程,調用的語言commit..
668     從性能上說,如果做很多大事務,不commit,必然對undo產生沖擊,可能會干爆undo...但是太頻繁也不行,太頻繁commit,redo受不了
669     從鎖上說,高並發的,你多條語句如果commit一次,語句慢的話,產生鎖的時間必然長,會造成其他session等待的問題。。。"
670     
671     and TOM:"數據庫的這些組件(臨時段、undo和redo等)不是開銷,而是系統的關鍵組件。必須適當地設置大小(不要太大,也不要太小)"
672      (如果你使用了pragma autonomous_transaction,則務必得記住commit語句的添加,否則沒有在DML操作后沒有提交或會滾回導致報錯
673      “ORA-06519: 檢測到活動的獨立的事務處理, 已經回退”)
674   */
675 exception
676   -- 處理異常
677   when no_data_found then
678     rollback; --回退,其實一般捕獲這種特定的異常基本表示是需要特殊處理這些異常,而不是直接回滾
679               --說明是一個預料中的異常,用對應的措施去處理。
680     -- 一種oracle的預定義異常,表示select into沒有找到數據
681     dbms_output.put_line('我也不曉得你的哪條select into沒有找到數據');
682   when bulk_error then
683     rollback; --回退
684     for i in 1 .. sql%bulk_exceptions.count loop
685       dbms_output.put_line(sqlerrm(-sql%bulk_exceptions(i).error_code));
686       dbms_output.put_line('index='||sql%bulk_exceptions(i).error_index);
687       dbms_output.put_line('table_name='||v_members(sql%bulk_exceptions(i).error_index).table_name);
688     end loop;
689   when others then
690   /* 
691     ps: (2017-9-16)TOM:"實際上,我認為所有只包含WHEN OTHERS異常處理程序,而不包括RAISE或RAISE_APPLICATION_ERROR
692     來重新拋出這個異常的代碼都是bug。它只是悄無聲息地忽略這個錯誤,這會改變事務語義。"
693   */
694     rollback; --回退
695     v_sqlcode := sqlcode;
696     v_sqlerrm := sqlerrm;
697     dbms_output.put_line('本次的異常code:' || v_sqlcode || '\n本次的異常信息:' ||
698                          v_sqlerrm);
699     /*
700       異常處理基本格式:
701         begin
702 703         exception -- 跟begin對應的,一個begin…end塊里面只能有一處這個異常處理,所以如果想要詳細確定哪一步出的錯,
704                   -- 將每個步驟單獨用begin…end(可嵌套使用)包裹起來,然后細致明確的拋出對應的異常情況。
705             [when 某種預定義或用戶自定義的異常1 then
706                 content1;
707             [when 某種預定義或用戶自定義的異常2 then
708                 content2;]
709 710             ]
711             when others then
712                 content_others;
713             end; -- 和begin對應
714        ps:異常的更多信息暫不講述,待后續添加,想了解的可網絡查詢。
715        ps: (2017-9-16)在使用when others then時務必要注意,你可能因此寫了個bug,如果是你打算把所有異常都記錄至日志表中,以此反饋告知也還可行,如果沒有任何記錄此異常或拋出此異常的行為,那么你很可能把所有未知的異常都吞掉了,從表面上來看,你這塊代碼總是沒問題的(就算它沒正常完成自己的任務)。
716       */
717 
718   /*
719     存過的調試、存過的調用見另一篇文章《懵懂oracle之存儲過程2》:http://www.cnblogs.com/snowballed/p/7028912.html
720     存過相關oracle JOB處理見另一篇文章《懵懂oracle之存儲過程3--JOB詳解》:http://www.cnblogs.com/snowballed/p/7245739.html
721   */
722 end sp_hll_test_20170415; -- 筆者的習慣,最外層的end總是“end 本個存儲過程或本個函數等”,有始有終,當然不加后面的存過或函數名稱也能正常使用。
723 /  
724 --如果存儲過程或者函數或者PL/SQL塊后邊還有其它的內容如普通語句或其它存儲過程、函數等的創建,需用"/"作為分割符,
725 --在sqlplus中則需加此來表示內容寫完了需要執行了~~

 

 

作者:滾雪球俱樂部-何理利

出處: http://www.cnblogs.com/snowballed/

本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出 原文鏈接
如有疑問, 可郵件(he.lili1@ztesoft.com)咨詢。


免責聲明!

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



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