entでeager_loadingを実装する

# 書くこと
- GolangのORMであるentでEager Loadingを実現する方法
  - INNER JOIN、LEFT JOINなど
  - 他テーブルのWHERE、ORDERなど
  - 他テーブルのデータの取得はPreloadになってしまう

# 書かないこと
- テーブルの定義方法
- schemaの書き方

# なぜ記事を書いたか
- INNER JOINやLEFT JOINの方法が書かれている記事や公式が少なく、自分で色々試して知見が深まったため

# 参考文献
https://entgo.io/ja/docs/predicates/

# JOINの行い方
## ER図
```mermaid
erDiagram
organizations ||--o{ users : has
organizations {
    int id
    string name
}
users {
    int id
    string name
    int organization_id
}
```

## Source
```golang
import (
	entsql "entgo.io/ent/dialect/sql"
    entUser "github.com/hoge/pkg/ent/user" //entから出力されたファイル
    entOrganization "github.com/hoge/pkg/ent/organization" //entから出力されたファイル
)

// 中略

client.
User.
Query().
Where(func(u *entsql.Selector){
    o := entsql.Table(entOrganization.Table)

    u.
    Join(organization).On(
        u.C(entUser.FieldOrganizationID),
        o.C(entOrganization.FeildID),
    )
})
```
## 出力されるSQL

```
SELECT
 id,
 name,
 organization_id
FROM
　　users
INNER JOIN
 organizations t1
ON
 users.organization_id = t1.id
```

## 解説
entではWhereの中でJOINの条件を記載します。外でWith〇〇と記載するのはPreloadになります。

Where句の中で関数を書いていくのですが、注意としてu *sql.Selectorはclientの後に指定したもののSelectorになります。今回は、Userのデータが入っていると思ってください。
```
Where(func(u *entsql.Selector)
```

ここでは、WHEREやORDER BY、JOINで使うテーブルを変数として定義しています。
今回はOrganizationsテーブルしか定義していませんが、他にも必要な定義がある場合は同様に追加します。
entsql変数でテーブル名称を宣言してあげることでそのテーブル情報が変数に格納されます。entOrganization.Tableはentが自動出力するコードで中にはテーブル名称のStringが入っています。
```
o := entsql.Table(entOrganization.Table)
```

次に実際に結合を行います。
uの後にJoin関数を読んであげることで結合が行えます。JoinはInnerJoinになります。他にもLeftJoin関数、RightJoin関数、FullJoin関数が用意されているので、ケースごとに使い分けてください。
Join関数の引数には先ほど定義した変数oを入れてあげます。これでuとoを結合したことになります。
あとはOn句を指定してあげます。
u.Cと書いていますが、Cと記載することでSQL上ではテーブル名称がPrefixにつくことになります。
つけた場合はusers.organization_idとなり、ない場合はorganization_idとなるので、同名の列が存在する場合にエラーとならないよう忘れずにつけるようにしましょう。
```
    u.
    Join(o).On(
        u.C(entUser.FieldOrganizationID),
        o.C(entOrganization.FeildID),
    )
```

複数テーブルJOINしたい場合も上記のJoin以下を繋げていくだけで簡単にEager_Loadを行うことができます！


## 注意点
上記はWhere句の中でJoin関数を読んでいることから分かるように、あくまで結合のみを行なっています。
結合先のデータを取得するには今まで通りWith⚪︎⚪︎を読んであげる必要があります。Where句の中でSelect句を指定することもできますが、クエリ上ではASでテーブル名称が変更されてしまうのでエラーになってしまいます。どうにかする方法もありそうな気はしますが、そこまで見れていないです・・・。

```
client.
User.
Query().
WithOrganization().
Where(func(u *entsql.Selector){
    o := entsql.Table(entOrganization.Table)

    u.
    Join(organization).On(
        u.C(entUser.FieldOrganizationID),
        o.C(entOrganization.FeildID),
    )
})
```


# Joinしたものに対してのWhere句実行
## Source
```golang
import (
	entsql "entgo.io/ent/dialect/sql"
    entUser "github.com/hoge/pkg/ent/user" //entから出力されたファイル
    entOrganization "github.com/hoge/pkg/ent/organization" //entから出力されたファイル
)

// 中略

client.
User.
Query().
Where(func(u *entsql.Selector){
    o := entsql.Table(entOrganization.Table)

    u.
    Join(organization).On(
        u.C(entUser.FieldOrganizationID),
        o.C(entOrganization.FeildID),
    ).
    Where(
        entsql.And(
            entsql.Contains(o.C(entOrganization.FieldName), "検索文字列")
        )
    )
})
```

## 解説
Containsは部分一致検索を行うときの関数になります。
第１引数でJoinと同じようにo.C(entOrganization.FieldName)でテーブルと列名を指定して、第２引数で検索文字列を指定します。
もちろん検索ワードは他にもEQ、InValues、NotNull、IsNull、HasPrefix、HasSuffixなど色々と用意されています。要望があればこのあたりも書こうかなと思います。

```
entsql.Contains(o.C(entOrganization.FieldName), "検索文字列")
```

## 注意点１
entsql.AndはWhereの対象を（）で囲むため、不用意に使用すると検索結果が変わってしまう恐れがあります。
,をつけることで複数の検索条件を指定することができるため出力されたSQL文を見て意図通りになっているか確認するようにしましょう！
```
entsql.And(
    entsql.Contains(o.C(entOrganization.FieldName), "検索文字列"),
    entsql.EQ(u.C(entUser.FieldName), "")
)
```

## 注意点２
Whereの中身でIF文を記載することができますが、IF文が実行されないときはnilエラーになります。
nilは返さないようにWhereの呼び出しには注意してください。
必須のものとそうでないものがあるなら下記のように書く方法もあります。

```
entsql.And(
    func() []*entsql.Predicate {
        predicates := []*entsql.Predicate{}
        append(predicates, entsql.Contains(o.C(entOrganization.FieldName), ""))
        if 条件式 {
            predicates = append(predicates, entsql.Contains(o.C(entOrganization.FieldName), ""))
        }
    }()
)

```


# Joinしたものに対してのOrder句実行
```
client.
User.
Query().
Where(func(u *entsql.Selector){
    o := entsql.Table(entOrganization.Table)

    u.
    Join(organization).On(
        u.C(entUser.FieldOrganizationID),
        o.C(entOrganization.FeildID),
    ).
    Where(
        entsql.And(
            entsql.Contains(o.C(entOrganization.FieldName), "検索文字列")
        )
    )
}).
OrderBy(
    entsql.Desc(u.C(entUser.FieldID))
)
```

今までの解説を見ていれば簡単ですね。
OrderBy関数の引数としてテーブル名と列名を指定してあげます。
また昇順・降順によってAscやDesc関数を呼び出してください。

## わからなかったところ

Selectが用意されていますが、これの使い方はわからなかったので教えていただけるとありがたいです。
コードを見ている感じ、Selector構造体でSQLチックにクエリを組み立ててそれを別関数で実行する感じかな？　と思いながらもSelector構造体を実行する関数が見つけられなかったです

```
//entのcodeから取得

// Select returns a new selector for the `SELECT` statement.

t1 := Table("users").As("u")
t2 := Select().From(Table("groups")).Where(EQ("user_id", 10)).As("g")
return Select(t1.C("id"), t2.C("name")).
        From(t1).
        Join(t2).
        On(t1.C("id"), t2.C("user_id"))
```