按最后一个数组输入字段值

时间:2015-08-05 14:20:48

标签: 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)

1 个答案:

答案 0 :(得分:1)

里程可能会有所不同,很可能会发现"目前"您正在关注的过程最适合"最适合"至少。但我们可以做得更有效率。

你现在可以做什么

如果您的阵列已经排序"排序"通过将$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被"索引"这仍然非常有效,因为可以使用索引来满足初始查询条件。不可能的部分是使用JavaScript评估的$where子句。

但是$where中的第二部分正在做的只是"切片"数组中的最后一个元素,并测试is_partner属性的值,看它是否为真。只有在满足该条件的情况下才返回文件。

还有$slice投影运算符。这在返回数组中的最后一个元素时也是如此。错误匹配已经过滤,因此这只显示最后一个为true的元素。

结合上面提到的索引,这应该很快,因为已经选择了文档,JavaScript条件只是过滤了其余部分。请注意,如果没有其他字段要匹配标准查询条件,则$where子句不能使用索引。所以总是尽量使用"谨慎和#34;其他查询条件到位。

您将来可以做什么

Next Up虽然在撰写本文时尚未提供,但在不久的将来肯定会是聚合框架的 $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,这使得它成为一个单一的布尔结果,适用于$cond

此处$redact会根据结果确定结果中的$$KEEP$$PRUNE结果。稍后我们在项目中再次使用$slice,以便在过滤后返回数组的最后一个元素。

这恰好与JavaScript版本完全相同,除了这是使用所有本机编码运算符,因此应该比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 }
)

所以即使你不是"推动"将新元素放入数组并只更新属性,您始终可以应用该基本结构来保持数组的排序方式。

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