【问题标题】:Compare last Value to Second Last value of an Array比较数组的最后一个值和倒数第二个值
【发布时间】:2016-07-30 23:04:05
【问题描述】:

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

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

MongoDB 可以做到这一点吗?你能指出我正确的方向吗?

非常感谢任何指针。

【问题讨论】:

    标签: javascript mongodb mongodb-query aggregation-framework


    【解决方案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 进行逻辑比较。每个聚合管道阶段都会为结果的整体处理时间增加“成本”,因此 “少即是多” 一如既往。

    【讨论】:

      【解决方案2】:

      如果你有 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
      }
      

      这是我们所希望的。

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-11-18
        • 1970-01-01
        • 2020-08-20
        • 2019-12-27
        • 2016-02-21
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多