杰瑞科技汇

Python如何调用Oracle存储过程?

  1. 环境准备:安装必要的 Python 库。
  2. 数据库连接:使用 cx_Oracle 库建立与 Oracle 数据库的连接。
  3. 创建存储过程:为了演示,我们先在 Oracle 数据库中创建一个简单的存储过程。
  4. 调用存储过程:使用 Python 的 callproc() 函数来执行存储过程。
  5. 处理不同类型的参数:演示如何处理输入参数、输出参数和输入/输出参数。
  6. 处理游标(Ref Cursor):这是从存储过程返回结果集最标准、最强大的方式。
  7. 最佳实践和错误处理:编写健壮、可维护的代码。

环境准备

你需要安装 cx_Oracle 库,它提供了 Python 与 Oracle 数据库的接口。

Python如何调用Oracle存储过程?-图1
(图片来源网络,侵删)
pip install cx_Oracle

重要提示cx_Oracle 需要 Oracle 客户端库,如果你的机器上没有安装 Oracle 客户端(如 Instant Client),你需要先下载并配置它。

  • Oracle Instant Client:这是一个轻量级的客户端,cx_Oracle 需要它来与数据库通信。
    • 下载地址:Oracle Instant Client Downloads
    • 下载适合你操作系统的版本(如 Windows x64, Linux x64 等)。
    • 解压下载的文件,并将解压后的 bin 目录添加到系统的 PATH 环境变量中,这是让 Python 能找到 Oracle 库的关键一步。

数据库连接

调用存储过程的第一步是建立数据库连接,你需要提供用户名、密码和连接字符串(DSN)。

import cx_Oracle
# --- 连接信息 ---
# 替换为你的实际数据库信息
username = "your_username"
password = "your_password"
dsn = "hostname:port/service_name"  #  "localhost:1521/XE"
# --- 建立连接 ---
try:
    # 创建连接对象
    connection = cx_Oracle.connect(user=username, password=password, dsn=dsn)
    # 创建游标对象,用于执行 SQL 语句
    cursor = connection.cursor()
    print("成功连接到 Oracle 数据库!")
except cx_Oracle.DatabaseError as e:
    error, = e.args
    print(f"数据库连接错误: {error.code} - {error.message}")
    # 在实际应用中,你可能需要在这里重新抛出异常或进行其他错误处理
    exit()
# ... 在这里执行你的操作 ...
# --- 操作完成后关闭连接 ---
# 使用 'with' 语句可以更优雅地管理连接和游标
cursor.close()
connection.close()

创建示例存储过程

为了演示,我们在 Oracle 数据库中创建一个简单的存储过程,这个存储过程接收一个员工的 ID,并返回该员工的姓名和薪水。

-- 在 SQL*Plus, SQL Developer 或其他 Oracle 客户端中执行
CREATE OR REPLACE PROCEDURE get_employee_details (
    p_emp_id IN NUMBER,
    p_emp_name OUT VARCHAR2,
    p_salary OUT NUMBER
)
IS
BEGIN
    SELECT first_name, salary
    INTO p_emp_name, p_salary
    FROM employees
    WHERE employee_id = p_emp_id;
    -- 如果找不到员工,则抛出异常
    IF SQL%NOTFOUND THEN
        RAISE_APPLICATION_ERROR(-20001, '员工未找到');
    END IF;
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        RAISE_APPLICATION_ERROR(-20001, '员工未找到');
END get_employee_details;
/

调用存储过程(基本示例)

我们用 Python 来调用上面创建的 get_employee_details 存储过程,这个存储过程有一个输入参数和两个输出参数。

Python如何调用Oracle存储过程?-图2
(图片来源网络,侵删)

cx_Oracle 使用 cursor.callproc(procname, [arg1, arg2, ...]) 来调用存储过程,输出参数的值会通过传入的变量列表返回。

import cx_Oracle
# --- 连接信息 (假设已经连接成功) ---
username = "your_username"
password = "your_password"
dsn = "hostname:port/service_name"
try:
    connection = cx_Oracle.connect(user=username, password=password, dsn=dsn)
    cursor = connection.cursor()
    # 要查询的员工ID
    employee_id_to_find = 101
    # 准备输出参数的变量
    # 这些变量需要在调用前初始化,特别是对于字符串,最好初始化为 None
    emp_name = None
    emp_salary = 0
    # 调用存储过程
    # callproc 会返回一个元组,包含所有输出参数的值
    # 我们传入的变量列表中的变量也会被修改
    print(f"正在查询员工 ID: {employee_id_to_find}...")
    cursor.callproc("get_employee_details", 
                    [employee_id_to_find, emp_name, emp_salary])
    # 从传入的变量列表中获取结果
    print(f"查询成功!")
    print(f"员工姓名: {emp_name}")
    print(f"员工薪水: {emp_salary}")
except cx_Oracle.DatabaseError as e:
    error, = e.args
    print(f"调用存储过程时发生错误: {error.code} - {error.message}")
finally:
    # 确保游标和连接被关闭
    if 'cursor' in locals():
        cursor.close()
    if 'connection' in locals():
        connection.close()
    print("数据库连接已关闭。")

处理不同类型的参数

cx_Oracle 会自动处理大部分数据类型的转换,对于 IN 参数,直接传入值即可,对于 OUTIN OUT 参数,你需要传入一个变量,该变量在调用后会被赋值。

  • IN 参数:直接传递值。
  • OUT 参数:传递一个变量(如 None0),存储过程会修改它。
  • IN OUT 参数:传递一个变量,存储过程会读取它的初始值,并修改它。

示例:一个包含 IN, OUT, IN OUT 参数的存储过程

CREATE OR REPLACE PROCEDURE process_value (
    p_in_val IN NUMBER,
    p_in_out_val IN OUT NUMBER,
    p_out_val OUT VARCHAR2
)
IS
BEGIN
    -- IN OUT 参数:增加 10
    p_in_out_val := p_in_out_val + 10;
    -- OUT 参数:根据 IN 参数生成字符串
    p_out_val := '输入值是: ' || TO_CHAR(p_in_val);
END;
/

Python 调用

Python如何调用Oracle存储过程?-图3
(图片来源网络,侵删)
import cx_Oracle
# ... (连接代码同上) ...
try:
    cursor = connection.cursor()
    in_val = 5
    in_out_val = 100  # 初始值
    out_val = None    # 初始值
    print(f"调用前: in_val={in_val}, in_out_val={in_out_val}, out_val={out_val}")
    cursor.callproc("process_value", [in_val, in_out_val, out_val])
    print(f"调用后: in_val={in_val}, in_out_val={in_out_val}, out_val={out_val}")
except cx_Oracle.DatabaseError as e:
    # ... (错误处理同上) ...
    pass
finally:
    # ... (关闭连接同上) ...
    pass

输出:

调用前: in_val=5, in_out_val=100, out_val=None
调用后: in_val=5, in_out_val=110, out_val=输入值是: 5

可以看到,in_val 没有改变,in_out_val100 变成了 110out_val 被赋予了新值。


处理游标(Ref Cursor) - 返回结果集

当存储过程需要返回一个多行的结果集时,最佳实践是使用 REF CURSOR(游标),这在 Oracle 中是标准做法。

创建一个 REF CURSOR 类型

-- 在包中定义类型是最佳实践
CREATE OR REPLACE PACKAGE emp_data_pkg AS
    TYPE emp_cursor IS REF CURSOR;
END emp_data_pkg;
/

创建一个返回 REF CURSOR 的存储过程

CREATE OR REPLACE PROCEDURE get_employees_by_dept (
    p_dept_id IN NUMBER,
    p_emp_cursor OUT emp_data_pkg.emp_cursor
)
IS
BEGIN
    OPEN p_emp_cursor FOR
        SELECT employee_id, first_name, last_name, salary
        FROM employees
        WHERE department_id = p_dept_id
        ORDER BY last_name;
END get_employees_by_dept;
/

在 Python 中调用并处理游标

在 Python 中,REF CURSOR 会被当作一个正常的游标对象返回。

import cx_Oracle
# ... (连接代码同上) ...
try:
    cursor = connection.cursor()
    department_id = 50  # 假设部门 50 存在
    # 调用存储过程
    # 注意:我们只传递了一个输入参数,输出参数 (p_emp_cursor) 会由 callproc 返回
    result_cursor = cursor.callproc("get_employees_by_dept", [department_id])
    # callproc 返回的第二个元素是 REF CURSOR 对象
    ref_cursor = result_cursor[1]
    print(f"部门 {department_id} 的员工列表:")
    print("---------------------------------")
    # 像操作普通游标一样遍历结果
    for row in ref_cursor:
        print(f"ID: {row[0]}, 姓名: {row[1]} {row[2]}, 薪水: {row[3]}")
    # 关闭 REF CURSOR
    ref_cursor.close()
except cx_Oracle.DatabaseError as e:
    # ... (错误处理同上) ...
    pass
finally:
    if 'cursor' in locals():
        cursor.close()
    if 'connection' in locals():
        connection.close()
    print("\n数据库连接已关闭。")

最佳实践和错误处理

  • 使用 with 语句cx_OracleConnectionCursor 对象都支持上下文管理器协议(with 语句),可以自动关闭资源,即使发生异常。
  • 参数化查询:虽然存储过程本身是预编译的,但在调用时传递参数也要注意,避免 SQL 注入风险(虽然风险比直接 SQL 小,但良好习惯是必须的)。
  • 显式关闭游标:特别是 REF CURSOR,用完后一定要显式关闭,以释放数据库资源。
  • 批量操作:如果需要调用存储过程处理大量数据,考虑使用 cursor.arraysizecursor.executemany(如果存储过程支持批量输入)来提高性能。

使用 with 语句的改进版

import cx_Oracle
# --- 连接信息 ---
username = "your_username"
password = "your_password"
dsn = "hostname:port/service_name"
try:
    # with 语句会自动处理连接的关闭
    with cx_Oracle.connect(user=username, password=password, dsn=dsn) as connection:
        # with 语句会自动处理游标的关闭
        with connection.cursor() as cursor:
            employee_id_to_find = 101
            emp_name = None
            emp_salary = 0
            cursor.callproc("get_employee_details", 
                            [employee_id_to_find, emp_name, emp_salary])
            print(f"员工姓名: {emp_name}")
            print(f"员工薪水: {emp_salary}")
except cx_Oracle.DatabaseError as e:
    error, = e.args
    print(f"发生数据库错误: {error.code} - {error.message}")

这个版本更简洁、更安全,是推荐的写法。

任务 Python 代码 说明
安装库 pip install cx_Oracle 需要 Oracle Instant Client 支持。
建立连接 cx_Oracle.connect(user, password, dsn) 获取 Connection 对象。
创建游标 connection.cursor() 获取 Cursor 对象,用于执行。
调用无返回值过程 cursor.callproc("proc_name", [arg1, ...]) 直接调用。
调用带 OUT 参数过程 cursor.callproc("proc_name", [in_val, out_var]) out_var 在调用后会被赋值。
调用返回游标过程 result = cursor.callproc("proc_name", [in_val]); ref_cursor = result[1] ref_cursor 是一个可迭代的游标对象,需手动关闭。
关闭资源 cursor.close(); connection.close() 或使用 with 语句自动管理。

希望这份详细的指南能帮助你顺利地在 Python 中调用 Oracle 存储过程!

分享:
扫描分享到社交APP
上一篇
下一篇