Oracle Python - Querying Best Practices
Oracle Python - Querying Best Practices
Application Development
Framew ork
SOLUTIONS
Articles
DOWNLOADS
STORE
SUPPORT
TRAINING
Communities
I am a...
PARTNERS
ABOUT
Search
Application Express
b y Przemyslaw Piotrowski
Big Data
As a first step, get familiar with the basic concepts of Oracle-Python connectivity
Business Intelligence
Cloud Computing
I w ant to...
Communications
Database Performance &
Availability
Data Warehousing
.NET
Dynamic Scripting Languages
Embedded
Social Enterprise
Enterprise Architecture
Enterprise Management
Identity & Security
Java
Linux
Service-Oriented Architecture
Solaris
SQL & PL/SQL
Systems - All Articles
Virtualization
Among the core principles of Python's way of doing things there is a rule about having high-level interfaces to APIs. The Database API (in this case
the Oracle API) is one example. Using the cx_Oracle Python module from Computronix, you can take command over the Oracle query model while
maintaining compatibility with Python Database API Specification v2.0.
The model of querying databases using DB API 2.0 remains consistent for all client libraries conforming to the specification. On top of this,
Anthony Tuininga, the principal developer of cx_Oracle, has added a wide set of properties and methods that expose Oracle-specific features to
developers. It is absolutely possible to use only the standard methods and forget about the "extra" ones, but in this installment you won't be doing
that. The concept of universal database wrappers might work in some cases but at the same time, you lose all the optimizations that the RDBMS
offers.
Introducing DB API 2.0 and cx_Oracle
The Python Database API Specification v2.0 is a community effort to unify the model of accessing different database systems. Having a relatively
small set of methods and properties, it is easy to learn and remains consistent when switching database vendors. It doesn't map database
objects to Python structures in any way. Users are still required to write SQL by hand. After changing to another database, this SQL would probably
need to be rewritten. Nevertheless it solves Python-database connectivity issues in an elegant and clean manner.
The specification defines parts of the API such as the module interface, connection objects, cursor objects, type objects and constructors, optional
extensions to the DB API and optional error handling mechanisms.
The gateway between the database and Python language is the Connection object. It contains all the ingredients for cooking database-driven
applications, not only adhering to the DB API 2.0 but being a superset of the specification methods and attributes. In multi-threaded programs,
modules as well as connections can be shared across threads; sharing cursors is not supported. This limitation is usually acceptable because
shareable cursors can carry the risk of deadlocks.
Python makes extensive use of the exception model and the DB API defines several standard exceptions that could be very helpful in debugging
problems in the application. Below are the standard exceptions with a short description of the types of causes:
WarningData was truncated during inserts, etc.
ErrorBase class for all of the exceptions mentioned here except for Warning
InterfaceErrorThe database interface failed rather than the database itself (a cx_Oracle problem in this case)
DatabaseErrorStrictly a database problem
DataErrorProblems with the result data: division by zero, value out of range, etc.
OperationalErrorDatabase error independent of the programmer: connection loss, memory allocation error, transaction processing error, etc.
IntegrityErrorDatabase relational integrity has been affected, e.g. foreign key constraint fails
InternalErrorDatabase has run into an internal error, e.g. invalid cursor, transaction out of synchronization
ProgrammingErrorTable not found, syntax error in SQL statement, wrong number of parameters specified etc.
NotSupportedErrorA non-existent part of API has been called
The connect process begins with the Connection object, which is the base for creating Cursor objects. Beside cursor operations, the Connection
object also manages transactions with the commit() and rollback() methods. The process of executing SQL queries, issuing DML/DCL
statements and fetching results are all controlled by cursors.
cx_Oracle extends the standard DB API 2.0 specification in its implementation of the Cursor and Connection classes at most. All such extensions
will be clearly marked in the text if needed.
Getting Started
Before working with queries and cursors, a connection to the database needs to be established. The credentials and data source names can be
supplied in one of several ways, with similar results. In the extract from the Python interactive session below, connection objects db, db1 and db2
are all equivalent. The makedsn() function creates a TNS entry based on the given parameter values. Here it is being assigned to the variable
dsn_tns. When environment settings are properly set then you can use the shorter form cx_Oracle.connect('hr/hrpwd'), skipping even the Easy
Connect string used for db and db1.
>>> import cx_Oracle
>>> db = cx_Oracle.connect('hr', 'hrpwd', 'localhost:1521/XE')
>>> db1 = cx_Oracle.connect('hr/hrpwd@localhost:1521/XE')
>>> dsn_tns = cx_Oracle.makedsn('localhost', 1521, 'XE')
>>> print dsn_tns
(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521)))(CONNECT_DATA=(SID=XE)))
>>> db2 = cx_Oracle.connect('hr', 'hrpwd', dsn_tns)
Within the scope of a Connection object (such as assigned to the db variable above) you can get the database version by querying the version
attribute (an extension to DB API 2.0). This can be used to make Python programs Oracle-version dependent. Likewise, you can get the connect
string for the connection by querying the dsn attribute.
Cursor Objects
You can define an arbitrary number of cursors using the cursor() method of the Connection object. Simple programs will do fine with just a single
cursor, which can be used over and over again. Larger projects might however require several distinct cursors.
>>> cursor = db.cursor()
Application logic often requires clearly distinguishing the stages of processing a statement issued against the database. This will help
understand performance bottlenecks better and allow writing faster, optimized code. The three stages of processing a statement are:
Parse (optional)
cx_Oracle.Cursor.parse([statement])
Not really required to be called because SQL statements are automatically parsed at the Execute stage. It can be used to validate statements
before executing them. When an error is detected in such a statement, a DatabaseError exception is raised with a corresponding error message,
most likely "ORA-00900: invalid SQL statement, ORA-01031: insufficient privileges or ORA-00921: unexpected end of SQL command."
Execute
cx_Oracle.Cursor.execute(statement, [parameters], **keyword_parameters)
This method can accept a single argument - a SQL statement - to be run directly against the database. Bind variables assigned through the
parameters or keyword_parameters arguments can be specified as a dictionary, sequence, or a set of keyword arguments. If dictionary or
keyword arguments are supplied then the values will be name-bound. If a sequence is given, the values will be resolved by their position. This
method returns a list of variable objects if it is a query, and None when it's not.
cx_Oracle.Cursor.executemany(statement, parameters)
Especially useful for bulk inserts because it can limit the number of required Oracle execute operations to just a single one. For more information
about how to use it please see the "Many at once" section below.
Fetch (optional)Only used for queries (because DDL and DCL statements don't return results). On a cursor that didn't execute a query, these
methods will raise an InterfaceError exception.
cx_Oracle.Cursor.fetchall()
Fetches all remaining rows of the result set as a list of tuples. If no more rows are available, it returns an empty list. Fetch actions can be finetuned by setting the arraysize attribute of the cursor which sets the number of rows to return from the database in each underlying request. The
higher setting of arraysize, the fewer number of network round trips required. The default value for arraysize is 1.
cx_Oracle.Cursor.fetchmany([rows_no])
Fetches the next rows_no rows from the database. If the parameter isn't specified it fetches the arraysize number of rows. In situations where
rows_no is greater than the number of fetched rows, it simply gets the remaining number of rows.
cx_Oracle.Cursor.fetchone()
Fetches a single tuple from the database or none if no more rows are available.
Before going forward with cursor examples please welcome the pprint function from the pprint module. It outputs Python data structures in a
clean, readable form.
>>> from pprint import pprint
>>> cursor.execute('SELECT feed_id, feed_url, XMLType.GetClobVal(feed_xml) FROM rss_feeds')
>>> cursor.execute('SELECT * FROM jobs')
[<cx_Oracle.STRING with value None>, <cx_Oracle.STRING with value None>, <cx_Oracle.NUMBER with value None>,
<cx_Oracle.NUMBER with value None>]
>>> pprint(cursor.fetchall())
[('AD_PRES', 'President', 20000, 40000),
('AD_VP', 'Administration Vice President', 15000, 30000),
('AD_ASST', 'Administration Assistant', 3000, 6000),
('FI_MGR', 'Finance Manager', 8200, 16000),
('FI_ACCOUNT', 'Accountant', 4200, 9000),
Datatypes
During the fetch stage, basic Oracle data types get mapped into their Python equivalents. cx_Oracle maintains a separate set of data types that
helps in this transition. The Oracle - cx_Oracle - Python mappings are:
Oracle
VARCHAR2
NVARCHAR2
LONG
CHAR
cx_Oracle
cx_Oracle.STRING
NUMBER
cx_Oracle.NUMBER
Python
str
cx_Oracle.FIXED_CHAR
FLOAT
int
float
DATE
cx_Oracle.DATETIME
TIMESTAMP
cx_Oracle.TIMESTAMP
CLOB
cx_Oracle.CLOB
BLOB
cx_Oracle.BLOB
datetime.datetime
cx_Oracle.LOB
The above data types are usually transparent to the user except for cases involving Large Objects. As of version 4.3, cx_Oracle still handles them
itself and not wrapped with the built-in file type.
Other data types that are not yet handled by cx_Oracle include XMLTYPE and all complex types. All queries involving columns of unsupported
types will currently fail with a NotSupportedError exception. You need to remove them from queries or cast to a supported data type.
For example, consider the following table for storing aggregated RSS feeds:
CREATE TABLE rss_feeds (
feed_id NUMBER PRIMARY KEY,
feed_url VARCHAR2(250) NOT NULL,
feed_xml XMLTYPE
);
When trying to query this table with Python, some additional steps need to be performed. In the example below XMLType.GetClobVal() is used to
return XML from the table as CLOB values.
>>> cursor.execute('SELECT * FROM rss_feeds')
Traceback (most recent call last):
File "<pyshell#37>", line 1, in <module>
cursor.execute('SELECT * FROM rss_feeds')
NotSupportedError: Variable_TypeByOracleDataType: unhandled data type 108
>>> cursor.execute('SELECT feed_id, feed_url, XMLType.GetClobVal(feed_xml) FROM rss_feeds')
[<cx_Oracle.NUMBER with value None>, <cx_Oracle.STRING with value None>, <cx_Oracle.CLOB with value None>]
You might have already noticed the cx_Oracle.Cursor.execute* family of methods returns column data types for queries. These are lists of
Variable objects (an extension to DB API 2.0), which get the value None before the fetch phase and proper data values after the fetch. Detailed
information about data types is available through the description attribute of cursor objects. The description is a list of 7-item tuples where each
tuple consists of a column name, column type, display size, internal size, precision, scale and whether null is possible. Note that column
information is only accessible for SQL statements that are queries.
>>> column_data_types = cursor.execute('SELECT * FROM employees')
>>> print column_data_types
[<cx_Oracle.NUMBER with value None>, <cx_Oracle.STRING with value None>, <cx_Oracle.STRING with value None>,
<cx_Oracle.STRING with value None>, <cx_Oracle.STRING with value None>, <cx_Oracle.DATETIME with value None>,
<cx_Oracle.STRING with value None>, <cx_Oracle.NUMBER with value None>, <cx_Oracle.NUMBER with value None>,
<cx_Oracle.NUMBER with value None>, <cx_Oracle.NUMBER with value None>]
>>> pprint(cursor.description)
[('EMPLOYEE_ID', <type 'cx_Oracle.NUMBER'>, 7, 22, 6, 0, 0),
('FIRST_NAME', <type 'cx_Oracle.STRING'>, 20, 20, 0, 0, 1),
('LAST_NAME', <type 'cx_Oracle.STRING'>, 25, 25, 0, 0, 0),
('EMAIL', <type 'cx_Oracle.STRING'>, 25, 25, 0, 0, 0),
('PHONE_NUMBER', <type 'cx_Oracle.STRING'>, 20, 20, 0, 0, 1),
('HIRE_DATE', <type 'datetime.datetime'>, 23, 7, 0, 0, 0),
('JOB_ID', <type 'cx_Oracle.STRING'>, 10, 10, 0, 0, 0),
('SALARY', <type 'cx_Oracle.NUMBER'>, 12, 22, 8, 2, 1),
('COMMISSION_PCT', <type 'cx_Oracle.NUMBER'>, 6, 22, 2, 2, 1),
('MANAGER_ID', <type 'cx_Oracle.NUMBER'>, 7, 22, 6, 0, 1),
('DEPARTMENT_ID', <type 'cx_Oracle.NUMBER'>, 5, 22, 4, 0, 1)]
Bind Variable Patterns
As advertised by Oracle guru Tom Kyte, bind variables are core principles of database development. They do not only make programs run faster
but also protect against SQL injection attacks. Consider the following queries:
perform. Bind variables are an inevitable part of database application development and Python enables binding them by name or by position.
You have also been introduced to the smooth transition between Oracle and Python datatypes and the natural way of handling database data in
the context of handling cursors as iterators. All these features boost productivity and enable focusing on the data, which is what it's all about.
Przemyslaw Piotrowski is an information technology specialist working with emerging technologies and dynamic, agile development
environments. Having a strong IT background that includes administration, development and design, he finds many paths of software
interoperability.
E-mail this page
ORACLE CLOUD
Learn About Oracle Cloud
JAVA
Learn About Java
Printer View
COMMUNITIES
Blogs
Discussion Forums
Wikis
Become a Partner
Oracle OpenWorld
Oracle ACEs
JavaOne
User Groups
Java Magazine
Subscribe Careers Contact Us Site Maps Legal Notices Terms of Use Privacy Cookie Preferences