mongodb - 按最后一个数组条目字段值过滤结果

标签 mongodb filtering mongodb-query aggregation-framework

具有此文档结构(为简洁起见省略了不相关的字段):

[
    {
        "_id" : 0,
        "partn" : [ 
            {
                "date" : ISODate("2015-07-28T00:59:14.963Z"),
                "is_partner" : true
            }, 
            {
                "date" : ISODate("2015-07-28T01:00:32.771Z"),
                "is_partner" : false
            }, 
            {
                "date" : ISODate("2015-07-28T01:15:29.916Z"),
                "is_partner" : true
            }, 
            {
                "date" : ISODate("2015-08-05T13:48:07.035Z"),
                "is_partner" : false
            }, 
            {
                "date" : ISODate("2015-08-05T13:50:56.482Z"),
                "is_partner" : true
            }
        ]
    },
    {
        "_id" : 149,
        "partn" : [ 
            {
                "date" : ISODate("2015-07-30T12:42:18.894Z"),
                "is_partner" : true
            }, 
            {
                "date" : ISODate("2015-07-31T00:01:51.176Z"),
                "is_partner" : false
            }
        ]
    }
]

我需要过滤最后一个(最近的)partn.is_partnertrue 的文档,这是最好的方法吗?

db.somedb
    .aggregate([ 
        // pre-filter only the docs with at least one is_partner === true, is it efficient/needed?
        {$match: {partn: { $elemMatch: { is_partner: true } } } },
        {$unwind: '$partn'},
        // do I need to sort by _id too, here?
        {$sort: {_id: 1, 'partn.date': 1} },
        // then group back fetching the last one by _id
        {$group : {
           _id : '$_id',
           partn: {$last: '$partn'},
        }},
        // and return only those with is_partner === true
        {$match: {'partn.is_partner': true } },
    ])

我得到了我需要的东西,但是,作为一个经验不足的 mongodb 开发人员,在该聚合中感觉有些开销。我考虑只获取每个 .partn 数组的最后一个条目,但有时必须导出/导入集合,如果我没记错的话,可以更改排序顺序 - 因此按日期聚合和排序可能会失败-证明这方面。

这是最好(最有效)的方法吗?如果不是,为什么?

谢谢。 (顺便说一句,这是 MongoDB 2.6)

最佳答案

里程可能会有所不同,很可能会证明“目前”您正在遵循的过程至少是“最适合”的。但我们或许可以做得更有效率。

你现在可以做什么

如果您的数组已经通过使用 $sort 进行了“排序”带有 $push 的修饰符,那么你或许可以这样做:

db.somedb.find(
  { 
    "partn.is_partner": true,
    "$where": function() {
      return this.partn.slice(-1)[0].is_partner == true;
    }
  },
  { "partn": { "$slice": -1 } }
)

只要 partn,is_partner 被“索引”,这仍然非常有效,因为可以使用索引来满足初始查询条件。不能的部分是 $where此处使用 JavaScript 评估的子句。

但是 $where 中的第二部分所做的只是简单地“切片”数组中的最后一个元素并测试它的 is_partner 属性的值以查看是否是真的。只有当该条件也得到满足时,才会返回文档。

还有 $slice投影运算符。这在返回数组的最后一个元素时做同样的事情。错误匹配已被过滤,因此这仅显示最后一个为真的元素。

结合前面提到的索引,考虑到已经选择了文档并且 JavaScript 条件只是过滤了其余部分,这应该会非常快。请注意,如果没有另一个字段与标准查询条件匹配,$where 子句不能使用索引。因此,请始终尝试“谨慎”地使用其他查询条件。

future 你能做什么

下一步,虽然在撰写本文时尚不可用,但肯定会在不久的将来为聚合框架提供 $slice 运算符。这目前在开发分支中,但这里是它如何工作的一瞥:

db.somedb.aggregate([
  { "$match": { "partn.is_partner": true } },
  { "$redact": {
    "$cond": {
      "if": { 
        "$anyElementTrue": {
          "$map": {
            "input": { "$slice": ["$partn",-1] },
            "as": "el",
            "in": "$$el.is_partner"
          }
        }
      },
      "then": "$$KEEP",
      "else": "$$PRUNE"
    }
  }},
  { "$project": {
      "partn": { "$slice": [ "$partn",-1 ] }
  }}
])

$redact 中组合 $slice此处的阶段允许使用逻辑条件过滤文档,测试文档。在这种情况下,$slice 生成一个发送到 $map 的单个元素数组。为了只提取单个 is_partner 值(仍然是一个数组)。由于这充其量仍然是单个元素数组,因此另一个测试是 $anyElementTrue这使得这是一个单一的 bool 结果,适用于 $cond .

这里的$redact 决定是$$KEEP 还是$$PRUNE 结果中的文档。稍后我们在项目中再次使用$slice,只返回过滤后数组的最后一个元素。

这与 JavaScript 版本几乎完全相同,除了它使用所有 native 编码运算符,因此应该比 JavaScript 替代方案快一点。

两种形式都按预期返回您的第一个文档:

{
    "_id" : 0,
    "partn" : [
            {
                    "date" : ISODate("2015-07-28T00:59:14.963Z"),
                    "is_partner" : true
            },
            {
                    "date" : ISODate("2015-07-28T01:00:32.771Z"),
                    "is_partner" : false
            },
            {
                    "date" : ISODate("2015-07-28T01:15:29.916Z"),
                    "is_partner" : true
            },
            {
                    "date" : ISODate("2015-08-05T13:48:07.035Z"),
                    "is_partner" : false
            },
            {
                    "date" : ISODate("2015-08-05T13:50:56.482Z"),
                    "is_partner" : true
            }
    ]
}

两者的最大问题是您的数组必须已经排序,因此最新日期排在第一位。否则,您需要聚合框架来$sort 数组,就像您现在所做的那样。

不是很有效,所以这就是为什么你应该“预排序”你的数组并在每次更新时保持顺序。

作为一个方便的技巧,这实际上将在一个简单的语句中重新排序所有集合文档中的所有数组元素:

db.somedb.update(
    {},
    { "$push": { 
        "partn": { "$each": [], "$sort": { "date": 1 } }
    }},
    { "multi": true }
)

因此,即使您不是将新元素“推送”到数组中而只是更新属性,您始终可以应用该基本构造来保持数组按照您想要的方式排序。

值得考虑,因为它会让事情变得更快。

关于mongodb - 按最后一个数组条目字段值过滤结果,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31835202/

相关文章:

node.js - Mongodb试图让选定的字段从聚合中返回

python - 多个键的总计

c++ - QTableView实时过滤

PHP:以智能编码方式进行排序?

javascript - 使用 MongoDB 和 Nodejs 创建嵌套子文档/子元素

mongodb - 在mongodb中过滤嵌套数组?

javascript - Mongodb递归搜索

mongodb - 无法将类型 bson.Raw 转换为 BSON 文档 : length read exceeds number of bytes available

mongodb - Mongoose 选择、填充和保存在 Mac 和 Windows 上的行为不同

Android加速度计过滤?