2019独角兽企业重金招聘Python工程师标准>>>
Using Python with Oracle
This page discusses using Python with Oracle.
The page is based on the cx_oracle Python extension module. It was developed on a VM running Oracle Enterprise Linux 6U4 runnng Oracle 11.2.0.4 and Python 2.6.6.
Installation and Configuration
Assuming Python 2.6 has already been installed, the first job is to install the cx_oracle module. At the time of writing the current version was still 5.1.3 for which binaries are available for both Windows and Linux. For other platforms the sources can be downloaded and compiled.
I downloaded version 5.1.2 from http://sourceforge.net/projects/cx-oracle/files where the following RPM is available. This was the closest match to my environment
cx_Oracle-5.1.2-11g-py26-1.x86_64.rpm
Installation is standard. As the root user:
rpm -ivh cx_Oracle-5.1.2-11g-py26-1.x86_64.rpm
My VM already contained Oracle 11.2.0.4 Enterprise Edition with a database called "TARGET". I set the following environment variables in .bash_profile
export ORACLE_HOME=/u01/app/oracle/product/11.2.0/db_1 export LD_LIBRARY_PATH=$ORACLE_HOME/lib export ORACLE_SID=TARGET
I rarely need to set $LD_LIBRARY_PATH, but in this case it was necessary to avoid raising the following error when the cx_Oracle is imported:
ImportError: libclntsh.so.11.1: cannot open shared object file: No such file or directory
Preparation
cx_oracle supports various connection methods. If possible I like to use a TNS alias with the Oracle client as the alias can be configured to use connection load balancing, failover etc. For this post my VM was called vm16, so the entry in $ORACLE_HOME/network/admin/tnsnames.ora was:
TARGET= (DESCRIPTION=(ADDRESS=(HOST=vm16)(PROTOCOL=TCP)(PORT=1521))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=TARGET)) )
Example Script
The following is a complete script that connects to the database, executes a simple SELECT aggregate statement and returns the result
#!/usr/bin/python # Example of fetchoneimport sys import cx_Oracledef printf (format,*args):sys.stdout.write (format % args)def printException (exception):error, = exception.argsprintf ("Error code = %s\n",error.code);printf ("Error message = %s\n",error.message);username = 'scott' password = 'tiger' databaseName = "TARGET"try:connection = cx_Oracle.connect (username,password,databaseName) except cx_Oracle.DatabaseError, exception:printf ('Failed to connect to %s\n',databaseName)printException (exception)exit (1)cursor = connection.cursor ()try:cursor.execute ('SELECT COUNT(*) FROM emp') except cx_Oracle.DatabaseError, exception:printf ('Failed to select from EMP\n')printException (exception)exit (1)count = cursor.fetchone ()[0] printf ('Count = %d\n',count)cursor.close ()connection.close ()exit (0)
The above code is discussed in the following sections
printf statement
Python does not include a printf statement. However the following library function provides similar functionality.
import sysdef printf (format,*args):sys.stdout.write (format % args)
This can be called using the familiar C syntax. For example:
printf ("Str %s Int %d\n",s,i)
printException function
I have also created a function to print details of any exceptions raised when accessing the database:
def printException (exception):error, = exception.argsprintf ("Error code = %s\n",error.code);printf ("Error message = %s\n",error.message);
Connection
The connection requires a username, password and TNS alias:
username = 'scott' password = 'tiger' databaseName = "TARGET"
Experience has demonstrated that it is good practice to execute cx_oracle methods that access the database within a try..except structure in order to catch and report any exceptions that they might throw. The printException function prints more detail
For the connection I have used the following code:
try:connection = cx_Oracle.connect (username,password,databaseName) except cx_Oracle.DatabaseError, exception:printf ('Failed to connect to %s\n',databaseName)printException (exception)exit (1)
The connection should be closed when no longer required using:
connection.close ()
It is a good idea to develop the exception handling code at the outset, then most can be reused for other cx_oracle method calls. Remember to rollback any uncommitted transactions, close cursors and close any connections when the exception is raised.
Alternatively consider using a finally clause in the try statement. The finally clause of a try statement is executed irrespective of any exceptions that may be raised.
Cursor Management
In the previous section we created a connection. We now need to create a cursor as follows:
cursor = connection.cursor ()
The cursor should be closed when no longer required using:
cursor.close ()
The Cursor class has a number of powerful methods that allow statements and PL/SQL blocks to be prepared and executed
Execute method
The cursor.execute method is the easiest way to execute simple statements including DDL.
The simple SELECT statement is an aggregate returning a single column in a single row:
try:cursor.execute ('SELECT COUNT(*) FROM emp') except cx_Oracle.DatabaseError, exception:printf ('Failed to select from EMP\n')printException (exception)exit (1)
We can check the result as follows:
count = cursor.fetchone ()[0] printf ('Count = %d\n',count)
In this case the fetchone method is more appropriate when we are expecting a single row.
Alternatively we could have used the Cursor.fetchall method which fetches all rows returned by the statement:
count = cursor.fetchall ()[0][0] printf ("Count = %d\n",count)
SELECT Statements
This section examines more complex examples of the SELECT statement. The next example returns multiple rows and columns:
sql = """\\SELECT empno,ename,sal FROM empWHERE sal > 2000ORDER BY sal DESC"""try:cursor.execute (sql) except cx_Oracle.DatabaseError, exception:printf ('Failed to select from EMP\n')printException (exception)result = cursor.fetchall () for row in result:printf (" %s;%s;%d\n",row[0],row[1],row[2])
I have separated the SQL statement from the call to cursor.execute to improve readability. The triple quotes at the start and end of the SQL statement allow it to be written across multiple lines. This is particularly important for longer SQL statements where indentation is necessary. The backslash after the opening triple quote suppresses a newline character from being appended before the first line. A newline character will be appended to the remaining lines until the closing triple quote.
The results are returned using the Cursor.fetchall�method which returns a 2-dimensional array. All results are returned by the single call to fetchall and are stored in process memory. Obviously this works when there are only 14 employees - it would not be appropriate for larger result sets. In this case I am iterating through the results, printing them one line at a time. This works but is not particularly readable.
An alternative version of the code to handle the call to cursor.fetchall() follows:
for empno, ename, sal in cursor.fetchall ():printf (" %s;%s;%d\n",empno, ename, sal)
This version is more readable and requires less process memory
Bind Variables
So far our statements have only used literal values. These are not particularly scalable in high volume workloads so most applications use bind variables to reduce the parsing overhead.
The following example shows a statement with a couple of bind variables in the predicates
sql = """\\SELECT empno,ename,sal FROM empWHERE job = :jobAND deptno = :deptORDER BY sal DESC"""try:cursor.execute (sql,job = 'SALESMAN',dept = 30) except cx_Oracle.DatabaseError, exception:printf ('Failed to select from EMP\n')printException (exception)for empno, ename, sal in cursor.fetchall ():printf (" %s;%s;%d\n",empno, ename, sal)
In the above example the call to cursor.execute includes the sql statement and values for the bind variables 'job' and 'dept'. Note from the second bind variable that the bind variable name does not have to be the same as the column value.
Prepare Statements
Whilst bind variables reduce parsing to a limited extent, they cannot eliminate it completely. To minimize parsing it is best to assign a prepared statement to a dedicated cursor. It is not then necessary to parse the statement again until the cursor is closed.
In the following code, the bind variable example from the previous section has been modified to use a separate prepare call.
sql = """\\SELECT empno,ename,sal FROM empWHERE job = :jobAND deptno = :deptORDER BY sal DESC"""try:cursor.prepare (sql) except cx_Oracle.DatabaseError, exception:printf ('Failed to prepare cursor')printException (exception)exit (1)try:cursor.execute (None,job = 'SALESMAN',dept = 30) except cx_Oracle.DatabaseError, exception:printf ('Failed to execute cursor')printException (exception)exit (1)for empno, ename, sal in cursor.fetchall ():printf (" %s;%s;%d\n",empno, ename, sal)
In the above example the statement is prepared using the cursor.prepare call and is then executed by the cursor.execute call.
Note that the first parameter of the cursor.execute call is "None". This specifies that the cursor should use the existing prepared statement instead of parsing a new SQL statement.
Alternatively the cursor.execute statement can take cursor.statement as the first parameter. For example:
try:cursor.execute (cursor.statement,job = 'SALESMAN',dept = 30) except cx_Oracle.DatabaseError, exception:printf ('Failed to execute cursor')printException (exception)exit (1)
Cursor.statement returns the last statement parsed by the cursor.
INSERT Statements
This section discusses INSERT statements. The first example uses bind variables to insert a new row into the DEPT table.
sql = "INSERT INTO dept (deptno, dname, loc) VALUES (:deptno, :dname, :loc)"try:cursor.execute (sql,deptno=50, dname='MARKETING', loc='LONDON') except cx_Oracle.DatabaseError, exception:printf ('Failed to insert row')printException (exception)exit (1)cursor.close ()connection.commit ()
In this example the values are supplied as parameters for the cursor.execute() method.
Note the commit statement at the end of the transaction
The following example uses a dictionary instead of the parameter list:
sql = "INSERT INTO dept (deptno, dname, loc) VALUES (:deptno, :dname, :loc)"try:cursor.execute (sql,{ 'deptno':50, 'dname': 'MARKETING', 'loc': 'LONDON'}) except cx_Oracle.DatabaseError, exception:printf ('Failed to insert row')printException (exception)exit (1)
In the above example a dictionary is passed to the cursor.execute () method. The dictionary contains name-value pairs for the column values to be inserted into the new row.
The next example demonstrates the use of a prepared INSERT statement:
sql = "INSERT INTO dept (deptno, dname, loc) VALUES (:deptno, :dname, :loc)"try:cursor.prepare (sql) except cx_Oracle.DatabaseError, exception:printf ('Failed to prepare cursor')printException (exception)exit (1)try:cursor.execute (None,deptno=50, dname='MARKETING', loc='LONDON') except cx_Oracle.DatabaseError, exception:printf ('Failed to insert row')printException (exception)exit (1)
As with the SELECT statement example, the cursor.execute method can use cursor.statement() instead of None, as shown below
cursor.execute (cursor.statement,deptno=50, dname='MARKETING', loc='LONDON')
UPDATE Statement
This section discusses UPDATE statements. The first example uses bind variables.
sql = "UPDATE dept SET loc = :loc WHERE deptno = :deptno"try:cursor.execute (sql,deptno = 50,loc = 'LONDON') except cx_Oracle.DatabaseError, exception:printf ('Failed to execute cursor\n')printException (exception)exit (1)
In the above example, bind variable values are specified in the arguments of the call to cursor.execute() The next example uses a prepare statement
sql = "UPDATE dept SET loc = :loc WHERE deptno = :deptno"try:cursor.prepare (sql) except cx_Oracle.DatabaseError, exception:printf ('Failed to prepare cursor\n')printException (exception)exit (1)try:cursor.execute (None,deptno = 50,loc = 'LONDON') except cx_Oracle.DatabaseError, exception:printf ('Failed to execute cursor\n')printException (exception)exit (1)
As with the INSERT statement example, the Cursor.execute method can use Cursor.statement instead of None, as shown below:
cursor.execute (cursor.statement,deptno = 50,loc = 'LONDON')
DELETE Statement
This section discusses DELETE statements. The following is an example of a delete that uses a bind variable:
sql = "DELETE FROM dept WHERE deptno = :deptno"try:cursor.prepare (sql) except:printf ('Failed to prepare cursor\n')exit (1)try:cursor.execute (None,deptno = 50) except cx_Oracle.DatabaseError, exception:printf ('Failed to execute cursor\n')printException (exception)exit (1)
As with previous examples, Cursor.execute can use Cursor.statement instead of None, as shown below:
cursor.execute (cursor.statement,deptno = 50)
Array Operations
So far we have concentrated on statements which only make a single visit to the database for each execution. However, if we want our application to scale, at some stage we will need to consider batch operations to reduce the number of round trips from the client to the server. In this section we will examine batching techniques for SELECT statements and also INSERT, UPDATE and DELETE statements.
SELECT statements
For SELECT statements we can control the number of statements retrieved for each fetch using Cursor.arraysize.
The following statement sets the array size to 4 and then queries the EMP table:
cursor = connection.cursor ()sql = """\\SELECT empno, ename, jobFROM empORDER BY ename"""# set array size to 4 cursor.arraysize = 4try:cursor.execute (sql) except cx_Oracle.DatabaseError, exception:printf ('Failed to select from EMP\n')printException (exception)exit (1)while True:rows = cursor.fetchmany()if rows == []:break;printf ("Fetched %d rows\n", len(rows))for empno,ename,job in rows:printf (" %s;%s;%s\n",empno,ename,job)
The above example includes the creation of the cursor using connection.cursor() as the cursor must exist before the array size can be modified.
In this example we are using the Cursor.fetchmany method to fetch the rows. Every fetch with the exception of the last one will return four rows to the application.
The output of the above script is:
Fetched 4 rows7876;ADAMS;CLERK7499;ALLEN;SALESMAN7698;BLAKE;MANAGER7782;CLARK;MANAGER Fetched 4 rows7902;FORD;ANALYST7900;JAMES;CLERK7566;JONES;MANAGER7839;KING;PRESIDENT Fetched 4 rows7654;MARTIN;SALESMAN7934;MILLER;CLERK7788;SCOTT;ANALYST7369;SMITH;CLERK Fetched 2 rows7844;TURNER;SALESMAN7521;WARD;SALESMAN
There are 14 rows in the EMP table.
The advantage of specifying an array size is that the client has more control over memory usage. Only the rows retrieved by one call to fetchmany, in this case four, will be stored in memory on the client. If we had used Cursor.fetchall instead of fetchmany, then all rows would be returned and would need to be stored in client memory.
The impact on the database server depends on the nature of the query. If the query was a set operation then all result rows would need to be stored in local memory on the database server until they had been fetched by the application or the cursor was closed. If the query was a row operation then it may only be necessary to store sufficient results to satisfy the latest fetch request.
As the above query includes an ORDER by clause, it is almost certainly executing as a set operation as there is not index on the ENAME column.
Setting the array size controls the number of network messages exchanged between the client and the server.
The DB API recommends that the default array size is 1. However, cx_oracle ignores this recommendation and sets the default to 50. We can check this using:
printf ("Array Size = %d\n",cursor.arraysize);
If cursor.arraysize is not set, then the query will fetch all rows. Output will be:
Fetched 14 rows7876;ADAMS;CLERK7499;ALLEN;SALESMAN7698;BLAKE;MANAGER7782;CLARK;MANAGER7902;FORD;ANALYST7900;JAMES;CLERK7566;JONES;MANAGER7839;KING;PRESIDENT7654;MARTIN;SALESMAN7934;MILLER;CLERK7788;SCOTT;ANALYST7369;SMITH;CLERK7844;TURNER;SALESMAN7521;WARD;SALESMAN
If cursor.arraysize is set to 1, then the query will fetch one row at a time. Output will be:
Fetched 1 rows7876;ADAMS;CLERK Fetched 1 rows7499;ALLEN;SALESMAN Fetched 1 rows7698;BLAKE;MANAGER Fetched 1 rows7782;CLARK;MANAGER Fetched 1 rows7902;FORD;ANALYST Fetched 1 rows7900;JAMES;CLERK Fetched 1 rows7566;JONES;MANAGER Fetched 1 rows7839;KING;PRESIDENT Fetched 1 rows7654;MARTIN;SALESMAN Fetched 1 rows7934;MILLER;CLERK Fetched 1 rows7788;SCOTT;ANALYST Fetched 1 rows7369;SMITH;CLERK Fetched 1 rows7844;TURNER;SALESMAN Fetched 1 rows7521;WARD;SALESMAN
Array INSERT Statement
The previous example investigated use of array size to manage fetches from SELECT statements. In the following sections we will discuss use of arrays with INSERT, UPDATE and DELETE statements. In this section we will cover array INSERT statements.
The following is an example of an array INSERT of three rows into the DEPT table:
sql = "INSERT INTO dept (deptno, dname, loc) VALUES (:deptno, :dname, :loc)"try:cursor.prepare (sql) except cx_Oracle.DatabaseError, exception:printf ('Failed to prepare cursor')printException (exception)exit (1)array=[] array.append ((50,'MARKETING','LONDON')) array.append ((60,'HR','PARIS')) array.append ((70,'DEVELOPMENT','MADRID'))try:cursor.executemany (None,array) except cx_Oracle.DatabaseError, exception:printf ('Failed to insert rows')printException (exception)exit (1)
Note that we are using the Cursor.executemany () method instead of Cursor.execute ()
If we enable trace for the above statement we can verify that an array insert was performed on the database server:
PARSING IN CURSOR #140238572911728 len=68 dep=0 uid=83 oct=2 lid=83 tim=1419870619434242 hv=3863288051 ad='91cf3288' sqlid='9fxqrt3m4a67m' INSERT INTO dept (deptno, dname, loc) VALUES (:deptno, :dname, :loc) END OF STMT PARSE #140238572911728:c=0,e=267,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=1,plh=0,tim=1419870619434241 EXEC #140238572911728:c=1000,e=516,p=0,cr=1,cu=7,mis=1,r=3,dep=0,og=1,plh=0,tim=1419870619434770 STAT #140238572911728 id=1 cnt=0 pid=0 pos=1 obj=0 op='LOAD TABLE CONVENTIONAL (cr=1 pr=0 pw=0 time=258 us)' WAIT #140238572911728: nam='SQL*Net message to client' ela= 17 driver id=1413697536 #bytes=1 p3=0 obj#=-1 tim=1419870619435236 WAIT #140238572911728: nam='SQL*Net message from client' ela= 174 driver id=1413697536 #bytes=1 p3=0 obj#=-1 tim=1419870619435451 CLOSE #140238572911728:c=0,e=5,dep=0,type=0,tim=1419870619435486 XCTEND rlbk=0, rd_only=0, tim=1419870619435523
In the above, the EXEC line includes r=3 which indicates that three rows were inserted.
We could also call executemany using cursor.statement as the first parameter:
cursor.executemany (cursor.statement,array)
Array UPDATE Statement
We can also use arrays with UPDATE statements. In the following example, we are using a dictionary to pass the bind variable values to the cursor.executemany method:
sql = "UPDATE dept SET loc = :loc WHERE deptno = :dept"try:cursor.prepare (sql) except:printf ('Failed to prepare cursor')exit (1)array=[] array.append ({'dept':50,'loc':'SHANGHAI'}) array.append ({'dept':60,'loc':'MOSCOW'}) array.append ({'dept':70,'loc':'MUMBAI'})try:cursor.executemany(None,array) except cx_Oracle.DatabaseError, exception:printf ('Failed to execute cursor')printException (exception)exit (1)
Again the trace shows that all three rows were updated using a single execution of the UPDATE statement:
PARSING IN CURSOR #140267303715936 len=47 dep=0 uid=83 oct=6 lid=83 tim=1419873983403707 hv=2665682896 ad='8bda6038' sqlid='gja8j7agf65yh' UPDATE dept SET loc = :loc WHERE deptno = :dept END OF STMT PARSE #140267303715936:c=0,e=450,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=1,plh=267198286,tim=1419873983403669 EXEC #140267303715936:c=0,e=317,p=0,cr=3,cu=5,mis=0,r=3,dep=0,og=1,plh=267198286,tim=1419873983404140 STAT #140267303715936 id=1 cnt=0 pid=0 pos=1 obj=0 op='UPDATE DEPT (cr=3 pr=0 pw=0 time=212 us)' STAT #140267303715936 id=2 cnt=3 pid=1 pos=1 obj=87107 op='INDEX UNIQUE SCAN PK_DEPT (cr=3 pr=0 pw=0 time=70 us cost=1 size=21 card=1)' WAIT #140267303715936: nam='SQL*Net message to client' ela= 1 driver id=1413697536 #bytes=1 p3=0 obj#=-1 tim=1419873983404265 WAIT #140267303715936: nam='SQL*Net message from client' ela= 133 driver id=1413697536 #bytes=1 p3=0 obj#=-1 tim=1419873983404418 CLOSE #140267303715936:c=0,e=4,dep=0,type=0,tim=1419873983404447 XCTEND rlbk=0, rd_only=0, tim=1419873983404492
As with the previous example, the EXEC line includes r=3 which indicates that three rows were updated.
Array DELETE Statement
Arrays can also be used with DELETE statements. As with UPDATE statements this example uses a dictionary to specify bind variables
sql = "DELETE FROM dept WHERE deptno = :dept"try:cursor.prepare (sql) except:printf ('Failed to prepare cursor')exit (1)array=[] array.append ({'dept':50}) array.append ({'dept':60}) array.append ({'dept':70})try:cursor.executemany(None,array) except cx_Oracle.DatabaseError, exception:printf ('Failed to execute cursor')printException (exception)exit (1)
In the above example, the array is used to specify the deptno for each of the three rows to be deleted
As in the previous DML examples, the EXEC line in the trace file includes r=3 indicating that three rows were deleted
PARSING IN CURSOR #140404237827184 len=37 dep=0 uid=83 oct=7 lid=83 tim=1419881960556726 hv=1258566750 ad='8be19350' sqlid='3mpppmd5h8d2y' DELETE FROM dept WHERE deptno = :dept END OF STMT PARSE #140404237827184:c=0,e=234,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=1,plh=0,tim=1419881960556725 EXEC #140404237827184:c=11999,e=12902,p=0,cr=49,cu=18,mis=1,r=3,dep=0,og=1,plh=2529563076,tim=1419881960569679 STAT #140404237827184 id=1 cnt=0 pid=0 pos=1 obj=0 op='DELETE DEPT (cr=49 pr=0 pw=0 time=12065 us)' STAT #140404237827184 id=2 cnt=3 pid=1 pos=1 obj=87107 op='INDEX UNIQUE SCAN PK_DEPT (cr=3 pr=0 pw=0 time=58 us cost=1 size=13 card=1)' WAIT #140404237827184: nam='SQL*Net message to client' ela= 15 driver id=1413697536 #bytes=1 p3=0 obj#=-1 tim=1419881960570446 WAIT #140404237827184: nam='SQL*Net message from client' ela= 200 driver id=1413697536 #bytes=1 p3=0 obj#=-1 tim=1419881960570681 CLOSE #140404237827184:c=0,e=22,dep=0,type=0,tim=1419881960570758 XCTEND rlbk=0, rd_only=0, tim=1419881960570869
Calling PL/SQL Subroutines
PL/SQL procedures and functions can be called using the Cursor.callproc and Cursor.callfunc methods
PL/SQL Procedures
Assume we have created the following PL/SQL procedure
CREATE OR REPLACE PROCEDURE proc1 (p_deptno NUMBER,p_dname VARCHAR2,p_loc VARCHAR2 ) IS BEGININSERT INTO dept (deptno, dname, loc) VALUES (p_deptno, p_dname, p_loc); END; /
We can call the procedure using the following code:
deptno = 50 dname = 'MARKETING' loc = 'LONDON'try:cursor.callproc('PROC1',(deptno,dname,loc)) except cx_Oracle.DatabaseError, exception:printf ('Failed to call procedure')printException (exception)exit (1)
The cursor.callproc method specifies the name of the procedure and also a list of bind variable parameters.
Note that in this example the bind variables are input parameters.
PL/SQL Functions
Assume we have created the following PL/SQL function which returns the number of employees with a specific job in a specific department:
CREATE OR REPLACE FUNCTION func1 (p_job VARCHAR2,p_deptno NUMBER ) RETURN NUMBER ISl_result NUMBER; BEGINSELECT COUNT(*) INTO l_result FROM emp WHERE job = p_job AND deptno = p_deptno;RETURN l_result; END; /
To call the function we use the cursor.callfunc method
job = 'SALESMAN' deptno = 30result = cursor.var (cx_Oracle.NUMBER)try:cursor.callfunc('FUNC1',result,(job,deptno)) except cx_Oracle.DatabaseError, exception:printf ('Failed to call function')printException (exception)exit (1)printf ("Result = %d\n",result.getvalue ())
The above code creates a result variable using the cursor.var method. It then calls the PL/SQL function FUNC1 passing the job and deptno as bind variables. The result is returned in a Variable object and is then converted to a number using the Variable.getvalue method.
Procedures with OUT parameters
If a PL/SQL subroutine only returns one value it will typically be written as a function. However if the PL/SQL subroutine needs to return more than one value it will usually return them as OUT parameters.
The following procedure returns the employee name and job for a specific employee number.
CREATE OR REPLACE PROCEDURE proc2 (p_empno NUMBER,p_ename OUT VARCHAR2,p_job OUT VARCHAR2 ) IS BEGINSELECT ename,job INTO p_ename,p_job FROM emp WHERE empno = p_empno; END; /
The procedure has one input parameter (p_empno) and two output parameters (p_ename and p_job)
The following code calls PROC2:
empno = 7839 ename = cursor.var (cx_Oracle.STRING) job = cursor.var (cx_Oracle.STRING)try:cursor.callproc('PROC2',(empno,ename,job)) except cx_Oracle.DatabaseError, exception:printf ('Failed to call procedure\n')printException (exception)exit (1)printf ("Ename = %s\n",ename.getvalue ()) printf ("Job = %s\n",job.getvalue ())
The above code creates an input parameter for empno and two output parameters to received the results for ename and job. The procedure is called using the Cursor.callproc method specifying the name of the procedure (PROC2) and a list of parameters.
The OUT parameters are returned as Variable objects. The Variable.getvalue method must be called to extract the value from the object.
Procedures with IN-OUT parameters
It is debatable whether IN-OUT parameters for simple data types are ever justified and it is therefore difficult to devise a credible example for the scott/tiger database. However, it is often necessary to call subroutines developed by others in which case they may follow different standards.
The following PL/SQL procedure adds the specified values for SAL and COMM to the existing values in the table for the specified EMPNO. The PL/SQL procedure returns the new values in the same parameters.
CREATE OR REPLACE PROCEDURE proc3 (p_empno NUMBER,p_sal IN OUT NUMBER,p_comm IN OUT NUMBER ) IS BEGINUPDATE emp SET sal = sal + p_sal, comm = comm + p_commWHERE empno = p_empnoRETURNING sal, comm INTO p_sal,p_comm; END; /
The following code calls the PROC3 procedure for EMPNO 7499
empno = 7499sal = cursor.var (cx_Oracle.NUMBER) sal.setvalue (0,400)comm = cursor.var (cx_Oracle.NUMBER) comm.setvalue (0,200)try:cursor.callproc('PROC3',(empno,sal,comm)) except cx_Oracle.DatabaseError, exception:printf ('Failed to call procedure')printException (exception)exit (1)printf ("sal = %d\n",sal.getvalue ()) printf ("comm = %d\n",comm.getvalue ())
The above code sets a value for EMPNO which is an input parameter. It then creates Variable objects for the two output parameters and sets initial values for these parameters using the Variable.setvalue method. The first parameter in the Cursor.var call will normally be 0.
The procedure then calls PROC3 using the Cursor.callproc method. When the procedure returns, the values of the SAL and COMM parameters are extracted from the Variable objects using the Variable.getvalue method