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

Filter results by the Last Array Entry Field Value

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

[
    {
        "_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 数组的最后一个条目,但集合有时必须是 exported/imported,如果我没记错的话,可以更改排序顺序 - 因此按日期聚合和排序可以防止失败那方面。

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

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

里程可能会有所不同,结果很可能 "currently" 您正在遵循的过程至少是 "most suited"。但我们或许可以做得更有效率。

你现在可以做什么

如果你的数组已经是 "sorted" 通过使用 $sort modifier with $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 是 "indexed" 这仍然非常有效,因为可以使用索引来满足初始查询条件。不能的部分是这里的 $where 子句使用 JavaScript 评估。

但是 $where 中的第二部分所做的只是 "slicing" 数组中的最后一个元素并测试它的 is_partner 属性 的值以查看如果这是真的。只有当该条件也满足时,文档 returned.

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

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

你以后可以做什么

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 stage here alows the documents to be filtered with a logical condition, testing the document. In this case the $slice produces a single element array that is sent to $map in order to just extract the single is_partner value ( still as an array ). As this is still a single element array at best, the other test is $anyElementTrue which makes this a singular boolean result, suitable for $cond.

中合并 $slice

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

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

两种形式 return 您的第一份文件符合预期:

{
    "_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 数组,就像您现在所做的那样。

不是很有效,所以这就是为什么你应该 "pre-sort" 你的数组并在每次更新时保持顺序。

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

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

因此,即使您不是 "pushing" 数组中的新元素并且只是更新 属性,您始终可以应用该基本构造来保持数组按您想要的顺序排列。

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