mongoDB 聚合:根据数组名称求和

2023-12-13

我有一场比赛的以下数据:

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

我的问题是赢/输信息仅存在于数组的名称中。


其中有很多内容,特别是如果您相对较新使用总计的, 但它can做完了。我将解释上市后的阶段:

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

Summary


这确实假设每个“胜利”和“失败”数组中的“玩家”都是唯一的。对于此处建模的内容来说,这似乎是合理的:

  1. 展开两个数组。这会创建重复项,但稍后将删除它们。

  2. 投影时有一些用法$cond运算符(三元)以获得一些文字字符串值。最后一个用法比较特殊,因为和数组是相加的。因此,在投影之后,该数组将再次展开。更多重复,但这就是重点。各有一次“胜”、一次“负”记录。

  3. 更多投影$cond运算符和使用$eq运营商也是如此。这次我们是merging将两个字段合二为一。因此,使用此功能,当字段的“类型”与“分数”中的值匹配时,“关键字段”将用于“结果”字段值。结果是两个不同的“赢”和“输”字段现在共享相同的名称,由“类型”标识。

  4. 删除每个文档中的重复项。简单地按文档分组_id和“结果”字段作为键。现在应该有与原始文档中相同的“赢”和“输”记录,只是形式不同,因为它们已从数组中删除。

  5. 最后对所有文档进行分组以获得每个“玩家”的总数。更多使用$cond and $eq但这一次要确定当前文档是“赢”还是“输”。因此,在匹配的地方我们返回 1,在 false 的地方我们返回 0。这些值被传递给$sum为了获得“胜利”和“失败”的总计数。

这解释了如何获得结果。

了解更多信息聚合运算符从文档中。一些“有趣”的用法$cond在该列表中应该可以替换为$literal操作员。但直到 2.6 及更高版本发布后才可用。


MongoDB 2.6 及更高版本的“简化”情况

当然还有新的集合运算符在撰写本文时即将发布的版本中,这将有助于在某种程度上简化这一点:

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 to merge数组结果。

但是,通过稍微更改您的架构,该“工作”就会失效,如下所示:

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

这使得事情very简单的。这可以在 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
        ]}}
    }}

])

因此,对我来说,如果这是我的应用程序,我希望使用上面显示的最后一种形式的模式,而不是您所拥有的模式。在提供的聚合操作中完成的所有工作(最后一个语句除外)都是为了采用现有的模式形式并将其操作为this形式,因此很容易运行如上所示的简单聚合语句。

由于每个玩家都被“标记”有“赢/输”属性,因此您始终可以谨慎地访问您的“赢家/输家”。

作为最后一件事。你的date是一个字符串。我不喜欢那样。

这样做可能是有原因的,但我不明白。如果您需要分组依据day只需使用适当的 BSON 日期即可在聚合中轻松完成此操作。然后,您还可以轻松地使用其他时间间隔。

所以如果你确定了日期并将其定为开始日期,并将“持续时间”替换为end_time,然后你就可以保留一些可以通过简单数学计算得到“持续时间”的东西+你会得到很多extra将它们作为日期值会带来好处。

所以这可能会给你一些关于你的模式的思考。


对于那些感兴趣的人,这里是我用来生成工作数据集的一些代码:

// 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);
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

mongoDB 聚合:根据数组名称求和 的相关文章

随机推荐

  • ASP.NET 返回多个变量进行查看

    我无法弄清楚如何将多个变量返回到视图 像这样的东西 我可以得到一点帮助吗 public ActionResult CheatSheet var var1 from ts in db thisdatabase select ts var va
  • 如何将日期时间分配给带有间隔和日期开始的高图表

    HI 在 highchart 中 有什么方法可以在 x 轴上给出时间 如下所示 1 通过开始时间2 给出时间点数组3 给出时间单位 例如 start time will set as pointStart Date UTC timeArr
  • TeamCity:使用工件的文件版本标记 VCS (Subversion)

    我想在 SVN 中创建一个带有文件版本的标签 标签 我已经通过获取构建生成的主要可执行文件的文件版本来重命名该工件 例如 MyInstaller 1 2 3 1 exe 现在我想在 SVN 中创建一个名为 tags 1 2 3 1 我找不到
  • 在多个文件中具有相同的“require”是否会增加运行时间

    所以我计划将我的函数分成单独的文件 然后将它们导入到一个单独的文件中index js然后成为主要出口国 所以我想知道是否有类似的东西var bcrypt require bcrypt 在我的几个文件中比仅在一个文件中慢 这是我计划在 ind
  • 在 Typescript 中实现 Bull 队列

    我尝试在 Typescript 和 NestJS 中实现 Bull 队列 我的代码 Injectable export class MailService constructor InjectQueue mail private reado
  • 设置短值 Java

    我正在 J2ME 中编写一些代码 我有一个带有方法的类setTableId Short tableId 现在当我尝试写作时setTableId 100 它给出了编译时错误 如何在不声明另一个短变量的情况下设置短值 设置时Long我可以使用的
  • Android 更新ListView

    我看过Android 如何更新当前显示的ListView项目 and http commonsware com Android excerpt pdf和Android文档 但我还是不明白 我的问题 使用处理程序 我尝试更新 Stock 数据
  • Matlab中预计算函数的缓存结果

    我有两个数组 x and y x是函数的输入 y是函数值 例如 x 1 2 3 4 5 6 7 8 9 10 y 3 6 2 4 1 6 7 0 1 8 两者的长度相同 假设我有另一个数组z含有 2 3 8 9 10 3 长度不等于x an
  • 找出所有可能的欧拉循环

    我已经实现了一种算法来查找无向图中给定起始顶点的欧拉循环 使用 DFS 并删除访问的边 但它总是只返回一条路径 如何修改算法以搜索顶点的所有可能的欧拉循环 这是相关代码 typedef int Graph 200 200 adjacency
  • 如何使用socket.io从节点连接到telnet服务器

    我可能没有输入正确的搜索词 但我似乎找不到允许我的节点应用程序启动与另一个 telnet 服务器 非节点 的 socket io 客户端连接的好例子 下面是我的节点应用程序尝试连接到 telnet 服务器 var ioc require s
  • 除了 COM 之外,还有更好的方法来远程控制 Excel 吗?

    我正在开发一个回归测试工具 该工具将验证大量的 Excel 电子表格 目前 我使用最新版本的 pywin32 产品通过 Python 脚本通过 COM 控制它们 不幸的是 COM 似乎有许多恼人的缺点 例如 最轻微的干扰似乎就能中断与 CO
  • 创建用于导航的 ViewModel

    我有一个带有多个视图的 MVC 4 应用程序 IE 产品 食谱 分销商和商店 每个视图都基于一个模型 让我们保持简单 假设我的所有控制器都传递一个类似的视图模型 看起来像我的 Product 操作 public ActionResult I
  • 从代码隐藏中将页面异步模式设置为 true

    是否可以在我的代码隐藏文件中设置页面指令的异步模式 我没有办法直接修改属性 并努力寻找一种在我的代码隐藏中实现此功能的方法 我尝试在我的 Page Load 方法中添加Page AsyncMode true 但它返回以下错误 由于其保护级别
  • 如何使用 # 作为 CoffeeScript hereregex 的一部分?

    我正在尝试匹配 jQuery Mobile URL 的哈希片段 如下所示 matches window location hash match we re interested in the hash fragment the path t
  • Python:Flask 的模拟补丁错误

    在编写 Python 方面 我完全是个新手 更不用说测试它了 这是我的 Flask 端点 blueprint route mailing finish
  • 如何更换|| (两个管道)来自带有 | 的字符串(一)管道

    我收到此标签内一些 json 格式图像的响应 xmlImageIds 57948916 57948917 57948918 57948919 57948920 57948921 57948 922 57948923 57948924 579
  • 复制构造函数需要调用依赖于对象的方法,但构造函数不能是虚拟的

    我有一个带有两个继承类的抽象基类 在这两个类中 我定义了一个由构造函数使用的虚拟方法 现在我需要创建一个复制构造函数 但我不能将复制构造函数声明为虚拟 但我希望其中的方法调用依赖于作为参数提供的对象的类型 我该如何解决这个问题 现在我使用基
  • 当有受保护的工作表时如何保持宏运行?

    我用密码保护了工作表 4 因为工作表 4 中的某些单元格不允许用户输入 密码是 1234 但是 我想运行我的宏 如果出现错误 单元格将自动突出显示 我的宏未运行并出错 因为我要突出显示的单元格位于受保护的工作表中 当我单击验证按钮时 如何使
  • 你能指定 std::getline 中什么不是分隔符吗?

    我希望它将任何非字母字符视为分隔符 我怎样才能做到这一点 你不能 默认分隔符是 n while std getline std cin str n is implicit 对于其他分隔符 请传递它们 while std getline st
  • mongoDB 聚合:根据数组名称求和

    我有一场比赛的以下数据 date 20140101 duration 23232 win player Player1 score 2344324 player Player4 score 23132 loss player Player2