我正在尝试根据日期值返回包含最大子文档的文档。到目前为止,我已经能够创建正确的对象,但是查询将返回所有子文档,而不是具有最大日期的子文档。例如,我的数据存储为:
{ value: 1,
_id: 5cb9ea0c75c61525e0176f96,
name: 'Test',
category: 'Development',
subcategory: 'Programming Languages',
status: 'Supported',
description: 'Test',
change:
[ { version: 1,
who: 'ATL User',
when: 2019-04-19T15:30:39.912Z,
what: 'Item Creation' },
{ version: 2,
who: 'ATL Other User',
when: 2019-04-19T15:30:39.912Z,
what: 'Name Change' } ],
}
在查询中,我选择所有具有相同subcategory
且具有其name
的项目。然后,我在对象中投影所需的所有值,展开并对数组进行排序,然后返回查询结果。从结构上讲,这可以为我提供在此处建模的正确输出:
{
_id: 5cb9ea0c75c61525e0176f96,
name: 'Test',
category: 'Development',
subcategory: 'Programming Languages',
status: 'Supported',
description: 'Test',
change: {
"who": "ATL User",
"when": ISODate("2019-04-19T17:11:36Z")
}
}
这里的问题是,如果一个文档有多个子文档-或版本-那么查询也将返回这些子文档而不是忽略它们,只保留最大日期(如果项目Test
具有三个版本,则返回了三个Test
文档)。
要使用此查询否定其他文档,我应该怎么看?
db.items.aggregate([
{$match: {subcategory: "Programming Languages", name: {$exists: true}}},
{$project: {"name": 1,
"category": 1,
"subcategory": 1,
"status": 1,
"description": 1,
"change.who": 1,
"change.when": {$max: "$change.when"}}},
{$unwind: "$change"},
{$sort: {"change.when": -1}}
]);
答案 0 :(得分:1)
首先,让我们以人们可以使用它并产生预期结果的方式来显示您的数据:
{ 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
值,并且它们不仅是相同的。现在我们可以审阅案件了
$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
找到该值数组的匹配“索引”,该返回返回找到的第一个匹配索引。然后,该“索引”位置(实际上只是来自以相同顺序转置的"when"
值的数组)与$arrayElemAt
一起使用,从"change"
数组中的“整个对象”中提取“指定的索引位置。
$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
当然是相同的,但是这次在$eq
中的$filter
比较中使用了该运算符返回的奇异值。这将检查每个数组元素,并查看 current "when"
值("$$this.when"
)。在“等于” 的情况下,返回元素。
与第一种方法基本相同,但$filter
允许返回“多个” 元素。因此,所有具有相同 $max
的值。
现在您可能会注意到,在我包含的示例数据中(根据您自己的数据改编,但带有实际的“最大”日期),“最大”值实际上是数组中的 last 值。这可能是由于$push
(默认情况下)“附加” 到现有数组内容的末尾而自然发生的。因此,“较新” 条目将倾向于位于数组的 end 。
这当然是默认行为,但是有充分理由说明您“可能” 想要更改它。简而言之,获取“最新” 数组条目的最好方法实际上是从数组中返回第一个元素。
您真正需要做的就是确保“最新” 实际上是第一而不是最后添加的。有两种方法:
使用$position
“预先添加”数组项:这是使用0
位置对$push
的简单修饰符以便始终添加到前:
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
这样的数组,但是通常的目的是当对数组进行修改以“改变”属性时就像一个Date
值被排序时,您将使用这样的语句来反映这些更改。或者确实只是用$sort
添加新项目,然后解决这个问题。
那么为什么“存储” 这样排列的数组呢?如前所述,您希望 first 项作为“最近” ,然后返回该查询的查询变成:
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 < / em>代替-1
代替 last 。
因此,您可以执行几种不同的操作,通过使用聚合方法来过滤数组内容,或者在对数据的实际存储方式进行了一些修改之后,通过标准查询表单。使用哪种查询取决于您自己的情况,但应注意,任何标准查询表单的运行速度都明显快于通过聚合框架或任何计算出的运算符进行的任何处理。