Goのmgoで$lookupと$projectを使って結合したデータを取得する
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" }} }
ここで$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 } ] } ]