5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

sqlcで動的なWHEREを作る

Last updated at Posted at 2024-08-23

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

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

Step 1. 単純なSQLを書く

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

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

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. 自動生成されたコードを参考に自分のメソッドを作る

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

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のような Query Builderを使うのが良いのではないかと思います。UserSearchCondition のような自前の型を渡すのではなくて、goqu.Expression を渡してやって、Query自体をgoquで作るというのもありかと思います。

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の検索条件を作れます。変な条件ですが、例ということで。

	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から型と定数作るやり方を紹介していますので、気が向いたらどうぞ。

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

終わり

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

次回は、型のOverridesについて書こうと思います。

5
3
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
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?