LoginSignup
7
5

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-04-08

mongoDBでの結合とフィルター

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

前半の説明は以下のページを参考にしています。
Mongodb Join on _id field from String to ObjectId
MongoPlayground

$lookup

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

{
   $lookup:
     {
       from: <join先のコレクション>,
       localField: <join元のフィールド>,
       foreignField: <join先のフィールド>,
       as: <join元に組み込まれることになる配列フィールド>
     }
}

mongoDB Documentation $lookup (aggregation)

$lookupのようなAggregationPipelineStagesはdocument.aggregate()に指定して実行します。
AggregationPipeline

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)について

ここで$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する部分だけ書きます。

// 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のカラムはそのまま出力すれば、問題は解決されます。

// 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
      }
    ]
  }
]
7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5