Go
MongoDB
mgo

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


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
}
]
}
]