1

我只是在学习 mongodb 聚合框架
有如下格式的数据:

{
  "questionType": "multiple",
  "multipleOptions": ["first", "second", "third", "forth"],
  "answers": ["first", "second", "second", "first", "first", "forth"]
},
{
  "questionType": "multiple",
  "multipleOptions": ["awful", "bad", "soso", "good", "excellent"],
  "answers": ["bad", "bad", "good", "soso", "bad", "excellent", "awful", "soso"]
}

我想将这些聚合成这样的东西:

{
  "result": { "first": 3, "second": 2, "forth": 1 }
},
{
  "result": { "awful": 1, "bad": 3, "soso": 2, "good": 1, "excellent": 1 }
}

或者像这样(没有区别):

{
  "result": [["first", 3], ["second", 2], ["forth", 1]]
},
{
  "result": [["awful", 1], ["bad", 3], ["soso", 2], ["good", 1], ["excellent", 1]]
}

有没有办法在一个$project阶段做到这一点?

4

2 回答 2

1

这可以通过一组数组运算符一起工作以产生所需的效果来完成。

您基本上需要一个操作来创建您需要的计数的键/值对数组。然后将其转换为哈希映射。键值对数组本质上是一个映射,它是通过遍历multipleOptions数组并检查答案数组中匹配的元素的大小来构造的。

TLDR;

您需要运行的最终管道如下:

db.collection.aggregate([
    { "$project": { 
       "result":  {
           "$arrayToObject": {
               "$map": {
                    "input": { "$range": [ 0, { "$size": "$multipleOptions" } ] },
                    "as": "idx",
                    "in": {
                        "$let": {
                            "vars": {
                                "k": { 
                                    "$arrayElemAt": [ 
                                        "$multipleOptions", 
                                        "$$idx" 
                                    ] 
                                },
                                "v": { 
                                    "$size": {
                                        "$filter": {
                                            "input": "$answers",
                                            "as": "ans",
                                            "cond": {
                                                "$eq": [
                                                    "$$ans",
                                                    { 
                                                        "$arrayElemAt": [ 
                                                            "$multipleOptions", 
                                                            "$$idx" 
                                                        ] 
                                                    }
                                                ]
                                            }
                                        }
                                    }
                                }
                            },
                            "in": {  "k": "$$k", "v": "$$v" }
                        }
                    }
               }
           }
       }
    } }
])

为了逐步演示这一点,让我们在聚合操作中创建一个附加字段,该字段将是相应数组元素的计数的数组。我们需要类似的东西

{
    "questionType": "multiple",
    "multipleOptions": ["awful", "bad", "soso", "good", "excellent"],
    "answersCount": [1, 3, 2, 1, 1],
    "answers": ["bad", "bad", "good", "soso", "bad", "excellent", "awful", "soso"]
}

为了得到这个,我们需要一种方法来循环multipleOptions遍历每个选项,迭代answers数组,过滤它并计算过滤数组中元素的数量。伪算法如下:

answersCount = []
for each elem in ["awful", "bad", "soso", "good", "excellent"]:
    filteredAnswers = [<answers array containing only elem>]
    count = filteredAnswers.length 
    answersCount.push(count)

在 mongo 中,过滤部分可以$filteranswers数组上完成,元素可以用$arrayElemAt

{
    "$filter": {
        "input": "$answers",
        "as": "ans",
        "cond": {
            "$eq": [
                "$$ans",
                { "$arrayElemAt": [ "$multipleOptions", "$$idx" ] }
            ]
        }
    }
}

$size使用上述表达式得出计数

{ 
    "$size": {
        "$filter": {
            "input": "$answers",
            "as": "ans",
            "cond": {
                "$eq": [
                    "$$ans",
                    { "$arrayElemAt": [ "$multipleOptions", "$$idx" ] }
                ]
            }
        }
    }
}

为了获得外循环,我们可以使用$rangeand $mapas

{
    "$map": {
        "input": { "$range": [ 0, { "$size": "$multipleOptions" } ] },
        "as": "idx",
        "in": {
            "$let": {
                "vars": {
                    "v": { 
                        "$size": {
                            "$filter": {
                                "input": "$answers",
                                "as": "ans",
                                "cond": {
                                    "$eq": [
                                        "$$ans",
                                        { "$arrayElemAt": [ "$multipleOptions", "$$idx" ] }
                                    ]
                                }
                            }
                        }
                    }
                },
                "in": "$$v"
            }
        }
    }
}

这将answersCount在以下聚合操作中产生

db.collection.aggregate([
    { "$addFields": { 
       "answersCount": {
            "$map": {
                "input": { "$range": [ 0, { "$size": "$multipleOptions" } ] },
                "as": "idx",
                "in": {
                    "$let": {
                        "vars": {
                            "v": { 
                                "$size": {
                                    "$filter": {
                                        "input": "$answers",
                                        "as": "ans",
                                        "cond": {
                                            "$eq": [
                                                "$$ans",
                                                { "$arrayElemAt": [ "$multipleOptions", "$$idx" ] }
                                            ]
                                        }
                                    }
                                }
                            }
                        },
                        "in": "$$v" 
                    }
                }
            }
        }
    } }
])

然后要获得所需的输出,您需要answersCount成为键/值对数组,即

{
    "answersCount": [
        { "k": "awful", "v": 1},
        { "k": "bad", "v": 3},
        { "k": "soso", "v": 2},
        { "k": "good", "v": 1},
        { "k": "excellent", "v": 1}
    ],
}

当你应用$arrayToObject上面的表达式时,即

{ "$arrayToObject": {
    "answersCount": [
        { "k": "awful", "v": 1},
        { "k": "bad", "v": 3},
        { "k": "soso", "v": 2},
        { "k": "good", "v": 1},
        { "k": "excellent", "v": 1}
    ],
} }

你得到

{
    "awful" : 1,
    "bad" : 3,
    "soso" : 2,
    "excellent" : 1,
    "good" : 1
}
于 2019-09-09T15:41:50.533 回答
1

这是“多阶段分组”的一个很好的用例。让我们从一个$unwind答案开始:

c = db.foo.aggregate([
{$unwind: "$answers"}
                      ]);
{
    "_id" : 0,
    "questionType" : "multiple",
    "multipleOptions" : [
        "first",
        "second",
        "third",
        "forth"
    ],
    "answers" : "first"
}
{
    "_id" : 0,
    "questionType" : "multiple",
    "multipleOptions" : [
        "first",
        "second",
        "third",
        "forth"
    ],
    "answers" : "second"
}
{
    "_id" : 0,
    "questionType" : "multiple",
    "multipleOptions" : [
        "first",
        "second",
        "third",
        "forth"
    ],
    "answers" : "second"
}
// ...

现在我们已经准备好分组了answers_id

db.foo.aggregate([
{$unwind: "$answers"}
,{$group: {_id: {Xid:"$_id", answer:"$answers"}, n:{$sum:1} }}
]);
{ "_id" : { "Xid" : 1, "answer" : "awful" }, "n" : 1 }
{ "_id" : { "Xid" : 1, "answer" : "excellent" }, "n" : 1 }
{ "_id" : { "Xid" : 1, "answer" : "soso" }, "n" : 2 }
{ "_id" : { "Xid" : 1, "answer" : "bad" }, "n" : 3 }
{ "_id" : { "Xid" : 0, "answer" : "forth" }, "n" : 1 }

现在我们再次分组,这次由_id.Xid然后使用$push来构造结果的输出数组:

db.foo.aggregate([
{$unwind: "$answers"}
,{$group: {_id: {Xid:"$_id", answer:"$answers"}, n:{$sum:1} }}
,{$group: {_id: "$_id.Xid", result: {$push: {answer: "$_id.answer", n: "$n" }} }}
]);
{
    "_id" : 0,
    "result" : [
        {
            "answer" : "forth",
            "n" : 1
        },
        {
            "answer" : "second",
            "n" : 2
        },
        {
            "answer" : "first",
            "n" : 3
        }
    ]
}
{
    "_id" : 1,
    "result" : [
        {
            "answer" : "awful",
            "n" : 1
        },
        {
            "answer" : "excellent",
            "n" : 1
        },
        {
            "answer" : "soso",
            "n" : 2
        },
        {
            "answer" : "bad",
            "n" : 3
        },
        {
            "answer" : "good",
            "n" : 1
        }
    ]
}

所以在精神上我们有一个解决方案,但要真正强调这一点,我们将使用该$arrayToObject函数将选项数组从键的值转换为answer它们自己的键。为此,我们将$push对象命名为 argskv正确驱动函数:

db.foo.aggregate([
{$unwind: "$answers"}
,{$group: {_id: {Xid:"$_id", answer:"$answers"}, n:{$sum:1} }}
,{$group: {_id: "$_id.Xid", QQ: {$push: {k: "$_id.answer", v: "$n" }} }}
,{$project: {_id: true, result: {$arrayToObject: "$QQ"} }}
                      ]);

产生:

{ "_id" : 0, "result" : { "forth" : 1, "second" : 2, "first" : 3 } }
{
    "_id" : 1,
    "result" : {
        "awful" : 1,
        "excellent" : 1,
        "soso" : 2,
        "bad" : 3,
        "good" : 1
    }
}
于 2019-09-09T20:47:32.680 回答