先に先日書いた
JOINのあるSQL結果をJSONにしたかったけれどRubyとPythonでサッとわからなかったのでClojureでやってみた。
でClojureでやっていたのですが勉強中のRubyでもやってみました。
免責事項的でずるいかもですが、私は業務ではどちらの言語も使ったことがないのでもっといい書き方があるかもしれません。比べる、という観点のネタ記事になります。
そもそもタイトルにあるJSONとはどんなものか
以下の関係の2つのTABLE、staff
とdepartment
から
staff
id | last_name | first_name | age | department_id |
---|---|---|---|---|
1 | taro | yamada | 37 | 1 |
2 | ichiro | suzuki | 30 | 1 |
3 | hanako | hujiwara | 25 | 2 |
department
id | name |
---|---|
1 | 人事 |
2 | 営業 |
以下のようなSQLを書いて
SQL
select * from staff s
inner join department dep
on s.department_id = dep.id;
以下のようなJSONがサッと欲しかったです。
[
{
"id": 1,
"name": "人事",
"staff": [
{
"id": 1,
"last_name": "taro",
"first_name": "yamada",
"age": 37,
"department_id": 1
},
{
"id": 2,
"last_name": "ichiro",
"first_name": "suzuki",
"age": 30,
"department_id": 1
}
]
},
{
"id": 2,
"name": "営業",
"staff": [
{
"id": 3,
"last_name": "hanako",
"first_name": "hujiwara",
"age": 25,
"department_id": 2
}
]
}
]
上記のようなJSONをサッとできるメソッドが見当たらなかったので(多分実装が簡単だから?)結果的に2つの結果をマージするような関数を書くことにし、はじめは勉強がてらClojureで書いていたのですがRubyも勉強していたので書いてClojureと比べました。
Ruby
準備
require 'pg'
require 'json'
# postgresqlで接続
connect = PG::connect(host: "localhost", user: "user", dbname: "playground")
JSON出力まで
# mapにしておく
staff_map = connect.exec("select * from staff").collect{|o| o}
department_map = connect.exec("select * from department").collect{|o| o}
def to_parent_child(parents, child_name, child, foreign_key)
# foreign_key => valueの形にしておく
child_group_by_foreign_key = child.group_by {|c| c[foreign_key]}
parents.each {|p|
p.store(child_name, child_group_by_foreign_key[p["id"]])
}
end
parent_child =
to_parent_child(department_map, "staffs", staff_map, "department_id")
puts(JSON.pretty_generate(parent_child))
結果
[
{
"id": "1",
"name": "人事",
"staffs": [
{
"id": "1",
"last_name": "taro",
"first_name": "yamada",
"age": "37",
"department_id": "1"
},
{
"id": "2",
"last_name": "ichiro",
"first_name": "suzuki",
"age": "30",
"department_id": "1"
}
]
},
{
"id": "2",
"name": "営業",
"staffs": [
{
"id": "3",
"last_name": "hanako",
"first_name": "hujiwara",
"age": "25",
"department_id": "2"
}
]
}
]
Clojure
準備
;cheshireはmapからjsonにするライブラリ
(ns json-try
(:require [clj-postgresql.core :as pg])
(:require [clojure.java.jdbc :as jdbc])
(:require [cheshire.core :refer :all ]))
(def playground (pg/pool
:host "localhost"
:user "user"
:dbname "playground"))
;jdbc/queryメソッドが少々冗長なためラップする。
(defn q [sql]
(jdbc/query playground sql))
JSON出力まで
;Clojureはクエリの結果はそのままmap
(def staff-map (q "select * from staff"))
(def department-map (q "select * from department"))
(defn to-parent-child [parent-map child-name child-map foreign-key]
(let [child-group-by-foreign-key (group-by foreign-key child-map)]
(map #(assoc % child-name (child-group-by-foreign-key (:id %)))
parent-map)))
;上記関数を使用して結果を束縛
(def parent-child
(to-parent-child department-map :staffs staff-map :department_id))
(println (generate-string parent-child {:pretty true}))
結果
[ {
"id" : 1,
"name" : "人事",
"staffs" : [ {
"id" : 1,
"last_name" : "taro",
"first_name" : "yamada",
"age" : 37,
"department_id" : 1
}, {
"id" : 2,
"last_name" : "ichiro",
"first_name" : "suzuki",
"age" : 30,
"department_id" : 1
} ]
}, {
"id" : 2,
"name" : "営業",
"staffs" : [ {
"id" : 3,
"last_name" : "hanako",
"first_name" : "hujiwara",
"age" : 25,
"department_id" : 2
} ]
} ]
比べてみて
これくらいのものなら特に大きな違いとかないですね。。
ちょっと気になったのが
- Rubyは関数の中の
each
をはじめはmap
で書いていたが関数の評価の結果はdepartment_map
のはじめの状態(mapの結果ではなかった)が返ってきた。eachで副作用を明示的にしたらうまくいった。 - Clojureは(当たり前だけれど)mapの結果が期待通りだった。
以上です。