[Dd]enzow(ill)? with DB and Python

DBとか資格とかPythonとかの話をつらつらと

PythonからOracleに繋いでみる!その1 (cx_Oracleの簡単な使い方)

この記事はJPOUG Advent Calendar 2017の3日目です。 さらに同日にもう1記事あげていますのでよろしければご覧ください。

次の仕事ではOracleとかかわらなくなりますが、思い入れがありますのでエントリ書いておきます。 PythonからOracle DatabaseにSQLを発行するまでの流れを簡単にまとめていきます。

PythonからOracle Databaseを利用するには

PythonからOracle DatabaseにSQLを発行したりするには、cx_Oracleを利用します。

cx_OracleはPythonのDB API2.0を実装するモジュールで、他のRDBMSに対するPython用モジュールとほぼ同じメソッドで利用できます。また、それらに加えてLOBやPL/SQLのカーソル等、Oracle独自の機能への対応も行われています。

今回は、cx_Oracleの導入から簡単なSELCTの実行までをまとめます。

検証環境について

本記事は以下の環境で作成しています。

接続先

  • OS: Oracle Linux 6.9
  • Oracle製品: Oracle Database 11.2.0.4

接続元

  • OS: Mac OS X 10.12.6
  • Software: Python 3.6
  • Driver: cx_Oracle 6.0.3

cx_Oracleのインストール

cx_Oracle自体はpip install cx_oracleで簡単に導入できます。

(ora) denzownoMacBook-Pro:tmp denzow$ conda install cx_oracle
Fetching package metadata ...........
Solving package specifications: .

Package plan for installation in environment /Users/denzow/miniconda3/envs/ora:

The following NEW packages will be INSTALLED:

    cx_oracle: 6.0.3-py36h6553998_0

Proceed ([y]/n)? y

cx_oracle-6.0. 100% |############################| Time: 0:00:00 165.91 kB/s

しかし、そのまま利用しようとすると以下のようなエラーが発生します。

>>> import cx_Oracle
>>> cx_Oracle.clientversion()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
cx_Oracle.DatabaseError: DPI-1047: 64-bit Oracle Client library cannot be loaded: "dlopen(libclntsh.dylib, 1): image not found". See https://oracle.github.io/odpi/doc/installation.html#macos for help

エラー自体はlibclntsh.dylibというライブラリが見つからなかったことを示しています。

これは、11.2までのODP.NETや12.1以降のの非管理対象のODP.NETと同じく、cx_OracleがOCI(正確にはODPI-C)のPythonバインディングであるためです。実際にOracleに接続する部分にはOCIを利用しているため、クライアント側には対応するライブラリが導入されている必要があります。

Orace ClientをOUIで入れても良いですが、instant Clientで十分ですのでそちらを利用する方が良いかと思います。各プラットフォームごとの手順は以下のドキュメントにまとめられています。

ODPI-C Installation

Macの場合はこちらよりinstantclient-basic-macos.x64-VERSIONのzipファイルを入手します。

今回は接続先にあわせてinstantclient-basic-macos.x64-11.2.0.4.0.zipをダウンロードします。zipを展開すると以下のようなファイルが含まれています。

(ora) denzownoMacBook-Pro:~ denzow$ ls -l Downloads/instantclient_11_2/
total 380640
-rw-rw-rw-@ 1 denzow  staff        484  4 11  2014 BASIC_README
-rwxrwxrwx@ 1 denzow  staff      14348  4 11  2014 adrci
-rwxrwxrwx@ 1 denzow  staff      40768  4 11  2014 genezi
-r-xr-xr-x@ 1 denzow  staff   66167420  2  7  2014 libclntsh.dylib.11.1 ★
-r-xr-xr-x@ 1 denzow  staff    2817872  1 30  2014 libnnz11.dylib ★
-r-xr-xr-x@ 1 denzow  staff    1897664  2  7  2014 libocci.dylib.11.1
-rwxrwxrwx@ 1 denzow  staff  118707148  4 11  2014 libociei.dylib ★
-r-xr-xr-x@ 1 denzow  staff     159004  1  7  2014 libocijdbc11.dylib
-r--r--r--@ 1 denzow  staff    2091135  1 29  2014 ojdbc5.jar
-r--r--r--@ 1 denzow  staff    2739616  1 29  2014 ojdbc6.jar
-rwxrwxrwx@ 1 denzow  staff     162380  4 11  2014 uidrvci
-r--r--r--@ 1 denzow  staff      66779  1  8  2014 xstreams.jar

このうち★をつけたライブラリを$HOME/lib配下にコピーします。該当ディレクトリが存在しない場合は事前に作成しておきます。

mkdir $HOME/lib
cp <any>/instantclient_11_2/{libclntsh.dylib.11.1,libnnz11.dylib,libociei.dylib} ~/lib/

Mac OSの場合、ライブラリの捜索パスのデフォルトは$HOME/lib;/usr/local/lib;/usr/libとなっているため、$HOME/libにコピーすることで利用可能になるわけです。捜索パスについての詳細はこちらにまとまっています。

なお、他のプラットフォームの場合はそれぞれのOSに対応するinstantclientを用意します。その上で、Linux環境の場合はinstantclient_11_2までのディレクトリをLD_LIBRARY_PATHに追加することで利用可能になります。また、Windowsでは同ディレクトリをPATH環境変数に追加することで利用可能になります。

これで準備が整ったはずですので、接続に移っていきます。

cx_Oracleでの接続

まずは先程エラーになったcx_Oracle.clientversion()を実行してみます。

>>> import cx_Oracle
>>> cx_Oracle.clientversion()
(11, 2, 0, 4, 0)

今度は正常に結果が帰ってきました。この値はcx_Oracleから呼び出されているOracleClientのライブラリのバージョンを示しています。

では、続いて接続してみます。なお、DB側のリスナーは以下の状態です。

[v1124@yamax admin]$ lsnrctl status

LSNRCTL for Linux: Version 11.2.0.4.0 - Production on 01-12月-2017 02:05:20

Copyright (c) 1991, 2013, Oracle.  All rights reserved.

(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.56.55)(PORT=11204)))に接続中
リスナーのステータス
------------------------
別名                      LISTENER
バージョン                TNSLSNR for Linux: Version 11.2.0.4.0 - Production
開始日                    01-12月-2017 01:25:16
稼働時間                  0 日 0 時間 40 分 3 秒
トレース・レベル          off
セキュリティ              ON: Local OS Authentication
SNMP                      OFF
パラメータ・ファイル      /home/v1124/app/v1124/product/11.2.0/dbhome_1/network/admin/listener.ora
ログ・ファイル            /home/v1124/app/v1124/diag/tnslsnr/yamax/listener/alert/log.xml
リスニング・エンドポイントのサマリー...
  (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=192.168.56.55)(PORT=11204)))
  (DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=EXTPROC11204)))
サービスのサマリー...
サービス"v1124.world"には、1件のインスタンスがあります。
  インスタンス"v1124"、状態READYには、このサービスに対する1件のハンドラがあります...
サービス"v1124XDB.world"には、1件のインスタンスがあります。
  インスタンス"v1124"、状態READYには、このサービスに対する1件のハンドラがあります...
コマンドは正常に終了しました。

(HOST=192.168.56.55)(PORT=11204)でv1124.wroldというサービス名で起動しています。こちらに接続していきます。なお、ユーザとパスワードはscott/tigerを用意してあります。

>>> cx_Oracle.connect(user='scott', password='tiger', dsn='oralin:11204/v1124.world')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
cx_Oracle.DatabaseError: ORA-21561: OID generation failed

見慣れぬエラーになりました。このエラーはクライアント側のホスト名を名前解決できない場合に発生するようです。

確かにMacOSで自ホスト名をhostsには書いていなかったので127.0.0.1になるように追記しておきます。以下のように自ホストへpingが通れば問題ありません。

(ora) denzownoMacBook-Pro:~ denzow$ ping `hostname`
PING localhost (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.066 ms
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.055 ms

では再度接続してみます。

>>> conn = cx_Oracle.connect(user='scott', password='tiger', dsn='oralin:11204/v1124.world')
>>> conn
<cx_Oracle.Connection to scott@oralin:11204/v1124.world>

無事に接続できたようです。念のためV$SESSIONからも確認しておきます。

SQL> select program, username from v$session where username='SCOTT';

PROGRAM                      USERNAME
------------------------------------------------ ------------------------------
python@denzownoMacBook-Pro.local (TNS V1-V3)     SCOTT

セッションもはれているようですね。これで接続までが完了しました。

cx_OracleからSQLの実行

続いて、SQLを実行していきます。といっても、DBAPI 2.0に従ったインターフェースをもっているため、他のライブラリと基本的に同じ使い方になります。

>>> cur = conn.cursor()
>>> for row in cur.execute('select * from v$version'):
...     print(row[0])
...
Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production
PL/SQL Release 11.2.0.4.0 - Production
CORE    11.2.0.4.0  Production
TNS for Linux: Version 11.2.0.4.0 - Production
NLSRTL Version 11.2.0.4.0 - Production

connectionオブジェクトから作成したcursorオブジェクトを通じてSQLを発行し、その結果をfor文等で簡単に取得することができます。

なお、他のドライバによっては結果セットをPythonのディクショナリライクな形式で戻すオプションもありますが、cx_Oracle自体には提供されていないようです。

代替案として以下が参考になります。

cx_OracleとOracle Databaseのバージョン互換性

本ドキュメントにもあるようにcx_Oracleが接続できるOracle Databaseのバージョンはcx_Oracleのバージョンではなく、利用しているOracle Clientのライブラリのバージョンに依存します。

そのため通常のOracle Database/Oracle Client間のバージョン互換性と同じく、以下のドキュメントに従います。

Client / Server Interoperability Support Matrix for Different Oracle Versions (Doc ID 207303.1)

ただし、cx_Oracleが現在動作保証されているOracle Clientは11.2,12.1,12.2だけですので11.2のClientが接続できる下限であるOracle Database 9.2がcx_Oracleで接続できる最も古いバージョンになります。

まとめ

簡単ではありますが、Pythonからcx_Oracleを用いて接続する方法をご紹介しました。2018年もますます利用者が増えると思われるPythonからOracle Databaseを利用されようとする方に参考になれば幸いです。