【问题标题】:MongoDB/Mongoose aggregate with lookup, nested arrays, sort带有查找、嵌套数组、排序的 MongoDB/Mongoose 聚合
【发布时间】:2019-08-01 11:53:13
【问题描述】:

所以,这些是我的模型

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 餐厅”等,所有这些都主要按日期排序。所以我一直在尝试使用聚合来提供包含用户名和餐厅名称以及时间戳的文档,这样我就可以使用 {'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,但我不确定管道的位置...

【问题讨论】:

  • 从您的架构看来,Restaurant 是用户文档beenTo : [RestaurantSchema] 的子文档。这会将餐厅文档存储为用户对象的子文档,在这种情况下,$lookup 不会按您的意愿工作。如果您只想将餐厅 ID 存储在数组中,并参考实际的餐厅文档,则需要更改您的架构:beenTo : [{type:mongoose.Schema.Types.Object, ref : 'Restaurant']。如果我误解了您的问题,请对其进行编辑以使其更易于理解。这里有点混乱。
  • 我明白了。抱歉,如果它令人困惑,我正在尝试自己理解其中的大部分内容。在这种情况下,假设我对我的架构进行了建议的编辑,我可以得到想要的结果吗?
  • 是的,您可以,请告诉我您想要的确切内容,我会帮助您。您想通过聚合管道实现什么目标?
  • 我最终想按日期对所有(其他)用户 betTo 和 toGoTo 数组中的所有条目进行排序,从最近开始。
  • 所以你想要所有用户,除了给定的用户,这就是你写的原因:username : {$ne : username} 对吗?

标签: mongodb mongoose aggregation-framework


【解决方案1】:

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

请将您的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 :接下来我们需要展开 toGoTo 数组,以便我们可以执行它的查找
  8. $lookup:从餐厅集合中填充相应的文档
  9. $unwind :我们需要再次放松,因为 $lookup 返回一个数组
  10. $sort : 根据 toGoTo.saved 字段对所有文档进行排序
  11. $group :根据用户_id 对所有文档进行分组,并将所有toGoTo 文档推送到其数组中,beenTo 将保持不变。

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

希望对你有所帮助。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-12-31
    • 2020-12-18
    • 2020-01-24
    • 2017-07-26
    • 1970-01-01
    • 2019-06-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多