视图与存储过程

最后更新: 2026-04-15 作者: Oracle Team
页面目录

第六章:视图与存储过程

文档信息
  • 适用版本:Oracle 12c / 18c / 19c / 21c
  • 阅读时间:约 55 分钟
  • 前置知识:第五章索引

6.1 视图

6.1.1 视图类型

┌─────────────────────────────────────────────────────────────────┐
│                      Oracle 视图类型                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  简单视图                  复杂视图                               │
│  ─────────                 ─────────                             │
│  • 基于单表                 • 基于多表                            │
│  • 不含函数                 • 含聚合函数                          │
│  • 可 DML 操作              • 通常只读                            │
│                                                                 │
│  ┌─────────────┐           ┌─────────────┐                       │
│  │  SELECT     │           │  SELECT     │                       │
│  │  ...        │           │  d.dept,    │                       │
│  │  FROM emp   │           │  COUNT(*),  │                       │
│  └─────────────┘           │  SUM(sal)   │                       │
│                            │  FROM emp   │                       │
│                            │  GROUP BY   │                       │
│                            └─────────────┘                       │
│                                                                 │
│  特殊视图                                                         │
│  ─────────                                                        │
│  • 物化视图 - 存储实际数据                                        │
│  • 对象视图 - 基于对象类型                                        │
│  • 嵌套视图 - 视图嵌套视图                                        │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

6.1.2 创建视图

-- 简单视图
CREATE OR REPLACE VIEW v_emp_it AS
SELECT 
    employee_id,
    first_name,
    last_name,
    salary,
    department_id
FROM employees
WHERE department_id = 50  -- IT 部门
WITH CHECK OPTION;  -- 禁止违反 WHERE 条件的修改

-- 复杂视图(含聚合)
CREATE OR REPLACE VIEW v_dept_salary AS
SELECT 
    d.department_id,
    d.department_name,
    COUNT(e.employee_id) AS emp_count,
    SUM(e.salary) AS total_salary,
    AVG(e.salary) AS avg_salary,
    MAX(e.salary) AS max_salary,
    MIN(e.salary) AS min_salary
FROM departments d
LEFT JOIN employees e ON d.department_id = e.department_id
GROUP BY d.department_id, d.department_name;

-- 连接视图
CREATE OR REPLACE VIEW v_emp_details AS
SELECT 
    e.employee_id,
    e.first_name || ' ' || e.last_name AS full_name,
    e.job_id,
    j.job_title,
    d.department_name,
    l.city,
    l.country_id
FROM employees e
JOIN departments d ON e.department_id = d.department_id
JOIN jobs j ON e.job_id = j.job_id
JOIN locations l ON d.location_id = l.location_id;

6.1.3 视图操作

-- 查询视图
SELECT * FROM v_emp_it WHERE salary > 5000;

-- 视图 DML 操作(简单视图可更新)
INSERT INTO v_emp_it (employee_id, first_name, last_name, salary, department_id)
VALUES (300, 'Test', 'User', 3000, 50);

UPDATE v_emp_it SET salary = 4000 WHERE employee_id = 300;

DELETE FROM v_emp_it WHERE employee_id = 300;

-- WITH READ ONLY:禁止 DML
CREATE OR REPLACE VIEW v_dept_salary_readonly AS
SELECT * FROM v_dept_salary
WITH READ ONLY;

-- 查看视图定义
SELECT text FROM user_views WHERE view_name = 'V_EMP_IT';

-- 删除视图
DROP VIEW v_emp_it;

6.1.4 物化视图

-- 创建物化视图
CREATE MATERIALIZED VIEW mv_dept_salary
BUILD IMMEDIATE                 -- 创建时立即填充
REFRESH FAST                    -- 增量刷新
START WITH SYSDATE              -- 立即开始
NEXT SYSDATE + 1                -- 每天刷新
AS
SELECT 
    department_id,
    COUNT(*) AS emp_count,
    SUM(salary) AS total_salary,
    AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id;

-- 物化视图类型
-- BUILD IMMEDIATE: 创建时立即填充
-- BUILD DEFERRED: 创建时不填充,后续刷新

-- 刷新方式
-- FAST: 增量刷新(需要物化视图日志)
-- COMPLETE: 全量刷新
-- FORCE: 尝试 FAST,失败则 COMPLETE
-- NEVER: 从不刷新

-- 手动刷新
EXEC DBMS_MVIEW.REFRESH('MV_DEPT_SALARY', 'C');

-- 创建物化视图日志(支持增量刷新)
CREATE MATERIALIZED VIEW LOG ON employees
WITH (salary, department_id), SEQUENCE, ROWID;

-- 查看物化视图
SELECT 
    mview_name,
    refresh_mode,
    refresh_method,
    build_mode,
    last_refresh_type,
    last_refresh_date
FROM user_mviews;

-- 删除物化视图
DROP MATERIALIZED VIEW mv_dept_salary;

6.2 PL/SQL 基础

6.2.1 PL/SQL 块结构

-- PL/SQL 匿名块
DECLARE
    v_name VARCHAR2(50);
    v_sal  NUMBER(10,2);
BEGIN
    SELECT first_name, salary 
    INTO v_name, v_sal
    FROM employees 
    WHERE employee_id = 100;
    
    DBMS_OUTPUT.PUT_LINE('Employee: ' || v_name || ', Salary: ' || v_sal);
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        DBMS_OUTPUT.PUT_LINE('Employee not found');
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('Error: ' || SQLERRM);
END;
/

-- 输出开关
SET SERVEROUTPUT ON

6.2.2 变量与数据类型

DECLARE
    -- 基本类型
    v_name    VARCHAR2(50);
    v_sal     NUMBER(10,2);
    v_hire    DATE;
    v_active  BOOLEAN := TRUE;
    
    -- 复杂类型
    v_emp     employees%ROWTYPE;     -- 行类型
    v_sal     employees.salary%TYPE; -- 列类型
    
    -- 记录类型
    TYPE emp_record IS RECORD (
        name    VARCHAR2(100),
        salary  NUMBER(10,2),
        dept    VARCHAR2(50)
    );
    v_emp_rec emp_record;
    
    -- 常量
    c_pi CONSTANT NUMBER := 3.14159;
BEGIN
    -- 使用 INTO 赋值
    SELECT first_name, salary, hire_date
    INTO v_name, v_sal, v_hire
    FROM employees WHERE employee_id = 100;
    
    -- 使用 %ROWTYPE
    SELECT * INTO v_emp FROM employees WHERE employee_id = 100;
    
    -- 记录赋值
    v_emp_rec.name := v_name;
    v_emp_rec.salary := v_sal;
END;
/

6.2.3 控制结构

-- IF 条件
DECLARE
    v_sal NUMBER := 5000;
BEGIN
    IF v_sal < 3000 THEN
        DBMS_OUTPUT.PUT_LINE('Low salary');
    ELSIF v_sal < 7000 THEN
        DBMS_OUTPUT.PUT_LINE('Medium salary');
    ELSE
        DBMS_OUTPUT.PUT_LINE('High salary');
    END IF;
END;
/

-- CASE 语句
DECLARE
    v_grade CHAR(1) := 'B';
    v_msg VARCHAR2(50);
BEGIN
    v_msg := CASE v_grade
        WHEN 'A' THEN 'Excellent'
        WHEN 'B' THEN 'Good'
        WHEN 'C' THEN 'Fair'
        ELSE 'Needs Improvement'
    END;
    
    DBMS_OUTPUT.PUT_LINE(v_msg);
END;
/

-- LOOP 循环
DECLARE
    v_counter NUMBER := 1;
BEGIN
    LOOP
        DBMS_OUTPUT.PUT_LINE('Count: ' || v_counter);
        v_counter := v_counter + 1;
        EXIT WHEN v_counter > 10;
    END LOOP;
END;
/

-- FOR 循环
BEGIN
    FOR i IN 1..10 LOOP
        DBMS_OUTPUT.PUT_LINE('i = ' || i);
    END LOOP;
    
    -- 逆序
    FOR i IN REVERSE 1..10 LOOP
        DBMS_OUTPUT.PUT_LINE('i = ' || i);
    END LOOP;
END;
/

-- WHILE 循环
DECLARE
    v_counter NUMBER := 1;
BEGIN
    WHILE v_counter <= 10 LOOP
        DBMS_OUTPUT.PUT_LINE('Count: ' || v_counter);
        v_counter := v_counter + 1;
    END LOOP;
END;
/

-- CONTINUE
BEGIN
    FOR i IN 1..10 LOOP
        IF MOD(i, 2) = 0 THEN
            CONTINUE;  -- 跳过偶数
        END IF;
        DBMS_OUTPUT.PUT_LINE(i);
    END LOOP;
END;
/

6.2.4 游标

-- 显式游标
DECLARE
    CURSOR emp_cursor IS
        SELECT employee_id, first_name, salary
        FROM employees
        WHERE department_id = 50
        ORDER BY salary DESC;
    
    v_emp emp_cursor%ROWTYPE;
BEGIN
    OPEN emp_cursor;
    
    LOOP
        FETCH emp_cursor INTO v_emp;
        EXIT WHEN emp_cursor%NOTFOUND;
        
        DBMS_OUTPUT.PUT_LINE(v_emp.employee_id || ': ' || 
                             v_emp.first_name || ' - ' || v_emp.salary);
    END LOOP;
    
    CLOSE emp_cursor;
END;
/

-- FOR 循环自动管理游标
BEGIN
    FOR rec IN (SELECT employee_id, first_name, salary 
                FROM employees WHERE department_id = 50)
    LOOP
        DBMS_OUTPUT.PUT_LINE(rec.first_name || ': ' || rec.salary);
    END LOOP;
END;
/

-- 带参数游标
DECLARE
    CURSOR emp_cursor(p_dept_id NUMBER) IS
        SELECT employee_id, first_name, salary
        FROM employees
        WHERE department_id = p_dept_id;
BEGIN
    FOR rec IN emp_cursor(50) LOOP
        DBMS_OUTPUT.PUT_LINE(rec.first_name);
    END LOOP;
END;
/

-- 游标属性
-- emp_cursor%ISOPEN
-- emp_cursor%FOUND
-- emp_cursor%NOTFOUND
-- emp_cursor%ROWCOUNT

6.2.5 异常处理

DECLARE
    v_emp_id NUMBER := 9999;
    v_name VARCHAR2(50);
BEGIN
    SELECT first_name INTO v_name
    FROM employees
    WHERE employee_id = v_emp_id;
    
    DBMS_OUTPUT.PUT_LINE('Found: ' || v_name);
    
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        DBMS_OUTPUT.PUT_LINE('Employee ' || v_emp_id || ' not found');
        
    WHEN TOO_MANY_ROWS THEN
        DBMS_OUTPUT.PUT_LINE('Multiple employees found');
        
    WHEN DUP_VAL_ON_INDEX THEN
        DBMS_OUTPUT.PUT_LINE('Duplicate value on index');
        
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('Error: ' || SQLERRM);
        DBMS_OUTPUT.PUT_LINE('Error Code: ' || SQLCODE);
END;
/

-- 用户自定义异常
DECLARE
    e_insufficient_sal EXCEPTION;
    PRAGMA EXCEPTION_INIT(e_insufficient_sal, -20001);
BEGIN
    UPDATE employees SET salary = 100 WHERE employee_id = 100;
    
    IF SQL%ROWCOUNT = 0 THEN
        RAISE e_insufficient_sal;
    END IF;
    
EXCEPTION
    WHEN e_insufficient_sal THEN
        DBMS_OUTPUT.PUT_LINE('Operation failed: Insufficient salary');
END;
/

-- RAISE_APPLICATION_ERROR
BEGIN
    RAISE_APPLICATION_ERROR(-20001, 'Custom error message');
END;
/

6.3 存储过程

6.3.1 创建存储过程

-- 基础存储过程
CREATE OR REPLACE PROCEDURE p_update_salary(
    p_emp_id IN employees.employee_id%TYPE,
    p_new_sal IN employees.salary%TYPE
)
IS
    v_old_sal employees.salary%TYPE;
BEGIN
    -- 获取旧工资
    SELECT salary INTO v_old_sal
    FROM employees
    WHERE employee_id = p_emp_id;
    
    -- 更新工资
    UPDATE employees
    SET salary = p_new_sal
    WHERE employee_id = p_emp_id;
    
    -- 记录日志
    DBMS_OUTPUT.PUT_LINE('Updated salary for emp ' || p_emp_id || 
                         ': ' || v_old_sal || ' -> ' || p_new_sal);
    
    COMMIT;
    
EXCEPTION
    WHEN OTHERS THEN
        ROLLBACK;
        RAISE_APPLICATION_ERROR(-20001, 'Failed to update salary: ' || SQLERRM);
END p_update_salary;
/

-- 调用存储过程
BEGIN
    p_update_salary(100, 25000);
END;
/

EXEC p_update_salary(100, 25000);

CALL p_update_salary(100, 25000);

6.3.2 OUT/IN OUT 参数

-- OUT 参数:返回多个值
CREATE OR REPLACE PROCEDURE p_get_emp_info(
    p_emp_id IN employees.employee_id%TYPE,
    p_name OUT VARCHAR2,
    p_sal OUT NUMBER,
    p_dept OUT VARCHAR2
)
IS
BEGIN
    SELECT e.first_name || ' ' || e.last_name,
           e.salary,
           d.department_name
    INTO p_name, p_sal, p_dept
    FROM employees e
    JOIN departments d ON e.department_id = d.department_id
    WHERE e.employee_id = p_emp_id;
END p_get_emp_info;
/

-- 调用
DECLARE
    v_name VARCHAR2(100);
    v_sal NUMBER;
    v_dept VARCHAR2(100);
BEGIN
    p_get_emp_info(100, v_name, v_sal, v_dept);
    DBMS_OUTPUT.PUT_LINE(v_name || ' | ' || v_sal || ' | ' || v_dept);
END;
/

-- IN OUT 参数
CREATE OR REPLACE PROCEDURE p_format_phone(
    p_phone IN OUT VARCHAR2
)
IS
BEGIN
    -- 移除非数字字符并格式化
    p_phone := REGEXP_REPLACE(p_phone, '[^0-9]', '');
    IF LENGTH(p_phone) = 11 THEN
        p_phone := SUBSTR(p_phone, 1, 3) || '-' || 
                   SUBSTR(p_phone, 4, 4) || '-' ||
                   SUBSTR(p_phone, 8, 4);
    END IF;
END p_format_phone;
/

DECLARE
    v_phone VARCHAR2(20) := '13812345678';
BEGIN
    DBMS_OUTPUT.PUT_LINE('Before: ' || v_phone);
    p_format_phone(v_phone);
    DBMS_OUTPUT.PUT_LINE('After: ' || v_phone);
END;
/

6.3.3 存储过程管理

-- 查看存储过程
SELECT object_name, object_type, status, created
FROM user_objects
WHERE object_type IN ('PROCEDURE', 'FUNCTION');

-- 查看源代码
SELECT text
FROM user_source
WHERE name = 'P_UPDATE_SALARY'
ORDER BY line;

-- 重新编译
ALTER PROCEDURE p_update_salary COMPILE;

-- 删除
DROP PROCEDURE p_update_salary;

6.4 函数

6.4.1 创建函数

-- 计算个人所得税函数
CREATE OR REPLACE FUNCTION f_calculate_tax(
    p_salary IN NUMBER
) 
RETURN NUMBER
IS
    v_tax NUMBER(10,2);
BEGIN
    IF p_salary <= 5000 THEN
        v_tax := 0;
    ELSIF p_salary <= 8000 THEN
        v_tax := (p_salary - 5000) * 0.03;
    ELSIF p_salary <= 17000 THEN
        v_tax := (p_salary - 8000) * 0.10 + 90;
    ELSIF p_salary <= 30000 THEN
        v_tax := (p_salary - 17000) * 0.20 + 990;
    ELSE
        v_tax := (p_salary - 30000) * 0.25 + 3590;
    END IF;
    
    RETURN v_tax;
END f_calculate_tax;
/

-- 调用函数
SELECT 
    employee_id,
    first_name,
    salary,
    f_calculate_tax(salary) AS tax
FROM employees;

-- 在 PL/SQL 中调用
DECLARE
    v_tax NUMBER;
BEGIN
    v_tax := f_calculate_tax(10000);
    DBMS_OUTPUT.PUT_LINE('Tax: ' || v_tax);
END;
/

6.4.2 函数与过程对比

特性 存储过程 函数
返回值 0-N 个(通过 OUT 参数) 必须有返回值(RETURN)
SQL 调用 不能 可以(除特定限制)
DML 操作 可以 不能(除非使用自治事务)
用途 业务逻辑、批量处理 计算、转换返回值
-- 函数用于 SQL
CREATE OR REPLACE FUNCTION f_get_dept_name(
    p_dept_id IN NUMBER
) RETURN VARCHAR2
IS
    v_name VARCHAR2(100);
BEGIN
    SELECT department_name INTO v_name
    FROM departments
    WHERE department_id = p_dept_id;
    RETURN v_name;
EXCEPTION
    WHEN OTHERS THEN RETURN NULL;
END f_get_dept_name;
/

-- SQL 中使用
SELECT 
    employee_id,
    first_name,
    f_get_dept_name(department_id) AS dept_name
FROM employees;

6.5 包

6.5.1 包结构

-- 创建包规范(声明)
CREATE OR REPLACE PACKAGE emp_api AS
    -- 常量
    c_max_salary CONSTANT NUMBER := 100000;
    
    -- 存储过程声明
    PROCEDURE p_add_employee(
        p_first_name IN VARCHAR2,
        p_last_name IN VARCHAR2,
        p_email IN VARCHAR2,
        p_dept_id IN NUMBER
    );
    
    PROCEDURE p_update_salary(
        p_emp_id IN NUMBER,
        p_new_sal IN NUMBER
    );
    
    -- 函数声明
    FUNCTION f_get_employee_count(p_dept_id IN NUMBER)
        RETURN NUMBER;
        
    FUNCTION f_validate_salary(p_sal IN NUMBER)
        RETURN BOOLEAN;
        
END emp_api;
/

-- 创建包体(实现)
CREATE OR REPLACE PACKAGE BODY emp_api AS
    
    -- 私有变量(包内全局)
    v_emp_count NUMBER := 0;
    
    -- 私有函数
    FUNCTION f_generate_email(
        p_first_name IN VARCHAR2,
        p_last_name IN VARCHAR2
    ) RETURN VARCHAR2
    IS
    BEGIN
        RETURN LOWER(p_first_name || '.' || p_last_name);
    END f_generate_email;
    
    -- 存储过程实现
    PROCEDURE p_add_employee(
        p_first_name IN VARCHAR2,
        p_last_name IN VARCHAR2,
        p_email IN VARCHAR2,
        p_dept_id IN NUMBER
    )
    IS
    BEGIN
        INSERT INTO employees (
            employee_id, first_name, last_name, 
            email, hire_date, department_id
        ) VALUES (
            seq_employee_id.NEXTVAL, p_first_name, p_last_name,
            p_email, SYSDATE, p_dept_id
        );
        COMMIT;
    END p_add_employee;
    
    PROCEDURE p_update_salary(
        p_emp_id IN NUMBER,
        p_new_sal IN NUMBER
    )
    IS
    BEGIN
        IF f_validate_salary(p_new_sal) THEN
            UPDATE employees 
            SET salary = p_new_sal
            WHERE employee_id = p_emp_id;
            COMMIT;
        ELSE
            RAISE_APPLICATION_ERROR(-20001, 'Invalid salary');
        END IF;
    END p_update_salary;
    
    -- 函数实现
    FUNCTION f_get_employee_count(p_dept_id IN NUMBER)
        RETURN NUMBER
    IS
        v_count NUMBER;
    BEGIN
        SELECT COUNT(*) INTO v_count
        FROM employees
        WHERE department_id = p_dept_id;
        RETURN v_count;
    END f_get_employee_count;
    
    FUNCTION f_validate_salary(p_sal IN NUMBER)
        RETURN BOOLEAN
    IS
    BEGIN
        RETURN (p_sal > 0 AND p_sal <= c_max_salary);
    END f_validate_salary;
    
END emp_api;
/

-- 调用包
BEGIN
    emp_api.p_add_employee('John', 'Doe', 'john.doe', 50);
    emp_api.p_update_salary(100, 8000);
    DBMS_OUTPUT.PUT_LINE('Count: ' || emp_api.f_get_employee_count(50));
END;
/

6.5.2 包管理

-- 查看包信息
SELECT object_name, object_type, status
FROM user_objects
WHERE object_type IN ('PACKAGE', 'PACKAGE BODY');

-- 重新编译包
ALTER PACKAGE emp_api COMPILE PACKAGE;
ALTER PACKAGE emp_api COMPILE BODY;

-- 删除包
DROP PACKAGE emp_api;

6.6 触发器

6.6.1 触发器类型

类型 触发时机 说明
BEFORE 执行前 验证/修改数据
AFTER 执行后 审计/日志
INSTEAD OF 替代执行 用于视图
STATEMENT 语句级 整条语句一次触发
ROW 行级 每行触发一次

6.6.2 创建触发器

-- 行级触发器:自动记录工资变更
CREATE OR REPLACE TRIGGER trg_emp_salary_audit
BEFORE UPDATE OF salary ON employees
FOR EACH ROW
WHEN (OLD.salary != NEW.salary)
DECLARE
    v_change NUMBER := 0;
BEGIN
    v_change := :NEW.salary - :OLD.salary;
    
    INSERT INTO salary_audit (
        audit_id, employee_id, old_salary, new_salary,
        change_amount, changed_by, changed_date
    ) VALUES (
        seq_audit.NEXTVAL, :OLD.employee_id, 
        :OLD.salary, :NEW.salary, v_change,
        USER, SYSDATE
    );
END trg_emp_salary_audit;
/

-- 语句级触发器:禁止周末操作
CREATE OR REPLACE TRIGGER trg_no_weekend_dml
BEFORE INSERT OR UPDATE OR DELETE ON employees
DECLARE
    v_day NUMBER := TO_CHAR(SYSDATE, 'D');
BEGIN
    IF v_day IN (1, 7) THEN  -- 周末
        RAISE_APPLICATION_ERROR(-20001, 
            'DML operations are not allowed on weekends');
    END IF;
END trg_no_weekend_dml;
/

-- 组合触发器(Oracle 11g+)
CREATE OR REPLACE TRIGGER trg_emp_compound
FOR INSERT OR UPDATE OR DELETE ON employees
COMPOUND TRIGGER
    v_count NUMBER := 0;
    
    BEFORE STATEMENT IS
    BEGIN
        DBMS_OUTPUT.PUT_LINE('Statement started');
    END BEFORE STATEMENT;
    
    BEFORE EACH ROW IS
    BEGIN
        IF INSERTING THEN
            DBMS_OUTPUT.PUT_LINE('Inserting row');
        ELSIF UPDATING THEN
            DBMS_OUTPUT.PUT_LINE('Updating row');
        ELSIF DELETING THEN
            DBMS_OUTPUT.PUT_LINE('Deleting row');
        END IF;
    END BEFORE EACH ROW;
    
    AFTER EACH ROW IS
    BEGIN
        v_count := v_count + 1;
    END AFTER EACH ROW;
    
    AFTER STATEMENT IS
    BEGIN
        DBMS_OUTPUT.PUT_LINE('Total rows: ' || v_count);
    END AFTER STATEMENT;
END trg_emp_compound;
/

6.6.3 INSTEAD OF 触发器(视图)

-- 创建复杂视图
CREATE OR REPLACE VIEW v_emp_dept AS
SELECT 
    e.employee_id,
    e.first_name,
    e.salary,
    d.department_id,
    d.department_name
FROM employees e
JOIN departments d ON e.department_id = d.department_id;

-- 创建 INSTEAD OF 触发器
CREATE OR REPLACE TRIGGER trg_v_emp_dept
INSTEAD OF INSERT ON v_emp_dept
FOR EACH ROW
DECLARE
    v_dept_id NUMBER;
BEGIN
    -- 获取部门ID
    SELECT department_id INTO v_dept_id
    FROM departments
    WHERE department_name = :NEW.department_name;
    
    -- 插入员工
    INSERT INTO employees (
        employee_id, first_name, salary, department_id
    ) VALUES (
        seq_employee_id.NEXTVAL, :NEW.first_name, 
        :NEW.salary, v_dept_id
    );
END trg_v_emp_dept;
/

-- 通过视图插入
INSERT INTO v_emp_dept (first_name, salary, department_name)
VALUES ('New Employee', 5000, 'IT');

6.6.4 触发器管理

-- 查看触发器
SELECT trigger_name, trigger_type, triggering_event, status
FROM user_triggers;

-- 禁用/启用触发器
ALTER TRIGGER trg_emp_salary_audit DISABLE;
ALTER TRIGGER trg_emp_salary_audit ENABLE;

-- 禁用表的所有触发器
ALTER TABLE employees DISABLE ALL TRIGGERS;
ALTER TABLE employees ENABLE ALL TRIGGERS;

-- 删除触发器
DROP TRIGGER trg_emp_salary_audit;

6.7 本章小结

✅ 视图类型:简单视图、复杂视图、物化视图
✅ PL/SQL 块结构:DECLARE、BEGIN、EXCEPTION、END
✅ 控制结构:IF、CASE、LOOP、FOR、WHILE
✅ 游标:显式游标、隐式游标、游标 FOR 循环
✅ 存储过程:IN/OUT 参数、异常处理
✅ 函数:SQL 可调用、返回单一值
✅ 包:封装相关过程和函数
✅ 触发器:BEFORE/AFTER/INSTEAD OF、行级/语句级

📖 下章预告:触发器与事件调度器

下一步:学习 第七章:触发器与事件调度器