1. 程式人生 > >Oracle通過Job呼叫儲存過程

Oracle通過Job呼叫儲存過程



這是本人第一次處理Oracle的儲存過程以及計劃任務(job)的事情;期間遇到了很多問題在此將這次試用job呼叫自己的編寫的儲存過程的過程中遇到的問題做一總結;

任務:實時採集系統會每5分鐘給中心繫統傳送一個流量資料,但是沒有給出累計流量資料;
1、編寫一個儲存過程WATERQCAL用於計算流量增量以及得出最後的累計流量;
2、用一個任務計劃每隔5分鐘,統一進行執行一次WATERQCAL儲存過程;

建立儲存過程程式碼如下:

CREATE OR REPLACE
PROCEDURE WaterQCal AS--如果用引號將儲存過程名引起來後就會儲存成WaterQCal儲存過程;如果不引起來的話最終儲存的是WATERQCAL;
oldACC_W  NUMBER;
oldACC_W_PQ NUMBER;
newACC_W  NUMBER;

BEGIN
  DECLARE
   --查詢出一個集合 
   CURSOR mpcd_number IS
    SELECT DISTINCT MP_CD from WR_MP_Q_R WHERE ACC_W is NULL;
   --定義行遊標變數
   rcd_number mpcd_number % ROWTYPE;
    --迴圈開始
  BEGIN
  --開啟遊標
    open mpcd_number;
    loop
  --開始遊標迴圈操作
      fetch mpcd_number
        into rcd_number;
      exit when mpcd_number%notfound;
     --開始查詢某一個個測站資料
   DECLARE
    --查詢出一個某測站的所有未計算瀏覽的集合
    CURSOR sinSTB IS
     select * from WR_MP_Q_R WHERE ACC_W is null and MP_CD=rcd_number.MP_CD order by TM ASC;
    --定義行遊標變數
    sinSt sinSTB % ROWTYPE;
    BEGIN
    --開啟單站遊標
      open sinSTB;
       loop
         fetch sinSTB
          into sinSt;
         exit when sinSTB%notfound;
          BEGIN
            --開始計算流量
            --查詢出最有一條計算過流量的記錄
            BEGIN
              select ACC_W into oldACC_W from WR_MP_Q_R WHERE ACC_W is not null and  MP_CD=sinSt.MP_CD and rownum=1 order by TM DESC;
              exception--處理異常沒有資料時;該如何處理;因為有異常處理此處的select語句以及異常都必須單獨用begin end括起來;
                  when no_data_found then
                  oldACC_W:=0;
            END;
            --計算流量增量
            oldACC_W_PQ:=ABS(sinSt.MP_Q)/12;
            --計算當前累計流量
            newACC_W:=ABS(oldACC_W)+ABS(oldACC_W_PQ);
            --更新當前累計流量
            update WR_MP_Q_R set ACC_W=newACC_W where MP_CD=sinSt.MP_CD and TM=sinSt.TM;
          END;
        END LOOP;
      close sinSTB;
    --關閉遊標
    END;
    END loop;
    close mpcd_number;
  END;
END;

建立Oracle計劃任務的程式碼如下:
declare jobNum NUMBER;
begin
  sys.dbms_job.submit(job => jobNum,
                      what => 'SWATER.WaterQCal;',
                      next_date => trunc(sysdate)+1+8/1440,
                      interval => 'trunc(sysdate,''mi'')+1+8/1440');
  commit;
end;
/

通過plsql

工具建立:


在編制儲存過程時應該注意的是
1、每一個語句結束時都必須採用;結束。否則會報sql語句不完整;
2、CREATE OR REPLACE PROCEDURE PROCEDURE_NAME AS --該語句部分PROCEDURE_NAME不能夠用引號引起來;如果引號引起來的話,名字中的小寫字母之類的還會原樣保留;但是Oracle預設是需要將儲存過程的名字轉換成大寫的;並且後面建立Job是如果輸入之前帶小寫字母的名字時又會轉換成大寫的時候就會報找不到儲存過程;
3、for迴圈來操作遊標,不用open 遊標;以及 close遊標 操作否則會報錯;
4、針對每一個包含異常處理語句的sql都必須防止在begin以及end之中;否則可能會出現部分語句不執行,直接跳轉到下一個end處;
5、迴圈處理
 1)、For 迴圈
  For ... in ... LOOP

  --執行語句

  end LOOP;
  
  (1)迴圈遍歷遊標
   create or replace procedure test() as
    Cursor cursor is select name from student; name varchar(20);
   begin
     for name in cursor LOOP
      begin
       dbms_output.putline(name);
      end;
     end LOOP;
   end test;
  (2)迴圈遍歷陣列
   create or replace procedure test(varArray in myPackage.TestArray) as
  --(輸入引數varArray 是自定義的陣列型別,定義方式見標題6)
  i number;
  begin
   i := 1;  --儲存過程陣列是起始位置是從1開始的,與java、C、C++等語言不同。因為在Oracle中本是沒有陣列的概念的,陣列其實就是一張
   --表(Table),每個陣列元素就是表中的一個記錄,所以遍歷陣列時就相當於從表中的第一條記錄開始遍歷
   for i in 1..varArray.count LOOP    
    dbms_output.putline('The No.'|| i || 'record in varArray is:'||varArray(i)); 
    end LOOP;
  end test;

 2)、While 迴圈
  while 條件語句 LOOP
  begin
  end;
  end LOOP;

  E.g

  create or replace procedure test(i in number) as
  begin
   while i < 10 LOOP
    begin  
     i:= i + 1;
    end;
   end LOOP;
  end test;

 3).遊標的使用

  Oracle中Cursor是非常有用的,用於遍歷臨時表中的查詢結果。其相關方法和屬性也很多,現僅就常用的用法做一二介紹:

  (1)Cursor型遊標(不能用於引數傳遞)

   create or replace procedure test() is 
    cusor_1 Cursor is select std_name from student where  ...;  --Cursor的使用方式1   cursor_2 Cursor;
   begin
    select class_name into cursor_2 from class where ...;  --Cursor的使用方式2
    可使用For x in cursor LOOP .... end LOOP; 來實現對Cursor的遍歷
   end test;

  (2)SYS_REFCURSOR型遊標,該遊標是Oracle以預先定義的遊標,可作出引數進行傳遞

  create or replace procedure test(rsCursor out SYS_REFCURSOR) is
   cursor SYS_REFCURSOR; name varhcar(20);
  begin
   OPEN cursor FOR select name from student where ... --SYS_REFCURSOR只能通過OPEN方法來開啟和賦值
   LOOP
    fetch cursor into name   --SYS_REFCURSOR只能通過fetch into來開啟和遍歷 exit when cursor%NOTFOUND;              --SYS_REFCURSOR中可使用三個狀態屬性:                                         ---%NOTFOUND(未找到記錄資訊) %FOUND(找到記錄資訊)                                         ---%ROWCOUNT(然後當前遊標所指向的行位置)
    dbms_output.putline(name);
   end LOOP;
   rsCursor := cursor;
  end test;


編輯JOB過程中該注意事項
1、what => 'SWATER.WaterQCal;'的WaterQCal的儲存過程名必須以;結尾;否則會報如下錯誤:
  [Err] ORA-06550: 第 1 行, 第 110 列:
  PLS-00103: 出現符號 "END"在需要下列之一時:
   := . ( @ % ;
  符號 ";" 被替換為 "END" 後繼續。
  ORA-06512: 在 "SYS.DBMS_JOB", line 82
  ORA-06512: 在 "SYS.DBMS_JOB", line 140
  ORA-06512: 在 line 3
2、如果前面建立儲存過程中將儲存過程名用引號引起來後,因為儲存郭晨被儲存成了WaterQCal;而what中指定的儲存過程SWATER.WaterQCal會被轉換SWATER.WATERQCAL;所以會報如下錯誤: 
  [Err] ORA-06550: 第 1 行, 第 93 列:
  PLS-00201: 必須宣告識別符號 'SWATER.WATERQCAL'
  ORA-06550: 第 1 行, 第 93 列:
  PL/SQL: Statement ignored
  ORA-06512: 在 "SYS.DBMS_JOB", line 82
  ORA-06512: 在 "SYS.DBMS_JOB", line 140
  ORA-06512: 在 line 3
3、時間間隔設定必須是在指定的時間必須是當前時間的後面的值(即如果由interval計算出來的next time如果在當前時間之前,會怎麼處理?就沒法處理,會報錯),否則會報如下錯誤
  ORA-23420:間隔必須以將來的一個時間作評估
  ORA-06512:在 "SYS.DBMS_JOB",line 57
  ORA-06512:在 "SYS.DBMS_JOB",line 134
  ORA-06512:在 line 3
4、如果  sys.dbms_job.submit(job => :job,用的是:job作為引數,並且是通過查詢語句執行的話會報如下錯誤:
  [Err] ORA-01008: 並非所有變數都已繫結
 解決辦法如本示例:declare job NUMBER;--定義變數;並採用sys.dbms_job.submit(job => job方式去掉冒號;

5、引數說明
 1)、job
 引數job是一個整數,用來唯一地標示一個任務。該引數既可由使用者指定也可由系統自動賦予,這完全取決於提交任務時選用了那一個任務提交過程。DBMS_JOB.SUBMIT過程通過獲得序列SYS.JOBSEQ的下一個值來自動賦予一個任務號。該任務號是作為一個OUT引數返回的,所以呼叫者隨後可以識別出提交的任務。而DBMS_JOB.ISUBMIT過程則由呼叫者給任務指定一個識別號,這時候,任務號的唯一性就完全取決於呼叫者了。
 
 除了刪除或者重新提交任務,一般來說任務號是不能改變的。即使當資料庫被匯出或者被匯入這樣極端的情況,任務號也將被保留下來。所以在執行含有任務的資料的匯入/匯出操作時很可能會發生任務號衝突的現象。
 
 2)、what
 what引數是一個可以轉化為合法PL/SQL呼叫的字串,該呼叫將被任務佇列自動執行。在what引數中,如果使用文字字串,則該字串必須用單引號括起來。 what引數也可以使用包含我們所需要字串值的VARCHAR2變數。實際的PL/SQL呼叫必須用分號隔開。在PL/SQL呼叫中如果要嵌入文字字串,則必須使用兩個單引號。
 
 what引數的長度在Oracle7.3中限制在2000個位元組以內,在Oracle 8.0以後,擴大到了4000個位元組,這對於一般的應用已完全足夠。該引數的值一般情況下都是對一個PL/SQL儲存過程的呼叫。在實際應用中,儘管可以使用大匿名Pl/SQL塊,但建議大家最好不要這樣使用。還有一個實際經驗就是最好將儲存過程呼叫封裝在一個匿名塊中,這樣可以避免一些比較莫名錯誤的產生。我來舉一個例子,一般情況下,what引數可以這樣引用:
 
 what =>’my_procedure(parameter1);’
 但是比較安全的引用,應該這樣寫:
 what =>’begin my_procedure(parameter1); end;’
 
 任何時候,我們只要通過更改what引數就可以達到更改任務定義的目的。但是有一點需要注意,通過改變what引數來改變任務定義時,使用者當前的會話設定也被記錄下來併成為任務執行環境的一部分。如果當前會話設定和最初提交任務時的會話設定不同,就有可能改變任務的執行行為。意識到這個潛在的副作用是非常重要的,無論何時只要應用到任何DBMS_JOB過程中的what引數時就一定要確保會話設定的正確。
 
 3)、next_date
 Next_date引數是用來排程任務佇列中該任務下一次執行的時間。這個引數對於DBMS_JOB.SUBMIT和DBMS_JOB.BROKEN這兩個過程確省為系統當前時間,也就是說任務將立即執行。
 
 當將一個任務的next_date引數賦值為null時,則該任務下一次執行的時間將被指定為4000年1月1日,也就是說該任務將永遠不再執行。在大多數情況下,這可能是我們不願意看到的情形。但是,換一個角度來考慮,如果想在任務佇列中保留該任務而又不想讓其執行,將next_date設定為null卻是一個非常簡單的辦法。
 
 Next_date也可以設定為過去的一個時間。這裡要注意,系統任務的執行順序是根據它們下一次的執行時間來確定的,於是將next_date引數設定回去就可以達到將該任務排在任務佇列前面的目的。這在任務佇列程序不能跟上將要執行的任務並且一個特定的任務需要儘快執行時是非常有用的。
 
 4)、Interval
 Internal引數是一個表示Oracle合法日期表示式的字串。這個日期字串的值在每次任務被執行時算出,算出的日期表示式有兩種可能,要麼是未來的一個時間要麼就是null。這裡要強調一點:很多開發者都沒有意識到next_date是在一個任務開始時算出的,而不是在任務成功完成時算出的。
 
 當任務成功完成時,系統通過更新任務佇列目錄表將前面算出的next_date值置為下一次任務要執行的時間。當由interval表示式算出next_date是null時,任務自動從任務佇列中移出,不會再繼續執行。因此,如果傳遞一個null值給interval引數,則該任務僅僅執行一次。
 
 通過給interval引數賦各種不同的值,可以設計出複雜執行時間計劃的任務。附錄的“任務間隔和日期演算法”將對interval表示式進行詳細討論,並給出一個實際有用interval表示式的例子。

6、時間間隔指定函式:TRUNC函式返回以指定元素格式截去一部分的日期值;語法格式TRUNC(date,[fmt])date是時間;fmt格式化字串;預設截取出日期部分值。
 
 1):每分鐘執行
   Interval => TRUNC(sysdate,'mi') + 1/ (24*60)
  2):每天定時執行
   例如:每天的凌晨1點執行
   Interval => TRUNC(sysdate) + 1 +1/ (24)
  3):每週定時執行
   例如:每週一凌晨1點執行
   Interval => TRUNC(next_day(sysdate,'星期一'))+1/24
  4):每月定時執行
   例如:每月1日凌晨1點執行
   Interval =>TRUNC(LAST_DAY(SYSDATE))+1+1/24
  5):每季度定時執行
   例如每季度的第一天凌晨1點執行
   Interval => TRUNC(ADD_MONTHS(SYSDATE,3),'Q') + 1/24
  6):每半年定時執行
   例如:每年7月1日和1月1日凌晨1點
   Interval => ADD_MONTHS(trunc(sysdate,'yyyy'),6)+1/24
  7):每年定時執行
  例如:每年1月1日凌晨1點執行
   Interval =>ADD_MONTHS(trunc(sysdate,'yyyy'),12)+1/24
 示例時間間隔:
 trunc(sysdate) + 1/1440為當天的 0時01分.而當前時間往往大於這個當天值.所以你需要設定為interval => 'trunc(sysdate)+1+1/1440');意思是,明天的凌晨 0時01分.
這樣,在每次oracle執行完當前job後,就可以將next_date設定為明天,而不是今天0時01分,因為今天的這個時間已經過去了;
 interval => 'trunc(sysdate)+1/1440');就是今天的0時01分;你只有在0:00的時間執行才可以通過;否則就報錯誤了。
7、通過plsql工具建立計劃任務時,fmt引數必須是用兩個單引號括起來,而不能用雙引號;
 TRUNC(sysdate,''mi'') +1+8/1440

額外補充:
 建立job語句如下:
 variable jobno number;
 begin
 sys.dbms_job.submit(job => :jobno,
 what => 'change_date;',
 next_date => to_date('18-11-2013', 'dd-mm-yyyy'),
 interval => 'sysdate+1/1440');--每天1440分鐘,即一分鐘執行change_date過程一次
 commit; --必須有commit,如果沒有是查不到該job的!!!
 end;
 /
 這兩種建立job的區別僅僅是jobno的定義方式不同:
 第一種是declare出的變數
 第二種是variable出的變數
 在網上一搜發現兩者作用域不同:
 variable相當於一個sql*plus環境的全域性變數,declare裡定義的是pl/sql中的區域性變數。