$lookup 多个级别而不使用 $unwind?

2024-05-01

我有以下收藏:

  • 场地集合
{    "_id" : ObjectId("5acdb8f65ea63a27c1facf86"),
     "name" : "ASA College - Manhattan Campus",
     "addedBy" : ObjectId("5ac8ba3582c2345af70d4658"),
     "reviews" : [ 
         ObjectId("5acdb8f65ea63a27c1facf8b"), 
         ObjectId("5ad8288ccdd9241781dce698")
     ] 
}
  • 评论集
{     "_id" : ObjectId("5acdb8f65ea63a27c1facf8b"),
      "createdAt" : ISODate("2018-04-07T12:31:49.503Z"),
      "venue" : ObjectId("5acdb8f65ea63a27c1facf86"),
      "author" : ObjectId("5ac8ba3582c2345af70d4658"),
      "content" : "nice place",
      "comments" : [ 
          ObjectId("5ad87113882d445c5cbc92c8")
      ]
 }
  • 评论收藏
{     "_id" : ObjectId("5ad87113882d445c5cbc92c8"),
      "author" : ObjectId("5ac8ba3582c2345af70d4658"),
      "comment" : "dcfdsfdcfdsfdcfdsfdcfdsfdcfdsfdcfdsfdcfdsfdcfdsf",
      "review" : ObjectId("5acdb8f65ea63a27c1facf8b"),
      "__v" : 0
}
  • 作者收藏
{    "_id" : ObjectId("5ac8ba3582c2345af70d4658"),
     "firstName" : "Bruce",
     "lastName" : "Wayne",
     "email" : "[email protected] /cdn-cgi/l/email-protection",
     "followers" : [ObjectId("5ac8b91482c2345af70d4650")]
}

现在以下填充查询工作正常

    const venues = await Venue.findOne({ _id: id.id })
    .populate({
      path: 'reviews',
      options: { sort: { createdAt: -1 } },
      populate: [
        {  path: 'author'  },
        {  path: 'comments', populate: [{ path: 'author' }] }
      ]
    })

但是,我想通过以下方式实现它$lookup查询,但当我对评论进行“$unwind”时,它会分割场地...我希望评论在同一数组中(如填充)并按相同的顺序...

我想实现以下查询$lookup因为作者有关注者字段,所以我需要发送字段isFollow通过做$project这是不能使用populate...

$project: {
    isFollow: { $in: [mongoose.Types.ObjectId(req.user.id), '$followers'] }
}

当然有几种方法,具体取决于您可用的 MongoDB 版本。这些因不同用途而异$lookup http://mongoosejs.com/docs/api.html#query_Query-populate通过启用对象操作.populate() http://mongoosejs.com/docs/api.html#query_Query-populate结果通过.lean() http://mongoosejs.com/docs/api.html#query_Query-populate.

我确实要求您仔细阅读这些部分,并注意在考虑您的实施解决方案时,一切可能并不像看起来那样。

MongoDB 3.6,“嵌套”$lookup

使用 MongoDB 3.6$lookup https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/操作员获得额外的能力,包括pipeline表达式而不是简单地将“本地”键值连接到“外部”键值,这意味着您基本上可以执行每个操作$lookup https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/作为“嵌套”在这些管道表达式中

Venue.aggregate([
  { "$match": { "_id": mongoose.Types.ObjectId(id.id) } },
  { "$lookup": {
    "from": Review.collection.name,
    "let": { "reviews": "$reviews" },
    "pipeline": [
       { "$match": { "$expr": { "$in": [ "$_id", "$$reviews" ] } } },
       { "$lookup": {
         "from": Comment.collection.name,
         "let": { "comments": "$comments" },
         "pipeline": [
           { "$match": { "$expr": { "$in": [ "$_id", "$$comments" ] } } },
           { "$lookup": {
             "from": Author.collection.name,
             "let": { "author": "$author" },
             "pipeline": [
               { "$match": { "$expr": { "$eq": [ "$_id", "$$author" ] } } },
               { "$addFields": {
                 "isFollower": { 
                   "$in": [ 
                     mongoose.Types.ObjectId(req.user.id),
                     "$followers"
                   ]
                 }
               }}
             ],
             "as": "author"
           }},
           { "$addFields": { 
             "author": { "$arrayElemAt": [ "$author", 0 ] }
           }}
         ],
         "as": "comments"
       }},
       { "$sort": { "createdAt": -1 } }
     ],
     "as": "reviews"
  }},
 ])

这确实非常强大,正如您从原始管道的角度看到的那样,它实际上只知道向"reviews"数组,然后每个后续的“嵌套”管道表达式也只能看到它来自连接的“内部”元素。

它很强大,并且在某些方面可能更清晰,因为所有字段路径都相对于嵌套级别,但它确实会在 BSON 结构中开始缩进蠕变,并且您确实需要知道是否与数组匹配或遍历结构时的奇异值。

请注意,我们还可以在这里执行一些操作,例如“展平作者属性”,如"comments"数组条目。全部$lookup https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/目标输出可能是一个“数组”,但在“子管道”中,我们可以将该单个元素数组重新整形为单个值。

标准 MongoDB $lookup

仍然保留“在服务器上加入”,您实际上可以这样做$lookup https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/,但只需要中间处理。这是解构数组的长期存在的方法$unwind https://docs.mongodb.com/manual/reference/operator/aggregation/unwind/以及使用$group https://docs.mongodb.com/manual/reference/operator/aggregation/group/重建阵列的阶段:

Venue.aggregate([
  { "$match": { "_id": mongoose.Types.ObjectId(id.id) } },
  { "$lookup": {
    "from": Review.collection.name,
    "localField": "reviews",
    "foreignField": "_id",
    "as": "reviews"
  }},
  { "$unwind": "$reviews" },
  { "$lookup": {
    "from": Comment.collection.name,
    "localField": "reviews.comments",
    "foreignField": "_id",
    "as": "reviews.comments",
  }},
  { "$unwind": "$reviews.comments" },
  { "$lookup": {
    "from": Author.collection.name,
    "localField": "reviews.comments.author",
    "foreignField": "_id",
    "as": "reviews.comments.author"
  }},
  { "$unwind": "$reviews.comments.author" },
  { "$addFields": {
    "reviews.comments.author.isFollower": {
      "$in": [ 
        mongoose.Types.ObjectId(req.user.id), 
        "$reviews.comments.author.followers"
      ]
    }
  }},
  { "$group": {
    "_id": { 
      "_id": "$_id",
      "reviewId": "$review._id"
    },
    "name": { "$first": "$name" },
    "addedBy": { "$first": "$addedBy" },
    "review": {
      "$first": {
        "_id": "$review._id",
        "createdAt": "$review.createdAt",
        "venue": "$review.venue",
        "author": "$review.author",
        "content": "$review.content"
      }
    },
    "comments": { "$push": "$reviews.comments" }
  }},
  { "$sort": { "_id._id": 1, "review.createdAt": -1 } },
  { "$group": {
    "_id": "$_id._id",
    "name": { "$first": "$name" },
    "addedBy": { "$first": "$addedBy" },
    "reviews": {
      "$push": {
        "_id": "$review._id",
        "venue": "$review.venue",
        "author": "$review.author",
        "content": "$review.content",
        "comments": "$comments"
      }
    }
  }}
])

这确实并不像您一开始想象的那么令人畏惧,并且遵循一个简单的模式:$lookup https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/ and $unwind https://docs.mongodb.com/manual/reference/operator/aggregation/unwind/当您在每个数组中前进时。

The "author"当然,细节是单一的,因此一旦“展开”,您只需保留这种方式,进行字段添加并开始“回滚”到数组中的过程。

只有two重建回原来的水平Venue文档,所以第一个详细级别是Review重建"comments"大批。你需要做的就是$push https://docs.mongodb.com/manual/reference/operator/aggregation/push/的路径"$reviews.comments"为了收集这些,并且只要"$reviews._id"字段位于“grouping _id”中,您唯一需要保留的其他内容是所有其他字段。您可以将所有这些放入_id也可以,或者你可以使用$first https://docs.mongodb.com/manual/reference/operator/aggregation/first/.

完成后就只剩下一个了$group https://docs.mongodb.com/manual/reference/operator/aggregation/group/阶段以便回到Venue本身。这次的分组键是"$_id"当然,场地本身的所有属性都使用$first https://docs.mongodb.com/manual/reference/operator/aggregation/first/和剩余的"$review"详细信息返回到数组中$push https://docs.mongodb.com/manual/reference/operator/aggregation/push/。当然"$comments"之前的输出$group https://docs.mongodb.com/manual/reference/operator/aggregation/group/成为"review.comments" path.

处理单个文档及其关系,这并不是那么糟糕。这$unwind https://docs.mongodb.com/manual/reference/operator/aggregation/unwind/管道操作员可以一般来说是一个性能问题,但在这种用法的背景下,它不应该真正造成那么大的影响。

由于数据仍在“连接到服务器上”,因此still比其他剩余替代方案的流量少得多。

JavaScript 操作

当然,这里的另一种情况是,您实际上操作的是结果,而不是更改服务器本身的数据。在most在这种情况下,我会赞成这种方法,因为对数据的任何“添加”可能最好在客户端处理。

当然使用的问题populate() http://mongoosejs.com/docs/api.html#query_Query-populate是虽然它可能'看起来像'一个更加简化的过程,实际上是不是加入以任何方式。全部populate() http://mongoosejs.com/docs/api.html#query_Query-populate实际上是"hide"提交的底层流程multiple查询数据库,然后通过异步处理等待结果。

So the “外貌”join的结果实际上是向服务器发出多个请求然后执行的结果“客户端操纵”将详细信息嵌入到数组中的数据。

所以除此之外明确警告性能特征与服务器相差甚远$lookup https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/,另一个需要注意的是,结果中的“mongoose Documents”实际上并不是需要进一步操作的纯 JavaScript 对象。

因此,为了采用这种方法,您需要添加.lean() http://mongoosejs.com/docs/api.html#query_Query-lean执行前查询的方法,以指示 mongoose 返回“纯 JavaScript 对象”而不是Document使用附加到模型的模式方法进行转换的类型。当然,请注意,结果数据不再能够访问任何与相关模型本身相关联的“实例方法”:

let venue = await Venue.findOne({ _id: id.id })
  .populate({ 
    path: 'reviews', 
    options: { sort: { createdAt: -1 } },
    populate: [
     { path: 'comments', populate: [{ path: 'author' }] }
    ]
  })
  .lean();

Now venue是一个普通的对象,我们可以根据需要进行简单的处理和调整:

venue.reviews = venue.reviews.map( r => 
  ({
    ...r,
    comments: r.comments.map( c =>
      ({
        ...c,
        author: {
          ...c.author,
          isAuthor: c.author.followers.map( f => f.toString() ).indexOf(req.user.id) != -1
        }
      })
    )
  })
);

所以这实际上只是循环遍历每个内部数组,直到您可以看到的级别followers数组内author细节。然后可以与ObjectId第一次使用后存储在该数组中的值.map() https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map返回“字符串”值以与req.user.id这也是一个字符串(如果不是,那么还要添加.toString()),因为一般来说通过 JavaScript 代码以这种方式比较这些值更容易。

尽管我需要再次强调它“看起来很简单”,但实际上这是您真正想要避免的系统性能问题,因为这些额外的查询以及服务器和客户端之间的传输会花费大量的处理时间甚至由于请求开销,这也增加了托管提供商之间的实际传输成本。


Summary

这些基本上是您可以采取的方法,除了您实际执行的“自行实施”之外“多次查询”自己访问数据库而不是使用帮助程序.populate() http://mongoosejs.com/docs/api.html#query_Query-populate is.

使用填充输出,您可以像任何其他数据结构一样简单地操作结果中的数据,只要您应用.lean() http://mongoosejs.com/docs/api.html#query_Query-lean到查询以转换或以其他方式从返回的猫​​鼬文档中提取纯对象数据。

虽然综合方法看起来复杂得多,但也有"a lot"在服务器上完成这项工作有更多优势。可以对更大的结果集进行排序,可以进行计算以进一步过滤,当然你会得到一个“单一回应” to a “单一请求”发送到服务器,所有这些都没有额外的开销。

管道本身可以简单地基于已经存储在模式上的属性来构建,这是完全有争议的。因此,编写自己的方法来根据附加模式执行此“构造”应该不会太困难。

当然从长远来看$lookup https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/是更好的解决方案,但是您可能需要在初始编码中投入更多的工作,当然,如果您不只是简单地从此处列出的内容进行复制;)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

$lookup 多个级别而不使用 $unwind? 的相关文章

  • 根据特定字符获取整个字符串或子字符串

    我有一个包含 MIME 类型的字符串 例如application json 现在我想将其与实际的 HTTP 标头进行比较 在本例中content type 如果标头包含 MIME 类型 那么就很简单 if mimeType contentT
  • Nodejs Express中间件函数返回值

    我正在使用 NodeJS 和 Express 我有以下路线 中间件功能是Mobile 如果我不使用 return next 在 isMobile 函数中 应用程序会卡住 因为 NodeJS 不会移至下一个函数 但我需要 isMobile 函
  • Node.js 和 Passport 对象没有 validPassword 方法

    我正在使用 Node js Express Passport 创建一个简单的身份验证 本地 到目前为止我所达到的效果是 当输入错误的用户名或密码时 用户将被重定向到错误页面 但是当用户输入正确的用户名和密码时 我收到此错误 node mod
  • 了解新的 mongo id 并将其与 Iron-router 一起使用

    我有一个简单的帖子路线来寻找帖子 id 问题是pathFor助手创建这样的路径 ObjectID 52e16453431fc2fba4b6d6a8 我猜 mongoDB 插入已更改 现在 id对象在其内部包含另一个对象 称为 str 这是我
  • 如何测试 jest Node JS 中 AWS 内置方法中使用的 .promise() 方法

    我想对其进行完整的单元测试 下面给出了我的函数的代码 function js async function sesSendEmail message var ses new aws SES apiVersion 2020 12 01 var
  • MongoDb 注册类映射

    我有以下代码 我希望 MiscellaneousData 覆盖抽象的 MiscellaneousDataBase 然而 IdMemberMap 总是出现空值 使用独立的 正常 类是可行的 if BsonClassMap IsClassMap
  • 在node.js中加载并执行外部文件

    从另一个节点js文件运行一个节点js文件是否容易 可能 例如 我有两个文件 test1 js 和 test2 js 我想从 test2 js 执行 test1 js 文件 我认为完成你想做的事情的更好方法是按照我的其他答案建议的去做 但是要
  • Plesk Windows 部署 Node.js

    我创建了一个以 Node js 作为后端的 Angular 项目 这是服务器文件结构 Home directory httpdocs node hm dist browser folder server folder server js p
  • 用于从字段中查找最大值的 MongoTemplate 方法或查询

    我正在使用 MongoTemplate 进行数据库操作 现在我想从所选结果中获取最大字段值 有人可以指导我如何编写查询 以便当我将查询传递给 find 方法时 它将返回我所需的文档最大字段 提前致谢 问候 可以在spring data mo
  • 通过 node-http-proxy 保留基于 cookie 的会话

    我有一个简单的基于 Express 的 Node js Web 服务器 用于开发 JavaScript 应用程序 我将服务器设置为使用 node http proxy 来代理应用程序向在不同域和端口上运行的 Jetty 服务器发出的 API
  • 流星内存不足

    我正在使用流星来制作报废引擎 我必须执行一个 HTTP GET 请求 这会向我发送一个 xml 但这个 xml 大于 400 ko 我得到一个异常 内存不足 result Meteor http get http SomeUrl com 致
  • 查询为空 Node Js Sequelize

    我正在尝试更新 Node js 应用程序中的数据 我和邮递员测试过 我的开发步骤是 从数据库 MySQL 获取ID为10的数据进行更新 gt gt 未处理的拒绝SequelizeDatabaseError 查询为空 我认识到 我使用了错误的
  • PrototypeJS 版本 1.6.0.2 覆盖 JSON.parse 和 JSON.stringify 并破坏 socket.io 功能

    基本上 socket io 使用 nativeJSON 来编码和解码数据包 而我的问题是我必须使用这个版本的原型来改变 JSON 行为 当我应该进入服务器时 如下所示 socket on event function a b c 我明白了s
  • 在node.js中生成密码重置令牌

    如何在 node js 中生成可在 url 中使用的密码重置令牌 我只需要生成令牌的方法 user reset password token user reset password expire expire date 编辑 这是解决方案
  • 运行“npm install”:Node-gyp 错误 - MSBUILD.exe 失败,退出代码:1

    我在跑npm install在 Windows 上安装我的项目中的所有软件包 然后我收到有关 MSBUILD exe 的错误 gyp ERR stack Error C Program Files x86 Microsoft Visual
  • Spring data 和 mongoDB - 继承和@DBRef

    我有这两份文件 用户 Document collection User public class User fields 和联系方式 Document collection Contact public class Contact exte
  • MongoDB 仅插入唯一电子邮件条件不起作用

    在我的猫鼬模式中 我有以下两个字段 username type String required true user name required trim true unique true email type String required
  • 在 MongoDB 中查找具有字符串 ID 数组的文档

    我有一个 id 字符串数组 我想将其与 find 函数一起使用 db companies find id in arr arr看起来像这样 563a2c60b511b7ff2c61e938 563a2c60b511b7ff2c61e8b7
  • Node.js 每个用户一个会话

    如何防止我的用户同时从两个设备登录我的系统 因此 如果用户从计算机登录 当他从不同的计算机登录时 首先会自动关闭会话 不需要使其实时 我使用node js express js mongoose passport connect mongo
  • 在浏览器中语音聊天? [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我们正在寻求建立一个小组 voice 使用服务器上的node js 在浏览器中聊天 这可能吗 如果您希望您的解决方案是基于服务器端和客

随机推荐