[golang] SQLBoiler のいろは (2) Query 応用編

[golang] SQLBoiler のいろは (2) Query 応用編

こんばんは、七色メガネです。

前回に引き続き、golang の ORM ツールである SQLBoiler について調査・学習していきたいと思います。
前回は SQLBoiler の設定と基本 CRUD について取り扱ったので、今回は応用編として、各種 Query の実行方法についてまとめていきます。

SQLBoiler ってなに?設定はどうやるの?

こちらは前回の記事で扱いましたので、よろしければ下記の記事をご覧ください。

[golang] SQLBoiler のいろは (1) 設定 ~ 基本 CRUD 実行まで

各種 Query の実践

データ準備

ではデータ準備から初めていきます。以降は DDL から model を生成できる、という前提に立って進めていきます。もしその方法がわからない場合、前回の記事をご参照ください。

DDL

前回同様、以下の DDL を使用します。

・User : 他テーブルから参照される親テーブル。
・Division : 必ず User を親に持つ子テーブル。外部キー制約で User と紐づく。
・Branch : 任意で User を親に持つ子テーブル。制約はなく、user_id は nullable。

ddl.sql

Division は 必ず User との relation を持つように作っていますから、Inner Join の test で使用します。
Branch は任意で User との relation を持つように作っていますから、Outer Join の test で使用します。

データ

DDL を実行し、model を生成したら、model を使ってデータを用意しましょう。
データを用意するためのコードは次の通りです。とりあえず今は、こんなデータを用意しているのだなという雰囲気だけ読み取ってもらえれば大丈夫です。

prepare_data

Query を指定し、Model に用意された Finisher で 実行する

まず最初は、生成された model に receiver として用意されている finisher を使用して Query を実行する方法です。

Where の指定が最低限で済み、また Query 実行結果は model に用意されている struct として返却されるので、一番簡単に条件付き Query を発行できます。

今回は「条件に一致する全てのレコードを取得する」という効果を持った All という finisher を使用しましたが、他にもレコードを一つだけ取得する One や条件を満たすレコードの数を返却する Count といったものも存在するので、基本的な Query 実行はこれで事足りることと思います。

デメリットとしては、レコードを取得しようとした時、返却される struct が model で用意されている struct の形になってしまうということです。自分で struct を定義してその形でデータを取得しようと思った時には、この方法は使えません。

Query を組み立て、結果を指定した struct に Bind する

用意された qm キーワードを使用して、もっと柔軟に Query を組み立て、実行することもできます。

このように qm キーワードを作成し、Query を部分的に定義・組み立てたうえで実行することもできます。結果は先ほどの例と全く同じです。

メリットとしてはより柔軟な Query 発行ができるということです。
また今回使用している finisher の Bind は User の model に用意されたものではなく汎用的なも関数であり、Query の実行結果を引数に渡した struct に bind してくれます。
ここでは model で定義されている struct をラップしただけの struct を渡しているだけなのでメリットを感じませんが、後々、この機能が必須になる場面が出てきます。

デメリットとしては、最初に紹介したパターンよりも処理が煩雑であるということでしょうか。特に query の組み立ては、複雑になればなるほど、可読性が落ちていくでしょう。

生 Query を記述し、結果を指定した struct に Bind する

上の2例では where 句の指定などを qm キーワードで行なっていましたが、通常の SQL のように、そのまま直接 Query を記述し、実行することもできます。

メリットはあらゆる形の query を組み立てられるということですが、もはや ORM の良さをかなぐり捨てているので、本来であればあまり使うべき手法ではありません。

しかし、特定の場合にはこの方法に頼らねければいけないことがあるので紹介しておきます。
特定の場合については、後述します。

Query を組み立て、結果を original な struct に Bind する

ここでの紹介は、Query 組み立てパターンと同じです。ただ bind する先の struct に、自分で定義した struct を使用してみます。

User テーブルは user_id と name と age を持っていますが、ここでは user_id と name だけを持った struct を定義し、それに bind しているため、取得結果が今までと異なっています。

この例ではいまいちメリットを感じませんが、次で紹介するように Join を行う時などは、model に用意されている単一テーブルの情報しか持たない struct では使用に堪えませんから、このように自分で struct を定義してそこへ bind するという必要性が生まれます。

注意点としては、Select するカラム名と struct で指定した boil:" xxx " の記述が一致しない場合、bind が上手く行われないということです。
またカラム名の重複がある場合にも、bind が正常に機能しません。重複がある場合には、AS キーワードを使うなどして、各カラム情報を明確に区別する必要があります。

Inner Join を行う

Inner Join は、今までに学んだことの組み合わせです。qm キーワードとして Inner Join が用意されているのでそれを使用し、Join した後の結果を bind するための struct を自分で用意し、後は実行するだけです。

気をつける点としては、上の例でも述べてしまったのですが、カラム名の区別です。

今回 User テーブルにも Division テーブルにも name というカラムがありそれを取得しようとしていますが、もし struct でこの2つの name を区別するように記述していなければ、bind 結果は期待するものになりません。

今回は struct にて、
・UserName string boil:"user_name"
・DivisionName string boil:"division_name"

と区別を行い、それぞれに適切に bind されるよう、Select 句 で AS による別名を指定しています。

Outer Join を行う

最後に Outer Join です。実は SQLBoiler は標準で Outer Join をサポートしていません。qm キーワードもありません。従って、ユーザー側の工夫で Outer Join を実現するしかありません。

まずは struct です。自分で専用の struct を用意しなければいけないのは今まで同じですが、定義する際、null を許容するように記述することが重要です。

今回は User をベースにした Branch との Left Outer Join を想定しているので、 Branch 側の結果が取得されない可能性があります。ので、Branch 側の結果を bind するフィールドは、null 許容型で定義しておく必要があります。これを忘れると、実行時に( Branch 側に一致するレコードがない場合 )エラーになります。

次は Query の設定ですが、qm キーワードがない以上、もう自分で生 Query を書くしかありません。先に述べていた生 Query を使わなければいけない場面というのは、ここです。

結果は次の通りです。最初のデータは Branch が存在しているので正常に取得され、以降のデータは Branch が存在しないので空のデータで bind できています。(int はデフォルトが 0 なので 0 に落ちていますが)

 

まとめ

・qm キーワードを使用して、Query の条件句を指定できる。(Outer Join 以外)
・model に用意された finisher を用意して Query を実行できる。
・bind という finisher を使用して自分で定義した struct に Query 実行結果を bind できる。
・生 SQL を実行することができる。
・サポートされていないが、Outer Join も使用することができる。

なんでも出来ますね。便利便利。

では今回はここまでです。
もうちょっと知見が溜まったら、次回はもっと複雑な Query 実行について触れていきたいですね。ネタが無いので、単発紹介になるかもしれませんが。

ここまでご覧いただき、ありがとうございました!

参考

https://github.com/volatiletech/sqlboiler

今回使用した src

https://github.com/NanairoMegane/sqlboiler_test

おすすめ書籍

Google Cloud Platform エンタープライズ設計ガイド

Google Cloud Platform 実践Webアプリ開発 ストーリーで学ぶGoogle App Engine

プログラマのためのGoogle Cloud Platform入門 サービスの全体像からクラウドネイティブアプリケーション構築まで

golangカテゴリの最新記事