首先,让我们以人们可以使用它并产生所需结果的方式展示您的数据:
{ value: 1,
_id: ObjectId('5cb9ea0c75c61525e0176f96'),
name: 'Test',
category: 'Development',
subcategory: 'Programming Languages',
status: 'Supported',
description: 'Test',
change:
[ { version: 1,
who: 'ATL User',
when: new Date('2019-04-19T15:30:39.912Z'),
what: 'Item Creation' },
{ version: 2,
who: 'ATL Other User',
when: new Date('2019-04-19T15:31:39.912Z'),
what: 'Name Change' } ],
}
请注意,"when" 日期实际上是不同的,因此会有一个 $max 值,它们并不完全相同。现在我们可以遍历这些案例了
案例 1 - 获取“单数”$max 值
这里的基本情况是使用$arrayElemAt 和$indexOfArray 运算符返回匹配的$max 值:
db.items.aggregate([
{ "$match": {
"subcategory": "Programming Languages", "name": { "$exists": true }
}},
{ "$addFields": {
"change": {
"$arrayElemAt": [
"$change",
{ "$indexOfArray": [
"$change.when",
{ "$max": "$change.when" }
]}
]
}
}}
])
返回:
{
"_id" : ObjectId("5cb9ea0c75c61525e0176f96"),
"value" : 1,
"name" : "Test",
"category" : "Development",
"subcategory" : "Programming Languages",
"status" : "Supported",
"description" : "Test",
"change" : {
"version" : 2,
"who" : "ATL Other User",
"when" : ISODate("2019-04-19T15:31:39.912Z"),
"what" : "Name Change"
}
}
基本上,"$max": "$change.when" 返回的值是该值数组中的“最大值”。然后,您通过 $indexOfArray 找到该值数组的匹配“索引”,它返回找到的 first 匹配索引。该“索引”位置(实际上只是一个以相同顺序转置的"when" 值数组)然后与$arrayElemAt 一起使用,以从指定索引位置的"change" 数组中提取“整个对象”。
案例 2 - 返回“多个”$max 条目
与$max 几乎相同,除了这次我们$filter 返回与$max 值匹配的多个“可能” 值:
db.items.aggregate([
{ "$match": {
"subcategory": "Programming Languages", "name": { "$exists": true }
}},
{ "$addFields": {
"change": {
"$filter": {
"input": "$change",
"cond": {
"$eq": [ "$$this.when", { "$max": "$change.when" } ]
}
}
}
}}
])
返回:
{
"_id" : ObjectId("5cb9ea0c75c61525e0176f96"),
"value" : 1,
"name" : "Test",
"category" : "Development",
"subcategory" : "Programming Languages",
"status" : "Supported",
"description" : "Test",
"change" : [
{
"version" : 2,
"who" : "ATL Other User",
"when" : ISODate("2019-04-19T15:31:39.912Z"),
"what" : "Name Change"
}
]
}
所以$max 当然是相同的,但是这次该运算符返回的奇异值用于$filter 内的$eq 比较。这将检查每个数组元素并查看 current "when" 值 ("$$this.when")。其中 "equal" 则返回元素。
与第一种方法基本相同,但$filter 允许返回“多个” 元素。因此 everything 具有 same $max 值。
案例 3 - 对数组内容进行预排序。
现在您可能会注意到,在我包含的示例数据中(改编自您自己的但具有实际“最大”日期),“最大”值实际上是数组中的 last 值。这可能会自然而然地发生,因为$push(默认情况下)“追加” 到现有数组内容的末尾。所以 "newer" 条目将倾向于位于数组的 end。
这当然是 默认 行为,但您有充分的理由“可能” 想要改变它。简而言之,获取“最近的”数组条目的最佳方法实际上是从数组中返回第一个元素。
您真正需要做的就是确保“最近的”实际上是首先而不是最后添加的。有两种方法:
-
使用$position“预置”数组项:这是一个简单的修饰符$push,使用0位置,以便始终添加到前面:
db.items.updateOne(
{ "_id" : ObjectId("5cb9ea0c75c61525e0176f96") },
{ "$push": {
"change": {
"$each": [{
"version": 3,
"who": "ATL User",
"when": new Date(),
"what": "Another change"
}],
"$position": 0
}
}}
)
这会将文档更改为:
{
"_id" : ObjectId("5cb9ea0c75c61525e0176f96"),
"value" : 1,
"name" : "Test",
"category" : "Development",
"subcategory" : "Programming Languages",
"status" : "Supported",
"description" : "Test",
"change" : [
{
"version" : 3,
"who" : "ATL User",
"when" : ISODate("2019-04-20T02:40:30.024Z"),
"what" : "Another change"
},
{
"version" : 1,
"who" : "ATL User",
"when" : ISODate("2019-04-19T15:30:39.912Z"),
"what" : "Item Creation"
},
{
"version" : 2,
"who" : "ATL Other User",
"when" : ISODate("2019-04-19T15:31:39.912Z"),
"what" : "Name Change"
}
]
}
请注意,这将需要您实际去“反转”所有数组元素,以便“最新”已经在前面,以便保持顺序。值得庆幸的是,这在第二种方法中有所涵盖...
-
使用$sort 在每个$push 上按顺序修改文档: 这是另一个修饰符,它实际上在每次添加新项目时自动“重新排序”。正常用法与上述$each 的任何新项目基本相同,甚至只是一个“空”数组,以便仅将$sort 应用于现有数据:
db.items.updateOne(
{ "_id" : ObjectId("5cb9ea0c75c61525e0176f96") },
{ "$push": {
"change": {
"$each": [],
"$sort": { "when": -1 }
}
}}
)
结果:
{
"_id" : ObjectId("5cb9ea0c75c61525e0176f96"),
"value" : 1,
"name" : "Test",
"category" : "Development",
"subcategory" : "Programming Languages",
"status" : "Supported",
"description" : "Test",
"change" : [
{
"version" : 3,
"who" : "ATL User",
"when" : ISODate("2019-04-20T02:40:30.024Z"),
"what" : "Another change"
},
{
"version" : 2,
"who" : "ATL Other User",
"when" : ISODate("2019-04-19T15:31:39.912Z"),
"what" : "Name Change"
},
{
"version" : 1,
"who" : "ATL User",
"when" : ISODate("2019-04-19T15:30:39.912Z"),
"what" : "Item Creation"
}
]
}
您可能需要花一点时间来理解为什么要 $push 以 $sort 这样的数组,但总体意图是当可能对数组进行修改以“改变”像 @987654388 这样的属性时@value 被排序,您将使用这样的语句来反映这些更改。或者实际上只是使用$sort 添加新项目并让它发挥作用。
那么为什么"store" 数组是这样排列的呢?如前所述,您希望第一个项作为“最近的”,然后返回的查询简单地变成:
db.items.find(
{
"subcategory": "Programming Languages",
"name": { "$exists": true }
},
{ "change": { "$slice": 1 } }
)
返回:
{
"_id" : ObjectId("5cb9ea0c75c61525e0176f96"),
"value" : 1,
"name" : "Test",
"category" : "Development",
"subcategory" : "Programming Languages",
"status" : "Supported",
"description" : "Test",
"change" : [
{
"version" : 3,
"who" : "ATL User",
"when" : ISODate("2019-04-20T02:40:30.024Z"),
"what" : "Another change"
}
]
}
因此,$slice 可以仅用于通过已知索引提取数组项。从技术上讲,您可以在那里使用-1 以返回数组的 last 项,但是最近的重新排序首先允许其他事情,例如确认最后一次修改是由某些用户和/或其他条件,例如日期范围限制。即:
db.items.find(
{
"subcategory": "Programming Languages",
"name": { "$exists": true },
"change.0.who": "ATL User",
"change.0.when": { "$gt": new Date("2018-04-01") }
},
{ "change": { "$slice": 1 } }
)
请注意,"change.-1.when" 之类的语句是非法语句,这就是我们重新排列数组的原因,以便您可以使用 legal 0 代替 first -1 的 最后。
结论
因此,您可以执行多种不同的操作,或者通过使用聚合方法过滤数组内容,或者在对数据的实际存储方式进行一些修改后通过标准查询表单。使用哪一个取决于您自己的情况,但应注意,任何标准查询表单的运行速度都将明显快于通过聚合框架或任何计算运算符进行的任何操作。