mgoで$lookupと$projectを使って結合したデータを取得する【Go】

# mongoDBでの結合とフィルター

mongoDBはドキュメント型のNoSQLですが、左外部結合ができます。
で、左外部結合したうえでフィルターにかけ、要求されたデータを返すことができます。

前半の説明は以下のページを参考にしています。
[Mongodb Join on _id field from String to ObjectId](https://stackoverflow.com/questions/41093647/mongodb-join-on-id-field-from-string-to-objectid)
[MongoPlayground](https://mongoplayground.net/p/JoLPVIb1OLS)

**$lookup**

`$lookup`により、join先の指定したフィールドのドキュメントから要求されたドキュメントを配列にし、join元の指定されたドキュメントに組み込みます。

```
{
   $lookup:
     {
       from: <join先のコレクション>,
       localField: <join元のフィールド>,
       foreignField: <join先のフィールド>,
       as: <join元に組み込まれることになる配列フィールド>
     }
}
```
[mongoDB Documentation $lookup (aggregation)](https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/)

`$lookup`のようなAggregationPipelineStagesはdocument.aggregate()に指定して実行します。
[AggregationPipeline](https://docs.mongodb.com/manual/core/aggregation-pipeline/)

`db.document.aggregate([{ここで指定する}])`

すなわち、

```
db.original_collection.aggregate([
  {
    "$lookup": {
      "from": "another_collection",
      "localField": "_id",
      "foreignField": "original_collection_id",
      "as": "another_collections"
    }
  }
])
```

このようにして使うことができます。

ですが、このままでは結合されたデータを取得することはできません。
取得するためにはもう一つ、`$project`というステージを追加する必要があります。

**$project**

`$project`では、取得するドキュメントに対して設定を割り当てることができます。

```
{ $project: { <設定> } }
```

設定の中では、どのフィールドの値を表示するかとか、特定のフィールドの値を変形させるなどして新しいフィールドに置き換えるとかなどを行えます。

ここでは、_idとjoin元に紐づくjoin先のデータをまとめた配列を指定します。
この時、_idはobjectIdなので、この中身だけを引っ張り出してきたstringに変換します。

したがって、`$project`の設定は以下のようになります。

```
{ "$project": {"_id": { "$toString": "$_id" }} }
```

[$toString (aggregation)について](https://docs.mongodb.com/manual/reference/operator/aggregation/toString/)

ここで`$lookup`と`$project`を組み合わせて、以下のクエリーを作成します。

```
db.original_collection.aggregate([
  { "$project": {"_id": { "$toString": "$_id" }} },
  {
    "$lookup": {
      "from": "another_collection",
      "localField": "_id",
      "foreignField": "original_collection_id",
      "as": "another_collections"
    }
  }
])
```

これを実行すると、おそらく以下のようなレスポンスが返ってくるでしょう（DBの中身が正しければ）。

```
{
  "_id" : "5caae079e1382335f04c5846",
  "another_collections" : [
    {
      "_id" : ObjectId("5caae1cce1382337367b5316"),
      "name" : "anotherCollection",
      "original_collection_id" : "5caae079e1382335f04c5846"
    }
  ]
}
```

# これをmgoでやりたい

上に述べたのは、mongoのDBに入って直接いじるやり方です。

Goのmgoでやるときは下のようにしてやります。
mgoの使い方とか細かい部分は端折って、typeの定義の部分と、dbでfindする部分だけ書きます。

```go
// typeの定義
type SubCollection struct { // join先
  Id bson.ObjectId `bson:"_id"`
  Name string `bson:"name"`
  OriginalCollectionId string `bson:"original_collection_id"`
}
type OriginalCollection struct { // join元
  Id bson.ObjectId `bson:"_id"`
  Name string `bson:"name"`
  SubCollections []SubCollection
}

// 結果を取得する
var AllOriginalCollections []OriginalCollection

query := []bson.M{
  {
    "$project": bson.M{
      "_id": bson.M{"$toString": "$_id"},
    },
  },
  {
    "$lookup": bson.M{
      "from": "SubCollections",
      "localField": "_id",
      "foreignField": "original_collection_id",
      "as": "original_collections",
    },
  },
}

// コレクションdatabasesを指定し、AggregationPipelineを使うためのPipe関数をqueryを引数にして呼び出す
pipe := db.C("databases").Pipe(query)
pipe.All(&AllOriginalCollections) // APの設定を踏まえたクエリー（All:全取得）の実行
fmt.Println(AllOriginalCollections) // 結果表示
```

構造体であらかじめ`$lookup`のasで指定するフィールドを設定しておかないといけない点と、AggregationPipelineを利用するためにPipe関数を使っている点以外は基本上で説明した通りです。

たぶん以下のような結果が返ってくると思います。

```
[
  {
    ObjectIdHex("356361616530373965313338323333356630346335383436")
    sampleOriginalCollection
    [
      {
        ObjectIdHex("5caae1cce1382337367b5316")
        sampleSubCollection
        5caae079e1382335f04c5846
      }
    ]
  }
]
```

ただ、この時ObjectIdHexがなぜかよくわからない数字の文字列になります。
原因はわかりませんが、以下のように$projectに結合用にObjectIdをstringに変換したidのカラムを作って、もともとのObjectIdのカラムはそのまま出力すれば、問題は解決されます。

```go
// queryの部分だけ
query := []bson.M{
  {
    "$project": bson.M{
      "_id": 1,
      "objectIdToString": bson.M{"$toString": "$_id"}
    },
  },
  {
    "$lookup": bson.M{
      "from": "SubCollections",
      "localField": "objectIdToString",
      "foreignField": "original_collection_id",
      "as": "original_collections",
    },
  },
}
```

objectIdHexが変形せずに出力されるようになります。

```
[
  {
    ObjectIdHex("5caae079e1382335f04c5846")
    sampleOriginalCollection
    [
      {
        ObjectIdHex("5caae1cce1382337367b5316")
        sampleSubCollection
        5caae079e1382335f04c5846
      }
    ]
  }
]
```