NaaN日記

やったこと、覚えたことを発信する場

sqlxでSUMを使う方法が分からなかった(わかった)

はじめに結論

SUMなどの集計関数を使うなら、モデルのタグに、`db:"SUM(hoge)"`とSUMも書きましょう

何故動かないのかわからなかった

例えば、次のようなデータのテーブル(sampleUser)があるとして、

name score
A 10
B 20
B 30

次の形のデータが欲しかったんですね。

name score
A 10
B 50

SQLを叩くとこうなるわけですが、このクエリをsqlxで実行することに躓いてしまいました。

SELECT name, SUM(score) FROM sampleUser GROUP BY name;
// 実行しようとしたもの
nameScore := []SampleUser{} // 結果を格納する構造体のスライス
err := sqlx.SelectContext(
	ctx,
	r.db,
	&nameScore,
	`
	SELECT name, SUM(score) FROM sampleUser
		GROUP BY name
	`,
)

// "missing destination name SUM(score) in *[]SampleUser"と怒られる(とても親切なメッセージ)

結論を言うと、モデルのタグが不適切でした。

type SampleUser struct {
	Name string `db:"name"`
	Score uint32 `db:"score"`
}

このような形で、nameとscoreを定義していたのですが、`db:"score"`と定義してしまっていました。
返ってくる値はscoreではなく、SUM(score)なので、`db:"SUM(score)"`とする必要がありました。
(この記事の最初から二つ目の表は間違っている)

mysql > SELECT name, SUM(score) FROM sampleUser GROUP BY name;
+------+------------+
| name | SUM(score) |
+------+------------+
| A    |         10 |
| B    |         50 |
+------+------------+

次のようにタグを変更することで、集計関数を含んだクエリを、sqlx.SelectContextで実行することができました。

type SampleUser struct {
	Name string `db:"name"`
	Score uint32 `db:"SUM(score)"`
}


もちろん、タグを変更せず、AS句を使ってSQLの方を変更しても動きますね。

type SampleUser struct {
	Name string `db:"name"`
	Score uint32 `db:"score"`
}

nameScore := []SampleUser{}
err := sqlx.SelectContext(
	ctx,
	r.db,
	&nameScore,
	`
	SELECT name, SUM(score) AS score FROM sampleUser
		GROUP BY name
	`,
)