来自:http://www.oracle.com/technetwork/cn/articles/dsl/prez-python-queries-101587-zhs.html
在 Python 做事方式的核心原则中,有一个规定是要求具有到 API 的高级接口。数据库 API(在此例中为 Oracle API)就是一个例子。使用 Computronix 的 cx_Oracle Python 模块,您可以在维持与 Python 数据库 API 规范 v2.0 的兼容性的同时,控制 Oracle 的查询模型。
对于所有遵循该规范的客户端库而言,使用 DB API 2.0 查询数据库的模型都是一致的。在此基础上,cx_Oracle 的主要开发人员 Anthony Tuininga 添加了一组丰富的属性和方法,以向开发人员揭示 Oracle 独有的特性。仅用标准的方法而忘掉“额外的”方法是绝对可能的,但在本文中您不会这么做。通用数据库包装这一概念可能在某些情况下起作用,但与此同时,您会失去 RDBMS 提供的所有优化。
Python 数据库 API 规范 v2.0 是集体努力的成果,用于统一不同数据库系统的访问模型。拥有一组相对较少的方法和属性,在更换数据库供应商时就易于学习并保持一致。它不以任何方式将数据库对象映射到 Python 结构中。用户仍然需要手工编写 SQL。在更换到另一数据库后,此 SQL 可能需要重新编写。尽管如此,它还是出色妥善地解决了 Python 数据库的连接性问题。
该规范定义了 API 的各个部分,如模块接口、连接对象、游标对象、类型对象和构造器、DB API 的可选扩展以及可选的错误处理机制。
数据库和 Python 语言之间的网关是连接对象。它包含制作数据库驱动的应用程序所需的全部组件,不仅符合 DB API 2.0,而且是规范方法和属性的一个超集。在多线程的程序中,模块和连接可以在不同线程间共享,但是不支持游标共享。这一限制通常是可接受的,因为共享游标可能带来死锁风险。
Python 大量使用了异常模型,DB API 定义了若干标准异常,它们在调试应用程序中的问题时会非常有用。下面是一些标准异常,同时提供了原因类型的简要说明:
连接过程首先从连接对象开始,这是创建游标对象的基础。除游标操作外,连接对象还使用 commit() 和 rollback() 方法对事务进行管理。执行 SQL 查询、发出 DML/DCL 语句和获取结果这些过程均受游标控制。
在游标和连接类的实现中,cx_Oracle 对标准的 DB API 2.0 规范进行了最大程度的扩展。如果需要,所有这些扩展都将在文本中清楚地标记。
在使用查询和游标之前,首先需要建立数据库连接。提供凭证和数据源名称的方法有多种,其结果都是相似的。在从下面的 Python 交互式会话提取的内容中,连接对象 db、db1 和 db2 都是等同的。makedsn() 函数根据给定的参数值创建一个 TNS 条目。此处将它赋值给变量 dsn_tns。如果环境设置得当,您可以使用更短的形式 cx_Oracle.connect('hr/hrpwd'),从而省略用于 db 和 db1 的简单连接字符串。
>>> import cx_Oracle
在连接对象的作用域内(如分配给上面的 db 变量的连接对象),您可以通过查询版本属性获得数据库版本(这是 DB API 2.0 的一个扩展)。这可以用于使 Python 程序依赖于具体版本的 Oracle 产品。同样地,您可以通过查询 dsn 属性获得连接的连接字符串。
>>> print db.version
10.2.0.1.0
>>> versioning = db.version.split('.')
>>> print versioning
['10', '2', '0', '1', '0']
>>> if versioning[0]=='10':
... print "Running 10g"
... elif versioning[0]=='9':
... print "Running 9i"
...
Running 10g
>>> print db.dsn
localhost:1521/XE
您可以使用连接对象的 cursor() 方法定义任意数量的游标。简单的程序使用一个游标就可以了,该游标可以一再地重复使用。但较大的项目可能要求几个不同的游标。
>>> cursor = db.cursor()
在继续了解游标示例前,请先了解 pprint 模块的 pprint 函数。它用于以清晰、可读的形式输出 Python 数据结构。
>>> 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),
?
('PR_REP', 'Public Relations Representative', 4500, 10500)]
cx_Oracle 游标是迭代器。利用这些强大的 Python 结构,您可以一种自然的方式对序列进行迭代,该方式仅根据需要获取后续的项。高成本的数据库选择操作自然符合这一思路,因为数据只在需要时才被获取。您可以进行迭代操作直至找到需要的值或满足另一条件,而不必创建或获取整个的结果集。
>>> cursor = db.cursor()
>>> 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>]
>>> for row in cursor: ## notice that this is plain English!
... print row
...
('AD_VP', 'Administration Vice President', 15000, 30000)
('AD_ASST', 'Administration Assistant', 3000, 6000)
('FI_MGR', 'Finance Manager', 8200, 16000)
('FI_ACCOUNT', 'Accountant', 4200, 9000)
('AC_MGR', 'Accounting Manager', 8200, 16000)
?
('PR_REP', 'Public Relations Representative', 4500, 10500)
在获取阶段,基本的 Oracle 数据类型会映射到它们在 Python 中的等同数据类型中。cx_Oracle 维护一个单独的、有助于这一转换的数据类型集合。Oracle - cx_Oracle - Python 映射为:
Oracle | cx_Oracle | Python |
VARCHAR2 NVARCHAR2 LONG |
cx_Oracle.STRING |
str |
CHAR |
cx_Oracle.FIXED_CHAR |
|
NUMBER |
cx_Oracle.NUMBER |
int |
FLOAT |
float |
|
DATE |
cx_Oracle.DATETIME |
datetime.datetime |
TIMESTAMP |
cx_Oracle.TIMESTAMP |
|
CLOB |
cx_Oracle.CLOB |
cx_Oracle.LOB |
BLOB |
cx_Oracle.BLOB |
除涉及大型对象的情况外,上述数据类型对于用户通常是透明的。截至版本 4.3,cx_Oracle 仍然自已处理这些数据类型,而没有与内置的文件类型打包到一起。
cx_Oracle 目前不负责处理的其他数据类型包括 XMLTYPE 和所有复杂的类型。目前所有对未支持类型的列的查询都会失败,同时引发 NotSupportedError 异常。您需要从查询中清除它们或将它们转换为支持的数据类型。
例如,考虑下面用于存储聚合的 RSS 信源提供的表:
CREATE TABLE rss_feeds (
feed_id NUMBER PRIMARY KEY,
feed_url VARCHAR2(250) NOT NULL,
feed_xml XMLTYPE
);
尝试使用 Python 查询此表时,需执行一些额外的步骤。在下例中,XMLType.GetClobVal() 用于以 CLOB 值形式从表中返回 XML。
您可能已经注意到了,cx_Oracle.Cursor.execute* 系列方法为查询返回列数据类型。这些是变量对象列表(DB API 2.0 的扩展),它们在获取阶段之前获取值 None,在获取阶段之后获取合适的数据值。有关数据类型的详细信息,可以通过游标对象的 description 属性获得。description 是一个包含 7 项内容的字节组,每个字节组包含列名、列类型、显示大小、内部大小、精度、小数位数以及是否存在空的可能。注意列信息仅可供 SQL 查询语句访问。
>>> 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)]
SELECT * FROM emp_details_view WHERE department_id=50
SELECT * FROM emp_details_view WHERE department_id=60
SELECT * FROM emp_details_view WHERE department_id=90
SELECT * FROM emp_details_view WHERE department_id=110
逐个运行时,它们需要分别进行分析,这为您的应用程序增加了额外的开销。通过使用绑定变量,您可以告诉 Oracle 对一个查询只分析一次。cx_Oracle 支持按名称或位置绑定变量。
按名称传递绑定变量要求执行方法的 parameters 参数是一个字典或一组关键字参数。下面的 query1 和 query2 是等同的:
>>> named_params = {'dept_id':50, 'sal':1000}
>>> query1 = cursor.execute('SELECT * FROM employees WHERE department_id=:dept_id AND salary>:sal', named_params)
>>> query2 = cursor.execute('SELECT * FROM employees WHERE department_id=:dept_id AND salary>:sal', dept_id=50, sal=1000)
>>> print cursor.bindnames()
['DEPT_ID', 'SAL']
>>> r1 = cursor.execute('SELECT * FROM locations WHERE country_id=:1 AND city=:2', ('US', 'Seattle'))
>>> r2 = cursor.execute('SELECT * FROM locations WHERE country_id=:9 AND city=:4', ('US', 'Seattle'))
>>> r3 = cursor.execute('SELECT * FROM locations WHERE country_id=:m AND city=:0', ('US', 'Seattle'))
>>> cursor.prepare('SELECT * FROM jobs WHERE min_salary>:min')
>>> r = cursor.execute(None, {'min':1000})
>>> print len(cursor.fetchall())
19
我们首先为 Python 模块列表创建一个表,这次直接从 Python 开始。您将在以后删除该表。
>>> create_table = """
CREATE TABLE python_modules (
module_name VARCHAR2(50) NOT NULL,
file_path VARCHAR2(300) NOT NULL
)
"""
>>> from sys import modules
>>> cursor.execute(create_table)
>>> M = []
>>> for m_name, m_info in modules.items():
... try:
... M.append((m_name, m_info.__file__))
... except AttributeError:
... pass
...
>>> len(M)
76
>>> cursor.prepare("INSERT INTO python_modules(module_name, file_path) VALUES (:1, :2)")
>>> cursor.executemany(None, M)
>>> db.commit()
>>> r = cursor.execute("SELECT COUNT(*) FROM python_modules")
>>> print cursor.fetchone()
(76,)
>>> cursor.execute("DROP TABLE python_modules PURGE")
熟悉 Oracle-Python 连接性的基本概念之后,您就可以开始编写自己的数据库驱动的应用程序了。我强烈建议花一些时间来使用 Python 交互式 shell,因为它确实会使学习过程更加容易。
您了解了 SQL 语句经历的三个阶段,也了解了如何将 Oracle 数据库需要执行的步骤减至最少。绑定变量是数据库应用程序开发不可避免的一部分,Python 支持按名称或位置进行绑定。
您还了解了 Oracle 和 Python 数据类型间的平滑转换,以及在将游标作为迭代器进行处理的上下文中数据库数据的自然处理方式。所有这些特性都促进了生产效率的提高并支持专注于数据,而这正是核心所在。