sqlcで動的なWHEREを作る

## sqlcで動的な検索条件を作りたい

[前回](https://qiita.com/ktat/items/2ed13afcd29f49aee6a1)に続きまして、GoのORM [sqlc](https://github.com/sqlc-dev/sqlc)の話です。[前回の紹介記事](https://qiita.com/ktat/items/2ed13afcd29f49aee6a1)で書いたとおり、sqlcで動的な検索条件(WHERE)を自動生成で行うのは無理ですが、以下の手順でやれば、そんなに難しくはありません。

### Step 1. 単純なSQLを書く

```sql
-- name: SearchUser :many
SELECT * FROM user;
```

`Search`と書いてますが、特に検索はしてませんね。これは、下記のようなコードができあがります。

```go
package your_db

// 省略

const searchUsers = `-- name: SearchUsers :many
select id, family_name, first_name, age, address from user`

func (q *Queries) SearchUsers(ctx context.Context) ([]User, error) {
	rows, err := q.db.QueryContext(ctx, searchUsers)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var items []User
	for rows.Next() {
		var i User
		if err := rows.Scan(
			&i.ID,
			&i.FamilyName,
			&i.FirstNmae,
			&i.Age,
			&i.Address,
		); err != nil {
			return nil, err
		}
		items = append(items, i)
	}
	if err := rows.Close(); err != nil {
		return nil, err
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return items, nil
}
```
めっちゃ単純ですね。SQLがconst `searchUsers` に定義されていて、それを実行しているだけです。ちなみに、`*`で書いた場合でも、上記のようにテーブルの持っているカラム名に展開してくれます。

このようなコードですので、これを参考に自分で検索条件を追加することは割と簡単ですね。

### Step 2. 自動生成されたコードを参考に自分のメソッドを作る

コード生成されたところと同じ場所に、適当なファイル名で下記のような内容のファイルを作ります。

```go
package your_db

import (
	"context"
	"strings"
)

const searchUsersWithCondition = `-- name: SearchUsers :many
select id, family_name, first_name, age, address from user WHERE`

type UserSearchCondition struct {
	FamilyName *string
	FirstName  *string
	Age        *int
}

func (q *Queries) SearchUsersWithCondition(ctx context.Context, condition *UserSearchCondition) ([]User, error) {
	conditions := make([]string, 0)
	args := make([]any, 0)
	if condition != nil {
		if condition.FamilyName != nil {
			conditions = append(conditions, "family_name = ?")
			args = append(args, &condition.FamilyName)
		}
		if condition.FirstName != nil {
			conditions = append(conditions, "first_name = ?")
			args = append(args, &condition.FirstName)
		}
		if condition.Age != nil {
			conditions = append(conditions, "age = ?")
			args = append(args, &condition.Age)
		}
	}
	rows, err := q.db.QueryContext(ctx, searchUsersWithCondition+strings.Join(conditions, " AND "), args...)
    // 以下は同じなので省略
}
```
これで、`UserSearchCondition`にデータを入れておけば、動的な検索条件を作れます。
自分で型定義して、検索条件を自前で作る必要がありますが、単純な条件であれば、割と簡単ですね。

ですが、「いちいち検索用のtypeを作るの？汎用性低くない？」という声が聞こえてきますね。

## Step 3. Query Builderを使ってみる

そうですね、「ただのANDならこれでも良いが、複雑だとWHERE作るのがめんどい...」ですね。そういう時は、[goqu](https://doug-martin.github.io/goqu/)のような Query Builderを使うのが良いのではないかと思います。`UserSearchCondition` のような自前の型を渡すのではなくて、`goqu.Expression` を渡してやって、Query自体をgoquで作るというのもありかと思います。

```go
func (q *Queries) SearchUsersWithCondition2(ctx context.Context, ex goqu.Expression) ([]User, error) {
	sql, arg, err := goqu.Dialect("mysql").From("User").Where(ex).Prepared(true).ToSQL()
	if err != nil {
		return nil, err
	}
    // 以下省略
}
```

呼び出し側でgoquを使ってSQLの検索条件を作れます。変な条件ですが、例ということで。
```go
	ex = goqu.Or(
		goqu.And(
			goqu.C("first_name").Eq(firstName),
			goqu.("age").Gt(10),
		),
		goqu.And(
			goqu.C("first_name").Eq(firstName),
			goqu.("age").Lt(20),
		),
	)
```
「カラム名やテーブル名のハードコーディングはちょっと...」という方や「typo するんですけど!!!」という僕のような方は、以下の記事で、information_schemaから型と定数作るやり方を紹介していますので、気が向いたらどうぞ。

https://ktat.hatenadiary.jp/entry/2024/08/20/214627

goqu自体の紹介ブログも書いていますので、良ければ参考にしてください。

https://ktat.hatenadiary.jp/entry/2024/08/20/082437

## 終わり

sqlcでは動的な検索条件は書けないのですが、自分で書くのはそんなに難しくないよっという話でした。

[次回](https://qiita.com/ktat/items/74c9f78e96e57282fc5d)は、型のOverridesについて書こうと思います。