皆さんがWeb APIを設計する時によく使うフォーマットにJSONがあります。他サービスとやり取りする際に、従来であればJSONの解析を行い、正規化を行い、必要なキーとバリューを取得し保存するという方法が一般的でした。
今回は、MySQL 5.
検証環境
今回はdockerで建てたMySQLを使用します。以下のコマンドでdockerを建てて、ローカルからアクセスをします。
% docker run --platform linux/x86_64 -p 3307:3306 -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:latest
アクセス方法は以下の通りになります。
% mysql -uroot -pmy-secret-pw -h127.0.0.1 -P3307
執筆時点では、以下の通りMySQL 8.
mysql> select version(); +-----------+ | version() | +-----------+ | 8.0.33 | +-----------+ 1 row in set (0.01 sec)
今回はtestデータベースとdataテーブルを作成します。
mysql> create database test; mysql> use test mysql> CREATE TABLE `json_data` (id SERIAL, doc json);
カラムにはSERIAL型のidとjson型のdocカラムを用意しています。
JSONを保存してみる
それでは、まずはjson型のデータを保存してみましょう。といっても難しいことはありません。docカラムにJSON形式のデータを文字列で入れてINSERTをしていきます。
mysql> INSERT INTO json_data(doc) VALUES ('[1, [2, 3], {"4": "5" }]'); mysql> INSERT INTO json_data(doc) VALUES ('{"a": {"b": ["c", "d"]}, "e": "f"}');
データが挿入できました。とりあえずSELECTしてみましょう。
mysql> select * from json_data; +----+------------------------------------+ | id | doc | +----+------------------------------------+ | 1 | [1, [2, 3], {"4": "5"}] | | 2 | {"a": {"b": ["c", "d"]}, "e": "f"} | +----+------------------------------------+ 2 rows in set (0.02 sec)
さて、ここで破損したJSONを入れてみましょう。
mysql> INSERT INTO json_data(doc) VALUES ('{"a": ');
上記のように、キーの後にVALUEがなく閉じ括弧が無い不正なJSONを挿入しようとしてみます。結果は以下のようになり、不正なJSONとして弾かれていることがわかります。
ERROR 3140 (22032): Invalid JSON text: "Invalid value." at position 6 in value for column 'json_data.doc'.
txt型やblob型でもJSONは保存できたのですが、JSONとしての正しさを保証してくれるところに良さがあります。
JSONを検索してみる
さて、MySQLにJSONを挿入することができました。では続いてJSONの値を検索してみたいと思います。検索する方法としては基本的には2つあって、JSON_
JSON_EXTRACTを使用してみる
JSON_
mysql> SELECT * FROM json_data WHERE JSON_EXTRACT(doc, '$[0]') = 1; +----+-------------------------+ | id | doc | +----+-------------------------+ | 1 | [1, [2, 3], {"4": "5"}] | +----+-------------------------+ 1 row in set (0.04 sec)
上記のクエリではJSONの配列の0番目の値をとってきています。$[0]
は配列へのアクセスとなっていて、JSON_
下記のようなクエリを実行して、どのような値が取得されているか確認してみましょう。
mysql> SELECT id, doc, JSON_EXTRACT(doc, '$[0]') FROM json_data WHERE JSON_EXTRACT(doc, '$[0]') = 1; +----+-------------------------+---------------------------+ | id | doc | JSON_EXTRACT(doc, '$[0]') | +----+-------------------------+---------------------------+ | 1 | [1, [2, 3], {"4": "5"}] | 1 | +----+-------------------------+---------------------------+ 1 row in set (0.01 sec)
想定通り1が返っていることがわかります。
では続いて、配列ではなくオブジェクトの値を取得する場合を試してみましょう。その場合は下記のように'$.key'
の形で指定をしてみましょう。
mysql> SELECT id, doc, JSON_EXTRACT(doc, '$.e') FROM json_data WHERE JSON_EXTRACT(doc, '$.e') = 'f'; +----+------------------------------------+--------------------------+ | id | doc | JSON_EXTRACT(doc, '$.e') | +----+------------------------------------+--------------------------+ | 2 | {"a": {"b": ["c", "d"]}, "e": "f"} | "f" | +----+------------------------------------+--------------------------+ 1 row in set (0.02 sec)
上記では、eというキーを指定して"f"を取得しました。このように取得することができます。また、もっと複雑なpathの指定もできます。
mysql> SELECT id, doc, JSON_EXTRACT(doc, '$.a.b[0]') FROM json_data WHERE JSON_EXTRACT(doc, '$.a.b[0]') IS NOT NULL; +----+------------------------------------+-------------------------------+ | id | doc | JSON_EXTRACT(doc, '$.a.b[0]') | +----+------------------------------------+-------------------------------+ | 2 | {"a": {"b": ["c", "d"]}, "e": "f"} | "c" | +----+------------------------------------+-------------------------------+ 1 row in set (0.02 sec)
上記では、'$.a.
のpathにアクセスをしています。結果は"c"となっているので、解析してアクセスできていることがわかります。今回はスカラー値での比較を行っていますが、配列やオブジェクトを取得することもできます。
mysql> SELECT id, doc, JSON_EXTRACT(doc, '$.a.b') FROM json_data WHERE id = 2; +----+------------------------------------+----------------------------+ | id | doc | JSON_EXTRACT(doc, '$.a.b') | +----+------------------------------------+----------------------------+ | 2 | {"a": {"b": ["c", "d"]}, "e": "f"} | ["c", "d"] | +----+------------------------------------+----------------------------+ 1 row in set (0.01 sec) mysql> SELECT id, doc, JSON_EXTRACT(doc, '$.a') FROM json_data WHERE id = 2; +----+------------------------------------+--------------------------+ | id | doc | JSON_EXTRACT(doc, '$.a') | +----+------------------------------------+--------------------------+ | 2 | {"a": {"b": ["c", "d"]}, "e": "f"} | {"b": ["c", "d"]} | +----+------------------------------------+--------------------------+ 1 row in set (0.01 sec)
JSON_EXTRACTの省略記法(->)を使用する
JSON_->
を使用してみましょう。
ここでは、$.a.
が"c"で$.e
が"f"の値を検索する場合を考えてみましょう。
mysql> SELECT id, doc FROM json_data WHERE JSON_EXTRACT(doc, '$.a.b[0]') = 'c' AND JSON_EXTRACT(doc, '$.e') = 'f'; +----+------------------------------------+ | id | doc | +----+------------------------------------+ | 2 | {"a": {"b": ["c", "d"]}, "e": "f"} | +----+------------------------------------+ 1 row in set (0.01 sec)
ベタに書くと上記のようになります。これを->を使って書き換えると以下のようになります。
mysql> SELECT id, doc FROM json_data WHERE doc->'$.a.b[0]' = 'c' AND doc->'$.e' = 'f'; +----+------------------------------------+ | id | doc | +----+------------------------------------+ | 2 | {"a": {"b": ["c", "d"]}, "e": "f"} | +----+------------------------------------+ 1 row in set (0.01 sec)
クエリ自体の文字数がだいぶ短くなって、見やすくなったと思います。
JSON_PRETTY関数で綺麗に出力する
今回出力しているJSONは、キーも入っている値も1文字なので見やすいと思うのですが、実環境のJSONはもっと長いキーが指定されていたりして、1行にまとめられてしまうと読みにくい場合があります。そんな場合に使えるのがJSON_
mysql> SELECT JSON_PRETTY(doc) FROM json_data WHERE id = 1; +-----------------------------------------------------+ | JSON_PRETTY(doc) | +-----------------------------------------------------+ | [ 1, [ 2, 3 ], { "4": "5" } ] | +-----------------------------------------------------+ 1 row in set (0.01 sec)
今回のようにキーが小さい場合は逆に見えづらくなってしまう場合もありますが、実環境で検索をする際には、知っておいて損はないと思います。他のカラムを含めて出す場合などは、反対に見えづらくなってしまうので注意しましょう。
mysql> SELECT id,JSON_PRETTY(doc),id FROM json_data WHERE id = 1; +----+-----------------------------------------------------+----+ | id | JSON_PRETTY(doc) | id | +----+-----------------------------------------------------+----+ | 1 | [ 1, [ 2, 3 ], { "4": "5" } ] | 1 | +----+-----------------------------------------------------+----+ 1 row in set (0.01 sec)
まとめ
今回は、MySQLでJSON型のデータを挿入する方法と基本的な検索方法を紹介しました。今回紹介した関数以外にも便利な関数が追加されていて、より使いやすくなっているので、今後も紹介していきたいと思ってます。
他社連携のAPIで取得した結果をログとして保存して検索をしたい場合に