从键值对中获取具有最高价值的属性

时间:2017-07-05 13:19:32

标签: mongodb mongodb-query aggregation-framework

在MongoDB中,我的文档结构如下:

{
    _id: "123456...", // an ObjectId
    name: "foobar",
    classification: {
        class_1: 0.45,
        class_2: 0.11,
        class_3: 0.44
    }
}

使用聚合管道,是否可以为我提供包含最高分类的对象?所以,鉴于上述情况,我希望得到这样的结果:

{
    _id: "123456...", // an ObjectId
    name: "foobar",
    classification: "class_1"
}

我以为我可以使用$unwind,但classification属性不是数组。

对于它的价值:我知道classification中总会有三个属性,因此可以对查询中的密钥进行硬编码。

3 个答案:

答案 0 :(得分:1)

你应该在这里注意,所应用的每一种技术都基于"强制" "键/值"成对"阵列"比较和提取的格式。因此,要学习的真正教训是,您的文档"应该"实际上将它存储为"数组"代替。但是在技术上。

如果你有MongoDB 3.4,那么你可以使用$objectToArray打开"键"进入一个数组,这样你就可以得到这个值:

动态

db.collection.aggregate([
   { "$addFields": {
     "classification": {
       "$arrayElemAt": [
           { "$map": {
             "input": {
               "$filter": {
                 "input": { "$objectToArray": "$classification" },
                 "as": "c",
                 "cond": { 
                   "$eq": [
                     "$$c.v",
                     { "$max": {
                       "$map": {
                         "input": { "$objectToArray": "$classification" },
                         "as": "c",
                         "in": "$$c.v"
                       } 
                     }}
                   ]          
                 }
               }
             },
             "as": "c",
             "in": "$$c.k",
           }},
           0
       ]
     }
   }}
])

否则只是在迭代光标时进行转换,如果你真的不需要它进行进一步的聚合。作为一个基本的JavaScript示例:

 db.collection.find().map(d => Object.assign(
   d,
   { classification: Object.keys(d.classification)
       .filter(k => d.classification[k] === Math.max.apply(null,
         Object.keys(d.classification).map(k => d.classification[k])
       ))[0]
   }
 ));

如果您实际上正在聚合某些东西,那么这也是您使用mapReduce应用的基本逻辑。

两者都产生:

/* 1 */
{
    "_id" : "123456...",
    "name" : "foobar",
    "classification" : "class_1"
}

硬编码

关于"硬编码"你说的情况还可以。然后,您可以使用$switch通过提供每个值来构建这样的内容:

db.collection.aggregate([
  { "$addFields": {
    "classification": {
      "$let": {
        "vars": {
          "max": {
            "$max": [ 
              "$classification.class_1", 
              "$classification.class_2",
              "$classification.class_3"
            ]
          }
        },
        "in": {
          "$switch": {
            "branches": [
              { "case": { "$eq": [ "$classification.class_1", "$$max" ] }, "then": "class_1" },
              { "case": { "$eq": [ "$classification.class_2", "$$max" ] }, "then": "class_2" },
              { "case": { "$eq": [ "$classification.class_3", "$$max" ] }, "then": "class_3" },               
            ] 
          }
        }   
      }
    }    
  }}
])

然后实际上能够使用$max更长时间地写出来,然后唯一真正的约束是MongoDB 3.2的$cond的变化,它允许一组参数而不是以前作为"累加器的角色":

db.collection.aggregate([
  { "$addFields": {
    "classification": {
      "$let": {
        "vars": {
          "max": {
            "$max": [ 
              "$classification.class_1", 
              "$classification.class_2",
              "$classification.class_3"
            ]
          }
        },
        "in": {
          "$cond": {
            "if": { "$eq": [ "$classification.class_1", "$$max" ] },
            "then": "class_1",
            "else": {
              "$cond": {
                "if": { "$eq": [ "$classification.class_2", "$$max" ] },
                "then": "class_2",
                "else": "class_3"
              }
            }
          }
        }   
      }
    }    
  }}
])

如果你真的"真的"然后你就可以强迫"强迫" "max"通过单独的管道阶段,然后使用$max$map再次使用$unwind$group。这将使操作与MongoDB 2.6兼容:

db.collection.aggregate([
  { "$project": {
    "name": 1,
    "classification": 1,
    "max": {
      "$map": {
        "input": [1,2,3],
        "as": "e",
        "in": {
          "$cond": {
            "if": { "$eq": [ "$$e", 1 ] },
            "then": "$classification.class_1",
            "else": {
              "$cond": {
                "if": { "$eq": [ "$$e", 2 ] },
                "then": "$classification.class_2",
                "else": "$classification.class_3"    
              }
            }    
          }
        }    
      }
    }    
  }},
  { "$unwind": "$max" },
  { "$group": {
    "_id": "$_id",
    "name": { "$first": "$name" },    
    "classification": { "$first": "$classification" },
    "max": { "$max": "$max" }
  }},
  { "$project": {
    "name": 1,
    "classification": {
      "$cond": {
        "if": { "$eq": [ "$classification.class_1", "$max" ] },
        "then": "class_1",
        "else": {
          "$cond": {
            "if": { "$eq": [ "$classification.class_2", "$max" ] },
            "then": "class_2",
            "else": "class_3"
          }
        }
      }
    }    
  }}
])

而且非常古老,那么我们可以$const来自db.collection.aggregate([ { "$project": { "name": 1, "classification": 1, "temp": { "$const": [1,2,3] } }}, { "$unwind": "$temp" }, { "$group": { "_id": "$_id", "name": { "$first": "$name" }, "classification": { "$first": "$classification" }, "max": { "$max": { "$cond": [ { "$eq": [ "$temp", 1 ] }, "$classification.class_1", { "$cond": [ { "$eq": [ "$temp", 2 ] }, "$classification.class_2", "$classification.class_3" ]} ] } } }}, { "$project": { "name": 1, "classification": { "$cond": [ { "$eq": [ "$max", "$classification.class_1" ] }, "class_1", { "$cond": [ { "$eq": [ "$max", "$classification.class_2" ] }, "class_2", "class_3" ]} ] } }} ]) ,这是(现在仍然是)隐藏的"在现代版本中,未记录的运算符与$unwind(在技术上别名)的函数相同,但也使用$literal的替代语法作为"数组"由于存在聚合框架,因此三元运算与所有版本兼容:

{{1}}

但它当然是可能的,即使非常混乱。

答案 1 :(得分:1)

您可以使用$indexOfArray运算符查找$max中的classification值,然后投射密钥。 $objectToArrayclassification嵌入式文档转换为3.4.4版本中的键值对数组。

db.collection.aggregate([
    {
      "$addFields": {
        "classification": {
          "$let": {
            "vars": {
              "classificationkv": {
                "$objectToArray": "$classification"
              }
            },
            "in": {
              "$let": {
                "vars": {
                  "classificationmax": {
                    "$arrayElemAt": [
                      "$$classificationkv",
                      {
                        "$indexOfArray": [
                          "$$classificationkv.v",
                          {
                            "$max": "$$classificationkv.v"
                          }
                        ]
                      }
                    ]
                  }
                },
                "in": "$$classificationmax.k"
              }
            }
          }
        }
      }
    }
])

答案 2 :(得分:0)

最后,我选择了一个更简单的解决方案,但不像其他发布的那样通用。我用这个开关案例声明:

{'$project': {'_id': 1, 'name': 1,
              'classification': {'$switch': {
                  'branches': [
                      {'case': {'$and': [{'$gt': ['$classification.class_1', '$classification.class_2']},
                                         {'$gt': ['$classification.class_1', '$classification.class_3']}]},
                           'then': "class1"},
                      {'case': {'$and': [{'$gt': ['$classification.class_2', '$classification.class_1']},
                                         {'$gt': ['$classification.class_2', '$classification.class_3']}]},
                           'then': "class_2"},
                      {'case': {'$and': [{'$gt': ['$classification.class_3', '$classification.class_1']},
                                         {'$gt': ['$classification.class_3', '$classification.class_2']}]},
                           'then': "class_3"}],
                      'default': ''}}
                  }}

这对我有用,但其他答案可能是更好的选择,YMMV。