我想找出最后一个元素等于某个值的数组文档。
可以通过特定的数组位置访问数组元素:
// i.e. comments[0].by == "Abe"
db.example.find( { "comments.0.by" : "Abe" } )
但是如何使用最后一个项目作为搜索条件呢?例如:
db.example.find( { "comments.last.by" : "Abe" } )
顺便说一下,我正在使用 PHP。
我想找出最后一个元素等于某个值的数组文档。
可以通过特定的数组位置访问数组元素:
// i.e. comments[0].by == "Abe"
db.example.find( { "comments.0.by" : "Abe" } )
但是如何使用最后一个项目作为搜索条件呢?例如:
db.example.find( { "comments.last.by" : "Abe" } )
顺便说一下,我正在使用 PHP。
我知道这个问题很老了,但在回答一个类似的新问题后,我在谷歌上找到了它。所以我认为这个问题值得同等对待。
db.example.aggregate([
// Use an index, which $where cannot to narrow down
{$match: { "comments.by": "Abe" }},
// De-normalize the Array
{$unwind: "$comments"},
// The order of the array is maintained, so just look for the $last by _id
{$group: { _id: "$_id", comments: {$last: "$comment"} }},
// Match only where that $last comment by `by.Abe`
{$match: { "comments.by": "Abe" }},
// Retain the original _id order
{$sort: { _id: 1 }}
])
考虑到我们已经能够缩小只针对首先有 "Abe" 评论的文档进行测试,因此应该比 $where 更高效。正如警告的那样,$where将测试集合中的每个文档,并且即使存在可用的索引也永远不会使用。
当然,您也可以使用此处描述的技术来保留原始文档,这样一切都像find()
一样工作。
这只是让任何人思考的食物。
现代版本已添加了$redact
管道表达式以及$arrayElemAt
(后者从3.2版本开始,因此这是最低版本)这两者的组合将允许一个逻辑表达式检查数组的最后一个元素而无需处理$unwind
阶段:
db.example.aggregate([
{ "$match": { "comments.by": "Abe" }},
{ "$redact": {
"$cond": {
"if": {
"$eq": [
{ "$arrayElemAt": [ "$comments.by", -1 ] },
"Abe"
]
},
"then": "$$KEEP",
"else": "$$PRUNE"
}
}}
])
这里的逻辑是通过比较完成的,其中$arrayElemAt
获取了数组-1
的最后一个索引,这个索引通过$map
转换成了仅包含"by"
属性值的数组。这使得单个值可以与所需参数"Abe"
进行比较。甚至更加现代的方法是在MongoDB 3.6及以上版本中使用$expr
:
db.example.find({
"comments.by": "Abe",
"$expr": {
"$eq": [
{ "$arrayElemAt": [ "$comments.by", -1 ] },
"Abe"
]
}
})
这将是在数组内匹配最后一个元素的最有效解决方案,预计实际上会在大多数情况下甚至特别是在这里代替使用$where
。
使用这种模式设计,无法一次完成此操作。您可以将长度存储起来并执行两个查询,或者另外在另一个字段中存储最后一条评论:
{
'_id': 'foo';
'comments' [
{ 'value': 'comment #1', 'by': 'Ford' },
{ 'value': 'comment #2', 'by': 'Arthur' },
{ 'value': 'comment #3', 'by': 'Zaphod' }
],
'last_comment': {
'value': 'comment #3', 'by': 'Zaphod'
}
}
$set
将此数据与comment
的$push
一起设置。$comment = array(
'value' => 'comment #3',
'by' => 'Zaphod',
);
$collection->update(
array( '_id' => 'foo' ),
array(
'$set' => array( 'last_comment' => $comment ),
'$push' => array( 'comments' => $comment )
)
);
您可以使用$where
操作符来实现:
db.example.find({ $where:
'this.comments.length && this.comments[this.comments.length-1].by === "Abe"'
})
$where
通常会导致较慢的性能。不过,您可以通过在查询中包含"comments.by": "Abe"
来帮助改善性能:
db.example.find({
"comments.by": "Abe",
$where: 'this.comments.length && this.comments[this.comments.length-1].by === "Abe"'
})
通过这种方式,$where
只需要针对包含 Abe 评论的文档进行评估,新术语将能够使用 "comments.by"
上的索引。
我只是在做:
db.products.find({'statusHistory.status':'AVAILABLE'},{'statusHistory': {$slice: -1}})
这将返回具有数组中最后一个statusHistory
元素包含属性status='AVAILABLE'
的products
。
我不确定为什么我的上面的回答被删除了。我正在重新发布它。我相信在不更改模式的情况下,您应该能够以这种方式完成。
db.example.find({ "comments:{$slice:-1}.by" : "Abe" }
// ... 或者
db.example.find({ "comments.by" : "Abe" }
默认情况下,它会取数组中的最后一个元素。
$slice
是一个投影操作符,而不是查询操作符,第二个查询会查看所有评论,而不仅仅是最后一个。 - JohnnyHK