聚合两个数组的结果

时间:2016-02-21 19:47:25

标签: mongodb mongodb-query aggregation-framework

我对MongoDB相当新,我试图在" Matches"上汇总一些统计信息。看起来像这样的集合:

{
    team1: {
        players: ["player1", "player2"],
        score: 10
    },
    team2: {
        players: ["player3", "player4"],
        score: 5
    }
},
{
    team1: {
        players: ["player1", "player3"],
        score: 15
    },
    team2: {
        players: ["player2", "player4"],
        score: 21
    }
},
{
    team1: {
        players: ["player4", "player1"],
        score: 21
    },
    team2: {
        players: ["player3", "player2"],
        score: 9
    }
},
{
    team1: {
        players: ["player1"],
        score: 5
    },
    team2: {
        players: ["player3"],
        score: 10
    }
}

我希望每位玩家都能赢得比赛,输球和赢/输比率。我是一个新的聚合函数,无法实现某些功能。有人能指出我正确的方向吗?

2 个答案:

答案 0 :(得分:3)

处理结构中的多个数组并不是一个简单的聚合任务,特别是当你的结果真的想要考虑两个数组的组合时。

幸运的是,有一些操作和/或技术可以在这里提供帮助,以及每个游戏包含每组/匹配和结果的“一组”独特球员的事实。

最简化的方法是使用MongoDB 2.6及更高版本的功能将阵列有效地“组合”到一个阵列中进行处理:

db.league.aggregate([
    { "$project": {
        "players": {
            "$concatArrays": [
                { "$map": {
                    "input": "$team1.players",
                    "as": "el",
                    "in": { 
                        "player": "$$el", 
                        "win": {
                            "$cond": {
                                "if": { "$gt": [ "$team1.score", "$team2.score" ] },
                                "then": 1,
                                "else": 0
                            }
                        },
                        "loss": {
                            "$cond": {
                                "if": { "$lt": [ "$team1.score", "$team2.score" ] },
                                "then": 1,
                                "else": 0
                            }
                        }
                    }
                }},
                { "$map": {
                    "input": "$team2.players",
                    "as": "el",
                    "in": { 
                        "player": "$$el", 
                        "win": {
                            "$cond": {
                                "if": { "$gt": [ "$team2.score", "$team1.score" ] },
                                "then": 1,
                                "else": 0
                            }
                        },
                        "loss": {
                            "$cond": {
                                "if": { "$lt": [ "$team2.score", "$team1.score" ] },
                                "then": 1,
                                "else": 0
                            }
                        }
                    }
                }}
            ]
        }
    }},
    { "$unwind": "$players" },
    { "$group": {
        "_id": "$players.player",
        "win": { "$sum": "$players.win" },
        "loss": { "$sum": "$players.loss" }
    }},
    { "$project": {
        "win": 1,
        "loss": 1,
        "ratio": { "$divide": [ "$win", "$loss" ] }
    }},
    { "$sort": { "_id": 1 } }
])

此列表正在使用来自MongoDB 3.2的$concatArrays,但考虑到每个游戏的玩家列表是“唯一的”并且因此是“设置”,该实际操作符可以很容易地被$setUnion替换。基于内部操作的输出,任何一个操作符基本上都是将一个数组与另一个数组连接。

对于那些内部操作,我们使用$map,它在线处理每个数组(“team1 / team2”),并且只为每个玩家计算游戏结果是否为“赢/输” 。这使得以下阶段的工作变得更容易。

虽然MongoDB的3.2和2.6版本都引入了运算符以便更容易地使用数组,但是一般原则可以回溯到如果你想在数组中“聚合”数据,那么你可以使用$unwind进行处理第一。这会从之前的映射中公开每个游戏中的每个“玩家”数据。

现在只需使用$group将每个玩家的结果汇总在一起,每个总字段都会$sum。为了获得总计结果的“比率”,请使用$project处理结果值之间的$divide,然后选择$sort每个玩家的结果键。

旧解决方案

在MongoDB 2.6之前,处理数组的唯一真正工具是$unwind。所以同样的原则在这里发挥作用:

  1. 将每个数组“映射”为“赢/输”。
  2. 将每场比赛的内容合并为一个“不同的列表”
  3. 根据常见的“玩家”字段汇总内容
  4. 方法中唯一真正的区别在于,我们将在这里的每个游戏的“不同列表”将“在”拉开映射数组之后,而是每个“游戏/玩家”组合返回一个文档:< / p>

    db.league.aggregate([
        { "$unwind": "$team1.players" },
        { "$group": {
            "_id": "$_id",
            "team1": {
                "$push": {
                    "player": "$team1.players",
                    "win": { 
                        "$cond": [
                            { "$gt": [ "$team1.score", "$team2.score" ] },
                            1,
                            0
                        ]
                    },
                    "loss": { 
                        "$cond": [
                            { "$lt": [ "$team1.score", "$team2.score" ] },
                            1,
                            0
                        ]
                    }
                }
            },
            "team1Score": { "$first": "$team1.score" },
            "team2": { "$first": "$team2" }
        }},
        { "$unwind": "$team2.players" },
        { "$group": {
            "_id": "$_id",
            "team1": { "$first": "$team1" },
            "team2": {
                "$push": {
                    "player": "$team2.players",
                    "win": { 
                        "$cond": [
                            { "$gt": [ "$team2.score", "$team1Score" ] },
                            1,
                            0
                        ]
                    },
                    "loss": { 
                        "$cond": [
                            { "$lt": [ "$team2.score", "$team1Score" ] },
                            1,
                            0
                        ]
                    }
                }
            },
            "type": { "$first": { "$const": ["A","B" ] } }
        }},
        { "$unwind": "$team1" },
        { "$unwind": "$team2" },
        { "$unwind": "$type" },
        { "$group": {
            "_id": {
                "_id": "$_id",
                "player": {
                    "$cond": [
                        { "$eq": [ "$type", "A" ] },
                        "$team1.player",
                        "$team2.player"
                    ]
                },
                "win": {
                    "$cond": [
                        { "$eq": [ "$type", "A" ] },
                        "$team1.win",
                        "$team2.win"
                    ]
                },
                "loss": {
                    "$cond": [
                        { "$eq": [ "$type", "A" ] },
                        "$team1.loss",
                        "$team2.loss"
                    ]
                }
            }
        }},
        { "$group": {
            "_id": "$_id.player",
            "win": { "$sum": "$_id.win" },
            "loss": { "$sum": "$_id.loss" }
        }},
        { "$project": {
            "win": 1,
            "loss": 1,
            "ratio": { "$divide": [ "$win", "$loss" ] }
        }},
        { "$sort": { "_id": 1 } }
    ])
    

    所以这是有趣的部分:

        { "$group": {
            "_id": {
                "_id": "$_id",
                "player": {
                    "$cond": [
                        { "$eq": [ "$type", "A" ] },
                        "$team1.player",
                        "$team2.player"
                    ]
                },
                "win": {
                    "$cond": [
                        { "$eq": [ "$type", "A" ] },
                        "$team1.win",
                        "$team2.win"
                    ]
                },
                "loss": {
                    "$cond": [
                        { "$eq": [ "$type", "A" ] },
                        "$team1.loss",
                        "$team2.loss"
                    ]
                }
            }
        }},
    

    这基本上消除了每个游戏中由于每个$unwind在不同阵列上产生的任何重复。当你$unwind一个数组时,你得到每个数组成员的整个文档的副本。如果你然后$unwind另一个包含数组,那么你刚刚“解开”的内容也会为每个数组成员再次“复制”。

    幸运的是,这很好,因为任何玩家每场比赛只列出一次,所以每个玩家每场比赛只有一组结果。编写该阶段的另一种方法是使用$addToSet处理到另一个数组:

        { "$group": {
            "_id": "$_id",
            "players": {
                "$addToSet": {
                    "$cond": [
                        { "$eq": [ "$type", "A" ] },
                        "$team1",
                        "$team2"
                    ]
                }
            }
        }},
        { "$unwind": "$players" }
    

    但由于这会产生另一个“数组”,因此将结果保留为单独的文档更为可取,而不是再次使用$unwind进行处理。

    因此,这真的是“将结果加入到一个不同的列表中”,在这种情况下,由于我们缺少将“team1”和“team2”同时“加入”的运算符,所以阵列被拉开然后有条件地“合并“取决于正在处理的当前”A“或”B“值。

    结束“加入”会查看许多“副本”的数据,但每个参与者基本上只有“每场比赛一个不同的玩家记录”,而且由于我们在“重复”发生之前计算了数字,那么首先要从每场比赛中挑选其中一个。

    同样的结果,由他们总结每个玩家并从总计算。

    结论

    所以你通常可以在这里得出结论,在任何一种情况下,所涉及的大部分工作都是为了将​​这两个数据阵列组合成一个阵列,或者实际上是每个玩家每个游戏的单个文档,以便进入简单的聚合总计。

    考虑到你需要从这些来源汇总总数,你可能会认为那个“那个”可能是比现在格式更好的数据结构。

    N.B $const运算符未记录,但自MongoDB 2.2引入聚合框架以来一直存在。它提供与$literal(在MongoDB 2.6中引入)完全相同的功能,实际上在代码库中“完全”相同,新的定义只是指向较旧的定义。

    它在列表中使用,因为预期的MongoDB目标(前2.6)不会有$literal,而另一个列表适用于MongoDB 2.6及更高版本。当然应用$setUnion

答案 1 :(得分:0)

嗯,说实话,我不想在mongoldb中进行这种操作,因为它效率不高。但是,为了争论,您可以尝试:

注意:以下查询目标是MongoDB版本3.2

db.matches.aggregate([
{$project:{_id:1, teams:["$team1","$team2"], 
  tscore:{$max:["$team1.score","$team2.score"]}}},
{$unwind:"$teams"},
{$unwind:"$teams.players"},
{$project:{player:"$teams.players", 
  won:{$cond:[{$eq:["$teams.score","$tscore"]},1,0]},
  lost:{$cond:[{$lt:["$teams.score","$tscore"]},1,0]}}},
{$group:{_id:"$player", won:{$sum:"$won"}, lost:{$sum:"$lost"}}},
{$project:{_id:0, player:"$_id", won:1, lost:1, 
  ratio:{$cond:[{$eq:[0, "$lost"]},"$won", 
  {$divide:["$won","$lost"]}]}}}
])

并且它将从您的样本数据集输出以下内容:注意:我的数学在计算比率时可能是错误的,但是,这不是我们在这里看到的。我只是使用赢/输

{ 
    "won" : NumberInt(2), 
    "lost" : NumberInt(1), 
    "player" : "player4", 
    "ratio" : 2.0
}
{ 
    "won" : NumberInt(1), 
    "lost" : NumberInt(3), 
    "player" : "player3", 
    "ratio" : 0.3333333333333333
}
{ 
    "won" : NumberInt(2), 
    "lost" : NumberInt(1), 
    "player" : "player2", 
    "ratio" : 2.0
}
{ 
    "won" : NumberInt(2), 
    "lost" : NumberInt(2), 
    "player" : "player1", 
    "ratio" : 1.0
}