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