您好,登录后才能下订单哦!
# Oracle中怎么实现存储过程
## 一、存储过程概述
### 1.1 什么是存储过程
存储过程(Stored Procedure)是Oracle数据库中一组为了完成特定功能的SQL语句集合,经编译后存储在数据库中。用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它。
存储过程具有以下特点:
- 预编译的SQL语句集合
- 存储在数据库服务器端
- 可通过名称和参数调用
- 支持流程控制语句
- 可返回多个结果集
### 1.2 存储过程的优势
1. **提高性能**:存储过程在创建时进行编译,后续调用无需重新编译
2. **减少网络流量**:客户端只需传递存储过程名和参数,而非完整SQL
3. **增强安全性**:可通过授权机制控制对存储过程的访问
4. **代码复用**:一次编写,多次调用
5. **模块化编程**:将复杂业务逻辑封装在数据库层
### 1.3 存储过程与函数的区别
| 特性 | 存储过程 | 函数 |
|---------------------|-------------------------|-------------------------|
| 返回值 | 可以没有或返回多个 | 必须返回一个值 |
| 调用方式 | EXECUTE或CALL语句 | 在SQL语句中直接调用 |
| 参数模式 | IN,OUT,IN OUT | 只有IN模式 |
| DML操作 | 可以执行 | 通常不执行(除非是自治事务)|
| 异常处理 | 有完善的异常处理机制 | 异常处理较简单 |
## 二、存储过程基础语法
### 2.1 创建存储过程
基本语法结构:
```sql
CREATE [OR REPLACE] PROCEDURE procedure_name
[ (parameter [,parameter]) ]
IS
[declaration_section]
BEGIN
executable_section
[EXCEPTION
exception_section]
END [procedure_name];
示例:创建一个简单存储过程
CREATE OR REPLACE PROCEDURE greet_employee
IS
BEGIN
DBMS_OUTPUT.PUT_LINE('Hello, employees!');
END greet_employee;
Oracle存储过程支持三种参数模式:
IN参数(默认模式):传入参数,过程内部不能修改
CREATE OR REPLACE PROCEDURE raise_salary (
p_emp_id IN NUMBER,
p_amount IN NUMBER
) IS
BEGIN
UPDATE employees
SET salary = salary + p_amount
WHERE employee_id = p_emp_id;
END raise_salary;
OUT参数:输出参数,过程内部可修改并返回
CREATE OR REPLACE PROCEDURE get_employee_info (
p_emp_id IN NUMBER,
p_name OUT VARCHAR2,
p_salary OUT NUMBER
) IS
BEGIN
SELECT last_name, salary
INTO p_name, p_salary
FROM employees
WHERE employee_id = p_emp_id;
END get_employee_info;
IN OUT参数:双向参数,传入值并可被修改后返回
CREATE OR REPLACE PROCEDURE format_phone_number (
p_phone IN OUT VARCHAR2
) IS
BEGIN
p_phone := '(' || SUBSTR(p_phone,1,3) || ') ' ||
SUBSTR(p_phone,4,3) || '-' ||
SUBSTR(p_phone,7);
END format_phone_number;
使用EXECUTE命令:
EXECUTE procedure_name(parameters);
使用CALL语句:
CALL procedure_name(parameters);
在PL/SQL块中调用:
BEGIN
procedure_name(parameters);
END;
带OUT参数调用的示例:
DECLARE
v_name VARCHAR2(100);
v_salary NUMBER;
BEGIN
get_employee_info(100, v_name, v_salary);
DBMS_OUTPUT.PUT_LINE('Name: ' || v_name || ', Salary: ' || v_salary);
END;
存储过程中可以使用完整的异常处理机制:
CREATE OR REPLACE PROCEDURE process_order (
p_order_id IN NUMBER
) IS
v_status VARCHAR2(20);
BEGIN
-- 业务逻辑
SELECT status INTO v_status FROM orders WHERE order_id = p_order_id;
IF v_status != 'OPEN' THEN
RSE_APPLICATION_ERROR(-20001, 'Order is not open');
END IF;
-- 更多处理逻辑
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('Order not found');
WHEN TOO_MANY_ROWS THEN
DBMS_OUTPUT.PUT_LINE('Multiple orders found');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Error: ' || SQLERRM);
ROLLBACK;
END process_order;
存储过程中可以包含完整的事务控制语句:
CREATE OR REPLACE PROCEDURE transfer_funds (
p_from_acct IN NUMBER,
p_to_acct IN NUMBER,
p_amount IN NUMBER
) IS
v_balance NUMBER;
BEGIN
-- 检查账户余额
SELECT balance INTO v_balance
FROM accounts
WHERE account_id = p_from_acct
FOR UPDATE; -- 加锁
IF v_balance < p_amount THEN
RSE_APPLICATION_ERROR(-20002, 'Insufficient funds');
END IF;
-- 转账操作
UPDATE accounts SET balance = balance - p_amount
WHERE account_id = p_from_acct;
UPDATE accounts SET balance = balance + p_amount
WHERE account_id = p_to_acct;
-- 记录交易
INSERT INTO transactions VALUES (
trans_seq.NEXTVAL,
p_from_acct,
p_to_acct,
p_amount,
SYSDATE
);
COMMIT;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
RSE;
END transfer_funds;
存储过程中可以使用游标处理多行数据:
CREATE OR REPLACE PROCEDURE update_department_salaries (
p_dept_id IN NUMBER,
p_percent IN NUMBER
) IS
CURSOR emp_cur IS
SELECT employee_id, salary
FROM employees
WHERE department_id = p_dept_id
FOR UPDATE OF salary;
v_count NUMBER := 0;
BEGIN
FOR emp_rec IN emp_cur LOOP
UPDATE employees
SET salary = salary * (1 + p_percent/100)
WHERE CURRENT OF emp_cur;
v_count := v_count + 1;
END LOOP;
DBMS_OUTPUT.PUT_LINE('Updated ' || v_count || ' employees');
COMMIT;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
DBMS_OUTPUT.PUT_LINE('Error: ' || SQLERRM);
END update_department_salaries;
推荐采用以下命名约定:
- 过程名:动词开头,如calculate_totals
- 参数名:前缀表示模式,如p_
表示参数,v_
表示变量
- 游标名:cur_
前缀
- 异常名:e_
前缀
使用NOCOPY
提示减少大型OUT/IN OUT参数的复制开销:
CREATE PROCEDURE process_large_data (
p_data IN OUT NOCOPY CLOB
) IS
批量操作使用FORALL
代替循环:
CREATE PROCEDURE bulk_update IS
TYPE id_array IS TABLE OF NUMBER;
v_ids id_array := id_array(101, 102, 103);
BEGIN
FORALL i IN 1..v_ids.COUNT
UPDATE employees SET salary = salary * 1.1
WHERE employee_id = v_ids(i);
END;
使用绑定变量而非字符串拼接: “`sql – 不推荐 EXECUTE IMMEDIATE ‘SELECT * FROM ’ || p_table_name;
– 推荐 EXECUTE IMMEDIATE ‘SELECT * FROM ’ || DBMS_ASSERT.SQL_OBJECT_NAME(p_table_name);
### 4.3 安全考虑
1. 使用`AUTHID`子句控制执行权限:
```sql
CREATE OR REPLACE PROCEDURE sensitive_operation
AUTHID DEFINER IS -- 以定义者权限执行
BEGIN
-- 访问定义者有权限但调用者无权限的对象
END;
防范SQL注入:
CREATE PROCEDURE search_products (
p_name IN VARCHAR2
) IS
v_sql VARCHAR2(1000);
BEGIN
-- 不安全
-- v_sql := 'SELECT * FROM products WHERE name LIKE ''%' || p_name || '%''';
-- 安全方式
v_sql := 'SELECT * FROM products WHERE name LIKE ''%'' || :name || ''%''';
EXECUTE IMMEDIATE v_sql USING p_name;
END;
CREATE OR REPLACE PROCEDURE migrate_customer_data AS
CURSOR cust_cur IS
SELECT customer_id, old_credit_limit, old_status
FROM legacy_customers
WHERE migration_flag = 'N';
v_count NUMBER := 0;
v_start_time TIMESTAMP := SYSTIMESTAMP;
BEGIN
FOR cust_rec IN cust_cur LOOP
BEGIN
-- 插入新系统
INSERT INTO customers (
customer_id,
credit_limit,
status,
create_date
) VALUES (
cust_rec.customer_id,
cust_rec.old_credit_limit * 1.1, -- 新系统信用额度增加10%
CASE cust_rec.old_status
WHEN 'A' THEN 'ACTIVE'
WHEN 'I' THEN 'INACTIVE'
ELSE 'PENDING'
END,
SYSDATE
);
-- 更新迁移标志
UPDATE legacy_customers
SET migration_flag = 'Y',
migration_date = SYSDATE
WHERE customer_id = cust_rec.customer_id;
v_count := v_count + 1;
-- 每100条提交一次
IF MOD(v_count, 100) = 0 THEN
COMMIT;
END IF;
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
DBMS_OUTPUT.PUT_LINE('Duplicate customer: ' || cust_rec.customer_id);
UPDATE legacy_customers
SET migration_flag = 'E',
migration_error = 'Duplicate ID'
WHERE customer_id = cust_rec.customer_id;
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Error migrating customer ' ||
cust_rec.customer_id || ': ' || SQLERRM);
UPDATE legacy_customers
SET migration_flag = 'E',
migration_error = SUBSTR(SQLERRM, 1, 200)
WHERE customer_id = cust_rec.customer_id;
END;
END LOOP;
COMMIT;
DBMS_OUTPUT.PUT_LINE('Migration completed');
DBMS_OUTPUT.PUT_LINE('Total records processed: ' || v_count);
DBMS_OUTPUT.PUT_LINE('Elapsed time: ' ||
EXTRACT(SECOND FROM (SYSTIMESTAMP - v_start_time)) || ' seconds');
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
DBMS_OUTPUT.PUT_LINE('Critical error: ' || SQLERRM);
RSE;
END migrate_customer_data;
CREATE OR REPLACE PROCEDURE generate_monthly_sales_report (
p_month IN NUMBER,
p_year IN NUMBER,
p_dept_id IN NUMBER DEFAULT NULL
) AS
v_report_id NUMBER;
v_start_date DATE;
v_end_date DATE;
v_dept_name VARCHAR2(100);
-- 定义记录类型
TYPE sales_rec IS RECORD (
product_id NUMBER,
product_name VARCHAR2(100),
total_quantity NUMBER,
total_amount NUMBER(15,2)
);
-- 定义表类型
TYPE sales_table IS TABLE OF sales_rec;
-- 声明变量
v_sales_data sales_table;
v_total_sales NUMBER(15,2) := 0;
v_total_quantity NUMBER := 0;
BEGIN
-- 计算日期范围
v_start_date := TO_DATE('01-' || p_month || '-' || p_year, 'DD-MM-YYYY');
v_end_date := LAST_DAY(v_start_date);
-- 获取部门名称(如果指定了部门)
IF p_dept_id IS NOT NULL THEN
BEGIN
SELECT department_name INTO v_dept_name
FROM departments
WHERE department_id = p_dept_id;
EXCEPTION
WHEN NO_DATA_FOUND THEN
v_dept_name := 'Department ' || p_dept_id || ' (Not Found)';
END;
ELSE
v_dept_name := 'All Departments';
END IF;
-- 生成唯一报告ID
SELECT report_seq.NEXTVAL INTO v_report_id FROM dual;
-- 插入报告头信息
INSERT INTO sales_reports (
report_id,
report_month,
report_year,
department_id,
department_name,
generation_date
) VALUES (
v_report_id,
p_month,
p_year,
p_dept_id,
v_dept_name,
SYSDATE
);
-- 获取销售数据
IF p_dept_id IS NULL THEN
-- 全公司销售数据
SELECT
p.product_id,
p.product_name,
SUM(oi.quantity),
SUM(oi.quantity * oi.unit_price)
BULK COLLECT INTO v_sales_data
FROM order_items oi
JOIN products p ON oi.product_id = p.product_id
JOIN orders o ON oi.order_id = o.order_id
WHERE o.order_date BETWEEN v_start_date AND v_end_date
GROUP BY p.product_id, p.product_name
ORDER BY SUM(oi.quantity * oi.unit_price) DESC;
ELSE
-- 特定部门销售数据
SELECT
p.product_id,
p.product_name,
SUM(oi.quantity),
SUM(oi.quantity * oi.unit_price)
BULK COLLECT INTO v_sales_data
FROM order_items oi
JOIN products p ON oi.product_id = p.product_id
JOIN orders o ON oi.order_id = o.order_id
JOIN employees e ON o.sales_rep_id = e.employee_id
WHERE o.order_date BETWEEN v_start_date AND v_end_date
AND e.department_id = p_dept_id
GROUP BY p.product_id, p.product_name
ORDER BY SUM(oi.quantity * oi.unit_price) DESC;
END IF;
-- 处理销售数据
FOR i IN 1..v_sales_data.COUNT LOOP
-- 插入明细记录
INSERT INTO sales_report_details (
report_id,
line_number,
product_id,
product_name,
quantity,
amount
) VALUES (
v_report_id,
i,
v_sales_data(i).product_id,
v_sales_data(i).product_name,
v_sales_data(i).total_quantity,
v_sales_data(i).total_amount
);
-- 累计总数
v_total_quantity := v_total_quantity + v_sales_data(i).total_quantity;
v_total_sales := v_total_sales + v_sales_data(i).total_amount;
END LOOP;
-- 更新报告汇总信息
UPDATE sales_reports
SET total_quantity = v_total_quantity,
total_amount = v_total_sales,
record_count = v_sales_data.COUNT
WHERE report_id = v_report_id;
COMMIT;
DBMS_OUTPUT.PUT_LINE('Report generated successfully. ID: ' || v_report_id);
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
DBMS_OUTPUT.PUT_LINE('Error generating report: ' || SQLERRM);
RSE;
END generate_monthly_sales_report;
使用DBMS_OUTPUT进行简单调试:
CREATE PROCEDURE debug_example IS
v_temp NUMBER := 0;
BEGIN
DBMS_OUTPUT.PUT_LINE('Starting procedure');
FOR i IN 1..10 LOOP
v_temp := v_temp + i;
DBMS_OUTPUT.PUT_LINE('Iteration ' || i || ': ' || v_temp);
END LOOP;
DBMS_OUTPUT.PUT_LINE('Final total: ' || v_temp);
END;
使用条件编译进行调试: “`sql CREATE PROCEDURE debug_with_cc IS BEGIN \(IF \)\(DEBUG \)THEN DBMS_OUTPUT.PUT_LINE(‘Debug mode: Starting procedure’); $END
– 业务逻辑
\(IF \)\(DEBUG \)THEN DBMS_OUTPUT.PUT_LINE(‘Debug mode: Procedure completed’); $END END;
– 启用调试 ALTER PROCEDURE debug_with_cc COMPILE PLSQL_CCFLAGS = ‘DEBUG:TRUE’ REUSE SETTINGS; “`
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。