gpt4 book ai didi

node.js - 没有$ unwind的$ lookup多个级别?

转载 作者:太空宇宙 更新时间:2023-11-04 01:33:43 25 4
gpt4 key购买 nike

我有以下收藏

场地集合

{
"_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" : "bruce@linkites.com",
"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实现以下查询,因为作者有followers字段,所以我需要通过执行 isFollow发送字段 $project,而使用 populate则无法完成...

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

最佳答案

当然,有两种方法取决于您可用的MongoDB版本。从$lookup的不同用法到通过.populate().lean()结果上启用对象操纵的方式,这些都有所不同。

我的确要求您仔细阅读本节,并且请注意,考虑实施解决方案时,所有内容可能都不尽相同。

MongoDB 3.6,“嵌套” $ lookup

使用MongoDB 3.6,$lookup运算符获得了包括pipeline表达式的附加功能,而不是简单地将“ local”转换为“ foreign”键值,这意味着您基本上可以将每个$lookup设置为“ nested”在这些管道表达式中

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目标输出都可以是“数组”,但是在“子管道”中,我们可以将单个元素数组重新整形为单个值。

标准MongoDB $ lookup

实际上,仍然可以使用“ $lookup”来实现“服务器上的联接”,但是它只需要进行中间处理。这是使用 $unwind解构数组并使用 $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$unwind的简单模式。

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

只有两个级别可以重新构造回原始的 Venue文档,因此第一个详细信息级别是通过 Review来重建 "comments"数组。您需要做的就是 $push "$reviews.comments"的路径以收集这些,并且只要 "$reviews._id"字段位于“ grouping _id”中,您需要保留的其他所有内容就是所有其他字段。您也可以将所有这些都放入 _id中,或者可以使用 $first

完成后,只有一个 $group阶段才能返回到 Venue本身。这次,分组键当然是 "$_id",场所本身的所有属性都使用 $first,其余的 "$review"详细信息又返回到使用 $push的数组中。当然,前一个 "$comments"$group输出将成为 "review.comments"路径。

处理单个文档及其关系,这并不是很糟糕。 $unwind管道运算符通常可能是性能问题,但是在此用法的上下文中,它实际上不应造成太大的影响。

由于数据仍在“连接到服务器上”,因此流量仍然远远少于其他方式。

JavaScript操作

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

当然,使用 populate()的问题在于,尽管它看起来像是一个简化得多的过程,但实际上无论如何都不是JOIN。 populate()实际所做的只是“隐藏”向数据库提交多个查询,然后通过异步处理等待结果的底层过程。

因此,联接的“外观”实际上是对服务器的多个请求的结果,然后对数据进行“客户端操作”以将详细信息嵌入到数组中。

因此,除了明确的警告,即性能特征远不能与服务器 $lookup相提并论之外,另一个警告是,结果中的“猫鼬文档”当然不是经过进一步处理的普通JavaScript对象。

因此,为了采用这种方法,您需要在执行之前向查询添加 .lean()方法,以指示猫鼬返回“普通JavaScript对象”,而不是使用附加到架构方法的强制转换的 Document类型。模型。当然,请注意,所得数据不再可以访问任何将与相关模型本身关联的“实例方法”:

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


现在 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()进行比较之后,可以与存储在该数组中的 req.user.id值进行比较(如果不是,则还添加)上的 .toString(),因为通常这样更容易通过JavaScript代码比较这些值。

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



摘要

基本上,您可以采用这些方法,而无需“滚动自己”,而您实际上是自己对数据库执行“多个查询”,而不是使用 .populate()的帮助程序。

使用填充输出,然后您就可以像处理任何其他数据结构一样简单地操作结果中的数据,只要将 .lean()应用于查询以转换或以其他方式从返回的猫​​鼬文档中提取纯对象数据即可。

尽管聚合方法看起来涉及更多,但在服务器上进行此工作还有“很多”优点。可以对更大的结果集进行排序,可以进行计算以进行进一步的过滤,当然,您会对服务器发出的“单个请求”获得“单个响应”,而没有任何额外的开销。

完全可以争论的是,流水线本身可以简单地基于已经存储在模式中的属性来构造。因此,编写您自己的方法以基于附加模式执行此“构造”应该不会太困难。

从长远来看,当然 $lookup是更好的解决方案,但是您可能需要在初始编码中做更多的工作,如果您当然不只是从此处列出的内容中进行复制的话;)

关于node.js - 没有$ unwind的$ lookup多个级别?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55198479/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com