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

玩轉 Oracle 學習第七天:PL/SQL 進階、視圖與觸發器實戰

     編輯:Oracle 教學

玩轉 Oracle 學習第七天:PL/SQL 進階、視圖與觸發器實戰

第七天課程聚焦 PL/SQL 進階控制結構、例外處理機制、視圖 與 觸發器 四大核心主題。學習目標:能撰寫分頁程序、下單邏輯等實務模組;熟練處理常見例外;靈活設計各類觸發器;深度理解視圖優勢與限制並善用於安全與簡化查詢。

一、流程控制結構速查

1. 條件分支

-- 單分支
IF condition THEN statements; END IF;

-- 雙分支
IF condition THEN statements; ELSE statements; END IF;

-- 多分支
IF cond1 THEN stmt1;
ELSIF cond2 THEN stmt2;
ELSE stmt3;
END IF;
實務範例:依薪資調整薪資
CREATE OR REPLACE PROCEDURE adj_sal_by_name (p_ename VARCHAR2) IS
  v_sal emp.sal%TYPE;
BEGIN
  SELECT sal INTO v_sal FROM emp WHERE ename = p_ename;
  IF v_sal < 2000 THEN
    UPDATE emp SET sal = sal * 1.1 WHERE ename = p_ename;
  END IF;
END;
/
實務範例:依職稱差異調薪
CREATE OR REPLACE PROCEDURE adj_sal_by_job (p_empno NUMBER) IS
  v_job emp.job%TYPE;
BEGIN
  SELECT job INTO v_job FROM emp WHERE empno = p_empno;
  IF v_job = 'PRESIDENT' THEN
    UPDATE emp SET sal = sal + 500 WHERE empno = p_empno;
  ELSIF v_job = 'MANAGER' THEN
    UPDATE emp SET sal = sal + 300 WHERE empno = p_empno;
  ELSE
    UPDATE emp SET sal = sal + 100 WHERE empno = p_empno;
  END IF;
END;

2. 迴圈結構

類型語法骨架適用場景
基本 LOOPLOOP ... EXIT WHEN cond; ... END LOOP;至少執行一次、條件在迴圈中產生
WHILE LOOPWHILE cond LOOP ... END LOOP;先判斷再執行、次數不定
FOR LOOPFOR i IN [REVERSE] start..end LOOP ... END LOOP;次數固定、隱式計數器
-- 基本 LOOP 範例:批次新增 10 筆使用者
CREATE OR REPLACE PROCEDURE batch_ins_user (p_name VARCHAR2) IS
  v_num NUMBER := 1;
BEGIN
  LOOP
    INSERT INTO users VALUES (v_num, p_name);
    EXIT WHEN v_num = 10;
    v_num := v_num + 1;
  END LOOP;
END;

-- WHILE 範例:續寫 11~20
v_num := 11;
WHILE v_num <= 20 LOOP
  INSERT INTO users VALUES (v_num, p_name);
  v_num := v_num + 1;
END LOOP;

-- FOR 範例:反向 10..1
BEGIN
  FOR i IN REVERSE 1..10 LOOP
    INSERT INTO users VALUES (i, '世陽');
  END LOOP;
END;

3. GOTO 與 NULL 語句

  • GOTO label; 跳轉至 <<label>>,限制:不可從外層跳入內層區塊/迴圈/IF。
  • NULL; 為合法空陳述式,常用於 ELSE NULL; 明確表達「不處理」。
DECLARE i INT := 1;
BEGIN
  LOOP
    DBMS_OUTPUT.PUT_LINE('i=' || i);
    IF i = 10 THEN GOTO end_loop; END IF;
    i := i + 1;
  END LOOP;
  <<end_loop>>
  NULL;
END;

二、實務級存儲程序開發:參數模式、Java 呼叫、分頁程序

1. 參數模式:IN / OUT / IN OUT

  • IN(預設):唯讀輸入
  • OUT:回傳值,呼叫端需註冊
  • IN OUT:可讀可寫

2. 無回傳值程序 + Java JDBC 呼叫

-- PL/SQL
CREATE OR REPLACE PROCEDURE add_book (
  p_bid   IN NUMBER,
  p_bname IN VARCHAR2,
  p_pub   IN VARCHAR2
) IS
BEGIN
  INSERT INTO book VALUES (p_bid, p_bname, p_pub);
END;
// Java
CallableStatement cs = conn.prepareCall("{call add_book(?,?,?)}");
cs.setInt(1, 10);
cs.setString(2, "笑傲江湖");
cs.setString(3, "人民出版社");
cs.execute();

3. 有回傳值程序(非游標)+ Java

-- PL/SQL
CREATE OR REPLACE PROCEDURE get_emp (
  p_empno IN NUMBER,
  p_ename OUT VARCHAR2,
  p_sal   OUT NUMBER,
  p_job   OUT VARCHAR2
) IS
BEGIN
  SELECT ename, sal, job INTO p_ename, p_sal, p_job
  FROM emp WHERE empno = p_empno;
END;
// Java
CallableStatement cs = conn.prepareCall("{call get_emp(?,?,?,?)}");
cs.setInt(1, 7788);
cs.registerOutParameter(2, OracleTypes.VARCHAR);
cs.registerOutParameter(3, OracleTypes.DOUBLE);
cs.registerOutParameter(4, OracleTypes.VARCHAR);
cs.execute();
String name = cs.getString(2);
double sal  = cs.getDouble(3);
String job  = cs.getString(4);

4. 回傳結果集:SYS_REFCURSOR + 套件定義

-- 步驟 1:建立套件定義游標型別
CREATE OR REPLACE PACKAGE pkg_cursor AS
  TYPE t_cursor IS REF CURSOR;
END pkg_cursor;

-- 步驟 2:程序輸出游標
CREATE OR REPLACE PROCEDURE get_dept_emps (
  p_deptno IN NUMBER,
  p_rc     OUT pkg_cursor.t_cursor
) IS
BEGIN
  OPEN p_rc FOR SELECT * FROM emp WHERE deptno = p_deptno;
END;
// Java
CallableStatement cs = conn.prepareCall("{call get_dept_emps(?,?)}");
cs.setInt(1, 10);
cs.registerOutParameter(2, OracleTypes.CURSOR);
cs.execute();
ResultSet rs = (ResultSet) cs.getObject(2);
while (rs.next()) {
  System.out.println(rs.getInt(1) + " " + rs.getString(2));
}

5. 通用分頁程序(動態 SQL + 計算總筆數/總頁數)

CREATE OR REPLACE PROCEDURE proc_page (
  p_tname     IN VARCHAR2,      -- 表名
  p_pagesize  IN NUMBER,        -- 每頁筆數
  p_pagenow   IN NUMBER,        -- 當前頁碼
  p_totalrows OUT NUMBER,       -- 總筆數
  p_totalpages OUT NUMBER,      -- 總頁數
  p_rc        OUT pkg_cursor.t_cursor
) IS
  v_sql      VARCHAR2(1024);
  v_start    NUMBER := (p_pagenow - 1) * p_pagesize + 1;
  v_end      NUMBER := p_pagenow * p_pagesize;
BEGIN
  -- 1. 分頁查詢(Oracle 經典 ROWNUM 兩層寫法)
  v_sql := 'SELECT * FROM (SELECT t1.*, ROWNUM rn FROM (SELECT * FROM '
         || p_tname || ') t1 WHERE ROWNUM <= ' || v_end
         || ') WHERE rn >= ' || v_start;
  OPEN p_rc FOR v_sql;

  -- 2. 計算總筆數
  v_sql := 'SELECT COUNT(*) FROM ' || p_tname;
  EXECUTE IMMEDIATE v_sql INTO p_totalrows;

  -- 3. 計算總頁數
  IF MOD(p_totalrows, p_pagesize) = 0 THEN
    p_totalpages := p_totalrows / p_pagesize;
  ELSE
    p_totalpages := p_totalrows / p_pagesize + 1;
  END IF;
END;
Java 驗證分頁
CallableStatement cs = conn.prepareCall("{call proc_page(?,?,?,?,?,?)}");
cs.setString(1, "emp");
cs.setInt(2, 5);
cs.setInt(3, 1);
cs.registerOutParameter(4, OracleTypes.INTEGER);
cs.registerOutParameter(5, OracleTypes.INTEGER);
cs.registerOutParameter(6, OracleTypes.CURSOR);
cs.execute();
int totalRows = cs.getInt(4);
int totalPages = cs.getInt(5);
ResultSet rs = (ResultSet) cs.getObject(6);

三、PL/SQL 例外處理機制

1. 例外分類

  • 預定義例外:NO_DATA_FOUND、TOO_MANY_ROWS、ZERO_DIVIDE、DUP_VAL_ON_INDEX、INVALID_NUMBER、VALUE_ERROR 等 20 種。
  • 非預定義例外:Oracle 錯誤碼未命名,可用 PRAGMA EXCEPTION_INIT 關聯。
  • 使用者自訂例外:DECLARE my_ex EXCEPTION; 配合 RAISE my_ex;。

2. 例外傳播規則

  • 未捕捉的例外會向外層區塊傳播,直到最外層或被宿主環境(SQL*Plus、Java、Python)捕獲。
  • RAISE; 在例外區重拋原例外;RAISE new_ex; 轉拋新例外。

3. 實戰範例

-- 捕捉預定義例外
DECLARE
  v_ename emp.ename%TYPE;
BEGIN
  SELECT ename INTO v_ename FROM emp WHERE empno = &gno;
  DBMS_OUTPUT.PUT_LINE(v_ename);
EXCEPTION
  WHEN NO_DATA_FOUND THEN
    DBMS_OUTPUT.PUT_LINE('查無此員工編號');
  WHEN TOO_MANY_ROWS THEN
    DBMS_OUTPUT.PUT_LINE('資料異常:單一編號對應多筆');
END;

-- 自訂例外:更新不存在員工視為業務錯誤
CREATE OR REPLACE PROCEDURE raise_sal (p_empno NUMBER) IS
  e_no_update EXCEPTION;
BEGIN
  UPDATE emp SET sal = sal + 1000 WHERE empno = p_empno;
  IF SQL%NOTFOUND THEN
    RAISE e_no_update;
  END IF;
EXCEPTION
  WHEN e_no_update THEN
    DBMS_OUTPUT.PUT_LINE('未更新任何員工,編號可能不存在');
END;

四、視圖:虛擬表的威力與限制

核心差異

特性表視圖
儲存空間佔用段/區塊不儲存資料,僅存查詢定義
索引可建立無(12c+ 可建立基於視圖的物化視圖索引)
查詢速度快每次執行底層查詢,相對較慢
安全性需授權表權限可僅授權視圖,隱藏敏感欄/列
簡化複雜查詢否是,封裝 JOIN/子查詢/聚合

建立視圖語法

-- 1. 簡單視圖:薪資 < 1000 員工
CREATE VIEW v_low_sal AS
SELECT * FROM emp WHERE sal < 1000;

-- 2. 複雜視圖:員工+部門名稱,唯讀
CREATE VIEW v_emp_dept AS
SELECT e.empno, e.ename, d.dname
FROM emp e JOIN dept d ON e.deptno = d.deptno
WITH READ ONLY;

-- 3. 條件限制視圖:僅允許插入符合條件的列
CREATE VIEW v_clerk AS
SELECT * FROM emp WHERE job = 'CLERK'
WITH CHECK OPTION;  -- DML 必須符合 WHERE 條件

修改與刪除

-- 修改視圖定義(OR REPLACE)
CREATE OR REPLACE VIEW v_low_sal AS
SELECT empno, ename, sal FROM emp WHERE sal < 1500;

-- 刪除視圖
DROP VIEW v_low_sal;

可更新視圖限制

  • 單一基礎表、無聚合/GROUP BY/DISTINCT/集合運算/ROWMID/子查詢。
  • 若視圖含 WITH CHECK OPTION,DML 必須符合視圖 WHERE 條件。
  • 複雜視圖需配合 INSTEAD OF 觸發器實現 DML。

五、觸發器:隱式執行的業務守門員

觸發器類型

分類觸發時機FOR EACH ROW典型用途
DML 列級BEFORE / AFTER INSERT/UPDATE/DELETE是衍生欄位計算、稽核、資料轉換
DML 陳述式級BEFORE / AFTER INSERT/UPDATE/DELETE否跨列驗證、記錄影響筆數
DDL/系統事件CREATE/ALTER/DROP、LOGON/LOGOFF、SHUTDOWN/STARTUP否結構變更稽核、安全登入審計
INSTEAD OF針對視圖的 DML是使不可更新視圖可寫入

語法範本

CREATE OR REPLACE TRIGGER trg_name
{BEFORE | AFTER | INSTEAD OF} {INSERT | UPDATE | DELETE} ON table_name
[FOR EACH ROW]
[WHEN (condition)]
DECLARE
  -- 宣告區
BEGIN
  -- 執行區
  -- :NEW.col 新值、:OLD.col 舊值(列級觸發器可用)
EXCEPTION
  -- 例外區
END;

實務範例:自動維護修改時間戳

CREATE OR REPLACE TRIGGER trg_emp_upd_time
BEFORE UPDATE ON emp
FOR EACH ROW
BEGIN
  :NEW.updated_at := SYSDATE;
  :NEW.updated_by := USER;
END;

實務範例:防止週末異動核心表

CREATE OR REPLACE TRIGGER trg_no_weekend_dml
BEFORE INSERT OR UPDATE OR DELETE ON emp
DECLARE
  v_day VARCHAR2(3);
BEGIN
  v_day := TO_CHAR(SYSDATE, 'DY', 'NLS_DATE_LANGUAGE=AMERICAN');
  IF v_day IN ('SAT','SUN') THEN
    RAISE_APPLICATION_ERROR(-20001, '週末禁止異動 EMP 表');
  END IF;
END;

六、本日重點回顧

  1. 熟練 IF/ELSIF/ELSE、三種迴圈、GOTO/NULL 控制流程。
  2. 掌握 IN/OUT/IN OUT 參數模式與 Java CallableStatement 呼叫全流程。
  3. 會撰寫動態 SQL 分頁程序,並正確計算總筆數/總頁數。
  4. 理解例外傳播、預定義/自訂例外處理模式。
  5. 視圖:簡化查詢、資料隱藏、WITH READ ONLY/CHECK OPTION、INSTEAD OF 觸發器。
  6. 觸發器:四大類型、:NEW/:OLD、條件觸發 WHEN、自訂錯誤 RAISE_APPLICATION_ERROR。

第七天內容紮實且實務導向,建議動手完成分頁程序、自訂例外、視圖與觸發器的完整建立與測試,為後續套件開發、大型專案架構打下堅實基礎。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved