TakayukiKoyama Geek Blog

Create, Entertain, Experience

【Mongo, Nodejs】ObjectIdでCollection同士をJoin(aggregate)するときの注意

CollectionをJoinしたい

  • 前提:mongooseではなくMongoClient
  • 例:Userコレクションのid(ObjectId)と Eventコレクションのuser_id(UserのidをInsert)をJoinする
// 色々省略:collectionはMongoClientで作った対象のコレクションを呼び出すメソッド
const COL = 'event';
collection(COL).aggregate(
  [
    { $match: { privacy: 'community' } },
    { $lookup: {
        from: 'user', // 結合対象のコレクション
        localField: 'user_id', // 結合元のコレクションフィールド
        foreignField: '_id', // 結合先のコレクションフィールド
        as: 'admin' // 結合結果のフィールド名
      }
    },
    { $unwind: "$admin" } // 配列でなく1ドキュメントになる場合は $unwind を指定
  ]
).toArray(function(err, docs){
    // WebAPIにする場合の記述
    if (err) {
        throw err;
    }
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify(docs));
})

注意すべき点

上記の方法でJoinしようとした場合、UserのidはObjectIdとなる。
(例:"
id": { “$oid”: “5985ef58ab567258537d6ac5” })

そのため、Eventコレクションに格納したuser_idがObjectIdではなく文字列にすると、_idとuser_idが一致しないため結果は空になる。
(例: “user_id”: “5985ef58ab567258537d6ac5")

調べてみたところ、StackOverflow的にはaggregate内で 文字列 -> ObjectId 変換させることはできないらしい。

参考:join - How to convert string to objectId in LocalField for $lookup Mongodb - Stack Overflow

解決策

現状の解決策は、Eventコレクションのuser_idにObjectIdを入れるしかない。

EventコレクションにInsertするときに以下のようにする。

// 文字列 -> ObjectId変換
req.body.user_id = new ObjectID( req.body.user_id );
collection(COL).insertOne( req.body ).then(function(doc) {
    // WebAPIのレスポンス処理
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify(doc));
});

↑ user_id文字列をObjectIdに変換して保存。

もう少しスマートな書き方がありそうな気もする。それは思いついたら書きます。