比较数组的最后一个值和倒数第二个值

时间:2016-04-09 18:04:26

标签: javascript mongodb mongodb-query aggregation-framework

我想编写一个查询,根据对文档中数组中存储的两个项的计算,从MongoDB返回文档。

在这种情况下,我想返回数组中最后一项少于同一数组中倒数第二项的所有文档。

MongoDB可以实现吗?你能指出我正确的方向吗?文件?

任何指针都非常感激。

2 个答案:

答案 0 :(得分:1)

在现代MongoDB版本(3.2及更高版本)中,此处的最佳操作是使用$redact根据条件和$arrayElemAt执行“逻辑过滤器”以从阵列中获取单个值。

给出样本:

{ "_id": 1, "data": [1,2,3] },
{ "_id": 2, "data": [3,2,1] }

然后查询:

db.collection.aggregate([
  { "$redact": {
     "$cond": {
       "if": { 
         "$lt": [
           { "$arrayElemAt": [ "$data", -1 ] },
           { "$arrayElemAt": [ "$data", -2 ] }
         ]
       },
       "then": "$$KEEP",
       "else": "$$PRUNE"
     }
  }}
])

如果文档在数组成员中的子文档中包含“属性”,则应用$map以便仅提取要检查的属性的值以进行比较。还有$let的一些帮助,所以你不需要重复表达。

作为样本:

{ 
  "_id": 1,
  "data": [
     { "name": "c", "value": 1 },
     { "name": "b", "value": 2 },
     { "name": "a", "value": 3 }
  ]
},
{ 
  "_id": 2, 
  "data": [
    { "name": "a", "value": 3 },
    { "name": "b", "value": 2 },
    { "name": "c", "value": 1 }
  ]
}

查询:

db.collection.aggregate([
  { "$redact": {
     "$cond": {
       "if": { 
         "$let": {
           "vars": { 
             "data": { 
               "$map": {
                 "input": "$data",
                 "as": "el",
                 "in": "$$el.value"
               }
             }
           },
           "in": {
             "$lt": [
               { "$arrayElemAt": [ "$$data", -1 ] },
               { "$arrayElemAt": [ "$$data", -2 ] }
             ]
           }
         }
       },
       "then": "$$KEEP",
       "else": "$$PRUNE"
     }
  }}
])

获取“属性”值非常重要,因为与其他数组元素相比,Object的词汇比较不一定与条件匹配。

对于较旧版本的MongoDB或作为替代版本,您可以使用$where来评估条件:

db.collection.find(function() {
    return this.data.pop().value < this.data.pop().value
})

这确实使用JavaScript评估来确定结果,该结果的运行速度比聚合框架的本机编码运算符慢。因此,虽然表达式很容易编写,但这并不是最有效的方法。

虽然在早期版本中使用聚合框架是“可能的”,但你真的不应该这样做。该过程将涉及通过“重新分组”数组从数组中获取$last元素,然后过滤掉比较以获得“下一个”$last元素。这对性能来说通常不是一个好主意:

db.collection.aggregate([
    // Unwind array
    { "$unwind": "$data" },

    // Group back and get $last
    { "$group": {
        "_id": "$_id",
        "data": { "$push": "$data" },
        "lastData": { "$last" "$data" }
    }},
    // Unwind again
    { "$unwind": "$data" },
    // Compare to mark the last element
    { "$project": {
        "data": 1,
        "lastData": 1,
        "seen": { "$eq": [ "$lastData", "$data" ] }
    }},
    // Filter the previous $last from the list
    { "$match": { "seen": false } },
    // Group back and compare values
    { "$group": {
        "_id": "$_id",
        "data": { "$push": "$data" },
        "lastData": { "$last": "$lastData" },
        "secondLastData": { "$last": "$data" },
        "greater": {
            "$last": { "$lt": [ "$data.value", "$lastData.value" ] } 
        }
    }},
    // Filter to return only true
    { "$match": { "greater": true } }
 ])

这是一个非常丑陋的过程,而$where在这种情况下更清洁,更高效。因此,只有在需要对匹配该条件的数据执行“进一步”聚合操作时,才能在早期的MongoDB版本中使用它。

因此,这里令人信服的案例是获取最新版本,并在“单一”管道阶段使用$redact进行逻辑比较。每个聚合流水线阶段都会为结果的整体处理时间增加“成本”,因此“少就是多”一如既往。

答案 1 :(得分:0)

如果您有MongoDB 3.2,则可以使用聚合框架来获取符合条件的文档ID。一旦你有了ID,你就可以根据需要迭代进行处理。

示例:

db.collection.aggregate([
  {$project:{
    cond :{$cond:[{
      $gt:[{
        $slice:["$v", -2,1]
       }, {
        $slice:["$v", -1,1]
       }]
      }, true, false]
    }}
  },
  {$match:{cond: true}}
])

我的收藏中有以下文件:

{ 
    "_id" : ObjectId("57094622b08be16cf12fcf6f"), 
    "v" : [
        1.0, 
        2.0, 
        3.0, 
        4.0, 
        8.0, 
        7.0
    ]
}
{ 
    "_id" : ObjectId("5709462bb08be16cf12fcf70"), 
    "v" : [
        1.0, 
        2.0, 
        3.0, 
        4.0, 
        8.0, 
        10.0
    ]
}

根据您的问题陈述,您要选择数组中最后一个元素小于倒数第二个的文档,因此应选择包含_id = ObjectId("57094622b08be16cf12fcf6f")的文档

运行聚合查询将产生。

{ 
    "_id" : ObjectId("57094622b08be16cf12fcf6f"), 
    "cond" : true
}

这是我们所希望的。

正如我上面提到的,您可以迭代返回的信息,并可以采取您想要的任何操作,包括获取完整文档。

  

注意:如果您的文档很简单,您可以投影字段,并且不需要光标迭代来获取完整文档。但是,在我的示例中,我假设文档很复杂,并且没有文档属性或属性的前期信息。