mongodb - mongoDB Aggregation:基于数组名称的总和

标签 mongodb aggregation-framework

我有以下匹配数据:

{
  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
  }
]

我的问题是胜负信息只存在于数组的名称中。

最佳答案

这里面有很多,特别是如果您对使用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
        ]}}
    }}
])

摘要
这需要假设每个“赢”和“输”数组中的“玩家”都是唯一的。对于这里的模型来说,这似乎是合理的:
展开两个阵列。这将创建重复项,但稍后将删除它们。
在投影时,为了得到一些文字字符串值,需要使用$cond运算符(三值)。最后一个用法是特殊的,因为正在添加和数组。因此,在投影后,阵列将再次展开。更多的重复,但这就是重点。一个“赢”,一个“输”的记录。
使用$cond运算符和$eq运算符的更多投影。这次我们把两个字段合并成一个。因此,当字段的“type”与“score”中的值匹配时,该“key field”将用于“result”字段值。结果是两个不同的“win”和“loss”字段现在共享相同的名称,由“type”标识。
删除每个文档中的重复项。只需按文档_id和“结果”字段作为键进行分组。现在应该有与原始文档中相同的“win”和“loss”记录,只是形式不同,因为它们是从数组中删除的。
最后对所有文档进行分组,得到每个“播放器”的总数。更多使用$cond$eq但这次要确定当前文档是“赢”还是“输”。所以在匹配的地方我们返回1,在错误的地方我们返回0。这些值被传递到$sum以获得“赢”和“输”的总数。
这就解释了如何得到结果。
从文档中了解有关aggregation operators的更多信息。列表中$cond的一些“有趣”用法应该可以用$literal运算符替换。但在2.6及以上版本发布之前,这是不可用的。
MongoDB 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日期就可以轻松地进行聚合。你也将能够轻松地与其他时间间隔工作。
因此,如果你确定了日期,并将其设为开始日期,并将“持续时间”替换为结束时间,那么你就可以保留一些可以通过简单的数学计算得到“持续时间”的东西+你可以通过将这些作为日期值来获得很多额外的好处。
所以这可能会给你一些思考你的模式的食物。
对于那些感兴趣的人,下面是一些我用来生成工作数据集的代码:
// 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);
}

关于mongodb - mongoDB Aggregation:基于数组名称的总和,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22301716/

相关文章:

javascript - 如何在 mongodb-native findAndModify 中使用变量作为字段名?

spring - 请使用 'MongoMappingContext#setAutoIndexCreation(boolean)' 或覆盖 'MongoConfigurationSupport#autoIndexCreation()' 以明确

linux - 如何使用配置文件在 Linux 上作为服务启动

mongodb - 由于 DataDog/zstd,Gcloud 应用程序部署因 mongo-driver 失败

mongodb - mongodb中单个查询的多个计数

mongodb - Mongo聚合,按2个不同数组中的相同字段分组

mongodb - 如何将数字 1 格式化为字符串 "01"以进行聚合?

使用 Deno 或 Rust 的 MongoDB Schema

php - 如何在 phalcon 中将 "group by"、 "having"查询从 mysql 转换为 mongodb

MongoDB $redact 过滤掉数组的一些元素