首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 數據庫知識 >  >> Oracle 資料庫 >> Oracle 教學

Oracle 存儲過程開發規範與進階範本:參數模式、游標、集合、異常處理、動態 SQL、套件化實戰

     編輯:Oracle 教學

Oracle 存儲過程開發規範與進階範本:參數模式、游標、集合、異常處理、動態 SQL、套件化實戰

本文整理企業級 PL/SQL 開發規範與可直接落地的進階範本,涵蓋參數傳遞陷阱、游標變數、集合類型選擇、異常處理模式、動態 SQL、套件化設計、學生成績計算綜合案例,助您寫出可維護、高效能、安全的存儲過程。

1. 參數模式與寬度鐵律

  • IN:傳值,唯讀,可設 DEFAULT,寬度由呼叫端決定。
  • OUT:傳址,進入時為 NULL,寬度由程序內部指派決定 (呼叫端定義寬度不足不會報錯但資料會被截斷)。
  • IN OUT:傳址,雙向,寬度由程序內部決定。
  • 最佳實踐:參數型別盡量用 table.column%TYPE,確保雙端一致。

2. 進階範本:帶游標、集合、異常日誌的標準過程

CREATE OR REPLACE PROCEDURE proc_monthly_summary(
  p_ym        IN  CHAR,           -- 格式 YYYYMM
  p_rowcount  OUT NUMBER
) AS
  v_err_msg   VARCHAR2(4000);
  v_ym_beg    CHAR(6);
  v_ym_end    CHAR(6);
  -- 遊標宣告
  CURSOR c_summary IS
    SELECT area_code, cmcode, SUM(rmb_amt)/10000 AS rmb_amt, SUM(usd_amt)/10000 AS usd_amt
    FROM bgd_area_cm_m_base_t
    WHERE ym BETWEEN v_ym_beg AND v_ym_end
    GROUP BY area_code, cmcode;
BEGIN
  -- 參數解析與轉換
  v_ym_beg := SUBSTR(p_ym, 1, 6);
  v_ym_end := SUBSTR(p_ym, 7, 6);

  -- 清理舊資料
  DELETE FROM summary_table WHERE ym = p_ym;
  DBMS_OUTPUT.PUT_LINE('Deleted ' || SQL%ROWCOUNT || ' rows');

  -- 彙總插入
  INSERT INTO summary_table (area_code, ym, cmcode, rmb_amt, usd_amt)
  SELECT area_code, p_ym, cmcode, SUM(rmb_amt)/10000, SUM(usd_amt)/10000
  FROM bgd_area_cm_m_base_t
  WHERE ym BETWEEN v_ym_beg AND v_ym_end
  GROUP BY area_code, cmcode;

  -- 遊標遍歷更新同期欄位
  FOR rec IN c_summary LOOP
    UPDATE summary_table
    SET rmb_amt_sn = rec.rmb_amt, usd_amt_sn = rec.usd_amt
    WHERE area_code = rec.area_code AND cmcode = rec.cmcode AND ym = p_ym;
  END LOOP;

  p_rowcount := SQL%ROWCOUNT;
  COMMIT;
EXCEPTION
  WHEN OTHERS THEN
    v_err_msg := 'ERROR IN proc_monthly_summary('||p_ym||'): ' || SUBSTR(SQLERRM,1,500);
    ROLLBACK;
    INSERT INTO proc_log(proc_name, error_info, op_date) VALUES ('proc_monthly_summary', v_err_msg, SYSDATE);
    COMMIT;
    RAISE;  -- 重拋異常讓呼叫端感知
END;
/
-- 呼叫
VAR v_cnt NUMBER;
EXEC proc_monthly_summary('202401202412', :v_cnt);
PRINT v_cnt;

3. 集合類型選擇決策表

需求推薦類型宣告範例
暫存 Session 級鍵值對、稀疏索引、負索引關聯陣列TYPE t IS TABLE OF VARCHAR2(20) INDEX BY BINARY_INTEGER;
需持久化至資料表欄位、需 DML 操作巢狀表TYPE t IS TABLE OF VARCHAR2(20); v t := t('a','b');
固定上限、順序重要、小量資料VARRAYTYPE t IS VARRAY(10) OF NUMBER;

4. SYS_REFCURSOR 遊標變數傳遞模式

-- 回傳遊標給呼叫端 (Java/.NET/報表工具常用)
CREATE OR REPLACE PROCEDURE get_emp_cursor(p_deptno IN NUMBER, p_rc OUT SYS_REFCURSOR) AS
BEGIN
  OPEN p_rc FOR SELECT empno, ename, sal FROM emp WHERE deptno = p_deptno;
END;
/
-- 呼叫端需 FETCH 直到 %NOTFOUND

5. 異常處理進階模式

EXCEPTION
  WHEN NO_DATA_FOUND THEN
    -- 特定業務異常
    LOG_ERROR('QUERY_EMPTY', SQLERRM);
  WHEN DUP_VAL_ON_INDEX THEN
    -- 唯一鍵衝突
    LOG_ERROR('DUP_KEY', SQLERRM);
  WHEN OTHERS THEN
    -- 兜底:記錄完整堆疊
    LOG_ERROR('UNEXPECTED', DBMS_UTILITY.FORMAT_ERROR_STACK || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
    RAISE;  -- 重拋
END;

6. 動態 SQL (EXECUTE IMMEDIATE) 安全範本

-- 參數化綁定防注入
v_sql := 'SELECT COUNT(*) FROM ' || v_table_name || ' WHERE created_date > :dt';
EXECUTE IMMEDIATE v_sql INTO v_cnt USING v_date;

-- DDL 動態執行
EXECUTE IMMEDIATE 'TRUNCATE TABLE ' || v_table_name;

7. 套件化設計:公開介面與私有實作分離

-- 規格
CREATE OR REPLACE PACKAGE pkg_emp_mgmt AS
  PROCEDURE adjust_salary(p_ename VARCHAR2, p_pct NUMBER);
  FUNCTION annual_income(p_ename VARCHAR2) RETURN NUMBER;
  -- 私有程序不在此宣告
END pkg_emp_mgmt;
/

-- 主體
CREATE OR REPLACE PACKAGE BODY pkg_emp_mgmt AS
  -- 私有輔助函數
  FUNCTION calc_tax(p_sal NUMBER) RETURN NUMBER IS
  BEGIN RETURN p_sal * 0.03; END;

  PROCEDURE adjust_salary(p_ename VARCHAR2, p_pct NUMBER) AS
  BEGIN
    UPDATE emp SET sal = sal * (1 + p_pct/100) WHERE ename = p_ename;
  END;

  FUNCTION annual_income(p_ename VARCHAR2) RETURN NUMBER AS
    v_sal NUMBER; v_comm NUMBER;
  BEGIN
    SELECT sal, NVL(comm,0) INTO v_sal, v_comm FROM emp WHERE ename = p_ename;
    RETURN (v_sal + v_comm) * 12;
  END;
END pkg_emp_mgmt;
/
-- 呼叫
EXEC pkg_emp_mgmt.adjust_salary('SCOTT', 10);

8. 綜合案例:學生成績自動計算 (含課外加分、遊標、集合、GOTO)

CREATE OR REPLACE PROCEDURE auto_compute_score(p_step IN NUMBER) AS
  TYPE rec_comment IS RECORD (std_id VARCHAR2(30), comment VARCHAR2(1));
  TYPE arr_comment IS TABLE OF rec_comment INDEX BY BINARY_INTEGER;
  v_comments arr_comment;

  CURSOR c_students IS
    SELECT std_id, math, article, language, music, sport
    FROM student WHERE step = p_step;

  v_total NUMBER; v_avg NUMBER;
BEGIN
  -- 1. 取得課外評語集合
  SELECT std_id, comment BULK COLLECT INTO v_comments
  FROM out_school;

  -- 2. 遍歷學生計算
  FOR rec IN c_students LOOP
    v_total := rec.math + rec.article + rec.language + rec.music + rec.sport;

    -- 查找課外評語 (關聯陣列遍歷)
    FOR i IN 1..v_comments.COUNT LOOP
      IF rec.std_id = v_comments(i).std_id THEN
        IF v_comments(i).comment = 'A' THEN
          v_total := v_total + 20;
          GOTO calc_avg;  -- 找到即跳出
        END IF;
      END IF;
    END LOOP;

    <>
    v_avg := v_total / 5;
    UPDATE student SET total = v_total, average = v_avg WHERE std_id = rec.std_id;
  END LOOP;
  COMMIT;
END;
/
-- 注意:GOTO 在 PL/SQL 中合法但慎用,此處為演示標籤跳出雙層迴圈

9. 命名慣例建議

物件前綴/後綴範例
區域變數v_v_emp_count
常數c_c_tax_rate
游標_cur / _cursorc_emp_cur
例外e_e_no_data
記錄型別_rec_typeemp_rec_type
集合型別_tab_type / _arr_typeemp_arr_type
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved