[cx_Oracle入門](第15回) タプル以外の形式で結果セットを作成する

# 連載目次
[連載：cx_Oracle入門　目次](https://qiita.com/nakaie/items/df170c3fa6895cf1b44d)

# 検証環境
- Oracle Cloud利用
- Oracle Linux 7.7 (VM.Standard2.1)
- Python 3.8
- cx_Oracle 8.0
- Oracle Database 19.5 (ATP, 1OCPU)
- Oracle Instant Client 18.5

# 概要
cx_Oracleは結果セットをタプルのリストで戻します。しかし、これをリストや辞書で返したいケースもあるかと思います。その場合の備えがcx_Oracleにはあるので、その方法を解説します。

# Cursor.rowfactory
Cursorオブジェクトのrowfactory属性は、レコードを取り出す際に呼ばれるメソッドを定義します。この属性は、デフォルトではタプルを生成します。この動きを上書きすることで、レコードの形式を他の形に変更することが可能になります。

以下のアプリケーションを改定する形で、実際のコーディングを見ていきます。

```python:sample15a.py
import cx_Oracle

USERID = "admin"
PASSWORD = "FooBar"
DESTINATION = "atp1_low"
SQL = """
    select object_id, owner, object_name, object_type
      from all_objects
     order by object_id
     fetch first 5 rows only
"""

with cx_Oracle.connect(USERID, PASSWORD, DESTINATION) as connection:
    with connection.cursor() as cursor:
        for row in cursor.execute(SQL):
            print(row)
```

```実行結果
$ python sample15a.py
(2, 'SYS', 'C_OBJ#', 'CLUSTER')
(3, 'SYS', 'I_OBJ#', 'INDEX')
(4, 'SYS', 'TAB$', 'TABLE')
(5, 'SYS', 'CLU$', 'TABLE')
(6, 'SYS', 'C_TS#', 'CLUSTER')
```

# 結果セットをリストで返す
```python:sample15b.py(抜粋)
with cx_Oracle.connect(USERID, PASSWORD, DESTINATION) as connection:
    with connection.cursor() as cursor:
        cursor.execute(SQL)
        cursor.rowfactory = lambda *args: list(args)
        rows = cursor.fetchall()
        for row in rows:
            print(row)
```

下から4行目がrowfactoryの実装です。ラムダ式を使用して各レコードをタプルからリストに変換しています。この1行が追加されているだけで、他のコーディングには特段の変更は入っていません。実行結果は以下のとおり、リストになっています。

```実行結果
$ python sample15b.py
[2, 'SYS', 'C_OBJ#', 'CLUSTER']
[3, 'SYS', 'I_OBJ#', 'INDEX']
[4, 'SYS', 'TAB$', 'TABLE']
[5, 'SYS', 'CLU$', 'TABLE']
[6, 'SYS', 'C_TS#', 'CLUSTER']
```

# 結果セットを辞書で返す
列名を要素にした辞書でレコードを戻すことも可能です。

```python:sample15c.py(抜粋)
with cx_Oracle.connect(USERID, PASSWORD, DESTINATION) as connection:
    with connection.cursor() as cursor:
        cursor.execute(SQL)
        columns = [col[0] for col in cursor.description]
        cursor.rowfactory = lambda *args: dict(zip(columns, args))
        rows = cursor.fetchall()
        for row in rows:
            print(row)
        for row in rows:
            print(row["OBJECT_NAME"])
```

列名は上から4行目で取得しています。cursor.descriptionは、各列のメタデータを格納しているタプルのリストの形式の読み取り専用属性になります。この属性の各タプルの要素番号0が列名となります。上から5行目で、ラムダ式を使用して、前の行の情報を利用して辞書を作成しています。実行結果は以下のとおり、辞書になっています。

```実行結果
$ python sample15c.py
{'OBJECT_ID': 2, 'OWNER': 'SYS', 'OBJECT_NAME': 'C_OBJ#', 'OBJECT_TYPE': 'CLUSTER'}
{'OBJECT_ID': 3, 'OWNER': 'SYS', 'OBJECT_NAME': 'I_OBJ#', 'OBJECT_TYPE': 'INDEX'}
{'OBJECT_ID': 4, 'OWNER': 'SYS', 'OBJECT_NAME': 'TAB$', 'OBJECT_TYPE': 'TABLE'}
{'OBJECT_ID': 5, 'OWNER': 'SYS', 'OBJECT_NAME': 'CLU$', 'OBJECT_TYPE': 'TABLE'}
{'OBJECT_ID': 6, 'OWNER': 'SYS', 'OBJECT_NAME': 'C_TS#', 'OBJECT_TYPE': 'CLUSTER'}
C_OBJ#
I_OBJ#
TAB$
CLU$
C_TS#
```

# 結果セットをData Classで返す
rowfactoryはPython3.7の新機能であるData Classにも対応可能です。結果セットと同じData Classを作成しておくと、何かと便利そうです。Data Class自体の説明は以下をご参照ください。

[Python3.7からは「Data Classes」がクラス定義のスタンダードになるかもしれない](https://qiita.com/tag1216/items/13b032348c893667862a)

以下、サンプルと実行結果です。

```python:sample15d.py
import cx_Oracle
from dataclasses import dataclass

@dataclass
class AllObject:
    object_id: int
    owner: str
    object_name: str
    object_type: str


    def display(self):
        return f"{self.owner}.{self.object_name}は{self.object_type}(ID:{self.object_id})です"


USERID = "admin"
PASSWORD = "FooBar"
DESTINATION = "atp1_low"
SQL = """
    select object_id, owner, object_name, object_type
      from all_objects
     order by object_id
     fetch first 5 rows only
"""

with cx_Oracle.connect(USERID, PASSWORD, DESTINATION) as connection:
    with connection.cursor() as cursor:
        cursor.execute(SQL)
        cursor.rowfactory = lambda *args: AllObject(*args)
        rows = cursor.fetchall()
        [print(r.display()) for r in rows]
```

```実行結果
$ python sample15d.py
SYS.C_OBJ#はCLUSTER(ID:2)です
SYS.I_OBJ#はINDEX(ID:3)です
SYS.TAB$はTABLE(ID:4)です
SYS.CLU$はTABLE(ID:5)です
SYS.C_TS#はCLUSTER(ID:6)です
```