plsql

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Oracle PL/SQL Core Knowledge

Oracle PL/SQL 核心知识

Full Reference: See advanced.md for pipelined table functions, packages, collections, BULK COLLECT/FORALL, compound triggers, and advanced cursors.
Deep Knowledge: Use
mcp__documentation__fetch_docs
with technology:
oracle
for comprehensive documentation.
完整参考:查看 advanced.md 了解管道表函数、包、集合、BULK COLLECT/FORALL、复合触发器和高级游标相关内容。
深入学习:使用
mcp__documentation__fetch_docs
工具并指定技术类型为
oracle
,可获取全面的文档。

Basic Structure

基本结构

sql
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;
/
sql
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;
/

Procedures

存储过程

Basic Procedure

基础存储过程

sql
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;
/
sql
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;
/

Procedure with OUT Parameters

带OUT参数的存储过程

sql
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;
/
sql
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;
/

Functions

函数

Scalar Function

标量函数

sql
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;
sql
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;

Cursors

游标

Implicit Cursor

隐式游标

sql
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;
/
sql
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;
/

Explicit Cursor

显式游标

sql
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;
/
sql
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;
/

Cursor FOR Loop (Preferred)

游标FOR循环(推荐使用)

sql
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;
/
sql
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;
/

Collections

集合

Associative Array (INDEX BY)

关联数组(INDEX BY)

sql
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;
/
sql
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;
/

Exception Handling

异常处理

Predefined Exceptions

预定义异常

ExceptionDescription
NO_DATA_FOUND
SELECT INTO returned no rows
TOO_MANY_ROWS
SELECT INTO returned multiple rows
ZERO_DIVIDE
Division by zero
VALUE_ERROR
Numeric or value error
INVALID_CURSOR
Invalid cursor operation
DUP_VAL_ON_INDEX
Duplicate value on unique index
ExceptionDescription
NO_DATA_FOUND
SELECT INTO returned no rows
TOO_MANY_ROWS
SELECT INTO returned multiple rows
ZERO_DIVIDE
Division by zero
VALUE_ERROR
Numeric or value error
INVALID_CURSOR
Invalid cursor operation
DUP_VAL_ON_INDEX
Duplicate value on unique index

Exception Handling

异常处理示例

sql
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;
/
sql
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

RAISE_APPLICATION_ERROR使用

sql
BEGIN
    IF some_condition THEN
        RAISE_APPLICATION_ERROR(-20001, 'Custom error message');
    END IF;
END;
/
sql
BEGIN
    IF some_condition THEN
        RAISE_APPLICATION_ERROR(-20001, 'Custom error message');
    END IF;
END;
/

Triggers

触发器

Row-Level Trigger

行级触发器

sql
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;
/
sql
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;
/

Best Practices

最佳实践

DO

建议

  • Use packages to organize related code
  • Use BULK COLLECT and FORALL for large datasets
  • Use cursor FOR loops (auto open/close)
  • Define exceptions at package level
  • Use %TYPE and %ROWTYPE for type safety
  • Use bind variables to prevent SQL injection
  • 使用包来组织相关代码
  • 处理大型数据集时使用BULK COLLECT和FORALL
  • 使用游标FOR循环(自动打开/关闭)
  • 在包级别定义异常
  • 使用%TYPE和%ROWTYPE确保类型安全
  • 使用绑定变量防止SQL注入

DON'T

不建议

  • Use implicit commits in triggers
  • Ignore exceptions
  • Use SELECT INTO without handling NO_DATA_FOUND
  • Create excessive triggers (performance impact)
  • 在触发器中使用隐式提交
  • 忽略异常
  • 使用SELECT INTO时不处理NO_DATA_FOUND
  • 创建过多触发器(影响性能)

When NOT to Use This Skill

不适用场景

  • Basic Oracle SQL - Use
    oracle
    skill for queries, data types, partitioning
  • PL/pgSQL (PostgreSQL) - Use
    plpgsql
    skill for PostgreSQL procedures
  • T-SQL (SQL Server) - Use
    tsql
    skill for SQL Server procedures
  • Basic SQL - Use
    sql-fundamentals
    for ANSI SQL basics
  • 基础Oracle SQL - 如需查询、数据类型、分区相关内容,请使用
    oracle
    技能
  • PL/pgSQL(PostgreSQL) - 如需PostgreSQL存储过程相关内容,请使用
    plpgsql
    技能
  • T-SQL(SQL Server) - 如需SQL Server存储过程相关内容,请使用
    tsql
    技能
  • 基础SQL - 如需ANSI SQL基础内容,请使用
    sql-fundamentals
    技能

Anti-Patterns

反模式

Anti-PatternProblemSolution
Not using BULK COLLECTRow-by-row processingUse BULK COLLECT for large datasets
SELECT INTO without exceptionRuntime errorsHandle NO_DATA_FOUND
Not using packagesCode disorganizationOrganize related code in packages
Excessive triggersPerformance issuesMinimize trigger logic
WHEN OTHERS without RAISESilent failuresRe-raise or log exceptions
Implicit cursors for large setsMemory issuesUse explicit cursors with LIMIT
反模式问题解决方案
未使用BULK COLLECT逐行处理效率低处理大型数据集时使用BULK COLLECT
SELECT INTO未处理异常运行时错误处理NO_DATA_FOUND异常
未使用包代码结构混乱将相关代码组织到包中
过多触发器性能问题最小化触发器逻辑
WHEN OTHERS未使用RAISE静默失败重新抛出或记录异常
大型数据集使用隐式游标内存问题使用带LIMIT的显式游标

Quick Troubleshooting

快速故障排查

ProblemDiagnosticFix
NO_DATA_FOUNDSELECT INTO with no rowsAdd exception handler
TOO_MANY_ROWSSELECT INTO with multiple rowsAdd WHERE or use cursor
ORA-06502 numeric errorType conversion failureCheck data types, use TO_NUMBER
Slow procedureDBMS_PROFILERUse BULK operations
Package state lostSession resetUse PRAGMA SERIALLY_REUSABLE or re-initialize
问题诊断解决方法
NO_DATA_FOUNDSELECT INTO未返回任何行添加异常处理程序
TOO_MANY_ROWSSELECT INTO返回多行添加WHERE条件或使用游标
ORA-06502数值错误类型转换失败检查数据类型,使用TO_NUMBER
存储过程运行缓慢使用DBMS_PROFILER分析使用批量操作
包状态丢失会话重置使用PRAGMA SERIALLY_REUSABLE或重新初始化

Reference Documentation

参考文档

  • Procedures
  • Functions
  • Triggers
  • Packages
  • Cursors
  • Procedures
  • Functions
  • Triggers
  • Packages
  • Cursors