我对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
}
}
我希望每位玩家都能赢得比赛,输球和赢/输比率。我是一个新的聚合函数,无法实现某些功能。有人能指出我正确的方向吗?
答案 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
。所以同样的原则在这里发挥作用:
方法中唯一真正的区别在于,我们将在这里的每个游戏的“不同列表”将“在”拉开映射数组之后,而是每个“游戏/玩家”组合返回一个文档:< / 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
}