pythonoracle-databaseplsqldynamic-sqlcx-oracle

How to return columns from dynamic SQL in Python console


I'm trying to make a CRUD application that uses Oracle procedures for the operations. For the READ component, I wrote a procedure that returns one or more entire rows depending on the specified attributes. It works correctly on Oracle SQL Developer, but I can't get the data to display once I'm in Python.

This is the READ portion of the Python app:

def verEmpleado():
    opcionCorrecta = False
    while(not opcionCorrecta):
        print('================= CONSULTA =================')
        print('[1] Employee ID')
        print('[2] Employee name')
        print('[3] Job')
        print('[4] Manager')
        print('[5] Hire date')
        print('[6] Salary')
        print('[7] Commission')
        print('[8] Department ID')
        print('============================================')

        command = int(input('Seleccione la columna: '))
        match command:
            case 1:
                p_filter_att = 'empno'
                opcionCorrecta = True
            case 2:
                p_filter_att = 'ename'
                opcionCorrecta = True
            case 3:
                p_filter_att = 'job'
                opcionCorrecta = True
            case 4:
                p_filter_att = 'mgr'
                opcionCorrecta = True
            case 5:
                p_filter_att = 'hiredate'
                opcionCorrecta = True
            case 6:
                p_filter_att = 'sal'
                opcionCorrecta = True
            case 7:
                p_filter_att = 'comm'
                opcionCorrecta = True
            case 8:
                p_filter_att = 'deptno'
                opcionCorrecta = True
            case _:
                print('Comando incorrecto, intente de nuevo...\n')


    p_value_att = input('Ingrese el valor de la columna a seleccionar: ')

    data = [p_filter_att, p_value_att]
    return data

#CONNECT TO DB
def connVerEmpleado(data):
        try:
            conn = cx_Oracle.connect('HR/hr@localhost:1521/xepdb1')
        except Exception as err:
            print('Excepción al crear la conexión:', err)
        else:
            try:
                cursor = conn.cursor()
                cursor.callproc('query_emp', data)

                result = cursor.fetchall()
                print(result)
                cursor.close()

            except Exception as err:
                print('Error al conectar:', err)

This is the procedure

create or replace PROCEDURE query_emp(p_filter_att IN VARCHAR2, p_value_att IN VARCHAR2)
IS
    sql_qry              VARCHAR2(1000);
    TYPE bc_v_empno      IS TABLE OF NUMBER(4);
    v_empno              bc_v_empno;
    TYPE bc_v_ename      IS TABLE OF VARCHAR2(10);
    v_ename              bc_v_ename;
    TYPE bc_v_job        IS TABLE OF VARCHAR2(9);
    v_job                bc_v_job;
    TYPE bc_v_mgr        IS TABLE OF NUMBER(4);
    v_mgr                bc_v_mgr;
    TYPE bc_v_hiredate   IS TABLE OF DATE;
    v_hiredate           bc_v_hiredate;
    TYPE bc_v_sal        IS TABLE OF NUMBER(7,2);
    v_sal                bc_v_sal;
    TYPE bc_v_comm       IS TABLE OF NUMBER(7,2);
    v_comm               bc_v_comm;
    TYPE bc_v_deptno     IS TABLE OF NUMBER(2);
    v_deptno             bc_v_deptno;
BEGIN
    sql_qry := 'SELECT * FROM emp WHERE ' || p_filter_att || ' = UPPER(:p_value_att)';
    EXECUTE IMMEDIATE sql_qry BULK COLLECT INTO v_empno, v_ename, v_job, v_mgr, v_hiredate, v_sal, v_comm, v_deptno
    USING p_value_att;

    FOR i IN 1..v_empno.COUNT
        LOOP
            dbms_output.put_line('ID: ' || v_empno(i) || ' | NAME: ' || v_ename(i) || ' | JOB: ' || v_job(i) || ' | MANAGER: ' || v_mgr(i) || ' | HIRE DATE: ' || v_hiredate(i) || ' | SALARY: ' || v_sal(i) || ' | COMMISSION: ' || v_comm(i) || ' | DEPARTMENT: ' || v_deptno(i));
        END LOOP;

END query_emp;
/

When I use fetchall() it returns the error not a query, other methods don't return anything at all.


Solution

  • There's an example of using DBMS_OUTPUT in samples/dbms_output.py. It is also discussed in the documentation Using DBMS_OUTPUT.

    However, DBMS_OUTPUT is not the best choice. Instead, just return a REF CURSOR similar to samples/ref_cursor.py. The documentation for this is at REF CURSOR Bind Variables.

    From the doc, if you create a PL/SQL procedure like:

    CREATE OR REPLACE PROCEDURE find_employees (
        p_query IN VARCHAR2,
        p_results OUT SYS_REFCURSOR
    ) AS
    BEGIN
        OPEN p_results FOR
            SELECT employee_id, first_name, last_name
            FROM employees
            WHERE UPPER(first_name || ' ' || last_name || ' ' || email)
                LIKE '%' || UPPER(p_query) || '%';
    END;
    /
    

    then you can call it in Python like:

    ref_cursor = connection.cursor()
    cursor.callproc("find_employees", ['Smith', ref_cursor])
    for row in ref_cursor:
        print(row)