GoのSQLBoilerでDistinctを利用する方法とその問題点

Voicyアドベントカレンダー 23日目の記事です。

こんにちは!株式会社Voicyでバックエンドエンジニアをしているたーふーと申します。
簡単に自己紹介をさせていただくと、自分は今年の9月にVoicyに入社したので、まだ入社して3ヶ月ちょっとの新参者になります!

全然関係ないですが、クリスマス前の今皆さんいかがお過ごしでしょうか。
Voicyでは明日クリスマス会があるようなので、自分は楽しみにしている最中です!

では本題に戻りまして、、

今回はGoのSQLBoilerでDistinctを利用する方法とその問題点についてお話しできればと思います!

自分自身Goを実務で利用したのはVoicyに入社してからなので歴は浅いのですが、、
その中でSQLBoiler関連の情報が少ないように感じた経緯があり、今回この記事を書かせていただきます。

なので同じような方の参考になれば幸いです!もし興味があれば是非ご覧ください!!

SQLBoilerとは?

まずそもそもSQLBoilerとは何か説明します。

SQLBoilerとは名前から想像できると思いますが、Goで使われるORMライブラリの1つです。
Goでは他にも有名なGORMなどのORMがありますが、VoicyではSQLBoilerを採用しています!

Go初心者でGoのORM関連について気になる方は以下の記事が参考になったので、見てみてください!

zenn.dev

またSQLBoilerの特徴としては以下が挙げられています。

  • DBのスキーマを読み取り、コードを自動生成する
  • 静的型付けのため動作が高速

※参考 github.com

Distinctとは?

次にDistinctについて解説します!
(この記事を見る皆さんはご存知だと思いますが、、)

DistinctとはSQLのSELECT文で重複を取り除くための構文です。

例えば以下のようなSQLを実行すると、addressの値が重複しているものも全て取得されます。

SELECT address FROM user;

しかし以下のようにDISTINCTを利用すれば重複された結果は出力されません。

SELECT DISTINCT address FROM user;

SQLBoilerでDistinctを利用する方法

では本題のSQLBoilerでDistinctを利用するにはどう記載すれば良いのか。
結論以下のように記載すれば簡単に利用可能です!

m, err := models.Users(
    qm.Distinct("user.address"),
).AllG(ctx)

Selectと同じような使い方で簡単に実装可能ですね!

問題点

問題とまではいかないかもですが、使い方の注意点があります。
それはDistinctを利用して複数のカラムの情報を取得したい場合です。

例えばただのselect文であれば、以下のように記載すれば複数のカラムの情報が取得できます。

m, err := models.Users(
    qm.Select(
        "user.name",
        "user.address",
    ),
).AllG(ctx)

しかしDistinctの場合、下記のように記載してもエラーになってしまいます。。

m, err := models.Users(
    qm.Distinct(
        "user.name",
        "user.address",
    ),
).AllG(ctx)

それぞれライブラリ内をみてみると、

func Select(columns ...string) QueryMod {
    return selectQueryMod{
        columns: columns,
    }
}
func Distinct(clause string) QueryMod {
    return distinctQueryMod{
        clause: clause,
    }
}

「あ、可変長引数になってないのね、、」
とこれが理由になります。

解決策

では複数のカラムは取得できないのか、というとそんなことはもちろんありません!
以下のように文字列内に取得したい情報をまとめて入れることで複数のカラム情報の取得は可能です!

m, err := models.Users(
    qm.Distinct("user.name, user.address"),
).AllG(ctx)

ただこれだと取得するカラム数が増えるとコードがどんどん見づらくなってしまいます。。 
なので自分は以下の様に記述しています。 !

clause := "user.name,"
clause += "user.address"

m, err := models.Users(
    qm.Distinct(clause),
).AllG(ctx)

終わりに

最後まで記事をご覧いただきありがとうございます!
この記事が少しでも参考になれば幸いです!

Voicyアドベントカレンダー も残りわずかですが、引き続きよろしくお願いします!