6

我想找到数组中最后一个元素等于某个值的文档。数组元素可以通过特定的数组位置访问:

// i.e. comments[0].by == "Abe"
db.example.find( { "comments.0.by" : "Abe" } )

但是我如何使用最后一项作为条件进行搜索?IE

db.example.find( { "comments.last.by" : "Abe" } )

顺便说一句,我正在使用 php

4

5 回答 5

11

我知道这个问题很老,但我在回答了一个类似的新问题后在谷歌上找到了它。所以我认为这应该得到同样的待遇。

您可以通过使用聚合来避免$where的性能损失:

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将测试集合中的每个文档,并且即使有要使用的索引,也从不使用索引。

当然,您也可以使用此处描述的技术维护原始文档,因此一切都可以像find().

对于任何发现这一点的人来说都是值得深思的。


现代 MongoDB 版本的更新

现代版本添加了$redact管道表达式以及(后者从 3.2 开始,因此这将是这里的最小版本),这将允许逻辑表达式在不处理阶段$arrayElemAt的情况下检查数组的最后一个元素:$unwind

db.example.aggregate([
  { "$match": { "comments.by": "Abe" }},
  { "$redact": {
    "$cond": {
      "if": {
        "$eq": [
          { "$arrayElemAt": [ "$comments.by", -1 ] },
          "Abe"
        ]
      },
      "then": "$$KEEP",
      "else": "$$PRUNE"
    }
  }}
])

这里的逻辑是通过比较在哪里$arrayElemAt获取数组的最后一个索引完成的-1,它被转换为属性中的值的数组"by"via $map。这允许将单个值与所需参数 进行比较"Abe"

或者更现代一点的使用$exprMongoDB 3.6 及更高版本:

db.example.find({
  "comments.by": "Abe",
  "$expr": {
    "$eq": [
      { "$arrayElemAt": [ "$comments.by", -1 ] },
      "Abe"
    ]
  }
})

这将是迄今为止匹配数组中最后一个元素的最高效的解决方案,并且实际上有望取代$where在大多数情况下的使用,尤其是在这里。

于 2014-02-22T11:21:07.703 回答
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 )
    )
);

现在很容易找到最后一个!

于 2012-05-10T22:22:43.483 回答
5

您可以使用操作员执行此$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".

于 2013-01-05T01:23:14.613 回答
3

我只是在做:

db.products.find({'statusHistory.status':'AVAILABLE'},{'statusHistory': {$slice: -1}})

这让我知道数组中products的最后一项包含属性。statusHistorystatus='AVAILABLE'

于 2019-05-09T11:58:02.993 回答
0

我不确定为什么我上面的答案被删除了。我正在重新发布它。我很确定在不更改架构的情况下,您应该可以这样做。

db.example.find({ "comments:{$slice:-1}.by" : "Abe" } 

// ... 或者

db.example.find({ "comments.by" : "Abe" }

默认情况下,这采用数组中的最后一个元素。

于 2013-01-04T23:17:52.990 回答