在使用 Python 的过程中, 经常会需要连接数据库来处理数据,而连接 Oracle 数据库是一件比较麻烦的事,因为还需要安装各种 Oracle 客户端工具。

有了 cx_Oracle 库,我们就可以连接 Oracle 数据库并执行如下等操作:

本机(Win7)未安装数据库

本地电脑未安装 Oracle 数据库,使用 Python 连接远程 Oracle 数据库

系统环境

  • 本机环境:
操作系统   : Win7 64bit 
Python 版本: '3.6.4 [MSC v.1900 64 bit (AMD64)]' 
  • 远程环境:
操作系统   :  CentOS7 x86_64
Oracle 版本:  11.2.0.4 64 bit
  • 下载 Oracle 客户端工具:
instantclient-basic-windows.x64-11.2.0.4.0
instantclient-sdk-windows.x64-11.2.0.4.0
instantclient-sqlplus-windows.x64-11.2.0.4.0

需要注意的是,客户端版本必须要与远程 Oracle 服务端版本一致或客户端版本高于服务端版本
Oracle Instant Client Downloads

配置 Oracle Client

下载好 Oracle 客户端之后,我们还需要进行相应的配置

  • 解压客户端至 D:\oraClient
instantclient-basic-windows.x64-11.2.0.4.0
instantclient-sdk-windows.x64-11.2.0.4.0
instantclient-sqlplus-windows.x64-11.2.0.4.0
  • 将远程数据库的 tnsnames.ora 文件拷贝至该目录下,并修改 HOST 地址
# 将 HOST 修改为远程 IP 地址

(ADDRESS = (PROTOCOL = TCP)(HOST = ipadress)(PORT = 1521))
  • 配置用户环境变量 在 Windows 下配置 PATH 、ORACLE_HOME 、TNS_ADMIN 环境变量 ORACLE_HOME
    TNS_ADMIN
    PATH

  • 创建虚拟环境

mkvirtualenv oracle
  • 将 oraClient 目录中如下文件拷贝至虚拟环境下的 \Lib\site-packages 目录中
oci.dll
ocijdbc11.dll
oraocci11.dll
oraociei11.dll

安装 cx-Oracle

(oracle) $ pip install cx-Oracle

(oracle) $ python
>>> import cx_Oracle
>>> dir(cx_Oracle)
['ATTR_PURITY_DEFAULT', 'ATTR_PURITY_NEW', 'ATTR_PURITY_SELF', 
'BFILE', 'BINARY', 'BLOB', 'BOOLEAN', 'Binary', 'CLOB', 'CURSOR', 'Connection', 'Cursor', 'DATETIME'

操作远程数据库

(oracle) $ python
  • 导入 cx_Oracle 库
>>> import cx_Oracle
  • 建立连接(根据 tnsnames.ora 文件中的配置信息)
>>> conn = cx_Oracle.connect('scott/tiger@192.168.43.96:1521/orcl')
  • 创建 cursor 游标
>>> cursor = conn.cursor()
  • 执行 sql 语句

1. sql 查询语句

>>> query_sql = 'select * from emp where deptno = 10'

# 执行 sql 查询语句
>>> cursor.execute(query_sql)

# 获取记录集(获取一条记录,返回一个元祖)
>>> row = cursor.fetchone()
>>> print(row)
(7369, 'SMITH', 'CLERK', 7902, datetime.datetime(1980, 12, 17, 0, 0), 800.0, None, 20)

# 获取全部记录
>>> rows = cursor.fetchall()
>>> print(rows)
[(7839, 'KING', 'PRESIDENT', None, datetime.datetime(1981, 11, 17, 0, 0), 5000.0
, None, 10), (7934, 'MILLER', 'CLERK', 7782, datetime.datetime(1982, 1, 23, 0, 0
), 1300.0, None, 10)]

注: 当运行 cursor.execute 后,结果集会缓存在 cursor 游标中,因为第一次已经从游标中获取了一条记录,所以第二次获取全部记录时,相比数据库查询会少一条记录,再比如:

# 执行 sql 语句,将结果集缓存至 cursor 中
>>> cursor.execute(query_sql)
<cx_Oracle.Cursor on <cx_Oracle.Connection to scott@192.168.43.96:1521/orcl>>

# 第一次获取全部记录
>>> rows = cursor.fetchall()
>>> print(rows)
[(7782, 'CLARK', 'MANAGER', 7839, datetime.datetime(1981, 6, 9, 0, 0), 2450.0, None,
10), (7839, 'KING', 'PRESIDENT', None, datetime.datetime(1981, 11, 17, 0, 0), 5000.0,
 None, 10), (7934, 'MILLER', 'CLERK', 7782, datetime.datetime(1982, 1, 23, 0, 0), 130
0.0, None, 10)]

# 第二次获取一条记录(无数据,因为第一次已经全部获取了)
>>> row = cursor.fetchone()
>>> print(row)
None

2. sql 创建语句

>>> create_sql = """
create table employees (
id number not null,
name varchar2(50) not null
)"""

# 执行 sql 创建语句
>>> cursor.execute(create_sql)
<cx_Oracle.Cursor on <cx_Oracle.Connection to scott@192.168.43.96:1521/orcl>>

4. sql 插入语句

# 以字典形式,插入一条记录
>>> param = {'id':1001, 'name':'Smith'}
>>> cursor.execute('insert into employees(id, name) values(:id, :name)' ,param);
# 提交
>>> conn.commit()

# 以字典的列表形式,插入多条记录
>>> param = [{'id':1002, 'name':'King'}, {'id':1003, 'name':'Scott'}]
>>> cursor.executemany('insert into employees(id, name) values(:id, :name)' ,param);
# 提交
>>> conn.commit()

4. sql 更新语句

>>> update_sql = "update employees set name = 'Adams' where id = 1003"

# 执行 sql 更新语句
>>> cursor.execute(update_sql)

# 提交
>>> conn.commit()

5. sql 删除语句

>>> delete_sql = "delete from employees"

# 执行 sql 删除语句
>>> cursor.execute(delete_sql)

# 提交
>>> conn.commit()

6. 执行存储过程

# 创建存储过程

CREATE OR REPLACE PROCEDURE P_UPDATE_EMP(I_EMPNO IN NUMBER,
                                         O_ERRCODE OUT NUMBER,
                                         O_ERRMSG OUT VARCHAR2)
AS
  V_EMPNO NUMBER;
BEGIN
  O_ERRCODE   := 0;
  O_ERRMSG    := '运行成功';
  V_EMPNO     := I_EMPNO;

  UPDATE EMP E SET E.ENAME = 'Python'
  WHERE E.EMPNO = V_EMPNO;

  COMMIT;

EXCEPTION
  WHEN OTHERS THEN
    ROLLBACK;
    O_ERRCODE := SQLCODE;
    O_ERRMSG  := SUBSTR('未处理异常:' || SQLERRM || '。' ||
                        DBMS_UTILITY.FORMAT_ERROR_BACKTRACE,
                        1,
                        1000);
END P_UPDATE_EMP;

# 为存储过程参数赋值
>>> i_empno = 7369  # 入参
>>> o_code = cursor.var(cx_Oracle.NUMBER)  # 出参
>>> o_msg = cursor.var(cx_Oracle.STRING)   # 出参  

# 执行存储过程
>>> cursor.callproc('p_update_emp', [i_empno, o_code, o_msg])
[7369, 0.0, '????']

# 查看运行结果(根据返回参数)
>>> print(o_code, o_msg)
<cx_Oracle.NUMBER with value 0.0> <cx_Oracle.STRING with value '????'>

出现乱码的解决方案:

# 修改 Windows 环境变量
NLS_LANG = SIMPLIFIED CHINESE_CHINA.ZHS16GBK

# 执行存储过程前,先修改 Python 字符集
>>> import os
>>> os.environ['NLS_LANG'] = 'SIMPLIFIED CHINESE_CHINA.UTF8'

重新打开 cmd 执行存储过程:

>>> cursor.callproc('p_update_emp', [i_empno, o_code, o_msg])
[7369, 0.0, '运行成功']

>>> print(o_code, o_msg)
<cx_Oracle.NUMBER with value 0.0> <cx_Oracle.STRING with value '运行成功'>

>>> print(o_code.getvalue(), o_msg.getvalue())
0.0 运行成功

存储过程、cx_Oracle、Python 的对象类型之间的转换关系:

Oracle cx_Oracle Python
VARCHAR2, LONG cx_Oracle.STRING str
CHAR cx_Oracle.STRING str
NUMBER cx_Oracle.NUMBER int
FLOAT cx_Oracle.NUMBER float
DATE cx_Oracle.DATETIME datetime.datetime
TIMESTAMP cx_Oracle.TIMESTAMP datetime.datetime
CLOB cx_Oracle.CLOB cx_Oracle.LOB
BLOB cx_Oracle.BLOB cx_Oracle.LOB

7. 执行函数

# 创建函数

CREATE OR REPLACE FUNCTION FUN_GET_ENAME(I_EMPNO IN NUMBER)
RETURN VARCHAR2 IS
  V_EMPNO NUMBER;
  V_ENAME VARCHAR2(100);
BEGIN
  V_EMPNO := I_EMPNO;

  SELECT ENAME INTO V_ENAME FROM EMP WHERE EMPNO = V_EMPNO;

  RETURN V_ENAME;

END FUN_GET_ENAME;

# 为函数参数赋值
>>> i_empno = 7369

# 执行函数
>>> res = cursor.callfunc('FUN_GET_ENAME', cx_Oracle.STRING, [i_empno])

# 查看运行结果(根据返回参数)
>>> print(res)
Python
  • 关闭游标
>>> cursor.close()
  • 释放连接
>>> conn.close()

本机(Win7)已安装数据库

本地电脑已安装数据库,连接本地 Oracle 数据库和连接远程 Oracle 数据库

系统环境

  • 本机环境:
操作系统    : Win7 64bit 
Python  版本: '3.6.4 [MSC v.1900 64 bit (AMD64)]' 
Oracle  版本: 11.2.0.3 64bit

安装 Oracle 11g

Windows 环境下安装 Oracle 11g,这里就不再赘述

SQL> select * from v$version;

BANNER
-----------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production
PL/SQL Release 11.2.0.3.0 - Production
CORE    11.2.0.3.0      Production
TNS for 64-bit Windows: Version 11.2.0.3.0 - Production
NLSRTL Version 11.2.0.3.0 - Production

安装 cx-Oracle

  • 创建虚拟环境
mkvirtualenv oracle
  • 安装 cx-Oracle
pip install cx-Oracle

操作本地数据库

(oracle) $ python
>>> import cx_Oracle

>>> conn = cx_Oracle.connect('scott/tiger@127.0.0.1:1521/orcl')
>>> cursor = conn.cursor()

>>> quert_sql = 'select * from emp where deptno=20'
>>> cursor.execute(quert_sql)

>>> row = cursor.fetchone()
>>> print(row)
(7369, 'Python1', 'CLERK', 7902, datetime.datetime(1980, 12, 17, 0, 0), 800.0, None, 20)

>>> cursor.close()
>>> conn.close()

操作远程数据库

操作远程数据库,需要安装 Oracle 客户端,并将远程数据库的 tnsnames.ora 文件,拷贝至本地

  • 安装 Oracle 客户端

  • 将远程数据库的 tnsnames.ora 文件拷贝至该目录下,并修改 HOST 地址

(ADDRESS = (PROTOCOL = TCP)(HOST = ipadress)(PORT = 1521))
  • 创建虚拟环境
mkvirtualenv oracle
  • 将 oraClient 目录中如下文件拷贝至虚拟环境下的 \Lib\site-packages 目录中
oci.dll
ocijdbc11.dll
oraocci11.dll
oraociei11.dll
  • 安装 cx-Oracle
pip install cx-Oracle
  • 操作远程 Oracle 数据库
(oracle) $ python
>>> import cx_Oracle

>>> conn = cx_Oracle.connect('username/passwd@ip:port/instancename')
>>> cursor = conn.cursor()

>>> query_sql = 'select * from me_dim where rownum <= 2'
>>> cursor.execute(query_sql)

>>> rows = cursor.fetchall()
>>> print(rows)
[('1102', '交易性金融资产—基金', '33', 'TOTAL_SUBCODE', '持仓汇总编码', '1'), ('1103', '交易性金融资产—债券', '34', 'TOTAL_SUBCODE', '持仓汇总编码', '1')]

>>> cursor.close()
>>> conn.close()

MacOS 连接远程数据库

MacOS 目前不能安装 Oracle 11g,所以只能用于连接远程数据库

系统环境

  • 本机环境:
操作系统    : 10.13.2 64 bit
Python  版本: '3.6.0 64 bit' 
  • 远程环境:
操作系统   : Windows7 64 bit
Oracle 版本: 11.2.0.4 64 bit
  • MacOS 系统下的 Oracle 客户端安装包:
instantclient-basic-macos.x64-11.2.0.4.0.zip
instantclient-sqlplus-macos.x64-11.2.0.4.0.zip
instantclient-sdk-macos.x64-11.2.0.4.0.zip

配置 Oracle Client

  • 解压客户端至 /opt/oracle/instantclient_11_2
instantclient-basic-windows.x64-11.2.0.4.0
instantclient-sdk-windows.x64-11.2.0.4.0
instantclient-sqlplus-windows.x64-11.2.0.4.0
  • 将远程数据库的 tnsnames.ora 文件拷贝至该目录下,并修改 HOST地址
(ADDRESS = (PROTOCOL = TCP)(HOST = ipadress)(PORT = 1521))
  • 配置用户环境变量
[allenlideMacBook-Pro:~ allen$] vi .bash_profile 
export ORACLE_HOME=/opt/oracle/instantclient_11_2
export ORACLE_SID=orcl
export DYLD_LIBRARY_PATH=$ORACLE_HOME
export LD_LIBRARY_PATH=$ORACLE_HOME
export PATH=/opt/oracle/instantclient_11_2:$PATH
[allenlideMacBook-Pro:~ allen$] source .bash_profile
  • 问题:
若出现 ORA-12170: TNS:Connect timeout occurred 解决方法
a. ping ip 是否正常
b. telnet ip port 是否正常
c. 检查远程服务器防火墙是否已关闭

安装 cx-Oracle

[allenlideMacBook-Pro:~ allen$] pip3 install cx-Oracle
[allenlideMacBook-Pro:~ allen$] python3
>>> import cx_Oracle
>>> dir(cx_Oracle)

操作远程数据库

[allenlideMacBook-Pro:~ allen$] python3
>>> import cx_Oracle

>>> conn = cx_Oracle.connect('scott/tiger@192.168.1.100:1521/orcl')
>>> cursor = conn.cursor()

>>> query_sql = 'select * from emp'
>>> cursor.execute(query_sql)

>>> row = curs.fetchone()
>>> print(row)
(7369, 'SMITH', 'CLERK', 7902, datetime.datetime(1980, 12, 17, 0, 0), 800.0, None, 20)

>>> curs.close()
>>> conn.close()

原创文章,转载请注明出处:http://www.opcoder.cn/article/12/