GoのSQL Query Builder goquの紹介
GoでORMを使うのではなくて、SQLだけを作りたいという要望があって調べたところ、goquというものがみつかりましたので、試してみました。
書いてから気づきましたが、日本語の紹介記事は他にも結構ありました。
- Goのクエリビルダー goqu を使ってみる | RE:ENGINES
- SQLビルダーgoquの使い方 | フューチャー技術ブログ
- Goのクエリビルダーgoqu入門
- goquを駆使してgoでSQL構築も構造体マッピングもRDBテストもやる - エムスリーテックブログ
基本の使い方
Dialectを指定
MySQLやPostgreSQLなどをDialectで指定できます。MySQLの場合は、以下のようにします。
import ( "github.com/doug-martin/goqu/v9" _ "github.com/doug-martin/goqu/v9/dialect/mysql" )
上記のような importを行い、Dialectで指定します。
goqu.Dialect("mysql")
SELECTの使い方
Tableを指定する
続けて、Fromでテーブルを指定します。
goqu.Dialect("mysql").From("user")
SQLにする
最後に、ToSQL() で、SQLを受け取れます。
sql, _, err := goqu.Dialect("mysql").From("user").ToSQL() fmt.Println(sql)
SQL:
SELECT * FROM `user`
ToSQL() でSQLにするのではなく構造体にマッピングする
ScanStructs や ScanStruct がありますので、下記のように構造体にマッピングすることも出来ます。
type User struct{ FirstName string `db:"first_name"` LastName string `db:"last_name"` Age int `db:"-"` // a field that shouldn't be selected }
users := make([]Users, 0) sql, _, err := goqu.Dialect("mysql").From("user").ScanStructs(&users)
Selectで関数を使う
goqu.L を使うことで、任意の文字を渡すことができるので、関数を渡すことも出来ます。
sql, _, err := goqu.Dialect("mysql").Select(goqu.L("AES_ENCRYPT(num, \"hoge\")").As("crypted")).From("user").ToSQL() fmt.Println(sql)
SQL:
SELECT AES_ENCRYPT(num, "hoge") AS `crypted` FROM `user`
Whereを指定する
Whereで条件を指定することも普通にできます。
sql, _, err := goqu.Dialect("mysql").From("user").Where( goqu.Or( goqu.C("age").Gt(10), goqu.C("age").Lt(20), ), ).ToSQL() fmt.Println(sql)
SQL:
SELECT * FROM `user` WHERE ((`age` > 10) OR (`age` < 20))
このWhereの書き方は、下記のようにも書けます。
goqu.Or(
goqu.Ex{
"age": goqu.Op{"gt": 10},
},
goqu.Ex{
"age": goqu.Op{"lt": 20},
},
),
この書き方なら、カラムがかぶらなければ複数条件をまとめて書けます。
goqu.Or(
goqu.Ex{
"age": goqu.Op{"gt": 10},
"name": goqu.Op{"eq": "name"},
},
goqu.Ex{
"age": goqu.Op{"lt": 20},
},
),
まぁ、下記のようにも書けますし、このスタイルのほうが書きやすいし、読みやすいかなと思います。
goqu.Or(
goqu.And(
goqu.C("age").Gt(10),
goqu.C("name").Eq("name"),
),
goqu.C("age").Lt(20),
),
Placeholderを使う
今までのものは、すべて、SQLの中に変数でわたし値が埋め込み済みでしたが、Prepared(true)を使うことで、Placeholderを使うことも出来ます。
sql, args, err := goqu.Dialect("mysql").From("user").Where( goqu.Or( goqu.C("age").Gt(10), goqu.C("age").Lt(20), ), ).Prepared(true).ToSQL() fmt.Println(sql) // SELECT * FROM `user` WHERE ((`age` > ?) OR (`age` < ?)) fmt.Printf("%#v\n", args) // []interface {}{10, 20}
もっと複雑なSQL
かなり複雑なSQLも表現可能です(複雑っぽくしたかっただけなので、出来上がるSQLには特に意味がないです)。
ds := goqu.From("user_item").Select(goqu.COUNT("*")).GroupBy("user_id").As("ui") sql, _, _ = goqu.Dialect("mysql"). Select(goqu.COUNT("*")). From("user"). Join( goqu.T("user_hobby"), goqu.On(goqu.I("user.id").Eq(goqu.I("user_hobby.user_id"))), ). Join( ds, goqu.On(goqu.I("user.id").Eq(goqu.I("ui.user_id"))), ). Where( goqu.Or( goqu.And( goqu.C("age").Gt(10), goqu.C("name").Eq("name"), ), goqu.C("age").Lt(20), ), ).GroupBy("user.id").Having(goqu.SUM("income").Gt(1000)).ToSQL()
SQL:
SELECT COUNT(*) FROM `user` NNER JOIN `user_hobby` ON (`user`.`id` = `user_hobby`.`user_id`) INNER JOIN (SELECT COUNT(*) FROM "user_item" GROUP BY "user_id") AS `ui` ON (`user`.`id` = `ui`.`user_id`) WHERE (((`age` > 10) AND (`name` = 'name')) OR (`age` < 20)) GROUP BY `user`.`id` HAVING (SUM(`income`) > 1000)
その他
詳しくは、ドキュメントを見てもらえれば良いのですが、希望するものはだいたいできるんじゃないのかなぁという印象を持ちました。SELECT FOR UPDATE なんかも使えるようです。
Updateの使い方
WhereはSELECTのときと同じなので省略します。Updateでテーブル名を渡して、Setでデータをセットします。
sql, _, _ = goqu.Dialect("mysql").Update("user").Set(goqu.Record{"name": "name1"}).ToSQL() fmt.Println(sql)
SQL:
UPDATE `user` SET `name`='name1'
DELETの使い方
こちらも、WhereはSELECTのときと同じなので省略します。Deleteにテーブル名を渡します。
sql, _, _ = goqu.Dialect("mysql").Delete("user").ToSQL() fmt.Println(sql)
SQL:
DELETE `user` FROM `user`
DELETEの後ろにテーブル名入れて使った覚えがあまりないんですが、正しいSQLでした。
INSERTの使い方
INSERTは、goqu.Recordでレコードのセットを作って、Rowsに渡します。
users := []goqu.Record{
{"first_name": "Bob", "last_name": "Yukon", "created": time.Now()},
{"first_name": "Sally", "last_name": "Yukon", "created": time.Now()},
{"first_name": "Jimmy", "last_name": "Yukon", "created": time.Now()},
}
sql, _, _ = goqu.Dialect("mysql").Insert("user").Rows(users).ToSQL()
fmt.Println(sql)
INSERT INTO `user` (`created`, `first_name`, `last_name`) VALUES ('2024-08-19 00:29:07', 'Bob', 'Yukon'), ('2024-08-19 00:29:07', 'Sally', 'Yukon'), ('2024-08-19 00:29:07', 'Jimmy', 'Yukon')
Cols と Vals でわけて指定したり、Rows に構造体を渡すこともできるようです。
終わり
ということで、一通りgoquの使い方を説明しました。
途中でも紹介しましたが、下記に、それぞれのオペレーションについてのドキュメントが詳しく載っています。
デメリットとしては、テーブル名や、カラム名をハードコーディングしないといけないところですが、そこに関しては、コードを自動生成することでなんとかできそうですね。
次回に続きます。