8月
03
ちょっと Python + MySQL をいじってるんですが、デフォルトではクエリの結果はタプルorリストで返ってくるので、雑に SELECT * FROM table とかやるとどんな順番で列が返ってくるのか SQL 文を見ただけでは不明で扱いづらいため、 dict (連想配列)で取れると嬉しいです。
何とかならないのかなぁと思いつつ PyMySQL や MySQL Connector/Python や mysqlclient のドキュメントを読むと、 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
no comment untill now