关于 plsql
plsql 是面向 Oracle PL/SQL 开发的参考 Skill,提供过程、函数、游标、集合、异常处理和触发器示例,并涵盖编码实践与常见问题排查。
主要功能
- 01存储过程与函数示例
- 02游标和集合用法
- 03异常处理模式
- 04触发器示例与注意事项
- 05最佳实践和故障排查
适用场景
- 01编写 Oracle PL/SQL 过程和函数
- 02处理游标及批量数据操作
- 03排查 PL/SQL 异常与性能问题
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ claude-dev-suite/ claude-dev-suite --skill plsql安装后在 Claude Code 中输入/plsql确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
advanced.mdquick-ref/cursors.mdfunctions.mdpackages.mdprocedures.mdtriggers.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_FOUND | SELECT INTO 未返回任何行 |
TOO_MANY_ROWS | SELECT 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_FOUND | SELECT INTO 未返回任何行 | 添加异常处理程序 |
| TOO_MANY_ROWS | SELECT INTO 返回多行 | 添加 WHERE 条件或使用游标 |
| ORA-06502 数值错误 | 类型转换失败 | 检查数据类型,使用 TO_NUMBER |
| 过程运行缓慢 | DBMS_PROFILER | 使用 BULK 操作 |
| 包状态丢失 | 会话重置 | 使用 PRAGMA SERIALLY_REUSABLE 或重新初始化 |
参考文档
评论
支持 Markdown