Clojure: DuctでRSSリーダーを作る - HoneySQLその２

![advent_calendar_2024.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985550/f4cd8ce0-6352-a1d8-4cae-f39dd44f4def.png)

[*Advent Calendar 2024 Day 24*](https://qiita.com/advent-calendar/2024/clojure)

昨日はHoneySQLを導入し、SQLクエリをClojureのデータ構造（マップやベクター）として構築できるようになりました

今日は親子関係の関係をもつDBにデータを`insert`したりデータを取得してみます

## `insert` クエリを作る

まずは`insert-into`のSQLを作ってみたいと思います

HoneySQLがインポートされたREPLで作業していきます

:::note info
作成するfeed, articleのテーブルは以下のようなものです

feed
| Column | Type | Default |
| ------ | ---- | ------- |
| id     | int  | nextval |
| url    | text |         |
| title    | text |         |
| description    | text |         |

article
| Column | Type | Default |
| ------ | ---- | ------- |
| id     | int  | nextval |
| feed_id    | text |         |
| title    | text |         |
| url    | text |         |
| content    | text |         |
| author    | text |         |
| published_at    | text |         |
| created_at    | text | current_timestamp |
:::

では実装します。

```clojure:REPL
; feed
(sql/format {:insert-into
             :feed
             :columns [:url :title :description]
             :values [{:url "url" :title "title" :description "description"}]})
["INSERT INTO feed (url, title, description) VALUES (?, ?, ?)" "url" "title" "description"]

; article
(sql/format {:insert-into
             :article
             :columns [:feed_id :title :url :content :author :published]
             :values [{:feed_id "feed_id" :title "title" :url "url" :content "content" :author "author" :published_at "published"}]})
["INSERT INTO article (feed_id, title, url, content, author, published) VALUES (?, ?, ?, ?, ?, ?)"
 "feed_id"
 "title"
 "url"
 "content"
 "author"
 "published"]
```

このように実装できました。

これを関数にし、外からデータを受け取れるようにします。

```clojure:REPL
(defn insert-feed [{:keys [url title description]}]
  (sql/format {:insert-into
               :feed
               :columns [:url :title :description]
               :values [{:url url :title title :description description}]
               :returning [:id]}))

(insert-feed {:url "http://example.com" :title "example" :description "example description"})
["INSERT INTO feed (url, title, description) VALUES (?, ?, ?) RETURNING id"
 "http://example.com"
 "example"
 "example description"]

(defn insert-article [{:keys [feed_id title url content author published]}]
  (sql/format {:insert-into
               :article
               :columns [:feed_id :title :url :content :author :published]
               :values [{:feed_id feed_id :title title :url url :content content :author author :published published}]}))

(insert-article {:feed_id 1 :title "example" :url "http://example.com" :content "example content" :author "example author" :published "2024-12-23 19:55:10.062472"})
["INSERT INTO article (feed_id, title, url, content, author, published) VALUES (?, ?, ?, ?, ?, ?)"
 1
 "example"
 "http://example.com"
 "example content"
 "example author"
 "2024-12-23 19:55:10.062472"]
```

いい感じですね

## `insert` 実行

では実行してDBにちゃんと入るか確認します

```clojure
(defn insert-article [{:keys [feed_id title url content author published]}]
  (sql/format {:insert-into
               :article
               :columns [:feed_id :title :url :content :author :published_at]
               :values [{:feed_id feed_id :title title :url url :content content :author author :published_at published}]}))

(defn register-articles [db {:keys [feed_id items]}]
  (doall (map #(jdbc/execute! db (insert-article (assoc % :feed_id feed_id))) items)))

(defn insert-feed [{:keys [url title description]}]
  (sql/format {:insert-into
               :feed
               :columns [:url :title :description]
               :values [{:url url :title title :description description}]
               :returning [:id]}))

(defn register-feed [db feed-data]
  (let [feed-ids (jdbc/execute! db (insert-feed feed-data) {:builder-fn rs/as-unqualified-lower-maps})
        id (-> feed-ids first :id)]
    (register-articles db (assoc feed-data :feed_id id))))
```

```clojure
(register-feed db data)
```

DBを確認します

```sql
feed=# select * from feed;
 id |                 url                 |                  title                  |              description               
----+-------------------------------------+-----------------------------------------+----------------------------------------
  1 | https://qiita.com/tags/clojure/feed | Clojureタグが付けられた新着記事 - Qiita | QiitaでClojureタグが付けられた新着記事

feed=# select * from article;
 id | feed_id |                           title                           |                          url                          |                      content                       | author |       published_at        
----+---------+-----------------------------------------------------------+-------------------------------------------------------+----------------------------------------------------+--------+---------------------------
  1 |       1 | Clojure: DuctでRSSリーダーを作る - HoneySQL               | https://qiita.com/maaaashi/items/5d9f5d8ced8c5f0ecad5 |                                                   +|        | 2024-12-23T07:04:04+09:00
    |         |                                                           |                                                       | Advent Calendar 2024 Day 23                       +|        | 
    |         |                                                           |                                                       | 昨日は環境変数を扱いました。                      +|        | 
    |         |                                                           |                                                       | 今日はよりDBからのデータ取得周りを改善してみます  +|        | 
    |         |                                                           |                                                       | HoneySQLというSQLクエリをClojureのデータ…          |        | 
  2 |       1 | Clojure: DuctでRSSリーダーを作る - 環境変数               | https://qiita.com/maaaashi/items/525d676aba72872aac19 |                                                   +|        | 2024-12-22T07:03:53+09:00
    |         |                                                           |                                                       | Advent Calendar 2024 Day 22                       +|        | 
    |         |                                                           |                                                       | 昨日は以下のようにDB接続を設定しました。          +|        | 
    |         |                                                           |                                                       |   :duct.database.sql/hikaricp                     +|        | 
    |         |                                                           |                                                       |   {:adapter "postgresq…                            |        | 
  3 |       1 | Clojure: DuctでRSSリーダーを作る - DB接続(HikariCP, Jdbc) | https://qiita.com/maaaashi/items/32fee6a9b924d31e4c9a |                                                   +|        | 2024-12-21T23:45:41+09:00
    |         |                                                           |                                                       | Advent Calendar 2024 Day 21                       +|        | 
    |         |                                                           |                                                       | 昨日はDuctでルーティングを実装しました            +|        | 
    |         |                                                           |                                                       | 今日はDB接続したいと思います。                    +|        | 
    |         |                                                           |                                                       |                                                   +|        | 
    |         |                                                           |                                                       | 今回の記事ではpostgresqlに接続しますが、          +|        | 
    |         |                                                           |                                                       | DBの立ち…                                          |        | 
  4 |       1 | Clojure: DuctでRSSリーダーを作る - ルーティング           | https://qiita.com/maaaashi/items/a5dac663b974082dba9c |                                                   +|        | 2024-12-20T07:04:07+09:00
    |         |                                                           |                                                       | Advent Calendar 2024 Day 20                       +|        | 
    |         |                                                           |                                                       | 昨日はDuctでプロジェクトのセットアップをしました。+|        | 
    |         |                                                           |                                                       | 今日はataraxyのルーティングを実装してみます       +|        | 
    |         |                                                           |                                                       | Ataraxy                                           +|        | 
    |         |                                                           |                                                       | Ataraxyは…                                         |        | 
```

いい感じでデータを投入することができました

次に投入したデータを取得してみます

## `select` クエリを作る

`feed`テーブルからデータを取得するクエリ
`select * from feed;`
と、`article`テーブルから、`feed_id`で絞ってデータを取得するクエリ
`select * from article where feed_id = ?`を作ってみます

```clojure
(def select-feeds (sql/format {:select [:*]
                               :from [:feed]}))

(defn select-articles [{:keys [id]}]
  (sql/format {:select [:*]
               :from [:article]
               :where [:= :feed_id id]}))
```

こんな感じで作ってみました

## `select` 実行

```clojure
(def select-feeds (sql/format {:select [:*]
                               :from [:feed]}))

(defn select-articles [{:keys [id]}]
  (sql/format {:select [:*]
               :from [:article]
               :where [:= :feed_id id]}))

(defn get-feeds [db]
  (let [feeds (jdbc/execute! db select-feeds {:builder-fn rs/as-unqualified-lower-maps})
        feeds-articles (map #(->> (jdbc/execute! db (select-articles %) {:builder-fn rs/as-unqualified-lower-maps})
                                 (assoc % :articles)) feeds)]
    {:feeds feeds-articles}))
```

こんな感じで実装してみました。

昨日に引き続き`HoneySQL`を触ってみましたが、生のSQLを書かずにデータ構造でSQLを表現できていいですね