GORM で 1:N で Join したカラムの値を取得する

Golang の ORM、[GORM](http://gorm.io/)を使用していてちょっとした値の取得にえらく手間取ったのでメモ。
ちなみに使い方を知らないだけでもっと良いやり方があるかもしれない。
(というか、もっと良いやり方のツッコミ待ちのため記事を書いた)

### テーブル定義

このようなテーブルがあるとする。

```sql
MariaDB [zaneli]> desc foo;
+-------+--------------+------+-----+---------+-------+
| Field | Type         | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| id    | int(11)      | YES  |     | NULL    |       |
| name  | varchar(200) | YES  |     | NULL    |       |
+-------+--------------+------+-----+---------+-------+
2 rows in set (0.01 sec)

MariaDB [zaneli]> desc foobar;
+--------+---------+------+-----+---------+-------+
| Field  | Type    | Null | Key | Default | Extra |
+--------+---------+------+-----+---------+-------+
| foo_id | int(11) | YES  |     | NULL    |       |
| bar_id | int(11) | YES  |     | NULL    |       |
+--------+---------+------+-----+---------+-------+
2 rows in set (0.00 sec)
```

`foo` と `foo_bar` は 1:N で紐付いているとする。

```sql
MariaDB [zaneli]> select * from foo;
+------+------+
| id   | name |
+------+------+
|    1 | foo1 |
|    2 | foo2 |
|    3 | foo3 |
+------+------+
3 rows in set (0.00 sec)

MariaDB [zaneli]> select * from foobar;
+--------+--------+
| foo_id | bar_id |
+--------+--------+
|      1 |      1 |
|      1 |      2 |
|      1 |      3 |
|      2 |      1 |
+--------+--------+
4 rows in set (0.00 sec)

MariaDB [zaneli]> select * from foo f left outer join foobar fb on f.id = fb.foo_id;
+------+------+--------+--------+
| id   | name | foo_id | bar_id |
+------+------+--------+--------+
|    1 | foo1 |      1 |      1 |
|    1 | foo1 |      1 |      2 |
|    1 | foo1 |      1 |      3 |
|    2 | foo2 |      2 |      1 |
|    3 | foo3 |   NULL |   NULL |
+------+------+--------+--------+
5 rows in set (0.00 sec)
```

### テーブルに対応する構造体を定義

```go
type Foo struct {
	ID     int
	Name   string
	BarIDs []int
	// `Related` `Preload` を使う際には `FooBars` を持たせる
	// FooBars []FooBar
}

func (Foo) TableName() string { return "foo" }
```

```go
type FooBar struct {
	FooID int
	BarID int
}

func (FooBar) TableName() string { return "foobar" }
```

### GORM で値を取得

まずは foo だけ取得してみる。

```go
import (
	"fmt"
	"log"

	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"
)

func getFoo(db *gorm.DB) {
	foo := Foo{ID: 1}
	if err := db.Find(&foo).Error; err != nil {
		log.Fatal(err)
	}
	fmt.Println(foo)
}
```

発行したSQLと結果はこのように。
> [2018-08-07 12:38:30]  [0.86ms]  SELECT * FROM `foo`  WHERE `foo`.`id` = '1'
> [1 rows affected or returned ]
> {1 foo1 []}

`Foo.BarIDs` に join した結果の値を設定したい。

### Related を使用する

[Associations Has Many](http://doc.gorm.io/associations.html#has-many) にあるように、foo のデータがすでに手元にある場合、または foo のプライマリキーが手元にあって、foo 自体は読み取らなくていいが foobar がほしいときなどには `Related` が使えそうだ。
foo の値(今回の場合 `name`)もほしい場合は、別途 `Find` なり `First` なりしないといけない。

```go
func getFooAssociations(db *gorm.DB) {
	foo := Foo{ID: 1}
	if err := db.First(&foo).Error; err != nil {
		log.Fatal(err)
	}
	if err := db.Model(&foo).Related(&foo.FooBars).Error; err != nil {
		log.Fatal(err)
	}
	fmt.Println(foo)
}
```

> [2018-08-07 12:47:21]  [0.85ms]  SELECT * FROM `foo`  WHERE `foo`.`id` = '1'
> [1 rows affected or returned ]
> 
> [2018-08-07 12:47:21]  [0.69ms]  SELECT * FROM `foobar`  WHERE (`foo_id` = '1')
> [3 rows affected or returned ]
> {1 foo1 [{1 1} {1 2} {1 3}]}

### Preload を使用する
それぞれ別クエリを投げるのであれば、 [Preload](http://gorm.io/docs/preload.html#Preload)でもシンプルに書けそう。

```go
func preloadFoobar(db *gorm.DB) {
	foo := Foo{ID: 1}
	if err := db.Preload("FooBars").Find(&foo).Error; err != nil {
		log.Fatal(err)
	}
	fmt.Println(foo)
}
```

> [2018-08-07 12:52:14]  [0.87ms]  SELECT * FROM `foo`  WHERE `foo`.`id` = '1'
> [1 rows affected or returned ]

> [2018-08-07 12:52:14]  [0.57ms]  SELECT * FROM `foobar`  WHERE (`foo_id` IN ('1'))
> [3 rows affected or returned ]
> {1 foo1 [{1 1} {1 2} {1 3}]}

### Joins を使用する

ドキュメントに従って[Joins](http://doc.gorm.io/crud.html#joins)を使ってみる。

```go
func getFooWithBarIDs(db *gorm.DB) {
	query := db.Table("foo").
		Select("foo.*, foobar.bar_id").
		Joins("left outer join foobar on foo.id = foobar.foo_id").
		Where("foo.id = ?", 1)
	rows, err := query.Rows()
	if err != nil {
		log.Fatal(err)
	}
	var fooWithBarID struct {
		Foo
		BarID *int
	}
	var barIDs []int
	for rows.Next() {
		err := query.ScanRows(rows, &fooWithBarID)
		if err != nil {
			log.Fatal(err)
		}
		if fooWithBarID.BarID != nil {
			barIDs = append(barIDs, *fooWithBarID.BarID)
		}
	}

	foo := fooWithBarID.Foo
	foo.BarIDs = barIDs
	fmt.Println(foo)
}
```

だいぶ、SQLを手で書いている感が出てきたがこのようにするらしい。
`rows.Next()` のループの中で `foobar.bar_id` を取得して `foo.BarIDs` に設定する必要があるのだが、
`BarIDs []int` を持つ `Foo` とは別に
`BarID *int` を持つ `fooWithBarID` を定義して、これを `ScanRows` に使用する。
`int` ではなく `*int`　にしているのは、 `left outer join` なので foobar が無い(`bar_id` が NULL)の場合にゼロ値が `barIDs` に入らないように)

* foobar に紐付くレコードあり

> [2018-08-07 13:25:23]  [0.87ms]  SELECT foo.*, foobar.bar_id FROM `foo` left outer join foobar on foo.id = foobar.foo_id WHERE (foo.id = '1')
> [0 rows affected or returned ]
> {1 foo1 [1 2 3]}

* foobar に紐付くレコードなし

> [2018-08-07 13:28:23]  [0.83ms]  SELECT foo.*, foobar.bar_id FROM `foo` left outer join foobar on foo.id = foobar.foo_id WHERE (foo.id = '3')
> [0 rows affected or returned ]
> {3 foo3 []}

…もうちょっとマシな書き方はないですかね…。 :thinking:
`Joins` を使った1クエリに拘らず、大人しく `Preload` で2クエリ発行したほうがいいように思えてきた。