我有一个数组。
我想从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)
})
})
}
如何选择文档以便它只从历史数组中输出具有最高版本号的对象?
答案 0 :(得分:1)
您可以通过几种不同的方式解决这个问题。它们的方法和性能当然各不相同,我认为您需要对设计做出一些更大的考虑。最值得一提的是"需要"为"修订"实际应用程序的使用模式中的数据。
至于从内部数组"获取"最后一个元素的最重要的一点,那么你真的应该使用.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)
})
}
由于这是一个聚合语句,我们也可以在其中进行"加入"在"服务器"与使用$lookup
进行其他请求(这是.populate()
实际上在此处执行的操作)相反,我对实际的集合名称采取了一些自由,因为您的架构未包含在问题中。没关系,因为你没有意识到你实际上可以这样做。
当然"实际"服务器需要集合名称,它没有"应用程序端"的概念。定义的架构。为方便起见,您可以做些事情,但稍后会有更多内容。
您还应该注意,根据projectId
实际来自哪里,与.find()
等常规猫鼬方法不同,$match
实际上需要"施放"如果输入值实际上是"字符串"则为ObjectId
。猫鼬不能应用"架构类型"在聚合管道中,您可能需要自己执行此操作,尤其是projectId
来自请求参数时:
{ "$match": { "project_id": Schema.Types.ObjectId(projectId) } },
这里的基本部分是我们使用$map
迭代所有"uploaded_files"
条目,然后简单地提取"最新的"来自"history"
数组$arrayElemAt
使用" last" index,-1
。
这应该是合理的,因为它最有可能是最近的修订版"实际上是"最后"数组输入。我们可以通过将$max
作为$filter
的条件来调整此选项来寻找最大的"。因此管道阶段变为:
{ "$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
值进行比较之外,这或多或少是相同的,并且只从数组中返回"一个" 条目索引从"过滤"排列"第一个"位置,或0
索引。
关于使用$lookup
代替.populate()
的其他常规技巧,请参阅"Querying after populate in Mongoose"上的条目,其中介绍了采用此方法时可以优化的内容。< / p>
当然,我们也可以使用.populate()
调用并操纵生成的数组来进行(尽管效率不高)同样的操作:
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)
你当然在哪里回来&#34;所有&#34;来自"history"
的项目,但我们只需应用.map()
来调用这些元素上的.slice()
,以便再次获取每个元素的最后一个数组元素。
由于返回了所有历史记录而导致开销增加,.populate()
调用是其他请求,但它确实获得了相同的最终结果。
我在这里看到的主要问题是你甚至有一个&#34;历史&#34;内容中的数组。这不是一个好主意,因为您需要执行上述操作才能返回您想要的相关项目。
所以作为设计点&#34;,我不会这样做。但相反,我会&#34;分开&#34;从所有情况下的项目的历史。保持&#34;嵌入&#34;文件,我会保留&#34;历史&#34;在一个单独的数组中,只保留&#34;最新的&#34;修订与实际内容:
{
"_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
}
}
您可以通过设置$set
相关条目并在&#34;历史记录&#34;上使用$push
来维护此问题。在一次操作中:
.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
}
}
}
)
将数组分开,然后您可以简单地查询并始终得到最新的,并丢弃&#34;历史记录&#34;直到你真正想要提出这个要求为止:
Project.findOne({ "project_id": projectId })
.select('-file_history') // The '-' here removes the field from results
.populate(populateQuery)
作为一般情况,虽然我根本不打扰&#34;修订版&#34;完全没有数字。当&#34;追加&#34;保持大部分相同的结构时,你真的不需要它。自#&#34;最新&#34;以来的一个数组总是&#34;最后&#34;。对于改变结构也是如此,其中最新的&#34;最新的&#34;将永远是给定上传文件的最后一个条目。
努力维持这样一个人造的&#34;索引充满了问题,并且大多数都破坏了原子的变化。此处.update()
示例中显示的操作,因为您需要知道&#34;计数器&#34;值,以提供最新的修订号,因此需要&#34;读&#34;来自某个地方。