Using Python With Oracle Database 11g

<Do not delete this text because it is a placeholder for the generated list of "main" topics when run in a browser>

Purpose

This tutorial shows you how to use Python with Oracle Database 11g.

Time to Complete

Approximately 1 hour

Overview

Python is a popular general purpose dynamic scripting language. With the rise of Frameworks, Python is also becoming common for Web application development. If you want to use Python and an Oracle database, this tutorial helps you get started by giving examples. If you are new to Python review the Appendix: Python Primer to gain an understanding of the language.

Prerequisites


.

Install Oracle Database 11.2

.

Install the Python 2.x and the cx_Oracle 5.0 extension

.

Extract these files to your $HOME location.

Connecting to Oracle

To create a connection to Oracle, perform the following steps.

.

Open a terminal window and review the code contained in $HOME/connect.py

The cx_Oracle module is imported to provide the API for accessing the Oracle database. Many inbuilt and third party modules can be included in this way in Python scripts.

The connect() method is passed the username "pythonhol", the password "welcome" and the connection string. In this case, Oracle's Easy Connect connection string syntax is used. It consists of the IP of your machine and the DB service name "orcl".

The close() method closes the connection. Any connections not explicitly closed will be automatically released when the script ends.

In a command line terminal run:

python connect.py

If the connection succeeds, the version number is printed: An exception is thrown if the connection fails.

 

.

Indentation is used in Python to indicate the code structure. There are no statement terminators unlike many other languages and there are no begin/end keywords or braces to indicate blocks of code.

Open connect.py in an editor. Indent the print statement by two spaces ad save the file:

import cx_Oracle
con = cx_Oracle.connect('pythonhol/welcome@localhost/orcl')
  print con.version
con.close()

Run the script:

python connect.py

The number of spaces or tabs used for indentation is not important as long as it is used in a consistent way in each block. Here the Python interpreter is not expecting a new code block level after the connect() call so it warns about the different indentation.

In other cases such as in 'if' and loop blocks (shown later), take care that all statements in each block are equally indented.

If you are copying and pasting from this tutorial, check the pasted indentation is correct before running each example.

 

.

Python treats everything as an object. The "con" object has a "version" attribute, which is a string.

Change the script to use a "split" string method too:

import cx_Oracle
con = cx_Oracle.connect('pythonhol/welcome@localhost/orcl')
ver = con.version.split(".")
print ver
con.close()

Re-run the script in the command line terminal:

python connect.py

The output is a "list", which is a Python's name for an array.

 

.

Python Lists can be accessed via indexes.

Change connect.py to:

import cx_Oracle
con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl')
ver = con.version.split(".")
print ver
print ver[0]
print ver[-1]
print ver[1:4]
con.close()

Re-run the script in the command line terminal:

python connect.py

Python Lists are zero based, so ver[0] prints the first element of the list. The last element of the list is ver[-1]. A list slice is created by ver[1:4]. This returns the elements starting at position 1 and up to, but not including, elements from position 4.

 

.

Python lists have methods and can also be manipulated with operators.

Change connect.py to:

import cx_Oracle
con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl')
ver = con.version.split(".")
print ver
print ver.index("1")
ver.remove("2")
print ver

ver1 = ["11", "g"]
ver2 = ["R", "2"]
print ver1 + ver2	
con.close()

Re-run the script in the command line terminal:

python connect.py

The index("1") method returns the index of the "1" element, counting from zero. The remove("2") method deletes an element from the list. The "+" operator can be used to join two lists.

Other datatypes Python has are Dictionaries, which are associative arrays, and a type called a tuple, which is like a list but cannot be changed.

Loops can be used to iterate over lists.

Change connect.py to:

import cx_Oracle
con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl')
ver = con.version.split(".")
for v in ver:
    print v
    if v == "11":
        print "It's 11"
    else:
        print "Not 11"
con.close()

Make sure the indentation is correct!

The colon ":" is used to indicate a code block. The first print and if are at the same level of indentation because they are both inside the loop.

Re-run the script in the command line terminal:

python connect.py

The loop prints and tests each value from the list in turn.

 

Using Database Resident Connection Pooling

Database Resident Connection Pooling is a new feature of Oracle Database 11g. It is useful for short lived scripts such as typically used by web applications. It allows the number of connections to be scaled as web site usage grows. It allows multiple Apache processes on multiple machines to share a small pool of database server processes. Without DRCP, a Python connection must start and terminate a server process.

Below left is diagram of nonpooling. Every script has its own database server proces. Scripts not doing any database work still hold onto a connection until the connection is closed and the server is terminated. Below right is a diagram with DRCP. Scripts can use database servers from a pool of servers and return them when no longer needed.

Batch scripts doing long running jobs should generally use non-pooled connections.

This tutorial shows how DRCP can be used by new or existing applications without writing or changing any application logic. Perform the following steps:

.

Review the code contained in $HOME/connect_drcp.py

This is similar to connect.py but ":pooled" is appended to the connection string. A Connection Class "HOL" is also passed into the connect() method and the "purity" of the connection is defined as the ATTR_PURITY_SELF constant.

The connection class tells the database server pool that connections are related. Session information (such as the default date format) might be retained between connection calls, giving performance benefits. Session information will be discarded if a pooled server is later reused by an application with a different connection class name.

Applications that should never share session information should use a different connection class and/or use ATTR_PURITY_NEW to force creation of a new session. This reduces overall scalability but prevents applications mis-using session information.

Run connect_drcp.py

python connect_drcp.py

The output is again simply the version of the database.

Script logic does not need to be changed to benefit from DRCP connection pooling.

 

Creating a Simple Query

A common task when developing Web applications is to query a database and display the results in a Web browser. There are a number of functions you can use to query an Oracle database, but the basics of querying are always the same:

1. Parse the statement for execution.
2. Bind data values (optional).
3. Execute the statement.
4. Fetch the results from the database.

To create a simple query, and display the results, perform the following steps.

.

Review the code contained in $HOME/query.py

The cursor() method opens a cursor for statements to use.

The execute() method parses and executes the statement.

The loop fetches each row from the cursor and prints it.

Run the script in a terminal window:

python query.py

The results of the query are displayed as Python 'tuples', which are arrays that cannot be changed

 

Fetching Data

There are a number of ways to fetch data from an Oracle database. Perform the following steps.

.

Review the code contained in $HOME/query_one.py

This uses the fetchone() method to return just a single row as a tuple. When called multiple time, consecutive rows are returned:

Run the script in a terminal window:

python query_one.py

The two fetchone() calls print two records.

 

.

Review the code contained in $HOME/query_many.py

The fetchmany() method returns a list of tuples. Here the numRows parameter specifices that three rows should be returned.

Run the script in a terminal window:

python query_many.py

The first three rows of the table are returned as a list of tuples.

Review the code contained in $HOME/query_all.py

Run the script in a terminal window:

python query_all.py

This uses the fetchall() method to return all rows. The output is a list (Python's name for an array) of tuples. Each tuple contains the data for one row.

 

.

The list can be manipulated in the any Python manner. Edit $HOME/query_all.py and change the code to the following (in bold) and rerun the script again

import cx_Oracle
con = cx_Oracle.connect('pythonhol/welcome@localhost/orcl')
cur = con.cursor()
cur.execute('select * from departments order by department_id')
res = cur.fetchall()
for r in res:
print r
cur.close()
con.close()

Now each tuple is printed separately. The choice of which fetch method to use will depend on how you want to process the returned data.

 

Improving Query Performance

This section demonstrates a way to improve query performance by increasing the number of rows returned in each batch from Oracle to the Python program. Perform the following steps:

.

First, create a table with a large number of rows. Review $HOME/query_arraysize.sql

In a terminal window use SQL*Plus to run the script:

sqlplus pythonhol/welcome@localhost/orcl
@query_arraysize exit

 

.

Review the codecontained in $HOME/query_arraysize.py

This uses the 'time' module to measure elapsed time of the query. The arraysize is set to 100. This causes batches of 100 records at a time to be returned from the database to a cache in Python. This reduces the number of "roundtrips" made to the database, often reducing network load and reducing the number of context switches on the database server. The fetchone(), fetchmany() and even fetchall() methods will read from the cache before requesting more data from the database.

From a terminal window, run:

python query_arraysize.py

Reload a few times to see the average times.

Note: The time values you observe may differ from those displayed in the screenshots because of hardware or system load differences.

 

.

Edit $HOME/query_arraysize.py and change the arraysize from

cur.arraysize = 100

to

cur.arraysize = 2000

Rerun the script a few times and compare the performance of the two arraysize settings. In general, larger array sizes improve performance. Depending how fast your system is, you may need to use different arraysizes than those given here to see a meaningful time difference.

python query_arraysize.py 
    

The default arraysize used by cx_Oracle is 50. There is a time/space tradeoff for increasing the arraysize. Larger arraysizes will require more memory in Python for buffering the records.

Note: The time values you observe may differ from those displayed in the screenshot because of hardware or system load differences.

Using Bind Variables

Bind variables enable you to re-execute statements with new values, without the overhead of reparsing the statement. Bind variables improve code reusability, and can reduce the risk of SQL Injection attacks.

To use bind variables in this example, perform the following steps.

.

Review the code contained in $HOME/bind_query.py

The statement contains a bind variable ":id". The statement is only prepared once but executed twice with different values for the WHERE clause.

The special symbol 'None' is used in place of the statement text argument to execute() because the prepare() method has already set the statement. The second argument to the execute() call is a Python Dictionary. In the first execute call, this associative array has the value 210 for the key of "id".

The first execute uses the value 210 for the query. The second execute uses the value 110.

From a terminal window, run:

python bind_query.py

The output shows the details for the two departments.

 

.

The cx_Oracle driver supports array binds for INSERT statements, which can greatly improve performance over single row inserts.

Review the following commands to create a table for inserting data:

sqlplus pythonhol/welcome@localhost/orcl
drop table mytab;
create table mytab (id number, data varchar2(20)); exit

Run SQL*Plus and cut-and-paste the commands.


.

Review the code contained in $HOME/bind_insert.py

The 'rows' array contains the data to be inserted.

The bindarraysize is here set to 7, meaning to insert all seven rows in one step. The setinputsizes() call describes the columns. The first column is integral. The second column has a maximum of 20 bytes.

The executemany() call inserts all seven rows.

The commit call is commented out, and does not execute.

The final part of the script queries the results back and displays them as a list of tuples.

From a terminal window, run:

python bind_insert.py

The new results are automatically rolled back at the end of the script so re-running the script will always show the same number of rows in the table.

 

Creating Transactions

When you manipulate data in an Oracle Database (insert, update, or delete data), the changed or new data is only available within your database session until it is committed to the database. When the changed data is committed to the database, it is then available to other users and sessions. This is a database transaction. Perform the following steps:

.

Edit $HOME/bind_insert.py and uncomment the commit call (in bold below):

import cx_Oracle
con = cx_Oracle.connect('pythonhol/welcome@127.0.0.1/orcl')
rows = [ (1, "First" ),
         (2, "Second" ),
         (3, "Third" ),
         (4, "Fourth" ),
         (5, "Fifth" ),
         (6, "Sixth" ),
         (7, "Seventh" ) ]
cur = con.cursor()
cur.bindarraysize = 7
cur.setinputsizes(int, 20)
cur.executemany("insert into mytab(id, data) values (:1, :2)", rows)
con.commit()
# Now query the results back
cur2 = con.cursor()
cur2.execute('select * from mytab')
res = cur2.fetchall()
print res
cur.close()
cur2.close()
con.close()

The commit() is on the connection, not on the cursor.

Rerun the script several times and see the number of rows in the table increasing each time:

python bind_insert.py

If you need to initiate a rollback in a script, the con.rollback() method can be used.

In general you want all or none of your data committed. Doing your own transaction control has performance and data-integrity benefits.

Using PL/SQL Stored Functions and Procedures

PL/SQL is Oracle's procedural language extension to SQL. PL/SQL procedures and functions are stored and run in the database. Using PL/SQL lets all database applications reuse logic, no matter how the application accesses the database. Many data-related operations can be performed in PL/SQL faster than extracting the data into a program (for example, Python) and then processing it. Oracle also supports Java stored procedures.

In this tutorial, you will create a PL/SQL stored function and procedure and call them in Python scripts. Perform the following steps:

.

Start SQL*Plus and create a new table, ptab with the following command:

sqlplus pythonhol/welcome@localhost/orcl
create table ptab (mydata varchar(20), myid number); exit

 

.

Review $HOME/create_func.sql which creates a PL/SQL stored function myfunc() to insert a row into the ptab table, and return double the inserted value:

Start SQL*Plus and run the script:

sqlplus pythonhol/welcome@localhost/orcl
set echo on
@create_func
show errors
exit

 

.

Review the code in $HOME/plsql_func.py

This uses callfunc() to execute the function. The constant cx_oracle.NUMBER indicates that the return value is numeric. The two PL/SQL function parameters are passed as a tuple and bound to the function parameter arguments.

From a terminal window, run:

python plsql_func.py

The output is result of the PL/SQL function calculation.

 

.

To call a PL/SQL procedure, use the cur.callproc() method.

Review $HOME/create_proc.sql which creates a PL/SQL procedure myproc() to accept two parameters. The second parameter contains an OUT return value.

Start SQL*Plus and run the script::

sqlplus pythonhol/welcome@localhost/orcl
set echo on
@create_proc
show errors
exit

 

.

Review the code contained in $HOME/plsql_proc.py

This creates a numeric variable myvar to hold the OUT parameter. The number 123 and the return variable name are bound to the procedure call parameters using a tuple.

From a terminal window, run:

python plsql_proc.py

The getvalue() method displays the returned value.

 

Using Continuous Query Notification

Continuous Query Notification (also called Database Change Notification) allows applications to receive notifications when a table changes, for example when rows have been inserted. This can be useful in many circumstances, including for mid-tier cache invalidation. A cache might hold some values that depend on data in a table. If the table changes, the cached values must be updated with the new information.

This example show DCN events being handled in Python. Perform the following steps:

.

Review the code contained in $HOME/dcn.py

This script creates a function called DCNCallback(). This function will be called when a table changes. The 'message' parameter is a cx_Oracle object that will contain information about the changes. The function simply prints out the kinds of changes that have occurred and the affected rowids.

The main body of the script is below the function - note the indentation level of 'con = ...' is the same as for 'def ...'. The body creates the database connection with the 'events = True' parameter to allow the database to send an event notification to Python when a table change takes place.

The subscribe() call registers the DCNCallback() to be called in a new thread when an INSERT or UPDATE occurs. Only one Python thread can run at any given time. Python switches between threads as needed. The rowids = True parameter lets rowids be accessed in the callback.

The registerquery() call registers a query that selects everything from the MYTAB table. Any change to the table that is an UPDATE or DELETE will cause DCNCallback() to be called. The MYTAB table was created in a previous section of the lab.

The script concludes with a raw_input() call that waits for user input before terminating.

To run the DCN example, open two terminal windows. In the first window run:

python dcn.py

This will print a message and wait without returning a prompt:

Leave it running for the moment and continue with the next step.

 

.

In a second terminal window, enter the following commands

sqlplus pythonhol/welcome@localhost/orcl
insert into mytab (id) values (11);
commit;

When the commit occurs, the Python script (switch to your original terminal window) will receive notification and print the change notification message:

Note: The rowid will be different on your system.

 

.

Switch to your SQL*Plus terminal window and delete the new row by executing the following SQL commands:

delete from mytab where id = 11;
commit;

The new notification is printed and the Python terminal now looks like:

Note: The rowids will be different on your system.

Experiment with several operations. Try doing an INSERT followed by a DELETE before committing. This shows notification is received of each individual operation.


.

When you are finished, hit Enter to conclude the demo in the Python window to exit the script.

 

.

Extend dcn.py to also notify when an UPDATE occurs on MYTAB.

In the subscribe() call, change the operations parameter to

operations = cx_Oracle.OPCODE_INSERT | cx_Oracle.OPCODE_DELETE 
| cx_Oracle.OPCODE_UPDATE,

Add a new 'if' test to the DCNCallback function:

if row.operation & cx_Oracle.OPCODE_UPDATE: 
       print "UPDATE of rowid:", row.rowid

The dcn.py script should now look as follows (the changes are in bold):

import cx_Oracle 

def DCNCallback(message):
print "Notification:"
for tab in message.tables:
print "Table:", tab.name
for row in tab.rows:
if row.operation & cx_Oracle.OPCODE_INSERT:
print "INSERT of rowid:", row.rowid
if row.operation & cx_Oracle.OPCODE_DELETE:
print "DELETE of rowid:", row.rowid
if row.operation & cx_Oracle.OPCODE_UPDATE: print "UPDATE of rowid:", row.rowid

con = cx_Oracle.Connection("pythonhol/welcome@localhost/orcl",
events = True)
subscriptionInsDel = con.subscribe(callback = DCNCallback,
operations = cx_Oracle.OPCODE_INSERT | cx_Oracle.OPCODE_DELETE
| cx_Oracle.OPCODE_UPDATE
,
rowids = True)
subscriptionInsDel.registerquery('select * from mytab')

raw_input("Hit Enter to conclude this demo\n")

In the Python terminal, restart the script:

 

.

In the SQL*Plus terminal, create a row and update it:

insert into mytab (id) values (11);
update mytab set id = 12 where id = 11;
commit;

The new message should be displayed.

Note: The rowids will be different on your system.

 

.

When you are finished, hit Enter to conclude this demo in the Python window to exit the script.

Database Change Notification is an efficient way to monitor table changes. It can also be used to notify when any of a subset of rows selected by a given query are changed.

Summary

In this tutorial, you have learned how to:

Appendix: Python Primer

Python is a dynamically typed scripting language. It is most often
used to run command-line scripts but is also used in Web applications.

Strings can be enclosed in single or double quotes:

'A string constant'
"another constant"

Multi line strings use a triple-quote syntax

"""
This is
your string
"""

Variables do not need types declared:

count = 1
ename = 'Arnie'

Associative arrays are called 'dictionaries':

a2 = {'PI':3.1415, 'E':2.7182}

Ordered arrays are called 'lists':

a3 = [101, 4, 67]

Tuples are like lists but cannot be changed once they are created. They are created with parentheses:

a4 = (3, 7, 10)

Strings and variables can be displayed with a print statement:

print 'Hello, World!'
print 'Hi', x

Formatted output is also possible:

print "There are %d %s" % (v1, v2)

Everything in Python is an object. As one example, given the value of the list a3 above, the append() method can be used to add a value to the list.

a3.append(23)

Now a3 contains [101, 4, 67, 23]

Code flow can be controlled with tests and loops. The if/elif/else statements look like:

if sal > 900000:
    print 'Salary is way too big'
elif sal > 500000:
    print 'Salary is huge'
else:
    print 'Salary might be OK'

This also shows how the clauses are delimited with colons, and each sub block of code is indented.

A traditional loop is:

for i in range(0, 10):
    print i

This prints the numbers 0 to 9. The value of i is incremented in each iteration.

The 'for' command can also be used to iterate over lists and tuples:

a5 = ['Aa', 'Bb', 'Cc']
for v in a5:
    print v

This sets v to each element of the list a5 in turn.

A function may be defined:

def myfunc(p1, p2):
    "Function documentation: add two numbers"
    print p1, p2
    return p1 + p2

Functions may or may not return values. This function could be called using:

v3 = myfunc(1, 3)

Function calls must appear after their function definition.

Functions are also objects and have attributes. The inbuilt __doc__ attribute can be used to find the function description:

print myfunc.__doc__

Sub-files can be included in Python scripts with an import statement.

import os
import sys

Many predefined modules exist, such as the os and the sys modules.

Comments are either single line:

# a short comment

or multi-line using the triple-quote token:

"""
a longer
comment
"""

Hardware and Software Engineered to Work Together About Oracle |Oracle and Sun | Oracle RSS Feeds | Careers | Contact Us | Site Maps | Legal Notices | Terms of Use | Your Privacy Rights