我想找到数组中最后一个元素等于某个值的文档。 可以通过特定的数组位置访问数组元素:
// i.e. comments[0].by == "Abe"
db.example.find( { "comments.0.by" : "Abe" } )
但我如何使用最后一项作为标准进行搜索? 即。
db.example.find( { "comments.last.by" : "Abe" } )
顺便说一下,我正在使用php
答案 0 :(得分:8)
我知道这个问题很老,但我在回答similar new question后在谷歌上发现了这个问题。所以我认为这应该得到同样的待遇。
您可以使用$where代替aggregate来避免性能损失:
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 }}
])
这应该围绕 $ where 运行,因为我们能够缩小 首先由“Abe”发表评论的文档。正如警告的那样, $ where 将测试集合中的每个文档,即使有一个索引也不会使用索引。
当然,您也可以使用technique described here维护原始文档,因此一切都可以像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
的最后一个索引的情况下完成的,该变量仅通过{转换为"by"
属性中值的数组{3}}。这允许将单个值与所需参数"Abe"
进行比较。
甚至使用$map
为MongoDB 3.6及更高版本更加现代化:
db.example.find({
"comments.by": "Abe",
"$expr": {
"$eq": [
{ "$arrayElemAt": [ "$comments.by", -1 ] },
"Abe"
]
}
})
这是迄今为止用于匹配数组中最后一个元素的最高性能解决方案,实际上在大多数情况下,特别是在这里,预计会取代$expr
的使用。
答案 1 :(得分:7)
使用此架构设计无法一次性执行此操作。您可以存储长度并进行两次查询,也可以将最后一条注释另外存储在另一个字段中:
{
'_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
和$push
comment
设置此数据。
$comment = array(
'value' => 'comment #3',
'by' => 'Zaphod',
);
$collection->update(
array( '_id' => 'foo' ),
array(
'$set' => array( 'last_comment' => $comment ),
'$push' => array( 'comments' => $comment )
)
);
现在很容易找到最后一个!
答案 2 :(得分:4)
您可以使用$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"
上的索引。
答案 3 :(得分:2)
我正在做:
db.products.find({'statusHistory.status':'AVAILABLE'},{'statusHistory': {$slice: -1}})
这使我products
的数组中最后一个statusHistory
项包含属性status='AVAILABLE'
。
答案 4 :(得分:-1)
我不确定为什么上面的答案会被删除。我正在重新发布它。我很确定如果不改变架构,你应该能够这样做。
db.example.find({ "comments:{$slice:-1}.by" : "Abe" }
// ...或
db.example.find({ "comments.by" : "Abe" }
默认情况下,它采用数组中的最后一个元素。