MongoDB / Mongoose聚合查找,嵌套数组,排序

时间:2019-03-11 07:45:50

标签: mongodb mongoose aggregation-framework

所以,这些是我的模型

let restaurantSchema = mongoose.Schema({
    name: 'string',
    restId: 'string',
    saved: {type: 'date', default: Date.now()}
})

let userSchema = mongoose.Schema({
    firstName: {type: 'string', default: ''},
    lastName: {type: 'string', default: ''},
    username: {
        type: 'string',
        required: true,
        unique: true
    },
    beenTo: [restaurantSchema],
    toGoTo: [restaurantSchema]
})

我要进行一次提要:“用户A要去餐馆X,用户B要去餐馆Y,用户A曾经去餐馆​​Z”,依此类推,这些都主要按日期排序。因此我一直在尝试使用aggregate来提供包含用户名和餐厅名称以及时间戳的文档,以便我可以对所有文档(无论用户名如何)都使用{'saved':-1进行排序}或任何时间戳记。

这是我到目前为止的收获

User.aggregate([
    {$match: {username: {$ne: username}}},
    {$lookup: {
      from: "restaurants",
      localField: "beenTo.saved",
      foreignField: "saved",
      as: "savRest"
    }},
    {$project: {'savRest.saved': 1, 'savRest.name': 1, 'username': 1}},
    {$group: {
      _id: null,
      sav: {$push: '$savRest.saved'}
    }}
  ])

考虑到数组,我希望在某个时候需要$ unwind,但是我不确定在管道中的哪个位置...

1 个答案:

答案 0 :(得分:0)

我认为您需要重组schema,并在用户文档中仅包括餐厅_id,而不是将整个餐厅doc作为sub-document存储。 / p>

请将您的userSchema更改为此:

let userSchema = mongoose.Schema({
    firstName: {type: 'string', default: ''},
    lastName: {type: 'string', default: ''},
    username: {
        type: 'string',
        required: true,
        unique: true
    },
    beenTo: [{type : mongoose.Schema.Types.ObjectId,ref : "Restaurant"}],
    toGoTo: [{type : mongoose.Schema.Types.ObjectId,ref : "Restaurant"}]
})

请参阅我对您的架构所做的更改,而不是存储餐厅文档,我们将仅存储其_id(ObjectId)。

要获取所有餐厅并对其进行排序,您可以执行以下操作:

User.aggregate([
    {$match: {username: {$ne: username}}},
    // need to unwind the array, before we can do a $lookup over it
    {$unwind : "beenTo"},
    {$lookup: {
      from: "restaurants",
      localField: "beenTo",
      foreignField: "_id",
      as: "beenTo"
    }},

    //need to unwind again, as $lookup returns an array
    {$unwind : "beenTo"},

    //sort the result by beenTo.saved (date) first.
    {$sort : {"$beenTo.saved" : -1}},

    //after sorting, group all the docs and created sorted beenTo array
    {
        $group : {
            _id : "$_id",
            firstName : {$first : "$firstName"},
            lastName : {$first : "$lastName"},
            username : {$first : "$username"},
            beenTo : {$push : "$beenTo"},
            toGoTo : {$first : "$toGoTo"}
        }
    },

    //after sorting of beenTo, we can go for sorting of toGoTo array, 
    // we will follow the same procedure again.
    {$unwind : "toGoTo"},
    {$lookup: {
      from: "restaurants",
      localField: "toGoTo",
      foreignField: "_id",
      as: "toGoTo"
    }},

    //need to unwind again, as $lookup returns an array
    {$unwind : "toGoTo"},

    {$sort : {"$toGoTo.saved" : -1}},
    {
        $group : {
            _id : "$_id",
            firstName : {$first : "$firstName"},
            lastName : {$first : "$lastName"},
            username : {$first : "$username"},
            beenTo : {$first : "$beenTo"}
            toGoTo : {$push : "$toGoTo"},
        }
    }
  ])

聚合管道的说明:

  1. $match:必须匹配所有选定的文档。
  2. $unwind:我们需要先展开数组(beenTo),然后才能对其进行查找。
  3. $lookup:从餐厅收藏中填充受人尊敬的餐厅文档
  4. $unwind$lookup返回一个数组,我们需要再次将其转换为对象。
  5. $sort:根据beenTo.saved值对所有文档进行排序
  6. $group:基于用户的_id将所有文档分组,并将所有beenTo文档推送到其数组中。 (现在,我们在beenTo数组中填充了餐厅文档,并对其进行了排序)
  7. $unwind:接下来,我们需要展开{​​{1}}数组,以便我们可以执行其查找
  8. toGoTo:要填充餐厅收藏中的相应文档
  9. $lookup:同样,我们需要放松,因为$unwind返回一个数组
  10. $lookup:根据toGoTo.saved字段对所有文档进行排序
  11. $sort:基于用户$group将所有文档分组,并将所有_id文档推入其数组,而toGoTo将保持不变。

因此,在第11步结束时,我们将填充beenTobeenTo的所有文档,并按保存日期进行排序。

希望这对您有所帮助。

请注意:在将餐厅添加到用户文档中的toGoTobeenTo数组时,请仅按toGoTo而不是完整的restaurant's _id