3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SQLBoiler (高度な利用例)

Last updated at Posted at 2024-05-27

環境

はじめに

SQLBoiler の README 及び、そこからリンクが貼られているコンテンツを読めばここに書いていることは網羅されていますが、初見では少し分かりづらいところ、少し複雑なユースケースをまとめています。

SQLBoiler の面倒な点

特に、SQLBoiler では以下の処理が面倒(または少し分かりづらい)ですが、その点も本文中で解説しています。

  • Bind を使いデータをカスタム struct にマッピングする際に、alias を付ける必要がある点。
  • リレーションを辿る方法。

Bind 利用例

カスタムの struct にデータをマッピングする場合、Bind を使う必要があります。
以下、README → Videos の内、SQLBoiler: Advanced Queries and Relationships の25分目付近から説明されている内容を、まとめたものです。

以下、複数テーブルから値を返したい場合の例(↑の youtube からの例)。

type joinStruct struct {
	User  models.User  `boil:"users,bind"`
	Video models.Video `boil:"videos,bind"`
}

var joins []*joinStruct
rows, err := db.QueryContext(context.Background(),
    `select users.id as "users.id",
     users.name as "users.name",
     videos.id as "videos.id",
     videos.name as "videos.name"
    from users inner join videos on users.id = vodies.user_id`,
)
dieIf(err)
err = queries.Bind(rows, &joins)
fmt.Println(len(joins))
fmt.Println(joins[0].User.Name, joins[0].Video.Name)
  • ここでは users 及び videos テーブルのカラムを select に含めているため、カスタム struct(ここでは joinStruct という名前)を使う必要があります。
    • 例えば InnerJoin を使っていても、単一のテーブルからだけ select する場合は、カスタム struct を使う必要はないですし、Bind を使う必要もありません。Bind を使いたければ、使うことは出来ます
  • テーブルのカラムを struct にマッピングするために、alias を付ける必要があります(e.g. select users.id as "users.id"
    • これが無いと、意図しないマッピングがされます。
    • select 文で指定するカラムの内、複数テーブルで同じカラム名を持つものだけを alias を付ける必要があります。
      • 上記の例では、id と name が users と videos テーブルに含まれるため、alias を付けています。
      • それ以外のカラムに alias を付けても害はありません。
  • select で指定しなかったフィールドも、初期値(e.g. int であれば 0)が入って返ってくる点に注意。つまり、struct 内のフィールドへのアクセスは可能。

詳しくは README の Binding のセクションBind のドキュメント を参照。

Bind を使った query でエラーが出た場合

エラーが発生する条件を再現した raw query を実行すれば、詳細なエラーが確認できる(ことがあります)。
例えば、Go 上の error では以下の ERROR 部分しか表示されなくても、raw query を実行する事により、LINE.. 部分のエラーも表示されます。

ERROR:  column reference "id" is ambiguous
LINE 21: ...s.id WHERE ("users"."organization_id" = 1) ORDER BY id DESC LI...

Eager Loading

Load を使うことにより、eager loading が可能となります(多段も可)。

models.Users(qm.Load("Videos.Tags"))

or

qm.Rels を使うと型安全にアクセス可能。

models.Users(
  qm.Load(
    qm.Rels(
      models.UserRels.Videos,
      models.VideoRels.Tags,
    )
  )
)

以下のデータ構造を想定。

  • user 1-n videos
  • videos n-n tags (through tags_videos table)

Eager Loading を使うことで、以下のように複数の SQL が実行されるようになる。
データ量が多いテーブルへアクセスする場合は、left outer join を使わず、複数の SQL を発行するべき。

SELECT * FROM "users" WHERE ("users"."id" IN ($1));
[1]

SELECT * FROM "videos" WHERE ("videos"."user_id" IN ($1)) AND ("videos"."parent_id" is null);
[1]

SELECT * FROM "tags" WHERE ("tags"."video_id" IN ($1,$2));
[71 72]

尚、Eager Loading については、Rails の Eager Loading とは挙動が違う(Preload と同じ)ようなので、違いは以下を参照。

リレーションを辿る方法

上記の Load を利用して取得した リレーション は model 定義の struct の R に保存されます(以下の例では R *userR)。
そして、以下の例の通り user.R.GetDepartment()user.R.GetContacts() という形でリレーションを辿ることが出来ます。
これらは全て sqlboiler が自動生成したコードです。

// sqlboiler/models/user.go (sqlboiler が自動生成したコード)

type User struct {
     <snip>

	R *userR `boil:"-" json:"-" toml:"-" yaml:"-"`
	L userL  `boil:"-" json:"-" toml:"-" yaml:"-"`
}

// userR is where relationships are stored.
type userR struct {
	Department *Department `boil:"Department" json:"Department" toml:"Department" yaml:"Department"`
}

// NewStruct creates a new relationship struct
func (*userR) NewStruct() *userR {
	return &userR{}
}

// has-one or belongs-to の例
func (r *userR) GetDepartment() *Department {
	if r == nil {
		return nil
	}
	return r.Department
}

// has-many の例
func (r *userR) GetContacts() UserContactsSlice {
	if r == nil {
		return nil
	}
	return r.UserContactsSlice
}

// 以下、同様に他のリレーション用の GetXXX() メソッドが定義されている。

詳しくは、README の Relationships セクション を参照。

リレーションシップを持った struct を作る方法

※つまり、struct の R(sqlboiler が生成した struct で relationship が保存される field)配下にデータを生成する方法。
※基本的にテストコード記述時に利用。通常のロジックでは不要。

上記と同じ User struct を例に使います。
上記例で定義されている (*userR) NewStruct() を使います。

user := &models.User{DepartmentID: int64(1)}
user.R = user.R.NewStruct() // ↑のコードにある `(*userR) NewStruct()` を使う
user.R.Department = &models.Department{ID: int64(1)}

fmt.Println(user.R.Department.ID) // R 経由で Department にアクセス可能となる。

raw query の確認方法

boil.DebugMode = true

その他

SQLBoiler 本体でサポートされていない BULK 処理等の拡張を提供しているレポジトリ:
https://github.com/tiendc/sqlboiler-extensions

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?