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


とある帳票をつくるサービスがJSONを受け付ける形式であり、
検証にあたりSQLの結果から１対多のJSONが欲しかったのですが、DBのクライアントツールではサッと見当たらず
RubyかPythonでそんなものを作るライブラリなりコードなりありそうだなと思ったのですが、（多分あるとは思うのですが）
意外とコレといったものがなかったので勉強中のClojureで簡単なものを書いてみました。

先に申し上げますと、サービスに必要なJSONはフラットな階層でも工夫すれば問題ありませんでしたが。


## そもそも欲しい結果

以下の関係の２つのTABLE、```staff```と```department```から

### staff

<table border="1" style="border-collapse:collapse">
<tr><th>id</th><th>last_name</th><th>first_name</th><th>age</th><th>department_id</th></tr>
<tr><td>1</td><td>taro</td><td>yamada</td><td>37</td><td>1</td></tr>
<tr><td>2</td><td>ichiro</td><td>suzuki</td><td>30</td><td>1</td></tr>
<tr><td>3</td><td>hanako</td><td>hujiwara</td><td>25</td><td>2</td></tr></table>

### department

<table border="1" style="border-collapse:collapse">
<tr><th>id</th><th>name</th></tr>
<tr><td>1</td><td>人事</td></tr>
<tr><td>2</td><td>営業</td></tr></table>

以下のようなSQLを書いて

```mysql:SQL
select * from staff s 
inner join department dep 
on s.department_id = dep.id;
```


以下のようなJSONがサッと欲しかったです。

```JSON

[
  {
    "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
      }
    ]
  }
]


```


## SQLの結果をそのままJSONにするのは割とどれでもあるけれど階層にするものはない？

以下のような結果はどの言語でもDBクライアントツールでもサッとできます。
初めの結果と違って```department```に紐づく```staff```の結果がフラットな階層です。

```JSON

[
  {
    "id": 1,
    "last_name": "taro",
    "first_name": "yamada",
    "age": 37,
    "department_id": 1,
    "name": "人事"
  },
  {
    "id": 1,
    "last_name": "ichiro",
    "first_name": "suzuki",
    "age": 30,
    "department_id": 1,
    "name": "人事"
  },
  {
    "id": 2,
    "last_name": "hanako",
    "first_name": "hujiwara",
    "age": 25,
    "department_id": 2,
    "name": "営業"
  }
]


```


階層のある（って表現するのが正しいかはわかりませんが）JSONググってもだいたいプログラム上で普通に書いて変換しているような感じでしたので
それだったらClojureの方が楽なのでは？と思い勉強にもなると思ってプログラムしてみました。

## 実際に書く

### 準備

前提

- java 1.8
- clojure 1.10.0
- postgresql 11.2


ビルドツール

- leiningen


Jsonへ変換するClojureのライブラリ

- [Cheshire](https://github.com/dakrone/cheshire#cheshire)


Clojure関連の情報は以下のリンクでほぼ揃います。(お世話になっています。)

[Clojure/ClojureScript関連リンク集](https://qiita.com/lagenorhynque/items/68c314c288b75a9492ba)

###私の知識

- Javaを業務で使い３年目くらい
- RubyとPythonは基本的な構文はわかり、https://www.codewars.com の問題をたまに挑戦する。
- Clojure入門２、３ヶ月くらい


### ライブラリの追加

```clojure:project.clj

  :dependencies [[org.clojure/clojure "1.10.0"]
                 [clj-postgresql "0.7.0"]
                 [cheshire "5.8.1"]]

```

## 本体部

ライブラリの設定とDB接続情報とSQLを簡潔にするラッパーを書いておく

```clojure:json-try.cljの冒頭

(ns json-try
	(:require [clj-postgresql.core :as pg])
	(:require [clojure.java.jdbc :as jdbc])
	(:require [cheshire.core :refer :all ])
	(:require [clojure.java.io :refer [writer]])
	)

;DBの接続情報の設定 最低限の設定のためpasswordなし
(def playground (pg/pool
					:host "localhost"
					:user "user"
					:dbname "playground"))

;jdbc/queryメソッドが少々冗長なためラップする。				
(defn q [sql]
  (jdbc/query playground sql))
```


JSONファイルを吐くまで


```clojure:json-try.clj続き

;結果をそれぞれ束縛しておく
(def staff-map (q "select * from staff"))
(def department-map (q "select * from department"))


 ; staff-mapとdepartment-mapから階層をつくる関数
 ; child-nameはJSONにしたときのキーの名前として設定 foreign-keyはSQLにおける
 ; on staff.depertment_id = department.idの staff側の外部キー 
 ; 役割がinner join のようなものなので関数名もそれにすればよかったかも
(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))

;上記結果をjsonファイルとして出力（出力せずコンソールに出すことももちろん可能）
(with-open [fout (writer "./result.json")]
  (generate-stream parent-child fout {:pretty true}))


```

※参考REPLでのstaff-mapの動きとto-parent-childの動き
(もしClojureを触らない方がこの記事をみていただいたときのサンプル)

<details>
<summary>gifでのsample</summary>
<div>

sql実行
![Aug-01-2019 01-00-15.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136265/988ebbaa-e8ea-0acc-b934-b3b8f77792a0.gif)

to-parent-child関数実行
![Aug-01-2019 01-02-35.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136265/c51f9b36-3bbd-9a20-d98f-05c8e60a41bc.gif)

group-byのところの動き department_idをキーにしたマップにしておく
![Aug-01-2019 01-09-41.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136265/7a35d59d-7d6e-b6a9-ce97-7913c728dd80.gif)

上記で作ったものからキーを後ろに書くとその結果が取れる。1を後ろに起き、紐づく２つのスタッフが取得される。
![Aug-01-2019 01-11-36.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136265/35c254e8-45fa-1eba-59b9-5d8b6af01c10.gif)

</div>
</details>


結果

```result.json

[
  {
    "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": "営業",
    "staff": [
      {
        "id": 3,
        "last_name": "hanako",
        "first_name": "hujiwara",
        "age": 25,
        "department_id": 2
      }
    ]
  }
]

```

## Clojureでやろうと思った発想のきっかけ

ClojureでのDBを扱う基本であると思われる```clojure.java.jdbc```を
使用してSQLをREPLで実行したときそのまま画面上にMapとして返ってきたので「これは気持ちがいい、、Map（連想配列、辞書型とも）で返ってくるなら他の関数もそのまま使えるじゃないか、ORMって実際いるものだったのか？」と感想を持っていました。
冒頭の件があったときにまずRubyやPythonで探したのですが階層をもったJSONは普通にプログラムとして書いていたサンプルが多く、そもそもあまり需要、必要性のないものと思ったのですが、せっかくClojure勉強中だったのでMapで結果が変えるならJSONも割と楽に行けるのでは？というのがきっかけでした。

ただし、Clojureにまだまだ慣れていないので上記```to-parent-child```関数を書くのも１−２時間かかったのでまぁ楽ではなかったですし、もっといい書き方がきっとあるのでしょう。精進します。

以上です。