代码之家  ›  专栏  ›  技术社区  ›  dorriz

从包含对象的数组中使用MongoDB聚合获取平均值

  •  2
  • dorriz  · 技术社区  · 7 年前

    我很难从使用MongoDB聚合管道方法在测试中回答问题的所有学生那里得到分数。

    我的聚合管道提供了一个对象数组,由每个学生回答测试问题组成。

    管道将类似于下面的管道,我的示例是根据实际问题简化的。基本上,我将每个用户的每个问题数组分组并推送到“分数”字段中。然后我用reduce将分数场变平

    { $group: { 
        _id: {}, 
        scores: { $push: "$questions" }
    } }, 
    { $addFields: { 
        testScores: {
            $reduce: {
                input: "$scores",
                initialValue: [ ],
                in: { $concatArrays: [ "$$value", "$$this" ] }
            }
        } 
    } }
    

    结果如下:

    testScores: [
        { id: 'questionOne' score: 1 }, 
        { id: 'questionOne', score: 3 }, 
        { id: 'questionOne', score: 8 }, 
        { id: 'questionOne' score: 2 },
        ....
        { id: 'questionFifty' score: 1 }, 
        { id: 'questionFifty', score: 3 }, 
        { id: 'questionFifty', score: 8 }, 
        { id: 'questionFifty' score: 2 }
    ]
    

    我的问题是,我如何获得“问题一”和所有其他问题的平均分数? 我无法展开数组,因为我有大量测试,而且似乎MongoDB无法在不返回聚合结果空值的情况下展开足够数量的数组。

    在javascript中,我会使用reduce,但据我所知,mongodb允许在reduce函数之外使用var,但据我所知,您无法修改reduce函数,因此不可能使用与下面的函数类似的函数。

    myArray.reduce((acc, next){
        if(acc[next.id]}{
           acc[next.id].score += next.score
           acc[next.id].count+= 1
           acc[next].avg = acc[next.id].score/acc[next.id].count
        }else{
            acc[next.id].score = next.score
            acc[next.id].count = 1
        }
     return acc
    },{} }
    

    谢谢你的指点

    1 回复  |  直到 7 年前
        1
  •  0
  •   mickl    7 年前

    是的,有可能 $reduce 但有两个重要的警告:

    • 里面 $let 当你定义 vars 第节您只能引用在外部作用域中定义的变量,但不能定义多个变量并在同一块中相互引用-这就是为什么这个解决方案中必须有大量嵌套的原因。
    • $reduce 暴露 $$value 表示聚合当前状态的变量。问题是你应该把这个变量当作 不变的 这意味着你可以引用它,但不能修改它

    然后您可以尝试以下聚合:

    db.col.aggregate([
        {
            $project: {
                averages: {
                    $reduce: {
                        input: "$testScores",
                        initialValue: [],
                        in: {
                            $let: {
                                vars: {
                                    index: { $indexOfArray: [ "$$value.id", "$$this.id" ] }
                                },
                                in: {
                                    $let: {
                                        vars: { 
                                            prev: { 
                                                $cond: [ { $ne: [ "$$index", -1 ] }, { $arrayElemAt: [ "$$value", "$$index" ] }, { id: "$$this.id", score: 0, count: 0 } ] 
                                            } 
                                        },
                                        in: {
                                            $let: {
                                                vars: {
                                                    updated: {
                                                        id: "$$prev.id",
                                                        score: { $add: [ "$$prev.score", "$$this.score" ] },
                                                        count: { $add: [ "$$prev.count", 1 ] },
                                                        avg: {
                                                            $divide: [ { $add: [ "$$prev.score", "$$this.score" ] }, { $add: [ "$$prev.count", 1 ] } ]
                                                        }
                                                    }
                                                },
                                                in: {
                                                    $cond: {
                                                        if: { $eq: [ "$$index", -1 ] },
                                                        then: { $concatArrays: [ "$$value", [ "$$updated" ] ] },
                                                        else: { $concatArrays: [ { $slice: [ "$$value", "$$index"] }, [ "$$updated" ], { $slice: [ "$$value", { $add: [ "$$index", 1 ] }, { $size: "$$value" }] } ] }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    ])
    

    其实每个 $let 在此定义算法步骤:

    • 使用 $arrayElemAt 检查当前处理的文档是否有聚合
    • 使用 $cond 阿雷亚米拉特 获取当前处理的上一个值 id 或提供默认值
    • 然后在 updated 我们计算新值,包括 average

    要返回值,我们应该考虑两种情况:

    {
        "averages" : [
                {
                        "id" : "questionOne",
                        "score" : 14,
                        "count" : 4,
                        "avg" : 3.5
                },
                {
                        "id" : "questionFifty",
                        "score" : 14,
                        "count" : 4,
                        "avg" : 3.5
                }
        ]
    }