我有以下匹配数据:
{
date: 20140101,
duration: 23232,
win:[
{
player: "Player1",
score : 2344324
},
{
player: "Player4",
score : 23132
}
],
loss:[
{
player: "Player2",
score : 324
},
{
player: "Player3",
score : 232
}
]
}
现在我想计算所有这些球员的输赢:
result :
[
{
player : "Player1",
wins : 12,
losses : 2
},
{
player : "Player2",
wins : 7,
losses : 8
}
]
我的问题是赢/输信息只存在于数组的名称中。
答案 0 :(得分:2)
这里有很多内容,特别是如果您使用aggregate相对较新,但可以完成。我会解释上市后的各个阶段:
db.collection.aggregate([
// 1. Unwind both arrays
{"$unwind": "$win"},
{"$unwind": "$loss"},
// 2. Cast each field with a type and the array on the end
{"$project":{
"win.player": "$win.player",
"win.type": {"$cond":[1,"win",0]},
"loss.player": "$loss.player",
"loss.type": {"$cond": [1,"loss",0]},
"score": {"$cond":[1,["win", "loss"],0]}
}},
// Unwind the "score" array
{"$unwind": "$score"},
// 3. Reshape to "result" based on the value of "score"
{"$project": {
"result.player": {"$cond": [
{"$eq": ["$win.type","$score"]},
"$win.player",
"$loss.player"
] },
"result.type": {"$cond": [
{"$eq":["$win.type", "$score"]},
"$win.type",
"$loss.type"
]}
}},
// 4. Get all unique result within each document
{"$group": { "_id": { "_id":"$_id", "result": "$result" } }},
// 5. Sum wins and losses across documents
{"$group": {
"_id": "$_id.result.player",
"wins": {"$sum": {"$cond": [
{"$eq":["$_id.result.type","win"]},1,0
]}},
"losses": {"$sum":{"$cond": [
{"$eq":["$_id.result.type","loss"]},1,0
]}}
}}
])
这确实假设每个“win”和“loss”数组中的“玩家”都是独一无二的。对于似乎在这里建模的内容,这似乎是合理的:
展开两个阵列。这会创建重复项,但稍后会将其删除。
投影时,$cond运算符(三元)的一些用法是为了得到一些文字字符串值。最后一个用法是特殊的,因为正在添加数组。因此,在预测该阵列将再次解开之后。更多重复,但这就是重点。一个“胜利”,一个“损失”记录。
使用$cond运算符进行更多投影,并使用$eq运算符。这次我们将两个字段合并合而为一。因此,使用此项,当字段的“类型”与“得分”中的值匹配时,则“关键字段”用于“结果”字段值。结果是两个不同的“胜利”和“损失”字段现在共享相同的名称,由“类型”标识。
删除每个文档中的重复项。只需按文档_id
和“结果”字段分组即可。现在应该有与原始文档中相同的“获胜”和“丢失”记录,只是以不同的形式从阵列中删除它们。
最后将所有文档分组以获得每个“玩家”的总数。 $cond和$eq的更多用法,但这一次是为了确定当前文档是“赢”还是“丢失”。所以匹配的地方我们返回1而false返回0.这些值传递给$sum以获得“胜利”和“损失”的总计数。
这解释了如何获得结果。
从文档中了解有关aggregation operators的更多信息。该列表中$cond的某些“有趣”用法应该可以替换为$literal运算符。但是在版本2.6及更高版本发布之前,这将无法使用。
当然在撰写本文时即将发布的版本中有一个新的set operators,这将有助于简化这一点:
db.collection.aggregate([
{ "$unwind": "$win" },
{ "$project": {
"win.player": "$win.player",
"win.type": { "$literal": "win" },
"loss": 1,
}},
{ "$group": {
"_id" : {
"_id": "$_id",
"loss": "$loss"
},
"win": { "$push": "$win" }
}},
{ "$unwind": "$_id.loss" },
{ "$project": {
"loss.player": "$_id.loss.player",
"loss.type": { "$literal": "loss" },
"win": 1,
}},
{ "$group": {
"_id" : {
"_id": "$_id._id",
"win": "$win"
},
"loss": { "$push": "$loss" }
}},
{ "$project": {
"_id": "$_id._id",
"results": { "$setUnion": [ "$_id.win", "$loss" ] }
}},
{ "$unwind": "$results" },
{ "$group": {
"_id": "$results.player",
"wins": {"$sum": {"$cond": [
{"$eq":["$results.type","win"]},1,0
]}},
"losses": {"$sum":{"$cond": [
{"$eq":["$results.type","loss"]},1,0
]}}
}}
])
但“简化”是值得商榷的。对我而言,这只是“感觉”就像是“喋喋不休”并做更多的工作。它当然更传统,因为它只依赖于$setUnion 合并数组结果。
但是,通过稍微更改您的架构,“工作”将无效,如下所示:
{
"_id" : ObjectId("531ea2b1fcc997d5cc5cbbc9"),
"win": [
{
"player" : "Player2",
"type" : "win"
},
{
"player" : "Player4",
"type" : "win"
}
],
"loss" : [
{
"player" : "Player6",
"type" : "loss"
},
{
"player" : "Player5",
"type" : "loss"
},
]
}
这样就不需要像我们一样添加“type”属性来投射数组内容,并减少查询和完成的工作:
db.collection.aggregate([
{ "$project": {
"results": { "$setUnion": [ "$win", "$loss" ] }
}},
{ "$unwind": "$results" },
{ "$group": {
"_id": "$results.player",
"wins": {"$sum": {"$cond": [
{"$eq":["$results.type","win"]},1,0
]}},
"losses": {"$sum":{"$cond": [
{"$eq":["$results.type","loss"]},1,0
]}}
}}
])
当然只需更改您的架构如下:
{
"_id" : ObjectId("531ea2b1fcc997d5cc5cbbc9"),
"results" : [
{
"player" : "Player6",
"type" : "loss"
},
{
"player" : "Player5",
"type" : "loss"
},
{
"player" : "Player2",
"type" : "win"
},
{
"player" : "Player4",
"type" : "win"
}
]
}
这使得非常容易。这可以在2.6之前的版本中完成。所以你现在就可以做到:
db.collection.aggregate([
{ "$unwind": "$results" },
{ "$group": {
"_id": "$results.player",
"wins": {"$sum": {"$cond": [
{"$eq":["$results.type","win"]},1,0
]}},
"losses": {"$sum":{"$cond": [
{"$eq":["$results.type","loss"]},1,0
]}}
}}
])
对我来说,如果是我的应用程序,我希望上面显示的最后一种形式的模式而不是你拥有的模式。在提供的聚合操作中完成的所有工作(除了最后一个语句)都旨在获取现有的模式表单并将其操作为此表单,因此可以轻松运行简单聚合声明如上所示。
由于每个玩家都被“标记”了“赢/输”属性,无论如何,你总是可以随意离开你的“赢家/放松者”。
最后一件事。您的日期是一个字符串。我不喜欢那样。
可能有这样做的原因,但我没有看到。如果您需要按天进行分组,只需使用正确的BSON日期就可以轻松进行聚合。然后,您还可以轻松使用其他时间间隔。
因此,如果你修改了日期,并将其设为 start_date ,并用 end_time 替换了“持续时间”,那么你可以保留一些你可以获得的“持续时间“来自简单的数学+通过将这些作为日期值而获得大量额外的好处。
因此,您可以在架构上为您提供一些思考。
对于那些感兴趣的人,这里有一些我用来生成一组工作数据的代码:
// Ye-olde array shuffle
function shuffle(array) {
var m = array.length, t, i;
while (m) {
i = Math.floor(Math.random() * m--);
t = array[m];
array[m] = array[i];
array[i] = t;
}
return array;
}
for ( var l=0; l<10000; l++ ) {
var players = ["Player1","Player2","Player3","Player4"];
var playlist = shuffle(players);
for ( var x=0; x<playlist.length; x++ ) {
var obj = {
player: playlist[x],
score: Math.floor(Math.random() * (100000 - 50 + 1)) +50
};
playlist[x] = obj;
}
var rec = {
duration: Math.floor(Math.random() * (50000 - 15000 +1)) +15000,
date: new Date(),
win: playlist.slice(0,2),
loss: playlist.slice(2)
};
db.game.insert(rec);
}
答案 1 :(得分:1)
我怀疑这是否可以在一个查询中完成。这可以使用单独的查询来完成这样的胜负(对于胜利):
db.match.aggregate([{$unwind:"$win"}, {$group:{_id:"$win.player", wins:{$sum:1}}}])