Edited at

1:多のテーブルのそれぞれの結果をマージしてネストされたJSONにするのをRubyとClojureで関数を書いて比べてみた。

先に先日書いた

JOINのあるSQL結果をJSONにしたかったけれどRubyとPythonでサッとわからなかったのでClojureでやってみた。

でClojureでやっていたのですが勉強中のRubyでもやってみました。

免責事項的でずるいかもですが、私は業務ではどちらの言語も使ったことがないのでもっといい書き方があるかもしれません。比べる、という観点のネタ記事になります。


そもそもタイトルにあるJSONとはどんなものか

以下の関係の2つのTABLE、staffdepartmentから


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の結果が期待通りだった。

以上です。