LoginSignup
10
6

More than 3 years have passed since last update.

Go言語 SQLBoilerでタイプセーフに複数条件のWhere句を書く

Last updated at Posted at 2021-04-08

前置き

小ネタになります:pushpin:
最近Go言語でAPIを作っていてORMにSQLBoilerを採用しました。
少し複雑なWhere句をタイプセーフで書く方法の情報が意外に少ないのではないかと思い執筆しました。

テーブル構成

このようなテーブルがあったとします。

CREATE TABLE `foods` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(45) COLLATE utf8mb4_bin NOT NULL,
  `price` int(10) unsigned NOT NULL,
  `note` varchar(45) COLLATE utf8mb4_bin DEFAULT NULL,
  PRIMARY KEY (`id`),
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

image.png

生クエリ

少しも複雑なWhere句ではないと思いますが、このくらいの条件でもタイプセーフで書いていない記事が多いので執筆してます。
複雑なサブクエリなどを期待されていた方には申し訳ありません:bow:

SELECT 
    *
FROM
    foods
WHERE
    price >= 100 AND price < 500
        AND (note = '特売' OR note IS NULL)

SQLBoiler

比較的よく紹介されている記述

このように紹介されていることが多い印象です。「簡易的にビルダーでクエリを組み立てる」とでも申しましょうか。
モデルにマッピングしてくれる利点はありますが、あまり生クエリと変わりなく筆者としてはもっとORMの恩恵を受けたいと思います。恩恵を受けたくないのならORMを採用しない選択肢もあると考えます。

ソースコード
res, err := models.Foods(
    qm.Where("price >= ? AND price < ? AND (note = ? OR note IS NULL)", 100, 500, "特売")).
    All(context.Background(), db.DB)
SQLBoilerのデバッグモードで出力されたクエリ
SELECT * FROM `foods` WHERE (price >= ? AND price < ? AND (note = ? OR note IS NULL));
[100 500 特売]

WHERE/AND/OR/()毎に組立てる記述

上記の表現、公式ドキュメントから名前を発見できず、読解力や語彙力がなく申し訳ありません:sweat_drops:
このように小分けに書くこともできます。比較的よく紹介されている記述と比較したらORMの活用率は上がりましたね。ANDとかOR()のtypoは減るかもしれません。筆者としてはもっとORMの恩恵を受けたいと思います。

ソースコード
res, err := models.Foods(
    qm.Where("price >= ?", 100), qm.And("price < ?", 500),
    qm.Expr(qm.And("note = ?", "特売"), qm.Or("note IS NULL"))).
    All(context.Background(), db.DB)
SQLBoilerのデバッグモードで出力されたクエリ
SELECT * FROM `foods` WHERE price >= ? AND price < ? AND (note = ? OR note IS NULL);
[100 500 特売]

タイプセーフに組立てる記述

ORMの恩恵を多く受けたければ以下のように書けます。SQLBoilerの公式で強く推奨してる方法です。
「冗長的な記述だけど、タイプセーフでコンパイルエラーになるからいいよね」ということです。SQLBoilerに関わらず、他ORMでもこのような思想はありますね。
インテリセンス(コード補完)が活用できるのも魅力の1つだと感じます。

ソースコード
res, err := models.Foods(
    models.FoodWhere.Price.GTE(100), models.FoodWhere.Price.LT(500),
    qm.Expr(models.FoodWhere.Note.EQ(null.StringFrom("特売")), qm.Or2(models.FoodWhere.Note.IsNull()))).
    All(context.Background(), db.DB)
SQLBoilerのデバッグモードで出力されたクエリ
SELECT * FROM `foods` WHERE `foods`.`price` >= ? AND `foods`.`price` < ? AND (`foods`.`note` = ? OR `foods`.`note` is null);
[100 500 {特売 true}]

終わりに

ORM自体の是非というのは揉め易いネタだと思いますので割愛します。そもそもORMを採用するにしてもフルスタックかマイクロの議論もあります。全てをORMに頼るのは難しく生クエリで書いたほうが良い場面もありますので「ORMで消耗する」のではなくバランス良く使って生産性や保守性、堅牢性を上げられれば良いかなと思っています。また、公式ドキュメントにタイプセーフな記述方法も書いてあるのですが調査中はわかりませんでした。コードを書き終えてから結果論として「あぁ、このことか」と理解しました。筆者の読解力の問題を棚に上げつつ、ドキュメントをもう少し親切にしてほしいと思っております。

10
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
6