ちょっと Python + MySQL をいじってるんですが、デフォルトではクエリの結果はタプルorリストで返ってくるので、雑に SELECT * FROM table とかやるとどんな順番で列が返ってくるのか SQL 文を見ただけでは不明で扱いづらいため、 dict (連想配列)で取れると嬉しいです。

何とかならないのかなぁと思いつつ PyMySQLMySQL Connector/Pythonmysqlclient のドキュメントを読むと、 connection.cursor() の引数になんか渡すと結果を dict で返してくるカーソルが得られるようなので、これを使ってみます。

まず、以下のようなテーブルを作っておきます。

mysql> SELECT * FROM table1;
+------+------+
| hoge | fuga |
+------+------+
|    1 |    2 |
|    3 |    4 |
+------+------+
2 rows in set (0.00 sec)

mysql> SELECT * FROM table2;
+------+------+
| fuga | piyo |
+------+------+
|    4 |    5 |
+------+------+
1 row in set (0.00 sec)

そして以下のように実行してみます。

#!/usr/bin/python3

mysql_kwargs = {
	"host":     "localhost",
	"port":     3306,
	"user":     "test",
	"password": "hogehoge",
	"database": "testdb",
}


import pymysql
print("PyMySQL")
print()

conn = pymysql.connect(**mysql_kwargs)

def get_cursor():
	return conn.cursor()

def get_dict_cursor():
	return conn.cursor(pymysql.cursors.DictCursor)

cur = get_cursor()
cur.execute("SELECT * FROM table1")
print(cur.description)
print(cur.fetchall())
cur.close()

cur = get_dict_cursor()
cur.execute("SELECT * FROM table1")
print(cur.fetchall())
cur.close()

print()

cur = get_cursor()
cur.execute("SELECT * FROM table1, table2 WHERE table1.fuga = table2.fuga")
print(cur.description)
print(cur.fetchall())
cur.close()

cur = get_dict_cursor()
cur.execute("SELECT * FROM table1, table2 WHERE table1.fuga = table2.fuga")
print(cur.fetchall())
cur.close()

print()

cur = get_cursor()
cur.execute("SELECT * FROM table1 INNER JOIN table2 USING(fuga)")
print(cur.description)
print(cur.fetchall())
cur.close()

cur = get_dict_cursor()
cur.execute("SELECT * FROM table1 INNER JOIN table2 USING(fuga)")
print(cur.fetchall())
cur.close()
[umezawa@devubuntu:pts/1 ~]$ ./cursortest.py
PyMySQL

(('hoge', 3, None, 11, 11, 0, False), ('fuga', 3, None, 11, 11, 0, False))
((1, 2), (3, 4))
[{'hoge': 1, 'fuga': 2}, {'hoge': 3, 'fuga': 4}]

(('hoge', 3, None, 11, 11, 0, False), ('fuga', 3, None, 11, 11, 0, False), ('fuga', 3, None, 11, 11, 0, False), ('piyo', 3, None, 11, 11, 0, False))
((3, 4, 4, 5),)
[{'hoge': 3, 'fuga': 4, 'table2.fuga': 4, 'piyo': 5}]

(('fuga', 3, None, 11, 11, 0, False), ('hoge', 3, None, 11, 11, 0, False), ('piyo', 3, None, 11, 11, 0, False))
((4, 3, 5),)
[{'fuga': 4, 'hoge': 3, 'piyo': 5}]

スクリプトの先頭の部分を以下のように変えて MySQL Connector/Python を使ってみます。

import mysql.connector
print("MySQL Connector/Python")
print()

conn = mysql.connector.connect(**mysql_kwargs)

def get_cursor():
	return conn.cursor()

def get_dict_cursor():
	return conn.cursor(dictionary=True)
[umezawa@devubuntu:pts/1 ~]$ ./cursortest.py
MySQL Connector/Python

[('hoge', 3, None, None, None, None, 0, 4097), ('fuga', 3, None, None, None, None, 0, 4097)]
[(1, 2), (3, 4)]
[{'hoge': 1, 'fuga': 2}, {'hoge': 3, 'fuga': 4}]

[('hoge', 3, None, None, None, None, 0, 4097), ('fuga', 3, None, None, None, None, 0, 4097), ('fuga', 3, None, None, None, None, 0, 4097), ('piyo', 3, None, None, None, None, 0, 4097)]
[(3, 4, 4, 5)]
[{'hoge': 3, 'fuga': 4, 'piyo': 5}]

[('fuga', 3, None, None, None, None, 0, 4097), ('hoge', 3, None, None, None, None, 0, 4097), ('piyo', 3, None, None, None, None, 0, 4097)]
[(4, 3, 5)]
[{'fuga': 4, 'hoge': 3, 'piyo': 5}]

今度はこう書き換えて mysqlclient を使います。

import MySQLdb
print("mysqlclient")
print()

conn = MySQLdb.connect(**mysql_kwargs)

def get_cursor():
	return conn.cursor()

def get_dict_cursor():
	return conn.cursor(MySQLdb.cursors.DictCursor)
[umezawa@devubuntu:pts/1 ~]$ ./cursortest.py
mysqlclient

(('hoge', 3, 1, 11, 11, 0, 0), ('fuga', 3, 1, 11, 11, 0, 0))
((1, 2), (3, 4))
({'hoge': 1, 'fuga': 2}, {'hoge': 3, 'fuga': 4})

(('hoge', 3, 1, 11, 11, 0, 0), ('fuga', 3, 1, 11, 11, 0, 0), ('fuga', 3, 1, 11, 11, 0, 0), ('piyo', 3, 1, 11, 11, 0, 0))
((3, 4, 4, 5),)
({'hoge': 3, 'fuga': 4, 'table2.fuga': 4, 'piyo': 5},)

(('fuga', 3, 1, 11, 11, 0, 0), ('hoge', 3, 1, 11, 11, 0, 0), ('piyo', 3, 1, 11, 11, 0, 0))
((4, 3, 5),)
({'fuga': 4, 'hoge': 3, 'piyo': 5},)

というわけで何となく期待したような結果になっています。

MySQL Connector/Python は、同じ名前の列が複数出現するときの挙動が他の2つと異なる(他の2つは2つ目の出現にはテーブル名が付くので見分けがつくが、MySQL Connector/Python はテーブル名は付かずに上書きされるっぽい?)ので、その点だけ注意でしょうか。

ちなみに dict なカーソルが使えない場合は、以下のような idiom で同様の結果を得ることができます。(最初は全部こう書いててダサいなぁと思ってた)

dict_results= [dict((cursor.description[i][0], value) for i, value in enumerate(row)) for row in cursor.fetchall()]

ちなみに使った環境:

  • Ubuntu 18.04 LTS
  • Python 3.6.8
  • PyMySQL 0.9.3
  • MySQL Connector/Python 2.1.6
  • mysqlclient 1.3.10
Trackback

no comment untill now

Add your comment now