gpt4 book ai didi

node.js - 从数组获取最新的子文档

转载 作者:太空宇宙 更新时间:2023-11-04 00:19:47 24 4
gpt4 key购买 nike

我有一个数组。
我想从我的revision数组(复数)中选择具有最高history编号的对象。

我的文档看起来像这样(通常,它不只是uploaded_files中的一个对象):

{
"_id" : ObjectId("5935a41f12f3fac949a5f925"),
"project_id" : 13,
"updated_at" : ISODate("2017-07-02T22:11:43.426Z"),
"created_at" : ISODate("2017-06-05T18:34:07.150Z"),
"owner" : ObjectId("591eea4439e1ce33b47e73c3"),
"name" : "Demo project",
"uploaded_files" : [
{
"history" : [
{
"file" : ObjectId("59596f9fb6c89a031019bcae"),
"revision" : 0
}
],
"_id" : ObjectId("59596f9fb6c89a031019bcaf")
"display_name" : "Example filename.txt"
}
]
}


我选择文档的代码:

function getProject(req, projectId) {
let populateQuery = [
{path: 'owner'},
{path: 'uploaded_files.history.file'}
]
return new Promise(function (resolve, reject) {
Project.findOne({ project_id: projectId }).populate(populateQuery).then((project) => {
if (!project)
reject(new createError.NotFound(req.path))
resolve(project)
}).catch(function (err) {
reject(err)
})
})
}


如何选择文档,使其仅从历史记录数组中输出修订版本号最高的对象?

最佳答案

您可以通过几种不同的方式解决此问题。当然,它们在方法和性能上会有所不同,我认为您需要对设计进行一些较大的考虑。最值得注意的是,这是实际应用程序使用模式中“修订”数据的“需求”。

通过汇总查询

至于从内部数组中获取“最后一个元素”的最重要一点,那么您实际上应该使用.aggregate()操作来执行此操作:

function getProject(req,projectId) {

return new Promise((resolve,reject) => {
Project.aggregate([
{ "$match": { "project_id": projectId } },
{ "$addFields": {
"uploaded_files": {
"$map": {
"input": "$uploaded_files",
"as": "f",
"in": {
"latest": {
"$arrayElemAt": [
"$$f.history",
-1
]
},
"_id": "$$f._id",
"display_name": "$$f.display_name"
}
}
}
}},
{ "$lookup": {
"from": "owner_collection",
"localField": "owner",
"foreignField": "_id",
"as": "owner"
}},
{ "$unwind": "$uploaded_files" },
{ "$lookup": {
"from": "files_collection",
"localField": "uploaded_files.latest.file",
"foreignField": "_id",
"as": "uploaded_files.latest.file"
}},
{ "$group": {
"_id": "$_id",
"project_id": { "$first": "$project_id" },
"updated_at": { "$first": "$updated_at" },
"created_at": { "$first": "$created_at" },
"owner" : { "$first": { "$arrayElemAt": [ "$owner", 0 ] } },
"name": { "$first": "$name" },
"uploaded_files": {
"$push": {
"latest": { "$arrayElemAt": [ "$$uploaded_files", 0 ] },
"_id": "$$uploaded_files._id",
"display_name": "$$uploaded_files.display_name"
}
}
}}
])
.then(result => {
if (result.length === 0)
reject(new createError.NotFound(req.path));
resolve(result[0])
})
.catch(reject)
})
}


由于这是一条聚合语句,因此我们也可以在 上执行“ joins”,而不是通过使用 .populate()发出其他请求(这是 $lookup实际在此处执行的操作),因此我在实际的集合名称,因为问题中不包含您的架构。没关系,因为您没有意识到实际上可以这样做。

当然,服务器需要“实际的”集合名称,该名称没有“应用程序侧”定义的架构的概念。为了方便起见,您可以在这里做一些事情,但以后会做更多。

您还应注意,取决于 projectId实际来自何处,然后与常规猫鼬方法(例如 .find())不同,如果输入值实际上是“字符串”,则 $match实际上需要“投射”到 ObjectId ”。猫鼬不能在聚合管道中应用“模式类型”,因此您可能需要自己执行此操作,特别是如果 projectId来自请求参数:

  { "$match": { "project_id": Schema.Types.ObjectId(projectId) } },


这里最基本的部分是我们使用 $map遍历所有 "uploaded_files"条目,然后使用 "history"使用“最后”索引(即< cc>。

这应该是合理的,因为“最新修订”很可能实际上是“最后一个”数组条目。通过将 $arrayElemAt作为条件应用到 -1,我们可以使其适应于“最大”。这样管道阶段就变成了:

     { "$addFields": {
"uploaded_files": {
"$map": {
"input": "$uploaded_files",
"as": "f",
"in": {
"latest": {
"$arrayElemAt": [
{ "$filter": {
"input": "$$f.history.revision",
"as": "h",
"cond": {
"$eq": [
"$$h",
{ "$max": "$$f.history.revision" }
]
}
}},
0
]
},
"_id": "$$f._id",
"display_name": "$$f.display_name"
}
}
}
}},


除了我们与 $max值进行比较,并从数组中仅返回“一个”条目,使索引从“过滤”后的数组返回“第一个”位置之外,这几乎是同一件事,或者< cc>索引。

至于使用 $filter代替 $max的其他常规技术,请参阅我在 "Querying after populate in Mongoose"上的条目,其中详细介绍了采用这种方法时可以优化的内容。



通过填充查询

当然,我们也可以使用 0调用并操纵结果数组来执行(尽管效率不高)相同类型的操作:

Project.findOne({ "project_id": projectId })
.populate(populateQuery)
.lean()
.then(project => {
if (project === null)
reject(new createError.NotFound(req.path));

project.uploaded_files = project.uploaded_files.map( f => ({
latest: f.history.slice(-1)[0],
_id: f._id,
display_name: f.display_name
}));

resolve(project);
})
.catch(reject)


当然,您实际上是从 $lookup返回“所有”项目的位置,但是我们只需在这些元素上应用 .populate()来调用 .populate()即可再次获得每个元素的最后一个数组元素。

由于返回了所有历史记录,并且 "history"调用是其他请求,因此开销会增加一点,但是它的最终结果是相同的。



设计要点

不过,我在这里看到的主要问题是内容中甚至还有一个“历史”数组。这并不是一个好主意,因为您需要执行上述操作才能只返回所需的相关项目。

因此,作为“设计点”,我不会这样做。但是相反,在所有情况下,我都将历史与项目“分开”。与“嵌入式”文档保持一致,我将“历史”保留在单独的数组中,并且仅保留“最新”修订版的实际内容:

{
"_id" : ObjectId("5935a41f12f3fac949a5f925"),
"project_id" : 13,
"updated_at" : ISODate("2017-07-02T22:11:43.426Z"),
"created_at" : ISODate("2017-06-05T18:34:07.150Z"),
"owner" : ObjectId("591eea4439e1ce33b47e73c3"),
"name" : "Demo project",
"uploaded_files" : [
{
"latest" : {
{
"file" : ObjectId("59596f9fb6c89a031019bcae"),
"revision" : 1
}
},
"_id" : ObjectId("59596f9fb6c89a031019bcaf"),
"display_name" : "Example filename.txt"
}
]
"file_history": [
{
"_id": ObjectId("59596f9fb6c89a031019bcaf"),
"file": ObjectId("59596f9fb6c89a031019bcae"),
"revision": 0
},
{
"_id": ObjectId("59596f9fb6c89a031019bcaf"),
"file": ObjectId("59596f9fb6c89a031019bcae"),
"revision": 1
}

}


您只需设置 .map()相关条目并在一项操作中的“历史”上使用 .slice()即可维护此操作:

.update(
{ "project_id": projectId, "uploaded_files._id": fileId }
{
"$set": {
"uploaded_files.$.latest": {
"file": revisionId,
"revision": revisionNum
}
},
"$push": {
"file_history": {
"_id": fileId,
"file": revisionId,
"revision": revisionNum
}
}
}
)


将数组分开,然后您可以简单地查询并始终获取最新的数据,并丢弃“历史记录”,直到您真正想要发出该请求为止:

Project.findOne({ "project_id": projectId })
.select('-file_history') // The '-' here removes the field from results
.populate(populateQuery)


作为一般情况,我根本不会理会“修订”号。在“追加”到数组时,保持很多相同的结构并不是真正需要的,因为“最新”始终是“最后”。更改结构也是如此,其中“最新”将始终是给定上传文件的最后一个条目。

试图维护这样的“人工”索引充满了问题,并且大多破坏了“原子”操作的任何更改,如此处的 .populate()示例所示,因为您需要知道“计数器”值才能提供最新的版本号,因此需要从某个地方“读取”该版本号。

关于node.js - 从数组获取最新的子文档,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44876132/

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