如何使用MongoDB从聚合结果中投射其他数据?

时间:2015-11-03 09:19:25

标签: mongodb mongodb-query aggregation-framework

我正在学习MongoDB并尝试对一个集合进行分组。 我正在寻找的是按年份分组,获得最高"平均音符"字段并显示与此平均值相关的文档的字段主名称

例如,如果我有:

Name    | Average   | Year
Name_01 | 7.56      | 1995
Name_02 | 8.96      | 1995
Name_03 | 3.25      | 2005
Name_04 | 4.36      | 2005
Name_05 | 7.52      | 2020

我需要:

Name    | Average   | Year
Name_02 | 8.96      | 1995
Name_05 | 7.52      | 2020
Name_04 | 4.36      | 2005

我已经完成了小组和最大这是我的代码:

db.foobar.aggregate([
    {
        $group: { _id: '$year_published', max: { $max: '$statistics.average' }}
    },
    {
        $project: { _id: 1, max: 1 }
    }, 
    {
        $sort: { max: -1 }
    }    
])

这给了我这样的结果:

{
    "result" : [ 
        {
            "_id" : 1999,
            "max" : 8.0343000000000000
        }, 
        {
            "_id" : 1985,
            "max" : 7.8833299999999999
        }
        // An so on...
}

但我也想投影与" max"相关的文档的主要名称。得到类似的东西:

 {
    "result" : [ 
        {
            "_id" : 1999,
            "max" : 8.0343000000000000,
            "name": "Foo Bar"
        }, 
        {
            "_id" : 1985,
            "max" : 7.8833299999999999,
            "name": "Lorem Ipsum"
        }
        // An so on...
}

注意:问题的下一部分会增加名称的复杂性(因为我的文档结构)。这不是我现在主要关注的问题,但我将其添加到问题中以反映我的所有问题。

主要名称有点棘手。对于每个文档,我都有类似的对象数组:

{
    "names" : [ 
        {
            "type" : "primary",
            "value" : "Foo bar"
        }, 
        {
            "type" : "alternate",
            "value" : "Foo foo"
        }, 
        {
            "type" : "alternate",
            "value" : "Bar bar"
        }
    ]
}

而我想要获得的是" primary"类型(即。" Foo bar"在我的例子中)。

这是我的文件结构:

{
    "_id" : ObjectId("56338f2bdc99b8ec22a43328"),
    "names" : [ 
        {
            "type" : "primary",
            "value" : "Foo bar"
        },
        {
            "type" : "alternate",
            "value" : "Barr foo"
        }
    ],
    "year_published" : 1992
    "statistics" : {
        "average" : 6.6057699999999997
    }
}

我想我不是到目前为止,但我不知道该怎么做......你能帮助我吗?

2 个答案:

答案 0 :(得分:3)

如果你想要" paried"使用" max"的特定文档中的值那么值$max不适合你。相反,您需要先执行$sort数据,然后使用$first运算符。

db.foobar.aggregate([
    { "$sort":  { "year_published": 1, "statistics.average": -1 } },
    { "$group": { 
        "_id": "$year_published", 
        "max": { "$first": "$statistics.average" }},
        "name": { 
            "$first": {
                "$setDifference": [
                    { "$map": {
                        "input": "$names",
                        "as": "name",
                        "in": {
                            "$cond": {
                                "if": { "$eq": [ "$$name.type", "primary" ] },
                                "then": "$$name.value",
                                "else": false
                            }
                        }
                    }},
                    [false]
                ]
            }
        }
    }},
    { "$unwind": "$name" }
])

$first$last运算符作用于"分组边界"数据。这意味着它们从属性中返回数据,该数据发生在用于分组_id的值的开头或结尾处。

这就是你"排序"首先,所以文件是为了选择。

相比之下$max$min只需选择" max / min"来自样本文档中任何位置的值。如果你想要的话,这很好,但如果你想要"相关"字段,然后您必须排序。

这是它的基础知识。如图所示,使用$map$setDifference组合最佳地完成了处理数组过滤的另一部分。 $map允许通过$cond在每个数组元素" in-line"上测试条件,并根据哪个值为true或false返回值。结果当然仍然是一个相等长度的数组。

$setDifference基本上过滤了以false返回的所有内容,因此唯一剩下的应该是" primary"。仍然是一个数组,这就是$unwind仍在使用的原因,尽管它只是一个元素数组。

未来的MongoDB版本可以通过$filter$arrayElemAt更好地完成此操作。这是一瞥:

db.foobar.aggregate([
    { "$sort":  { "year_published": 1, "statistics.average": -1 } },
    { "$group": { 
        "_id": "$year_published", 
        "max": { "$first": "$statistics.average" }},
        "name": { 
            "$first": {
                "$arrayElemAt": [
                    { "$filter": {
                        "input": "$names",
                        "as": "name",
                        "cond": {
                            "$eq": [ "$$name.type", "primary" ]
                        }
                    }},
                    0
                ]
            }
        }
    }}
])

但这些都没有改变"排序第一"的基本规则。然后只需从分组边界中选取值。

答案 1 :(得分:1)

请尝试以下代码: 您需要在$First的帮助下选择组管道操作中的“名称”。

$First选择将表达式应用于按键共享同一组的一组文档中的第一个文档所产生的值。

db.foobar.aggregate([ 
{ "$unwind" : "$names" },
{ $match :
           { "$names.type" : "primary"}
} ,
{ $sort : 
          { "year_published" : 1, "statistics.average" : -1 } 
},
{ $group : 
          { _id :  "$year_published" , 
            name : {
                     $first : "$names.value" 
                   }, 
            max: { $max: "$statistics.average" } 
          }
},
{ $sort: 
        { max: -1 } 
}  
]).pretty();

这将为您提供所需的结果:

{
    "result" : [ 
        {
            "_id" : 1999,
            "max" : 8.0343000000000000,
            "name": "Foo Bar"
        }, 
        {
            "_id" : 1985,
            "max" : 7.8833299999999999,
            "name": "Lorem Ipsum"
        }
        // An so on...
}