Tech

SQLBoilerでJoin系のクエリを実行したい時はこうしよう

最終更新:2026.04.06

SQLBoilerでInnerJoinやLeftJoinをしたいとき、qmパッケージのInnerJoinとLeftJoinなどを使いがちです。

ただここで問題があって、

このqmパッケージの関数を使ってJoinしようとすると以下のように書けます。

models.Users(
    qm.InnerJoin("posts ON posts.user_id = users.id"),
).All(ctx, db)

つまり、生のSQL文字列を書く必要があるのです。

何が問題なのかというと、

可読性が低い
テーブル名やカラムを直書きしている時点でSQLBoilerを活用しきれていない
タイポの温床となる
DRYじゃない
同じような結合条件があると、このような文字列を大量に書く必要がある
正直今上げた問題点はかなりコードを書く上では辛いです。

ではどうするか?

関数化しておこう!

よく使うJoinのパターンを関数化(ヘルパー化)しておきましょう。

毎回 fmt.Sprintf や生の文字列を書くのではなく、構造化された引数を受け取る関数を用意することで、コードの見通しが良くなります。

例えば、以下のようなInnerJoin関数を作っておけば、

func InnerJoin(joinTable, baseTable, joinTableColumn, baseTableColumn string) qm.QueryMod {
	return qm.InnerJoin(fmt.Sprintf("%s ON %s.%s = %s.%s",
		joinTable,           // joinTable: 結合するテーブル名
		joinTable,           
		joinTableColumn,     // joinTableColumn:結合するテーブルのカラム
		baseTable,           // baseTable: 結合元のテーブル名 
		baseTableColumn,     // baseTableColumn:   結合元のテーブルのカラム
	))
}

以下のように使うことができます。

以下はTagsテーブルに対して、ContentsTagsテーブルをInnerJoinしたい時の例です。

	models, err := models.Tags(
		InnerJoin(
			models.TableNames.ContentsTags,
			models.TableNames.Tags,
			models.ContentsTagColumns.TagID,
			models.TagColumns.ID,
		),

このように書くことで次のようなメリットが得られます。

  • 安全性の向上
    • SQLBoilerが生成する models.TableNames や models.TagColumns といった定数と組み合わせることで、文字列のタイポを撲滅できます
  • 変更に強い
    • 万が一カラム名が変更になっても、定数を使っていればコンパイルエラーで検知できる可能性が高まります
  • 可読性
    • 「どのテーブル」「どのカラム」で結合しているかが引数として明確になります

LeftJoinやRightJoinも同様に関数化しておけばかなり便利です

まとめ

SQLBoilerのqmパッケージでJoinを行う際、生のSQL文字列を直書きするとタイポや変更漏れのリスクが生じます。

この課題を解決するために、SQLBoilerが生成するテーブル名やカラム名の定数を引数に取れるヘルパー関数を自作すること型安全性や可読性の高いコードを実現できます。