您好,欢迎来到爱够旅游网。
搜索
您的当前位置:首页精通 Oracle+Python

精通 Oracle+Python

来源:爱够旅游网
精通 Oracle+Python

作者Przemyslaw Piotrowski 是一名信息技术专家,专门研究新兴技术和动态、灵活的开发环境。他拥有很强的 IT 专业技术背景(包括管理、开发和设计),并发现了许多软件互操作方法。

第一部分http://blogold.chinaunix.net/u1/55091/showart_1924746.html。第二、三部分http://fsh.blogbus.com/logs/43873227.html。第四部分http://www.oracle.com/technetwork/cn/articles/prez-transactions-lobs-088114-zhs.html第五部分http://www.oracle.com/technetwork/cn/articles/prez-stored-proc-096180-zhs.html 第 1 部分:查询最佳应践

首先,熟悉 Oracle-Python 连接性的基本概念2007 年 9 月发布

在 Python 做事方式的核心原则中,有一个规定是要求具有到 API 的高级接口。数据库 API(在此例中为 Oracle API)就是一个例子。使用 Computronix 的 cx_Oracle Python 模块,您可以在维持与 Python 数据库 API 规范 v2.0 的兼容性的同时,控制 Oracle 的查询模型。

对于所有遵循该规范的客户端库而言,使用 DB API 2.0 查询数据库的模型都是一致的。在此基础上,cx_Oracle 的主要开发人员 Anthony Tuininga 添加了一组丰富的属性和方法,以向开发人员揭示 Oracle 独有的特性。仅用标准的方法而忘掉“额外的”方法是绝对可能的,但在本文中您不会这么做。通用数据库包装这一概念可能在某些情况下起作用,但与此同时,您会失去 RDBMS 提供的所有优化。DB API 2.0 和 cx_Oracle 介绍

Python 数据库 API 规范 v2.0 是集体努力的成果,用于统一不同数据库系统的访问模型。拥有一组相对较少的方法和属性,在更换数据库供应商时就易于学习并保持一致。它不以任何方式将数据库对象映射到 Python 结构中。用户仍然需要手写 SQL。在更换到另一数据库后,此 SQL 可能需要重新编写。尽管如此,它还是出色而妥善地解决了 Python 数据库的连接性问题。

该规范定义了 API 的各个部分,如模块接口、连接对象、游标对象、类型对象和构造器、DB API 的可选扩展以及可选的错误处理机制。

数据库和 Python 语言之间的网关是连接对象。它包含烹制数据库驱动的应用程序所需的全部组件,不仅符合 DB API 2.0,而且是规范方法和属性的一个超集。在多线程的程序中,模块和连接可以在不同线程间进行共享,但是不支持游标共享。这一通常是可接受的,因为共享游标可能带有死锁风险。

Python 大量使用了异常模型,DB API 定义了若干标准异常,它们在调试应用程序中的问题时会非常有用。下面是一些标准异常,同时提供了原因类型的简要说明:Warning — 数据在进行插入等操作时被截断

Error — 这里提到的除 Warning 外的所有异常的基类

InterfaceError — 数据库接口而非数据库本身故障(本例为 cx_Oracle 问题)DatabaseError — 严格意义上的数据库问题

DataError — 包含如下结果数据的问题:除数为 0,值超出范围等

OperationalError — 与编程人员无关的数据库错误:连接丢失、内存分配错误、事务处理错误等

IntegrityError — 数据库的关系完整性受到了影响,例如,外键约束失效InternalError — 数据库出现内部错误,例如,无效的游标、事务处理不同步ProgrammingError — 未找到表、SQL 语句中的语法错误、参数的数量指定错误等NotSupportedError — 调用的 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 的 Easy Connect 字符串。

>>> 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)

在连接对象的范围内(如分配给上面的 db 变量的连接对象),您可以通过查询版本属性获得数据库版本(这是 DB API 2.0 的一个扩展)。这可以用于使 Python 程序依赖于具体版本的 Oracle 产品。同样地,您可以通过查询 dsn 属性获得连接的连接字符串。>>> print db.version10.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.dsnlocalhost:1521/XE游标对象

您可以使用连接对象的 cursor() 方法定义任意数量的游标。简单的程序使用一个游标就可以了,该游标可以一再地重复使用。但较大的项目可能要求几个不同的游标。 >>> cursor = db.cursor()

应用程序逻辑通常要求清晰地区分处理针对数据库发出的语句时所经历的各个阶段。这有助于更好地理解性能瓶颈并编写更快且经过优化的代码。语句处理分三个阶段:1. 分析(可选)

1. cx_Oracle.Cursor.parse([statement])

不一定要调用,因为在执行阶段将自动分析 SQL 语句。该方法可以用于在执行语句前对其进行验证。当这类语句中检测出错误时,会引发 DatabaseError 异常,相应的错误消息通常可能是“ORA-00900:invalid SQL statement, ORA-01031:insufficient privileges or ORA-00921:unexpected end of SQL command.”2. 执行

0. cx_Oracle.Cursor.execute(statement, [parameters], **keyword_parameters)此方法可以接受单个参数 — 一条 SQL 语句 — 直接针对数据库来运行。通过

parameters 或 keyword_parameters 参数赋值的绑定变量可以指定为字典、序列或一组关键字参数。如果已经提供了字典或关键字参数,那么这些值将与名称绑定。如果给出的是序列,将根据这些值的 位置对它们进行解析。如果是查询操作,此方法返回一个变量对象列表;如果不是则返回 None。1. cx_Oracle.Cursor.executemany(statement, parameters)

对于批量插入尤其有用,因为它可以将所需的 Oracle 执行操作的数量为仅一个。有关如何使用该方法的详细信息,请参见下面的“一次多行”部分。3. 获取(可选)— 仅用于查询(因为 DDL 和 DCL 语句不返回结果)。在不执行查询的游标上,这些方法将引发 InterfaceError 异常。0. cx_Oracle.Cursor.fetchall()

以字节组列表形式获取结果集中的所有剩余行。如果没有剩余的行,它返回一个空白列表。获取操作可以通过设置游标的 arraysize 属性进行调整,该属性可设置在每个底层请求中从数据库中返回的行数。arraysize 的设置越高,需要在网络中往返传输的次数越少。arraysize 的默认值为 1。

1. cx_Oracle.Cursor.fetchmany([rows_no])

用于从数据库中获取接下来的 rows_no 行。如果该参数未指定,该方法获取的行数是 arraysize 的数量。如果 rows_no 大于获取到的行的数目,该方法获取的行数是剩余的行数。2. cx_Oracle.Cursor.fetchone()

从数据库中获取单个字节组,如果无剩余行则返回 none。

在继续了解游标示例前,请先了解 pprint 模块的 pprint 函数。它用于以清晰、可读的形式输出 Python 数据结构。>>> from pprint import pprint

>>> cursor.execute('SELECT * FROM jobs')

[, , , ]>>> 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')

[, , , ]>>> 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)

执行 list(cursor) 后,会针对 cursor.fetchall() 执行相同的任务。这是因为内置的 list() 函数会在给定的迭代器结束之前一直进行迭代。数据类型

在获取阶段,基本的 Oracle 数据类型会映射到它们在 Python 中的等同数据类型中。cx_Oracle 维护一个单独的、有助于这一转换的数据类型集合。Oracle - cx_Oracle - Python 映射为:

OracleVARCHAR2NVARCHAR2LONGCHARNUMBERFLOATDATETIMESTAMPCLOBBLOBcx_Oracle.DATETIMEcx_Oracle.TIMESTAMPcx_Oracle.CLOBcx_Oracle.BLOBcx_Oracle.LOBcx_Oraclecx_Oracle.STRINGPythonstrcx_Oracle.FIXED_CHARcx_Oracle.NUMBERintfloatdatetime.datetime除涉及大型对象的情况外,上述数据类型对于用户通常是透明的。截止到版本

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。>>> cursor.execute('SELECT * FROM rss_feeds')

Traceback (most recent call last): File \"\ line 1, in 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.Cursor.execute* 系列方法为查询返回列数据类型。这些是变量对象列表(DB API 2.0 的扩展),它们在获取阶段之前获取值 None,在获取阶段之后获取合适的数据值。有关数据类型的详细信息可以通过游标对象的描述属性获得。该描述是一个包含 7 项内容的字节组,每个字节组包含列名、列类型、显示大小、内部大小、精度、范围以及是否存在空的可能。注意列信息仅可供 SQL 查询语句访问。>>> column_data_types = cursor.execute('SELECT * FROM employees')>>> print column_data_types

[, , , , , , , , , , ]>>> pprint(cursor.description)

[('EMPLOYEE_ID', , 7, 22, 6, 0, 0), ('FIRST_NAME', , 20, 20, 0, 0, 1), ('LAST_NAME', , 25, 25, 0, 0, 0),

('EMAIL', , 25, 25, 0, 0, 0), ('PHONE_NUMBER', , 20, 20, 0, 0, 1), ('HIRE_DATE', , 23, 7, 0, 0, 0), ('JOB_ID', , 10, 10, 0, 0, 0), ('SALARY', , 12, 22, 8, 2, 1), ('COMMISSION_PCT', , 6, 22, 2, 2, 1), ('MANAGER_ID', , 7, 22, 6, 0, 1), ('DEPARTMENT_ID', , 5, 22, 4, 0, 1)]绑定变量模式

正如 Oracle 精英 Tom Kyte 介绍的那样,绑定变量是数据库开发的核心原则。它们不仅使程序运行更快,同时可以防范 SQL 注入攻击。请看以下查询:SELECT * FROM emp_details_view WHERE department_id=50SELECT * FROM emp_details_view WHERE department_id=60SELECT * FROM emp_details_view WHERE department_id=90SELECT * 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) 在使用已命名的绑定变量时,您可以使用游标的 bindnames() 方法检查目前已指定的绑定变量:

>>> print cursor.bindnames()['DEPT_ID', 'SAL']

按位置传递与此相似,但是您需要谨慎命名。变量名是任意的,因此这种方式很容易使查询混乱。在下例中,三个查询 r1、r2 和 r3 都是等同的。parameters 变量必须作为序列提供。

>>> 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'))

在绑定时,您可以首先准备该语句,然后利用改变的参数执行 None。根据绑定变量时准备一个语句即足够这一原则,Oracle 将如同在上例中一样对其进行处理。准备好的语句可执行任意次。

>>> cursor.prepare('SELECT * FROM jobs WHERE min_salary>:min')>>> r = cursor.execute(None, {'min':1000})>>> print len(cursor.fetchall())19

您已经了分析的次数。在下一段中,我们将清除不必要的执行,尤其是昂贵的批量插入。一次多行

大型的插入操作不需求多次的单独插入,这是因为 Python 通过

cx_Oracle.Cursor.executemany 方法完全支持一次插入多行。执行操作的数量极大地改善了程序性能,因此在编写存在大量插入操作的应用程序时应首先考虑这一功能。我们首先为 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\")

仅向数据库发出一个执行操作,要求将 76 个模块名称全部插入。这对大型插入操作而言是一个巨大的性能提升。注意此处的两点小的不同:cursor.execute(create_tab) 不产生任何输出,这是因为它是一个 DDL 语句,而 (76,) 是一个有单个元素的字节组。不含逗号的 (76) 仅等同于整数 76。结论

在熟悉了 Oracle-Python 连接性的基本概念后,您就可以开始编写您自己的数据库驱动的应用程序了。我强烈推荐利用 Python 交互式 shell,因它真的可以节省学习时间。您已经学习了 SQL 语句经过的三个阶段以及如何将 Oracle 数据库需要执行的步骤减至最少。绑定变量是数据库应用程序开发不可避免的一部分,Python 支持按名称或位置进行绑定。

您还了解了 Oracle 和 Python 数据类型间的平滑转换,以及在将游标作为迭代器进行处理的上下文中数据库数据的自然处理方式。所有这些特性都促进了生产效率的提高并支持专注于数据,而这正是核心所在。

第 2 部分:处理时间和日期

Oracle 和 Python 的日期处理介绍2007 年 9 月发布

从 Python 2.4 版开始,cx_Oracle 自身可以处理 DATE 和 TIMESTAMP 数据类型,将这些列的值映射到 Python 的 datetime 模块的 datetime 对象中。因为 datetime 对象支持原位的运算操作,这可以带来某些优势。内置的时区支持和若干专用模块使 Python 成为一台实时机器。感谢 cx_Oracle 的映射机制,Python 和 Oracle 间的日期/时间数据类型转换对开发人员是完全透明的。

Python 开发人员可能一开始会觉得 Oracle 的日期运算有点奇怪,但只需几点提示,该算法就会变得清楚且合理。本系列的这部分内容将帮助您从 Oracle 和 Python 两个角度来深入理解日期运算。二者均对日期/时间数据类型的处理提供丰富的支持,因此选择哪个由编程人员决定。如果您倾向于将应用程序逻辑放在数据库中, 或者喜欢将日期/时间操作封装在应用程序自身内部,Oracle 和 Python 的无缝集成会为您带来最大的灵活性,同时编程工作量却很少。

Oracle

DATE,TIMESTAMP

Oracle 的特色在于为时区和日期运算提供顶级的支持。用于处理时间和日期的基本的 Oracle 数据类型包括:

DATE — 日期和时间信息,包括世纪、年、月、日、小时、分和秒。这种类型的列支持的值范围在公元前 4712 年 1 月 1 日到公元 9999 年 12 月 31 日之间。

TIMESTAMP — DATE 数据类型的粒度精确到秒。TIMESTAMP 字段包含 DATE 中的全部信息,另外还包括指定精度的秒的小数(最多为 9 位)。默认精度为 6 位。

TIMESTAMP WITH TIME ZONE — 除 TIMESTAMP 列中包含的信息外,此变体还包括时区偏移量,它是当地时间和 UTC(全球统一时间)时间之间的差值。精度属性与上面相同。TIMESTAMP WITH LOCAL TIME ZONE — 与 TIMESTAMP WITH TIME ZONE 相对,此类型的值中不包含时区偏移量,而是由用户的当地会话时区确定该值。

日期时间由大量字段组成,数量由数据类型的粒度和变体决定。可以使用 EXTRACT 语句通过 SQL 查询将这些字段提取出来。要了解有关数据类型中的可用字段和时间间隔的详细信息,请参考 Oracle 数据库 SQL 语言参考 的数据类型部分。我们来了解一下工作原理:SQL> SELECT EXTRACT(YEAR FROM hire_date) FROM employees ORDER BY 1;EXTRACT(YEARFROMHIRE_DATE)-------------------------- 1987 1987 ? 2000107 rows selected.

利用此方法和 Oracle 的日期运算,您也可以得到两个日期之间的时间间隔:

SQL> SELECT hire_date, SYSDATE, EXTRACT(YEAR FROM (SYSDATE-hire_date) YEAR TO MONTH) \"Years\" 2 FROM employees WHERE ROWNUM <= 5;HIRE_DATE SYSDATE Years------------------ ------------------ ----------17-JUN-87 23-FEB-07 1921-SEP- 23-FEB-07 1713-JAN-93 23-FEB-07 1403-JAN-90 23-FEB-07 1721-MAY-91 23-FEB-07 155 rows selected.

INTERVAL

日期操作涉及的另一数据类型是 INTERVAL,它表示一段时间。在编写本文时,Python 不支持将 INTERVAL 数据类型作为查询的一部分返回。唯一的方法是使用 EXTRACT 从时间间隔中提取出所需的信息。尽管如此,包含返回 TIMESTAMP 类型的时间间隔的查询仍然运转良好。INTERVAL 类型有两个变体:

· INTERVAL YEAR TO MONTH — 存储年和月的数量信息。年的精度可以手动指定。

默认值是 (INTERVAL YEAR(2) TO MONTH)。

· Error — 这里提到的除 Warning 外的所有异常的基类。

· INTERVAL DAY TO SECOND — 在要求更高的精度时,此类型将天、小时、分和秒

的信息存储为一段时间。天和秒的精度都可以显式指定,范围为 0 到 9。默认值是 INTERVAL DAY(2) TO SECOND(6)。

SYSDATE+1 等于明天

现在看一下 Oracle 如何解决日期运算。在处理 datetime 列时,Oracle 认为 1 是指 1 天。这一方法确实非常直观。如果您想使用更小的单位,您需要使用除法:1 分钟是 1/1440,这是因为 1 天有 60*24 分钟;1 小时是 1/24;1 秒钟是 1/800,依此类推。

要查询从现在起的 15 分钟,使用 SELECT SYSDATE+15/1440 FROM dual。

格式化日期

Oracle 自身将日期显示为字符串,如上面的示例所示。格式化取决于从环境中继承的或显式设置的参数。要查看您数据库中的格式化参数,使用此查询:

SQL> SELECT * FROM v$nls_parameters WHERE REGEXP_LIKE(parameter, 'NLS_(DATE|TIME).*');PARAMETER VALUE--------------------------------------- NLS_DATE_FORMAT RR/MM/DDNLS_DATE_LANGUAGE POLISHNLS_TIME_FORMAT HH24:MI:SSXFF

NLS_TIMESTAMP_FORMAT RR/MM/DD HH24:MI:SSXFFNLS_TIME_TZ_FORMAT HH24:MI:SSXFF TZR

NLS_TIMESTAMP_TZ_FORMAT RR/MM/DD HH24:MI:SSXFF TZR

6 rows selected.

开发人员可以使用一组 Oracle 函数

(TO_DATE、TO_TIMESTAMP、TO_TIMESTAMP_TZ、TO_YMINTERVAL、TO_DSINTERVAL)将字符值 转换为日期时间。TO_CHAR 函数用于相反方向的转换。注意这些转换对于 Oracle 和 Python 间的转换通常不是必要的,这是因为我们处理的类型在两个方向都是可转换的。

尽管如此,Oracle 自身在格式化日期时间上仍然允许极大的灵活性:

SQL> SELECT TO_CHAR(TO_DATE('04-2007-07', 'DD-YYYY-MM'), 'DD/MM/YYYY') FROM dual;TO_CHAR(TO_DATE('04-2007-07','DD-YYYY-MM'),'DD/MM/YYYY')---------- 04/07/2007

获取当前的时间和时区信息同样容易。引入了两个新的格式化模型 TZH 和 TZM:

SQL> SELECT TO_CHAR(SYSTIMESTAMP, 'HH24:MI TZH:TZM') FROM dual;TO_CHAR(SYSTIMESTAMP,'HH24:MI TZH:TZM')------------ 16:24 +01:00

要获得可用格式化模型的完整列表,请参考 Oracle 数据库 SQL 语言参考 的格式模型部分,该部分还有大量的用法示例。在很多情况下,您可能会觉得为当前会话设置永久的格式模型非常有用。可使用 ALTER SESSION 语句进行此设置:Connected to:

Oracle Database 10g Express Edition Release 10.2.0.1.0 - ProductionSQL> SELECT SYSDATE FROM dual;SYSDATE--------------23-FEB-07

SQL> ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';Session altered.

SQL> SELECT SYSDATE FROM dual;SYSDATE

-------------------2007-02-23 17:50:15

国家语言支持 (NLS) 参数的设置过程在 Oracle 数据库 全球化支持指南 中进行了详细说明。Oracle 数据库 SQL 语言参考 包含与此主题相关的更多信息。尤其要查阅 日期时间 / 时间间隔运算 和日期时间函数这两部分,以了解日期运算和内置的日期函数的更多信息。

Python

Python 允许您在处理时间和日期时在低级和高级接口间进行自由选择。为了充分利用 Python 的标准库,我们将重点介绍 datetime 模块,它同时也是日期/时间运算的基础。该模块有 5 个核心类型:date、time、datetime、timedelta 和 tzinfo。>>> import datetime

>>> d = datetime.datetime.now()>>> print d

2007-03-03 16:48:27.734000>>> print type(d)

>>> print d.hour, d.minute, d.second(16, 48, 27)

如上所述,datetime 对象精确到微秒,所公开的一组属性与天、小时、秒等相对应。在 Python 中,了解某对象所拥有的属性和方法的最快途径是使用内置的 dir() 函数。关于 Python 标准库的介绍也非常丰富,因此您可以随时使用 help() 函数来了解该对象的简要说明 — 大多数情况下这足以让您很快入门。

>>> dir(datetime.datetime)

['__add__', '__class__', '__delattr__', '__doc__', '__eq__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__ne__', '__new__', '__radd__', '__reduce__',

'__reduce_ex__', '__repr__', '__rsub__', '__setattr__', '__str__', '__sub__', 'astimezone', 'combine', 'ctime', 'date', 'day', 'dst', 'fromordinal', 'fromtimestamp', 'hour', 'isocalendar', 'isoformat', 'isoweekday', 'max', 'microsecond', 'min', 'minute', 'month', 'now', 'replace', 'resolution', 'second', 'strftime', 'strptime', 'time', 'timetuple', 'timetz', 'today', 'toordinal', 'tzinfo', 'tzname', 'utcfromtimestamp', 'utcnow', 'utcoffset', 'utctimetuple', 'weekday', 'year']

>>> help(datetime.datetime.weekday)Help on method_descriptor:

weekday(...)

Return the day of the week represented by the date. Monday == 0 ... Sunday == 6

当只需要 datetime 的一个组件(date 或 time)时,您可以使用 datetime.datetime 对象的 date() 或 time() 方法来分别返回 datetime.date 或 datetime.time 对象。

Python 的日期运算

日期间的差异无须手动计算,因为 datetime 模块已经通过 timedelta 对象支持了这一运算。

Timedelta 表示持续时间,它在内部存储天、秒和微秒的数量。timedelta 属性还提供毫秒、分、小时和周等信息。此内容是根据内部表示计算出的。支持的取值范围是:-999999999 <= 天数 <=

999999999、0 <= 秒数 < 800、0 <= 微秒数 < 1000000。注意 timedelta 对象由所需数量的字段进行表示。因此 timedelta(hours=1) 与 timedelta(0, 3600) 的输出相同,即 0 天和 3600 秒。此处无毫秒,因为表示 1 小时不需要使用毫秒。

下面给出了 Python 中有关 datetime/timedelta 对象最常用的和受支持的运算操作。

对象 类型操作示例 对应的结果timedelta(minutes=10) + timedelta(hours=2) == timedelta(0, datetime.timedelttd2 + td3atd2 - td3td2 * ntd2 / ndatetime.dated2 + tdd2 - tdd1 - d27800)timedelta(weeks=3) - timedelta(hours=72) == timedelta(18)timedelta(minutes=5) * 5 == timedelta(0, 1500)timedelta(weeks=1) / 7 == timedelta(1)date(2007, 12, 31) + timedelta(days=1) == date(2008, 1, 1)date(2007, 12, 31) - timedelta(weeks=52) == date(2007, 1, 1)date(2007, 1, 1) - date(2006, 1, 1) == timedelta(365)# leap year 2004 had 366 days and Python is aware of that:date(2005, 1, 1) - date(2004, 1, 1) == timedelta(366)datetime.datetimed2 + tddatetime(2007, 12, 31, 23, 59) + timedelta(minutes=1) == datetime(2008, 1, 1, 0, 0)datetime(2007, 12, 31) - timedelta(weeks=52, seconds=1) == datetime(2006, 12, 31, 23, 59, 59)datetime(2007, 1, 1) - datetime(2008, 1, 1) == timedelta(-365)d2 - tdd1 - d2不要试图用获取 2007 年 2 月 29 日来愚弄 Python,因为此日期不存在 — 解释器将引发 ValueError 异常,同时显示消息“day is out of range for month”。您也可以随时使用 datetime.datetime.today() 为当前的日期和时间获取一个 datetime 对象。

如果您需要将现有的字符串分析为 date(time) 对象,您可以使用 datetime 对象的 strptime() 方法。>>> from datetime import datetime

>>> datetime.strptime(\"2007-12-31 23:59:59\ \"%Y-%m-%d %H:%M:%S\")datetime.datetime(2007, 12, 31, 23, 59, 59)

strptime 函数的格式字符串的完整列表包含在 Python 库参考的时间模块文档中。时区

Python 通过 datetime 模块提供了对时区的支持,但它还没有马上进入自己的黄金阶段,它只是为您的实现提供了一个框架。您需要创建您自己的、从 datetime.tzinfo 继承的类,同时把需要的逻辑放在里面。Python 库参考对此主题进行了大量介绍。

TO_DATE(Python)

利用 cx_Oracle 可以使 Oracle 和 Python 间的数据类型转换变得完全可见,这一点都不奇怪。包含 DATE 或 TIMESTAMP 列的查询返回 datetime 对象。

我们来看一下 Oracle 的 INTERVAL 运算和 Python 的 timedelta 计算是否等同。>>> import cx_Oracle

>>> db = cx_Oracle.connect('hr/hrpwd@localhost:1521/XE')>>> cursor = db.cursor()

>>> r = cursor.execute(\"SELECT end_date-start_date diff, end_date, start_date FROM job_history\")

>>> for diff, end_date, start_date in cursor:... print diff, '\', (end_date-start_date).days...2018 20181497 1497? 14 14

很好!它们的确匹配。

下一示例将演示:将日期/时间逻辑放在数据库上还是 Python 中由您决定。这一灵活性使得适合任意情形成为可能。我们来查一下 1998 年第 4 季度招聘的所有员工:>>> Q4 = (datetime.date(1998, 10, 1), datetime.date(1998, 12, 31))>>> r = cursor.execute(\"\"\"

SELECT last_name||' '||first_name name, TO_CHAR(hire_date, 'YYYY-MM-DD') FROM employees

WHERE hire_date BETWEEN :1 AND :2 ORDER BY hire_date ASC

\"\"\ Q4)

>>> for row in cursor:... print row...

('Sewall Sarath', '1998-11-03')('Himuro Guy', '1998-11-15')('Cambrault Nanette', '1998-12-09')

可以放心地使用 Python 的 datetime.date、datetime.time 和 datetime.datetime 对象作为绑定变量来查询日期。您可以选择所要求的粒度等级,但要记住,在处理秒的小数时,您需要指导 cx_Oracle 让其明白传递了一个小数部分。普通查询返回带有秒的小数部分的完全有效的时间戳,这仅与使用绑定变量有关。当然,您可以结合使用 Python 的 strptime() 函数和 Oracle 的 TO_DATE(),但说实话,干嘛要自找麻烦呢?

我们来创建一个简单表,结构如下:CREATE TABLE python_tstamps ( ts TIMESTAMP(6));

下面的示例说明了这一问题。ts 有一个小数部分,它在插入 ts = datetime.datetime.now() 时被截断了:

>>> ts = datetime.datetime.now()>>> print ts

2007-03-10 20:01:24.046000

>>> cursor.execute(\"INSERT INTO python_tstamps VALUES(:t)\ {'t':ts})>>> db.commit()

SQL> SELECT ts FROM python_tstamps;

TS

---------------------------------------------------------------10-MAR-07 08.01.24.000000 PM

解决方法是在准备和执行阶段之间使用 setinputsizes() 方法。它指导 cx_Oracle 如何处理特定的绑定变量。它在内存中预先定义了一些区域以存储这些对象。也可用它来为特定长度的字符串预先分配内存区域 — 它们应当以表示其长度的整数值给出。我们来改写一下插入操作:>>> ts = datetime.datetime.now()>>> print ts

2007-03-10 20:12:02.109000

>>> cursor.prepare(\"INSERT INTO python_tstamps VALUES(:t_val)\")>>> cursor.setinputsizes(t_val=cx_Oracle.TIMESTAMP)cursor.setinputsizes(t_val=cx_Oracle.TIMESTAMP)>>> cursor.execute(None, {'t_val':ts})>>> db.commit()

SQL> SELECT ts FROM python_tstamps;TS

---------------------------------------------------------------10-MAR-07 08.01.56.000000 PM10-MAR-07 08.12.02.109000 PM

结论

日期时间上下文中应当记住的有关 cx_Oracle 4.3 的重要内容:

· 不支持 INTERVAL 和 TIMESTAMP WITH (LOCAL) TIME ZONE

· 除非在 prepare() 和 execute() 间使用了 setinputsizes() 方法,作为绑定变

量传递的日期/时间值的秒的小数部分将被截取。

· 对于时区支持,或者选择使用标准库的 datetime.tzinfo 类来编写您自己的实现,

或者选择 SourceForge 中可用的 pytz 模块 。不过要做好充分准备,因为 WITH TIME ZONE 列类型和 Python datetime 对象间没有平滑的转换。

Python 标准库提供用于日期/时间任务的其他工具,包括:

· 一个日历模块,用于以文本和 HTML 格式来显示日历,同时用于编写您自己的导

出实现

· 一个 timeit 模块,用于对 Python 代码进行概要描述和基准评测· 一个 sched 模块,它的功能与 Linux/Unix 下的 cron 实用程序相同

完成本教程后,您应当已经熟悉了负责 Oracle 和 Python 日期处理的概念。熟悉了无缝集成的 datetime 数据类型后,您现在可以将它们注入到您的可感知日历的应用程序中,把 Python 放到您的开发工具箱中。

马上就需要日历吗?导入日历;打印 calendar.calendar(2007)。

第 3 部分:数据分析

学习在 Python 中进行数据分析所采用的一些基本和高级技巧。2007 年 9 月发布

进行数据分析的理由不计其数,相关的工具和技巧也同样如此。但是,当您需要用这些数据做一些新的事情时,即使有“合适的”工具可能也是不够的。这一担心对于异类数据源的集成同样存在。用来做这项工作的合适工具迟早应该是一种编程语言。

Oracle 提供了一些非常强大的实用程序来加载、处理和卸载数据。SQL*Loader、数据泵、外部表、Oracle Text 和正则表达式都能提供这些功能。然而人们常常会需要在数据库外做一些事情(或者,说得琐碎些,可能您还没有获得必要的数据库权限)。

利用 Python 可以进行高水平的、有效的数据分析。而利用互联网上免费提供的大量标准库和众多模块可以处理数据逻辑,不必手动剖析字节。

字符串理论

文本分析的最低级别是字符串。Python 并不把字符区分为单独的数据类型,但却区分普通字符串和 Unicode 字符串类型。它们可以包含在单引号、双引号或三重引号内,并且是 Python 的一种不变对象 — 一旦创建就不能对其进行修改。每一个操作都会创建一个新的字符串对象。对于具有静态类型语言经验的编程人员而言,乍听上去这可能真得很奇怪,但此类实现有 一些特定的原因,多数与性能有关。因为 Python 完全支持 Unicode,所以处理多语言信息不存在问题。在手动创建 Unicode 字符串时,您可以选择直接在字符串前使用 u 前缀(如 u\"Unicode text\")或者使用内置的 unicode() 函数。可以使用 unicode() 或 encode() 方法在任何支持的字符集中对字符串进行编码。有关支持的编码列表,请查阅 Python 库参考 的标准编码部分或使用导入编码;打印 encodings._aliases.keys()。您可以放心地使用 UTF-8 编写 Python 程序,记住仅变量名必须是有效的 ASCII 字符串。注释可以是希腊文、汉字或任意内容。不过,这样的文件或者要求使用附加字节顺序标记 (BOM) 的编辑器来保存,

或者,要求您来编写第一行代码:# -*- coding: utf-8 -*-

字符串提供有一组方法可用于进行大多数有用的文本操作,如 find()、split()、rjust() 或 upper()。它们在内置的 str 类型上实现,该类型可以表示普通字符串和 raw 字符串。(Raw 字符串与普通字符串对反斜线的解释不同。)>>> zen = \"Explicit is better than implicit.\">>> print zen.title()

'Explicit Is Better Than Implicit.'>>> zen.split(' ')

['Explicit', 'is', 'better', 'than', 'implicit.']

>>> zen.rstrip('.').lower().replace('is', 'is always')'explicit is always better than implicit'

Python 的 iterable 类型的最棒的一个特性是索引方法。普通索引以 0 开始而负索引向后计数,所以 [-1] 表示最后一个字符,[:5] 表示前 5 个字符,而 [5:-5] 表示前 5 个和后 5 个字符组成的字符串。

>>> sample = \"Oracle Database\">>> sample[0]'O'

>>> sample[0:6], sample[7:15]('Oracle', 'Database')>>> sample[-8:]'Database'

>>> sample[sample.index('Data')+4:]'base'正则表达式

Python 当然支持正则表达式。事实上,Python 的正则表达式 re 模块支持 Unicode、匹配、搜索、拆分、替换和分组。如果您熟悉 Oracle 对正则表达式的实现方式,您就不会对 Python 的函数感到陌生。

在详细比较 Python 和 Oracle 对正则表达式的实现时,值得注意的差异包括:

· 当关系设计要求一种不同于编程语言 1 的方法时,re.search() 可用于代替

Oracle 的 REGEXP_LIKE、REGEXP_INSTR 和 REGEXP_SUBSTR。

· 对 Python 语法改写后,re.sub() 的使用方式可以与 REGEXP_REPLACE 完全相同。

不过,要注意 Oracle 的位置参数从 1 开始,而 Python 编制任何索引时都从 0 开始。

· Oracle 的 match_parameter 表示正则表达式的一组标志,方式与 Python 在搜

索模式或模式对象编译属性中使用 (?iLmsux) 语法的方式相同。要获得有效标志的列表,请比较 Python 库参考 的 4.2.3 节 和 Oracle 数据库 SQL 语言参考 中 match_parameter 的有效值列表。

Python 的 re.search() 函数非常灵活,这归功于正则表达式这一基本概念。re 模块的最底层有一个对

象,它表示匹配模式的方式允许以多种不同的方法对源字符串进行剖析。re.compile() 函数返回一个采用某一模式和若干可选标志的编译模式对象,如 re.I,它表示不区分大小写的匹配。>>> import re

>>> p = re.compile(\"^a.*\ re.I)>>> print p

<_sre.SRE_Pattern object at 0x011CA660>

您无须显式编译正则表达式。re 模块中的函数以透明方式完成此工作。如果代码中多处用到编译模式,使用该模式非常有益,但是如果该模式仅使用一次则不需要这样的编码开销。

Python 中有 6 个正则表达式编译标志:

· I (IGNORECASE) 用于不区分大小的匹配

· L (LOCALE) 使得特殊的序列(如词和空格)与语言设置相关

· M (MULTILINE) 意味着在多行中搜索该模式,这样 ˆ 可以匹配字符串的开始位置

和每一个换行符后面的位置,$ 可以匹配每一个换行符前面的位置和字符串的结束位置

· S (DOTALL) 强制使用点专用字符 (.) 匹配任意字符,包括换行符· U (UNICODE) 使得特殊的序列可以感知 Unicode· X (VERBOSE) 可以增强您编写的正则表达式的可读性。

要一次使用多个标志,只需将它们加在一起即可 — 如 re.compile(\"Oracle\

re.I+re.S+re.M)。另一种方式是使用 (?iLmsux) 语法将使用所需数量的标志选项作为搜索模式的前缀。这样,前一表达式可写作 re.compile(\"(?ism)Oracle\")。

有关使用正则表达式的最好建议是尽可能地避免使用它们。在将它们嵌入代码前,请确定没有字符串方法可以完成相同的工作,因为字符串方法更快同时不会带来导入以及正则表达式处理这些额外的开销。在字符串对象上使用 dir() 就可以看到可用的内容。

下例展示了在 Python 这样一种动态语言中看待正则表达式的方式。分析 tnsnames.ora 文件以便为每个网络别名创建 Easy Connect 字符串(将 file() 函数指向您的 tnsnames.ora 文件的位置):>>> import re

>>> tnsnames = file(r'tnsnames.ora').read()>>> easy_connects = {}

>>> tns_re = \"^(\\w+?)\\s?=.*?HOST\\s?=\\s?(.+?)\\).*?PORT\\s?=\\s?(\\d+?)\\). *?SERVICE_NAME\\s?=\\s?(.+?)\\)\"

>>> for match in re.finditer(tns_re, tnsnames, re.M+re.S):... t = match.groups()

... easy_connects[t[0]] = \"%s:%s/%s\" % t[1:]>>> print easy_connects

此程序在 Oracle 数据库 XE 默认的 tnsnames.ora 文件上的输出是:

{'XE': 'localhost:1521/XE'}

请注意此正则表达式的静默程度足以为 IPC 条目所阻塞,因此需要把它们放在文件的未尾。分析匹配圆括号是一个 NP 完成问题。

因为提供有多种公开方法,Python 匹配对象的功能非常强大,这些方法包括 span()(它可以返回匹配范围)、group()(它可以按给定的索引返回匹配组)以及 groupdict()(它可以在模式含有命名的组时以字典形式返回匹配组)。

逗号分隔值

CSV 格式因其简洁性和跨平台设计常用于组织间的信息交换。使用正则表达式通常可以轻松地分析逗号分隔值,但使用 Python 的 csv 模块可以使此任务变得更为容易。

使用该模块要求开发人员熟悉该模块所采用的逻辑。有关 CSV 文件的最重要的信息是它的“方言”,它包含分隔符、引号字符、行终结符等相关信息。Python 2.5 中目前可用的方言是 excel 和 excel-tab。内置的嗅探器总是试图猜测正确的格式。写入器与阅读器对象支持 CSV 数据的输入和输出。就本例而言,我用的是 HR 模式的 JOBS_HISTORY 表中的数据。它演示了如何直接从一个 SQL 查询创建 CSV 文件 job_history.csv。

>>> import csv>>> import cx_Oracle

>>> db = cx_Oracle.connect('hr/hrpwd@localhost:1521/XE')>>> cursor = db.cursor()

>>> f = open(\"job_history.csv\ \"w\")

>>> writer = csv.writer(f, lineterminator=\"\\n\ quoting=csv.QUOTE_NONNUMERIC)

>>> r = cursor.execute(\" \"SELECT * FROM job_history ORDER BY employee_id, start_date\")

>>> for row in cursor:... writer.writerow(row)...

>>> f.close()

该文件包含:

101,\"19-09-21 00:00:00\1993-10-27 00:00:00\AC_ACCOUNT\110101,\"1993-10-28 00:00:00\1997-03-15 00:00:00\AC_MGR\110102,\"1993-01-13 00:00:00\1998-07-24 00:00:00\IT_PROG\60114,\"1998-03-24 00:00:00\1999-12-31 00:00:00\ST_CLERK\50122,\"1999-01-01 00:00:00\1999-12-31 00:00:00\ST_CLERK\50176,\"1998-03-24 00:00:00\1998-12-31 00:00:00\SA_REP\80176,\"1999-01-01 00:00:00\1999-12-31 00:00:00\SA_MAN\80200,\"1987-09-17 00:00:00\1993-06-17 00:00:00\AD_ASST\90200,\"1994-07-01 00:00:00\1998-12-31 00:00:00\AC_ACCOUNT\90201,\"1996-02-17 00:00:00\1999-12-19 00:00:00\MK_REP\20

或者,您可以使用 Oracle SQL Developer 将数据以 CSV 格式导出。

要读取 CSV 文件,使用:

>>> reader = csv.reader(open(\"job_history.csv\ \"r\"))

>>> for employee_id, start_date, end_date, job_id, department_id in reader:... print job_id,...

JOB_ID IT_PROG AC_ACCOUNT AC_MGR MK_REP ST_CLERK ST_CLERK AD_ASST SA_REP SA_MAN AC_ACCOUNT

注意我不必在上面显式指定方言,它是自动得出的。我只是打印了 job_id 列,但针对这一分析过的文件我真正可以做的是将其插入数据库中。为确保日期得到正确处理,在批量插入前对 NLS_DATE_FORMAT 进行手动设置。

SQL> CREATE TABLE job_his (

2 employee_id NUMBER(6) NOT NULL, 3 start_date DATE NOT NULL, 4 end_date DATE NOT NULL, 5 job_id VARCHAR2(10) NOT NULL, 6 department_id NUMBER(4) 7 );

>>> reader = csv.reader(open(\"job_history.csv\ \"r\"))>>> lines = []

>>> for line in reader:... lines.append(line)...

>>> cursor.execute(\"ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'\")>>> cursor.executemany(\"INSERT INTO job_his VALUES(:1,:2,:3,:4,:5)\ lines)>>> db.commit()

如果您使用 SQL Developer 创建了 CSV 文件,您可能需要修改日期格式,如下所示:

>>> cursor.execute(\"ALTER SESSION SET NLS_DATE_FORMAT = 'YY/MM/DD'\")

csv 模块美中不足的地方是缺乏自身的 Unicode 支持。有关解决方案和使用 CSV 文件的更多示例,请参见 Python 库参考 的 9.1.5 示例部分 。URL

urlparse 模块使您可以将统一资源定位器字符串分解为各个组件,分别表示 URL 模式、网

络位置、路径、参数、查询字符串、碎片标识符、用户名、口令、主机名和/或端口。Python 2.5 支持 24 个最常用的模式,包括 svn+ssh、sftp 和 mms。下例显示了 urlparse 模块的一些特性:

>>> from urlparse import urlparse

>>> url = \"http://www.oracle.com/technology/index.html?rssid=rss_otn_news#section5\">>> pr = urlparse(url)>>> print type(pr)

>>> print pr.hostnamewww.oracle.com>>> print pr.queryrssid=rss_otn_news

>>> print url==pr.geturl()True

RSS 新闻提供

RSS 基于一个非常简单的概念:您在事件发生时获得它的最新消息,而不是碰巧发现。整合来自许多不同来源的 RSS 新闻提供是开发领域的一个流行趋势,对新闻提供聚合器和 Web 2.0 混搭尤其如此。

RSS 是 XML 的一种方言,因此使用 Python 提供的一种 XML 分析器可轻松地对其进行处理。Python 标准库本身还没有提供用于分析新闻提供的模块。不过,feedparser.org 免费提供一个稳定的、经过广泛检验的通用新闻提供分析器。由于它没有外部依赖性,因此这是快速熟悉模块安装概念的一个很好的机会。

下载 feedparser 模块的最新版本(撰写此文时为 4.1)后,对其进行解压缩并将工作目录修改为 feedparser-4.1。在控制台/命令提示符下,运行 python setup.py install。此命令将模块放入 Python 文件夹,使其立即可供使用。就这样了。了解一下 Oracle 的动态如何?>>> import feedparser>>> import time

>>> rss_oracle =

feedparser.parse(\"http://www.oracle.com/technology/syndication/rss_otn_news.xml\")>>> for e in rss_oracle.entries[:5]:

.. t = time.strftime(\"%Y/%m/%d\ e.updated_parsed).. print t, e.title

2007/07/23 Integrating Oracle Spatial with Google Earth

2007/07/11 Oracle Database 11g Technical Product Information Now Available2007/07/11 Explore the Oracle Secure Enterprise Search Training Center2007/07/09 Implementing Row-Level Security in Java Applications

2007/06/29 Build Your Own Oracle RAC Cluster on Oracle Enterprise Linux and iSCSI

feedparser 模块的智能程度相当高,可以正确地分析日期、处理 HTML 标记、规范内容以便为所有支持的 RSS 和 ATOM 变体使用一致的 API、解析相对链接、检测有效的字符编码等等。

接下来分析什么?

有了正则表达式工具箱,您可以搜索几乎所有的纯文本内容。至于分析文本数据,Python 有很多其他特性,包括:

· email.parse,用于分析电子邮件消息

· ConfigParser,用于分析从 Windows 系统中获得的 INI 配置文件· robotparser 模块,用于分析您 Web 站点的 robots.txt· optparse 模块,用于进行强大的命令行参数分析

· HTMLParse 模块中的 HTMLParse 类,用于有效地分析 HTML 和 XHTML(类似于

SAX)

· 若干 XML 分析器

(xml.dom、xml.sax、xml.parsers.expat、xml.etree.ElementTree)

对于二进制数据,您可以利用 binascii 模块,它包含一组函数用于在二进制数据和 ASCII 编码数据之间进行转换,另外还有 base 和 uu 模块,分别用于 base 和 uuencode 转换。

结论

这篇方法文档介绍了在 Python 中进行数据分析所采用的一些基本和高级的技巧。您现在应当已经认识到了 Python 附带的标准库的威力。在开始烹制您自己的分析器之前,首先检查一下所需的功能是否已可供导入。

字符串操作比正则表达式操作速度快,同时足以满足很多的编程需要。但是到底选用 Python 还是 Oracle 正则表达式函数取决于您的应用程序逻辑和业务需要。

第 4 部分:事务和大型对象

通过 Python 管理数据事务、处理大型对象2010 年 3 月发布

事务包含一组 SQL 语句,这组 SQL 语句构成数据库中的一个逻辑操作,如转帐或信用卡支付操作。将 SQL 语句聚合到一个逻辑组中,其效果完全取决于事务的成败,事务成功则提交更改,事务失败则撤销内部 SQL 的结果(整体撤消)。通过 Python,您可以利用 Oracle 数据库所提供的原子性、一致性、孤立性和持久性优势。

利用大型对象,可在一列中保存大量数据(从 Oracle Databaase 11g 起该数量可达到 128TB),但这种灵活性是要付出代价的 — 用于访问和操作 LOB 的方法不同于常规查询方法。

注意:Python 的 2.x 版本已升级到 2.6,cx_Oracle 模块已发展到 5.0。从现在起,在 MO+P 中将使用这些版本。此外,本教程依旧基于可用于 Oracle Database 10g 第 2 版快捷版中的 HR 模式。这就是 ACID

一个数据库事务是一些语句组成的一个逻辑组,具有以下四个特征:原子性:所有操作要么全部成功,要么全部失败一致性:提交事务不会导致数据损坏孤立性:其他事务始终不知道此事务的执行

持久性:即使在数据库崩溃的情况下,事务中提交的操作也将持续有效。

Python Oracle 数据库 API 提供了一种处理事务的自然方式,可将对数据的逻辑操作存储在数据库回滚段中以等待最终的决定:是提交还是回滚整组语句。

当第一条 SQL 语句通过 cursor.execute() 方法传给数据库时,一个事务就启动了。当没有其他事务已从该会话启动时,可以使用 db.begin() 方法显式启动一个新事务。为了获得最高一致性,当连接对象被关闭或删除时,cx_Oracle 会默认回滚所有事务。

cx_Oracle.Connection.autocommit 属性仍可设置为 1,从而使 Oracle 可提交通过 cursor.execute* 系列方法发出的每条语句。开发人员还应知道,由于 Oracle 的 DDL 不是事务性的,所有 DDL 语句都会隐式提交。最后,与 SQL*Plus 相反,用 db.close() 关闭连接不会提交正在进行的事务。

有一些 Oracle 事务语句包装在 cx_Oracle 中,如 db.begin()、db.commit() 和

db.rollback(),但您可通过显式地调用 SET TRANSACTION 语句来使用其余事务语句。无论如何,新的事务从会话中的首个 DML 开始,因此没必要显式进行,除非您需要利用特定的事务属性,如 SET TRANSACTION [ READ ONLY | ISOLATION LEVEL SERIALIZABLE ]。我们来看一个执行具有事务特征的 HR 操作的类:import cx_Oracle class HR:

def __enter__(self):

self.__db = cx_Oracle.Connection(\"hr/hrpwd@localhost:1521/XE\") self.__cursor = self.__db.cursor() return self

def __exit__(self, type, value, traceback): self.__db.close()

def swapDepartments(self, employee_id1, employee_id2): assert employee_id1!=employee_id2

select_sql = \"\"\"select employee_id, department_id from employees where employee_id in (:1, :2)\"\"\"

update_sql = \"update employees set department_id=:1 where employee_id=:2\"

self.__db.begin()

self.__cursor.execute(select_sql, (employee_id1, employee_id2)) D = dict(self.__cursor.fetchall())

self.__cursor.execute(update_sql, (D[employee_id2], employee_id1)) self.__cursor.execute(update_sql, (D[employee_id1], employee_id2)) self.__db.commit()

def raiseSalary(self, employee_ids, raise_pct):

update_sql = \"update employees set salary=salary*:1 where employee_id=:2\"

self.__db.begin()

for employee_id in employee_ids: try:

self.__cursor.execute(update_sql, [1+raise_pct/100, employee_id]) assert self.__cursor.rowcount==1 except AssertionError: self.__db.rollback()

raise Warning, \"invalid employee_id (%s)\" % employee_id

self.__db.commit()

if __name__ == \"__main__\": with HR() as hr:

hr.swapDepartments(106, 116) hr.raiseSalary([102, 106, 116], 20)

上述代码中定义了两个方法:一个方法可在指定的员工之间互换部门,另一个方法可为任意数量的员工加薪。为了安全起见,此类操作只能通过事务实现。为确保不会发生其他事务,这两个方法中显式调用了 self.__db.begin()(否则会引发 DatabaseError ORA-01453 错误)。双下划线前缀在 Python 中具有特殊含义,因为该语言实际上不允许您声明私有变量(类中一切皆为公有),从而增加了变量的访问难度:变量是通过 _Class__Member 语法(上例中的 _HR__db 和 _HR_cursor)公开的。同时,我们在该类中声明 __enter__ 和 __exit__ 方法,这样我们可以使用 WITH 语句 ,在 WITH 代码块结束时会自动关闭连接。除了通过 db.commit() 和 db.rollback() 语句执行 DCL 语句的标准方式外,还可使用 cursor.execute() 方法运行原始的 SQL 命令(例如,为了能够使用保存点)。使用

cursor.execute('ROLLBACK') 和 db.rollback() 除以下之外并无任何不同:前者可以带额外的参数,如 cursor.execute('ROLLBACK TO SAVEPOINT some_savepoint')。SQL 方法还需要对命令像一般 SQL 语句一样解析和执行,而 db.commit() 和 db.rollback() 方法映射到一个低级 API 调用并允许 cx_Oracle 驱动程序跟踪事务状态。大型对象 (LOB)

提到 Oracle 数据库的表列可用的数据类型,VARCHAR2 最多只能存储 4000 个字节的值。大型对象以其存储大型数据(如文本、图像、视频和其他多媒体格式)的能力而适用于大容量存储的情况。并且以您几乎不能称之为有限的存储能力而适用于这种情况 — 一个 LOB 的最大容量可高达 128 TB(自 11g 第 2 版开始)。

在 Oracle 数据库中可以使用几种类型的 LOB:二进制大型对象 (BLOB)、字符大型对象 (CLOB)、国家字符集大型对象 (NCLOB) 和外部二进制文件 (BFILE)。最后这种 LOB 用于以只读模式访问外部操作系统文件,而所有其他类型的 LOB 能以永久模式或临时模式在数据库中存储大量数据。每个 LOB 包含一个实际值和一个指向该值的小型定位器。传递 LOB 通常只是意味着传递 LOB 定位器。

在任何给定时间,一个 LOB 只能处于以下三种已定义状态之一:NULL、empty 或

populated。这类似于其他 RDBMS 引擎中常规 VARCHAR 列的行为(empty 字符串不等同于 NULL)。最后,对 LOB 有几个,其中的主要是:LOB 不能是主键

LOB 不能是集群的一部分

LOB 不能与 DISTINCT、ORDER BY 和 GROUP BY 子句一起使用

Oracle Database Application Developer's Guide 中提供了有关大型对象的大量文档资料。Python 的 cx_Oracle 模块支持对所有类型 LOB 的访问:

>>> [i for i in dir(cx_Oracle) if i.endswith('LOB') or i=='BFILE'] ['BFILE', 'BLOB', 'CLOB', 'LOB', 'NCLOB']

cx_Oracle 中针对所有那些不能自动断定其长度或类型的 Oracle 类型提供了一种特殊的数据类型。该数据类型专门用于处理存储过程的 IN/OUT 参数。我们来看一个变量对象,它填充了 1 MB 的数据(将一个哈希字符重复 2 的 20 次方):

>>> db = cx_Oracle.Connection('hr/hrpwd@localhost:1521/XE') >>> cursor = db.cursor()

>>> clob = cursor.var(cx_Oracle.CLOB) >>> clob.setvalue(0, '#'*2**20)

为了在 Python 中创建一个 LOB 对象,我们将一个 cx_Oracle.CLOB 类型传递给了该 Variable 对象的构造函数,该对象提供两个基本方法(另外还有别的方法):getvalue(pos=0) 用于获取给定位置(默认为 0)的值setvalue(pos, value) 用于在给定位置设置值

为了进行一些 LOB 试验,我们使用下面的 DDL 创建如下所列的一些对象:

CREATE TABLE lobs ( c CLOB, nc NCLOB, b BLOB, bf BFILE);

CREATE VIEW v_lobs ASSELECT ROWID id, c,

nc, b, bf,

dbms_lob.getlength(c) c_len, dbms_lob.getlength(nc) nc_len, dbms_lob.getlength(b) b_len, dbms_lob.getlength(bf) bf_lenFROM lobs;

在本示例中,BFILE 定位器将指向 /tmp 目录中的一个文件。以 SYSTEM 用户身份运行:

create directory lob_dir AS '/tmp';grant read on directory lob_dir to HR;

最后,使用编辑器创建 /tmp/example.txt 文件,其中包含您所选的任何虚拟文本。为了深入了解用于 LOB 表的默认的表创建选项,试着用 DBMS_METADATA.GET_DDL 过程生成全部 DDL:SET LONG 5000SELECT

DBMS_METADATA.GET_DDL('TABLE', TABLE_NAME)FROM USER_TABLES

WHERE TABLE_NAME = 'LOBS';

输出结果中有两个有趣的参数值得一瞧:

ENABLE STORAGE IN ROW 指示 Oracle 在 LOB 数据不超过 4000 个字节减去系统元数据这个大小时,尝试将该 LOB 数据与表数据放在一起(相反的参数是 DISABLE STORAGE IN ROW)。

CHUNK 确定处理 LOB 时分配的字节数(在 Python 中可通过 cx_Oracle.LOB.getchunksize() 方法访问该信息)。创建表时可使用这些选项对其行为和性能进行调优。

以下代码是使用大型对象的一个更为完整的示例。其中显示了四种不同类型的 LOB 以及用于将 LOB 插入数据库或从数据库选择 LOB 的四个方法。# -*- coding: utf8 -*- import cx_Oracleimport operatorimport os

from hashlib import md5from random import randint

class LobExample: def __enter__(self):

self.__db = cx_Oracle.Connection(\"hr/hrpwd@localhost:1521/XE\") self.__cursor = self.__db.cursor() return self

def __exit__(self, type, value, traceback):

# calling close methods on cursor and connection - # this technique can be used to close arbitrary number of cursors map(operator.methodcaller(\"close\"), (self.__cursor, self.__db))

def clob(self):

# populate the table with large data (1MB per insert) and then

# select the data including dbms_lob.getlength for validating assertion self.__cursor.execute(\"INSERT INTO lobs(c) VALUES(:1)\ [\"~\"*2**20]) self.__cursor.execute(\"SELECT c, c_len FROM v_lobs WHERE c IS NOT NULL\") c, c_len = self.__cursor.fetchone() clob_data = c.read() assert len(clob_data)==c_len self.__db.rollback()

def nclob(self):

unicode_data = u\"€\"*2**20

# define variable object holding the nclob unicode data nclob_var = self.__cursor.var(cx_Oracle.NCLOB) nclob_var.setvalue(0, unicode_data)

self.__cursor.execute(\"INSERT INTO lobs(nc) VALUES(:1)\ [nclob_var]) self.__cursor.execute(\"SELECT nc, nc_len FROM v_lobs WHERE nc IS NOT NULL\") nc, nc_len = self.__cursor.fetchone()

# reading only the first character just to check if encoding is right nclob_substr = nc.read(1, 1) assert nclob_substr==u\"€\" self.__db.rollback()

def blob(self):

# preparing the sample binary data with random 0-255 int and chr function binary_data = \"\".join(chr(randint(0, 255)) for c in xrange(2**2)) binary_md5 = md5(binary_data).hexdigest() binary_var = self.__cursor.var(cx_Oracle.BLOB) binary_var.setvalue(0, binary_data)

self.__cursor.execute(\"INSERT INTO lobs(b) VALUES(:1)\ [binary_var]) self.__cursor.execute(\"SELECT b FROM v_lobs WHERE b IS NOT NULL\") b, = self.__cursor.fetchone() blob_data = b.read()

blob_md5 = md5(blob_data).hexdigest()

# data par is measured in hashes equality, what comes in must come out assert binary_md5==blob_md5 self.__db.rollback()

def bfile(self):

# to insert bfile we need to use the bfilename function

self.__cursor.execute(\"INSERT INTO lobs(bf) VALUES(BFILENAME(:1, :2))\ [\"LOB_DIR\ \"example.txt\"])

self.__cursor.execute(\"SELECT bf FROM v_lobs WHERE bf IS NOT NULL\") # selecting is as simple as reading other types of large objects bf, = self.__cursor.fetchone() bfile_data = bf.read() assert bfile_data self.__db.rollback()

if __name__ == \"__main__\": with LobExample() as eg: eg.clob() eg.nclob() eg.blob() eg.bfile()

该文件包含 UTF-8 字符,因此第一行包含 Python 的源代码编码声明 (PEP-0263)。为确保

以该编码方式将数据传输给数据库,应将环境变量 NLS_LANG 设置为“.AL32UTF8”。这向 Oracle Client 库指明了应使用哪个字符集。该变量的设置应在 Oracle Client 初始化其内部数据结构之前进行,但不能保证发生在程序的哪个特定点。为安全起见,最好在调用该程序的 shell 环境中设置该变量。源代码中包含了其他一些解释和注释。

这里要注意几点。就 NCLOB 示例而言,unicode_data 不能用作绑定变量,因为如果它超过 4000 个字符,会引发“ValueError:unicode data too large”异常。BLOB 示例中将出现类似的问题。如果我们不使用 binary_var,则会引发“DatabaseError:ORA-01465:invalid hex number”异常,因为必须显式地声明绑定的内容。

通过 LOB 参数(IN 或者 OUT)调用存储过程还需要使用 cx_Oracle 的 Variable 对象,但这是本系列另一部分要讨论的内容。总结

在本教程中,您已经了解 Python 环境中有关事务处理和大型对象处理的各个方面。现在您应该已经熟悉了 cx_Oracle 模块在事务封装和访问所有四种 LOB 以及应对 UTF-8 编码需求这些方面的特点。

第 5 部分:存储过程、Python 编程

调用数据库存储过程及其他感兴趣的高级 Python 编程功能。2010 年 3 月发布

对于涉及数据库的软件开发来说,有两种主流开发方法:一种是在应用程序中(对于三层体系结构,也可以是在中间件中)实现所有业务逻辑,另一种是在数据库内部实现所有业务逻辑。本教程不讨论这两种解决方案的优缺点;不过,使用 Oracle 数据库方法还是会为面向数据库的应用程序带来某些好处。

用 PL/SQL 嵌入所有业务逻辑可大大减少应用程序与数据库之间的往返次数,从而此使处理都在服务器端进行。PL/SQL 与 SQL 紧密集成,并与 Python 类似,提供了大量的标准程序包库:从安排数据库作业时间 (DBMS_SCHEDULER),到自动查询调优 (DBMS_SQLTUNE) 和闪回 (DBMS_FLASHBACK),再到线性代数 (UTL_NLA) 和 LDAP 访问 (DBMS_LDAP)。

本教程介绍使用 cx_Oracle 模块在 Python 中调用 Oracle 数据库内部的 PL/SQL 存储过程和函数的各种方法,同时还介绍一些使用 PL/SQL 无法实现或者实现起来非常复杂的编程功能。在本文的最后,我们将简单介绍 Oracle Berkeley DB,它作为即取即用插件内置在 Python 中。IN-OUT 方式

Oracle 过程和函数是将 SQL 功能与编程语言功能相结合一些数据库对象。过程(从现在开始也称其为函数)的参数可以是以下三种类型之一:IN:传递给过程,但不能写入到过程内部OUT:从过程返回,在过程主体内部可写IN OUT:传递给过程,在过程内部完全可写

默认情况下,参数都是 IN 类型。

为了说明 Python 和 Oracle 过程之间的交互,我们考虑将以下程序包安装在 Oracle Database XE 实例的 HR 模式中。 CREATE OR REPLACE PACKAGE pkg_hr AS

PROCEDURE add_department( p_department_id OUT NUMBER, p_department_name IN VARCHAR2, p_manager_id IN NUMBER, p_location_id IN NUMBER );

FUNCTION get_employee_count( p_department_id IN NUMBER ) RETURN NUMBER;

PROCEDURE find_employees( p_query IN VARCHAR2, p_results OUT SYS_REFCURSOR );

END pkg_hr;/

CREATE OR REPLACE PACKAGE BODY pkg_hr AS

PROCEDURE add_department( p_department_id OUT NUMBER, p_department_name IN VARCHAR2, p_manager_id IN NUMBER, p_location_id IN NUMBER ) AS BEGIN

INSERT INTO departments(department_id,,,

department_name manager_idlocation_id)

VALUES (departments_seq.nextval, p_department_name, p_manager_id, p_location_id)

RETURNING department_id INTO p_department_id;

COMMIT;

END add_department;

FUNCTION get_employee_count( p_department_id IN NUMBER ) RETURN NUMBER AS l_count NUMBER; BEGIN

SELECT COUNT(*) INTO l_count FROM employees

WHERE department_id= p_department_id;

RETURN l_count; END get_employee_count;

PROCEDURE find_employees( p_query IN VARCHAR2, p_results OUT SYS_REFCURSOR ) AS BEGIN

OPEN p_results FOR SELECT * FROM employees

WHERE UPPER(first_name||' '||last_name||'UPPER(p_query)||'%'; END find_employees;

'||email) LIKE '%'|| END pkg_hr; /

上面的示例中引入了三种不同的访问方法:一个带有 IN 参数和 OUT 参数的过程、一个返回数字的函数以及一个带有 OUT REF CURSOR 参数的过程。每个过程需要不同的调用方法,如下所示。 import cx_Oracle class HR:

def __enter__(self):

self.__db = cx_Oracle.Connection(\"hr/hrpwd@//localhost:1521/XE\") self.__cursor = self.__db.cursor() return self

def __exit__(self, type, value, traceback): self.__cursor.close() self.__db.close()

def add_department(self, p_department_name, p_manager_id, p_location_id): l_department_id = self.__cursor.var(cx_Oracle.NUMBER) self.__cursor.callproc(\"PKG_HR.ADD_DEPARTMENT\

[l_department_id, p_department_name, p_manager_id, p_location_id])

# there are no OUT parameters in Python, regular return here return l_department_id

def get_employee_count(self, p_department_id):

l_count = self.__cursor.callfunc(\"PKG_HR.GET_EMPLOYEE_COUNT\ cx_Oracle.NUMBER, [p_department_id]) return l_count

def find_employees(self, p_query):

# as it comes to all complex types we need to tell Oracle Client # what type to expect from an OUT parameter l_cur = self.__cursor.var(cx_Oracle.CURSOR)

l_query, l_emp = self.__cursor.callproc(\"PKG_HR.FIND_EMPLOYEES\ [p_query, l_cur])

return list(l_emp)

从上面的示例可以看到,通过一些基本规则规定了从 Python 调用存储过程的方法:使用 cx_Oracle.Cursor.callproc(proc, [params]) 调用过程,使用

cx_Oracle.Cursor.callfunc(proc, returnType, [params]) 调用函数。需要预先定义函数的返回类型 — get_employee_count() 方法声明了从 PKG_HR.GET_EMPLOYEE_COUNT 返回的类型为 cx_Oracle.NUMBER。

使用 cx_Oracle 变量对象作为 callproc/callfunc 调用的参数可以返回类似 REF CURSOR 这样的复杂类型。使用 arrayvar 传递数组

cx_Oracle 中 DB API 2.0 的另一个扩展允许在存储过程调用中使用数组作为参数。当前支持使用 INDEX BY 子句的 PL/SQL 数组。作为一个使用 arrayvar 对象的示例,要确保下面的 DDL 找到自己进入数据库的道路。CREATE OR REPLACE PACKAGE pkg_arrayvar AS

TYPE num_array IS TABLE OF NUMBER INDEX BY PLS_INTEGER; FUNCTION sum(p_list IN NUM_ARRAY) RETURN NUMBER;END pkg_arrayvar;/

CREATE OR REPLACE PACKAGE BODY pkg_arrayvar AS

FUNCTION sum(p_list IN NUM_ARRAY) RETURN NUMBER AS l_sum NUMBER := 0; BEGIN

FOR i IN 1..p_list.COUNT LOOP l_sum := l_sum+p_list(i); END LOOP i;

RETURN l_sum; END sum;

END pkg_arrayvar;/

现在,对 Python 对象的声明和对函数的实际调用如下(后跟一个断言来验证结果):>>> db = cx_Oracle.connect(\"hr/hrpwd@//localhost:1521/XE\")>>> cursor = db.cursor()

>>> L = cursor.arrayvar(cx_Oracle.NUMBER, [1, 2, 3])

>>> sum_result = cursor.callfunc(\"pkg_arrayvar.sum\ cx_Oracle.NUMBER, [L])>>> assert sum_result==6

上面提到过,在 Python 中调用函数需要显式声明返回类型,这可能会造成混淆,因为 callproc() 只需要两个参数,但是这是必经之路。转到 Python

PL/SQL 是一种功能强大的语言,它尽可能地结合了 Oracle 数据库的功能,可显著减少开发工作量,并使您可以利用数据库的大多数特性。但某些编程功能无法通过 PL/SQL 的数据库中固有特性表达,或者无法通过 PL/SQL 的数据库中固有特性使用。因此,需要用其他编程语言对其进行补充时,Python 是一个不错的选择,它可以缩短开发时间,加快开发完成。Multiprocessing

从 2.6 版开始,Python 中的并行处理不再受 GIL(全局解释器锁)的。随标准库一起提供的 threading 模块被限定为一次只运行一个操作。通过用操作系统进程替换线程,现在可将所有 CPU 提供给应用程序,因此能够真正执行并行计算。Multiprocessing 模块让应用程序生成新的进程、锁定对象、在内存享对象,而且,所有这些既可本地进行,也可远程(不同的计算机上)进行。

下面是一个简单数据库基准测试实用程序的示例。import cx_Oracleimport osimport time

from multiprocessing import Poolfrom optparse import OptionParser

def benchmark(options):

params = eval(options.bind) if options.bind else {} with cx_Oracle.connect(options.db) as db: try:

cursor = db.cursor() before = time.clock()

for i in xrange(options.requests):

cursor.execute(options.sql, params) return (time.clock()-before)/options.requests except KeyboardInterrupt: pass finally:

cursor.close()

class Orabench:

def __init__(self, options): self.options = options

print \"Requests=%d, Concurrency=%d\" % (self.options.requests, self.options.concurrency)

def run(self):

pool = Pool(processes=self.options.concurrency)

result = pool.map_async(benchmark, [self.options]*self.options.concurrency) L = result.get() avg = sum(L)/len(L)

print \"Average=%.4f (%.4f requests per second)\" % (avg, 1/avg)

if __name__ == \"__main__\": opt = OptionParser()

opt.add_option(\"-d\ \"--database\ help=\"EZCONNECT string\ action=\"store\ type=\"string\ dest=\"db\")

opt.add_option(\"-n\ \"--requests\ help=\"number of requests\ action=\"store\ type=\"int\ dest=\"requests\ default=10)

opt.add_option(\"-c\ \"--concurrency\ help=\"number of concurrent connections\ action=\"store\ type=\"int\ dest=\"concurrency\ default=1) opt.add_option(\"-s\ \"--sql\ help=\"SQL query or PL/SQL block\ action=\"store\ type=\"string\ dest=\"sql\")

opt.add_option(\"-b\ \"--bind\ help=\"dictionary of bind parameters\ action=\"store\ type=\"string\ dest=\"bind\") (options, args) = opt.parse_args() bench = Orabench(options)

bench.run()

利用 optparse 模块(该模块可以很好地解析命令行参数),该工具在使用“--help”开关参数时会自动生成使用说明。

pp@oel:~$ python26 orabench.py --helpUsage: orabench.py [options] Options:

-h, --help show this help message and exit -d DB, --database=DB EZCONNECT string -n REQUESTS, --requests=REQUESTS number of requests -c CONCURRENCY, --concurrency=CONCURRENCY

number of concurrent connections -s SQL, --sql=SQL SQL query or PL/SQL block -b BIND, --bind=BIND dictionary of bind parameters然后,使用 HR 模式在 10 个进程中执行 1000 次查询的基准测试:

pp@oel:~$ python26 orabench.py -d hr/hrpwd@//localhost:1521/XE -n 1000 -c 10 -s \"select count(*) from employees\"Requests=1000, Concurrency=10

Average=0.0006 (1667.7460 requests per second)数据库外部的 GROUP BY(函数式编程)

对各种函数式编程功能,很少有模块能优于 itertools。它包含许多可以生成自定义、优化迭代器的遍历函数。简单提示一下,迭代器是一些对象,其中 __iter__() 方法返回迭代器本身,next() 方法步进到后续元素,或者引发结束迭代的 StopIteration 异常。通过遍历大型数据集,您可以发现使用迭代器与使用列表或字节组的差别,因为迭代器避免了在内存中提交整个集合。import cx_Oracleimport itertools

from operator import itemgetter

with cx_Oracle.connect(\"hr/hrpwd@//localhost:1521/XE\") as db: cursor = db.cursor()

# fetch all employee data into local variable, no aggregation here employees = cursor.execute(\"select * from employees\").fetchall()

D = {}

for dept, emp in itertools.groupby(employees, itemgetter(10)): D[dept] = len(list(emp))

operator 模块包括原生对象使用的所有核心运算符,这意味着无论您何时运行

2+2,operator.add() 方法都会处理这一计算。因为 itertools.groupby() 方法接受两个参数:可迭代的变量和主函数,我们需要使用 itemgetter(10) 从所有行中提取

department_id,itemgetter(10) 仅返回一个集合中的第 10 个元素。对 itertools 的结果进行遍历与您对列表、字节组和字典的遍历非常类似。我们为每个部门生成部门 ID 及该部门的所有员工数 (SELECT department_id, COUNT(*) FROM employees GROUP BY department_id)。序列化数据

在 Python 中,用 pickle 模块及其 C 中对应的 cPickle(比原生 Python 的 pickle 实现最多快 1000 倍)处理数据的序列化和反序列化。“序列化”对象意味着将对象转换为可逆的字节表示:>>> import pickle

>>> A = {'a':1, 'b':2, 'c':3}>>> B = pickle.dumps(A)>>> print B

\"(dp0\\nS'a'\\np1\\nI1\\nsS'c'\\np2\\nI3\\nsS'b'\\np3\\nI2\\ns.\">>> C = pickle.loads(B)>>> assert A==C

紧邻关系数据存储复杂结构时,序列化显得尤为有用,这样,我们就可以将常规 Python 对象当作数据库本来就支持的对象进行读写操作。

至于 pickle 支持的类型,只有很少的,因为它能处理的内容非常广泛,从字典和字节组,到数据集和函数,再到类和实例。其中一个不能序列化的对象是 cx_Oracle.Connection 对象,原因显而易见。惰性化缓存 (Sleepy Cache)

Oracle Berkeley DB 是一个事务性键值存储解决方案,具有细粒度锁定、高可用性和复制功能。它可以应对需要极端效率及完整关系数据库开销过高情况下的所有问题。(直到 2.6 版,Python 才以 bsddb 模块的形式包括了针对 Oracle Berkeley DB 的内置接口。Python 的新版本(从 3.0 开始)使用一个需要单独安装的外部模块 PyBSDDB。 )

下面,我们将利用 Python 2.6 附带的内置驱动程序将值从 Oracle 数据库缓存到 Oracle Berkeley DB 中:import bsddbimport cx_Oracle

import pickle

class Cache:

def __init__(self, tab):

self.__db = cx_Oracle.connect(\"hr/hrpwd@//localhost:1521/XE\") self.__cursor = self.__db.cursor() self.__bdb = bsddb.hashopen(None)

self.__cursor.execute(\"select * from employees\") d = self.__cursor.description for row in self.__cursor:

rowdict = dict((d[i][0].lower(), row[i]) for i in xrange(len(d))) self.__bdb[str(row[0])] = pickle.dumps(rowdict)

def __del__(self): self.__cursor.close() self.__db.close()

def __getitem__(self, name): try:

return pickle.loads(self.__bdb[str(name)]) except KeyError:

raise Warning, \"No such employee with ID %s\" % name

if __name__ == \"__main__\": emp = Cache(\"EMPLOYEES\")

现在可以像使用“emp[100]”一样方便地访问员工了,emp[100] 可以访问快速的内存中散列表,并对序列化的员工数据进行反序列化。您可以使用任一内置服务器

(SimpleHTTPServer、SimpleXMLRPCServer、wsgiref.simple_server)轻松包装这样的缓存,或者使用 Twisted 框架使缓存变得更强健。总结

这次我们介绍了 Oracle 和 Python 结合使用时的几个核心领域,包括 PL/SQL 存储过程调用以及如何处理 PL/SQL 函数结果。谈到 Python,现在您应对 multiprocessing 模块要点有所了解,它是该语言新添加的最重要的模块。最后,介绍了 Oracle Berkeley DB 在概念验证的内存缓存中的使用情况。

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- igbc.cn 版权所有 湘ICP备2023023988号-5

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务