mcpField
P
plsql

plsql

github.com/claude-dev-suite数据与数据库46102 次安装2 天前更新
代码仓库

关于 plsql

plsql 是面向 Oracle PL/SQL 开发的参考 Skill,提供过程、函数、游标、集合、异常处理和触发器示例,并涵盖编码实践与常见问题排查。

主要功能

  1. 01存储过程与函数示例
  2. 02游标和集合用法
  3. 03异常处理模式
  4. 04触发器示例与注意事项
  5. 05最佳实践和故障排查

适用场景

  1. 01编写 Oracle PL/SQL 过程和函数
  2. 02处理游标及批量数据操作
  3. 03排查 PL/SQL 异常与性能问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/claude-dev-suite/claude-dev-suite --skill plsql

安装后在 Claude Code 中输入/plsql确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

advanced.md
quick-ref/
cursors.md
functions.md
packages.md
procedures.md
triggers.md

详细介绍

Oracle PL/SQL 核心知识

完整参考:管道表函数、包、集合、BULK COLLECT/FORALL、复合触发器和高级游标,请参阅 advanced.md。

深入了解:使用 mcp__documentation__fetch_docs,并将 technology 设为 oracle,获取完整文档。

基本结构

DECLARE
    -- Variable declarations
    v_count NUMBER := 0;
BEGIN
    -- Executable statements
    DBMS_OUTPUT.PUT_LINE('Hello World');
EXCEPTION
    WHEN OTHERS THEN
        -- Exception handling
        DBMS_OUTPUT.PUT_LINE('Error: ' || SQLERRM);
END;
/

过程

基本过程
CREATE OR REPLACE PROCEDURE update_salary(
    p_employee_id IN NUMBER,
    p_percentage  IN NUMBER
)
IS
    v_current_salary NUMBER;
BEGIN
    SELECT salary INTO v_current_salary
    FROM employees
    WHERE employee_id = p_employee_id;

    UPDATE employees
    SET salary = salary * (1 + p_percentage / 100)
    WHERE employee_id = p_employee_id;

    COMMIT;
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        RAISE_APPLICATION_ERROR(-20001, 'Employee not found: ' || p_employee_id);
END update_salary;
/

-- Execute
EXEC update_salary(100, 10);
-- or
BEGIN
    update_salary(100, 10);
END;
/
带 OUT 参数的过程
CREATE OR REPLACE PROCEDURE get_employee_info(
    p_employee_id IN  NUMBER,
    p_name        OUT VARCHAR2,
    p_salary      OUT NUMBER,
    p_dept_name   OUT VARCHAR2
)
IS
BEGIN
    SELECT e.first_name || ' ' || e.last_name,
           e.salary,
           d.department_name
    INTO p_name, p_salary, p_dept_name
    FROM employees e
    JOIN departments d ON e.department_id = d.department_id
    WHERE e.employee_id = p_employee_id;
END;
/

-- Call with OUT parameters
DECLARE
    v_name VARCHAR2(100);
    v_salary NUMBER;
    v_dept VARCHAR2(100);
BEGIN
    get_employee_info(100, v_name, v_salary, v_dept);
    DBMS_OUTPUT.PUT_LINE(v_name || ': ' || v_salary);
END;
/

函数

标量函数
CREATE OR REPLACE FUNCTION calculate_bonus(
    p_salary IN NUMBER,
    p_years  IN NUMBER
)
RETURN NUMBER
DETERMINISTIC  -- Same inputs always return same output
IS
    v_bonus NUMBER;
BEGIN
    IF p_years >= 10 THEN
        v_bonus := p_salary * 0.15;
    ELSIF p_years >= 5 THEN
        v_bonus := p_salary * 0.10;
    ELSE
        v_bonus := p_salary * 0.05;
    END IF;

    RETURN v_bonus;
END;
/

-- Usage in SQL
SELECT employee_id, salary, calculate_bonus(salary, years_of_service) as bonus
FROM employees;

游标

隐式游标
BEGIN
    UPDATE employees SET salary = salary * 1.1 WHERE department_id = 10;
    DBMS_OUTPUT.PUT_LINE('Rows updated: ' || SQL%ROWCOUNT);

    IF SQL%NOTFOUND THEN
        DBMS_OUTPUT.PUT_LINE('No rows found');
    END IF;
END;
/
显式游标
DECLARE
    CURSOR emp_cursor IS
        SELECT employee_id, first_name, salary
        FROM employees
        WHERE department_id = 10;

    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.first_name || ': ' || v_emp.salary);
    END LOOP;
    CLOSE emp_cursor;
END;
/
游标 FOR 循环(推荐)
BEGIN
    FOR emp_rec IN (SELECT employee_id, first_name, salary
                    FROM employees WHERE department_id = 10)
    LOOP
        DBMS_OUTPUT.PUT_LINE(emp_rec.first_name || ': ' || emp_rec.salary);
    END LOOP;
END;
/

集合

关联数组(INDEX BY)
DECLARE
    TYPE salary_tab IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
    TYPE name_tab IS TABLE OF VARCHAR2(100) INDEX BY VARCHAR2(20);

    salaries salary_tab;
    names    name_tab;
BEGIN
    salaries(1) := 50000;
    salaries(2) := 60000;

    names('EMP001') := 'John Doe';
    names('EMP002') := 'Jane Smith';

    DBMS_OUTPUT.PUT_LINE(salaries(1));
    DBMS_OUTPUT.PUT_LINE(names('EMP001'));
END;
/

异常处理

预定义异常
异常描述
NO_DATA_FOUNDSELECT INTO 未返回任何行
TOO_MANY_ROWSSELECT INTO 返回了多行
ZERO_DIVIDE除以零
VALUE_ERROR数值或值错误
INVALID_CURSOR游标操作无效
DUP_VAL_ON_INDEX唯一索引中存在重复值
异常处理
DECLARE
    v_salary NUMBER;
BEGIN
    SELECT salary INTO v_salary FROM employees WHERE employee_id = 9999;
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        DBMS_OUTPUT.PUT_LINE('Employee not found');
    WHEN TOO_MANY_ROWS THEN
        DBMS_OUTPUT.PUT_LINE('Multiple employees found');
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('Error: ' || SQLCODE || ' - ' || SQLERRM);
        RAISE;
END;
/
RAISE_APPLICATION_ERROR
BEGIN
    IF some_condition THEN
        RAISE_APPLICATION_ERROR(-20001, 'Custom error message');
    END IF;
END;
/

触发器

行级触发器
CREATE OR REPLACE TRIGGER trg_emp_salary_check
BEFORE INSERT OR UPDATE OF salary ON employees
FOR EACH ROW
BEGIN
    IF :NEW.salary < 0 THEN
        RAISE_APPLICATION_ERROR(-20001, 'Salary cannot be negative');
    END IF;

    IF :NEW.salary > 1000000 THEN
        RAISE_APPLICATION_ERROR(-20002, 'Salary exceeds maximum');
    END IF;
END;
/

最佳实践

应该做
  • 使用包组织相关代码
  • 对大型数据集使用 BULK COLLECT 和 FORALL
  • 使用游标 FOR 循环(自动打开和关闭游标)
  • 在包级别定义异常
  • 使用 %TYPE 和 %ROWTYPE 确保类型安全
  • 使用绑定变量防止 SQL 注入
不应该做
  • 在触发器中使用隐式提交
  • 忽略异常
  • 使用 SELECT INTO 却不处理 NO_DATA_FOUND
  • 创建过多触发器(会影响性能)

何时不应使用此技能

  • 基本 Oracle SQL - 查询、数据类型和分区请使用 oracle 技能
  • PL/pgSQL(PostgreSQL) - PostgreSQL 过程请使用 plpgsql 技能
  • T-SQL(SQL Server) - SQL Server 过程请使用 tsql 技能
  • 基本 SQL - ANSI SQL 基础请使用 sql-fundamentals

反模式

反模式问题解决方案
不使用 BULK COLLECT逐行处理对大型数据集使用 BULK COLLECT
SELECT INTO 未处理异常运行时错误处理 NO_DATA_FOUND
不使用包代码组织混乱在包中组织相关代码
过多触发器性能问题尽量精简触发器逻辑
WHEN OTHERS 不使用 RAISE静默失败重新抛出或记录异常
对大型集合使用隐式游标内存问题使用带 LIMIT 的显式游标

快速故障排除

问题诊断修复方法
NO_DATA_FOUNDSELECT INTO 未返回任何行添加异常处理程序
TOO_MANY_ROWSSELECT INTO 返回多行添加 WHERE 条件或使用游标
ORA-06502 数值错误类型转换失败检查数据类型,使用 TO_NUMBER
过程运行缓慢DBMS_PROFILER使用 BULK 操作
包状态丢失会话重置使用 PRAGMA SERIALLY_REUSABLE 或重新初始化

参考文档

评论

支持 Markdown