草庐IT

mongodb - 在保留根字段的同时对子文档进行分组/计数

coder 2023-11-05 原文

在 mongodb 中,经过几个 $match 和 $project,我得到以下 2 个文件。我试图弄清楚如何将每个事件的每个组中每个团队的状态列表分组/计数在一起。简而言之,我需要知道每个州有多少支球队(0、1 或 2)。我从以下文件开始。

{ 
    "_id" : "event1", 
    "groups" : [
        {
            "_id" : "group1", 
            "wlActive" : true, 
            "teams" : [
                {"state" : NumberInt(2)}, 
                {"state" : NumberInt(2)}, 
                {"state" : NumberInt(1)}, 
                {"state" : NumberInt(1)}, 
                {"state" : NumberInt(1)}, 
                {"state" : NumberInt(0)}, 
                {"state" : NumberInt(0)} 
            ]
        }, 
        {
            "_id" : "group2", 
            "wlActive" : false, 
            "teams" : [
                {"state" : NumberInt(2)}, 
                {"state" : NumberInt(2)}, 
                {"state" : NumberInt(1)}, 
                {"state" : NumberInt(1)}, 
                {"state" : NumberInt(1)}, 
                {"state" : NumberInt(0)}, 
                {"state" : NumberInt(0)} 
            ]
        }
    ]
},
{ 
    "_id" : "event2", 
    "groups" : [
        {
            "_id" : "group3", 
            "wlActive" : true, 
            "teams" : [
                {"state" : NumberInt(2)}, 
                {"state" : NumberInt(2)}, 
                {"state" : NumberInt(1)}, 
                {"state" : NumberInt(1)}, 
                {"state" : NumberInt(1)}, 
                {"state" : NumberInt(0)}, 
                {"state" : NumberInt(0)} 
            ]
        }, 
        {
            "_id" : "group4",
            "wlActive" : false, 
            "teams" : [
                {"state" : NumberInt(2)}, 
                {"state" : NumberInt(2)}, 
                {"state" : NumberInt(1)}, 
                {"state" : NumberInt(1)}, 
                {"state" : NumberInt(1)}, 
                {"state" : NumberInt(0)}, 
                {"state" : NumberInt(0)} 
            ]
        }
    ]
}

我希望最终得到的结果是这样的:

{ 
    "_id" : "event1", 
    "groups" : [
        {
            "_id" : "group1", 
            "wlActive" : true, 
            "states":[
                {"state":NumberInt(2), count:2},
                {"state":NumberInt(1), count:3},
                {"state":NumberInt(0), count:2}
            }
        }, 
        {
            "_id" : "group2", 
            "wlActive" : false, 
            "states":[
                {"state":NumberInt(2), count:2},
                {"state":NumberInt(1), count:3},
                {"state":NumberInt(0), count:2}
            }
        }
    ]
},
{ 
    "_id" : "event2", 
    "groups" : [
        {
            "_id" : "group3", 
            "wlActive" : true, 
            "states":[
                {"state":NumberInt(2), count:2},
                {"state":NumberInt(1), count:3},
                {"state":NumberInt(0), count:2}
            }
        }, 
        {
            "_id" : "group4",
            "wlActive" : false, 
            "states":[
                {"state":NumberInt(2), count:2},
                {"state":NumberInt(1), count:3},
                {"state":NumberInt(0), count:2}
            }
        }
    ]
}

它不需要完全是这样,但只要我能得到每个团队状态的计数并为每个组保留字段,例如“wlActive”。我在这里看到过类似的例子,但我似乎无法解决这个问题。

最佳答案

您实际上可以使用 $addFields 来做到这一点或 $project

db.collection.aggregate([
  { "$addFields": {
    "groups": {
      "$map": {
        "input": "$groups",
        "in": {
          "$mergeObjects": [
            "$$this",
            { "teams": {
              "$reduce": {
                "input": "$$this.teams",
                "initialValue": [ ],
                "in": {
                  "$cond": {
                    "if": { 
                      "$ne": [ { "$indexOfArray":  ["$$value.state", "$$this.state"] }, -1 ]
                    },
                    "then": {
                      "$concatArrays": [
                        { "$filter": {
                          "input": "$$value",
                          "as": "v",
                          "cond": { "$ne": [ "$$v.state", "$$this.state" ]  }
                        }},
                        [{
                          "state": "$$this.state",
                          "count": { "$sum": [
                            { "$arrayElemAt": [
                              "$$value.count",
                              { "$indexOfArray": ["$$value.state", "$$this.state" ] }
                            ]},
                            1
                          ]}
                        }]
                      ]
                    },
                    "else": {
                      "$concatArrays": [
                        "$$value",
                        [{ "state": "$$this.state", "count": 1 }]
                      ]
                    }
                  }
                }
              }
            }}
          ]
        }
      }
    }
  }}
])

这很复杂,基本上使用 $reduce “内联”作为 $group 的替代品管道运营商。

$reduce是工作的主要部分,因为它迭代每个数组项“减少”到另一个数组,键上有“分组”总数。它通过 $indexOfArray 在当前缩减结果中查找 state 的值来实现这一点。 .当找不到某些东西时(返回 -1 ),它会通过 $concatArrays 附加到当前结果。新的 statecount1。这是 else 的情况。

找到(then 情况)时,我们通过 $filter 从结果数组中删除匹配的元素。并从 $indexOfArray 的匹配索引中连接一个新元素并使用 $arrayElemAt 提取值.这给出了使用 $sum 添加的匹配元素的当前 count为了将计数增加 1

当然,传统上您可以使用 $unwind 来做到这一点和 $group声明:

db.collection.aggregate([
  { "$unwind": "$groups" },
  { "$unwind": "$groups.teams" },
  { "$group": {
    "_id": {
      "_id": "$_id",
      "gId": "$groups._id",
      "wlActive": "$groups.wlActive",
      "state": "$groups.teams.state"
    },
    "count": { "$sum": 1 }
  }},
  { "$sort": { "_id": -1, "count": -1 } },
  { "$group": {
    "_id": {
      "_id": "$_id._id",
      "gId": "$_id.gId",
      "wlActive": "$_id.wlActive",
    },
    "teams": { "$push": { "state": "$_id.state", "count": "$count" } }
  }},
  { "$group": {
    "_id": "$_id._id",
    "groups": {
      "$push": {
        "_id": "$_id.gId",
        "wlActive": "$_id.wlActive",
        "teams": "$teams"
      }
    }
  }}
])

在这里$unwind用于将数组内容“展平”到单独的文档中。您将此执行到 teams 级别和 $group复合键上,它标识唯一性状态级别。

由于所有文档详细信息都是初始 $group 的一部分键,您删除了“唯一性” 的级别,这样teams 就变成了一个使用$push 的数组.为了回到原来的文件形式,另一个$group在文档的原始 _id 值和 $push 上完成重建 groups 数组。

这种形式可能“更容易”理解,但是它确实需要更长的运行时间和更多的资源。第一种形式是最佳,因为您实际上不需要 $group在现有文档中,您通常应该避免 $unwind除非绝对必要。即在所有文档中对state进行分组是必要的,但在单个文档中则不是。

两种方式基本上返回相同的结果:

{
        "_id" : "event1",
        "groups" : [
                {
                        "_id" : "group1",
                        "wlActive" : true,
                        "teams" : [
                                {
                                        "state" : 2,
                                        "count" : 2
                                },
                                {
                                        "state" : 1,
                                        "count" : 3
                                },
                                {
                                        "state" : 0,
                                        "count" : 2
                                }
                        ]
                },
                {
                        "_id" : "group2",
                        "wlActive" : false,
                        "teams" : [
                                {
                                        "state" : 2,
                                        "count" : 2
                                },
                                {
                                        "state" : 1,
                                        "count" : 3
                                },
                                {
                                        "state" : 0,
                                        "count" : 2
                                }
                        ]
                }
        ]
}
{
        "_id" : "event2",
        "groups" : [
                {
                        "_id" : "group3",
                        "wlActive" : true,
                        "teams" : [
                                {
                                        "state" : 2,
                                        "count" : 2
                                },
                                {
                                        "state" : 1,
                                        "count" : 3
                                },
                                {
                                        "state" : 0,
                                        "count" : 2
                                }
                        ]
                },
                {
                        "_id" : "group4",
                        "wlActive" : false,
                        "teams" : [
                                {
                                        "state" : 2,
                                        "count" : 2
                                },
                                {
                                        "state" : 1,
                                        "count" : 3
                                },
                                {
                                        "state" : 0,
                                        "count" : 2
                                }
                        ]
                }
        ]
}

就其值(value)而言,因为这不是真正的“聚合”文档中的任何内容,您可以简单地返回所有数据并“聚合”客户端代码中的数组项。

作为 mongo shell 的例子:

db.collection.find().map(doc => Object.assign({}, doc, {
  _id: doc._id,
  groups: doc.groups.map(g => Object.assign({}, g, {
    _id: g._id,
    wlActive: g.wlActive,
    teams: ((input) => {
      var obj = input.reduce((o, e) => 
      (o.hasOwnProperty(e.state)) ? 
        Object.assign({} , o, { [e.state]: o[e.state]+1 })
        : Object.assign({}, o, { [e.state]: 1 }),  {});
      return Object.keys(obj)
        .map(k => ({ state: parseInt(k), count: obj[k] }))
        .sort((a,b) => b.state - a.state);
    })(g.teams)
  }))
}))

关于mongodb - 在保留根字段的同时对子文档进行分组/计数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54992837/

有关mongodb - 在保留根字段的同时对子文档进行分组/计数的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby-on-rails - 按天对 Mongoid 对象进行分组 - 2

    在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev

  3. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  4. ruby-on-rails - 如何验证非模型(甚至非对象)字段 - 2

    我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss

  5. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

    我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

  6. ruby - 如何进行排列以有效地定制输出 - 2

    这是一道面试题,我没有答对,但还是很好奇怎么解。你有N个人的大家庭,分别是1,2,3,...,N岁。你想给你的大家庭拍张照片。所有的家庭成员都排成一排。“我是家里的friend,建议家庭成员安排如下:”1岁的家庭成员坐在这一排的最左边。每两个坐在一起的家庭成员的年龄相差不得超过2岁。输入:整数N,1≤N≤55。输出:摄影师可以拍摄的照片数量。示例->输入:4,输出:4符合条件的数组:[1,2,3,4][1,2,4,3][1,3,2,4][1,3,4,2]另一个例子:输入:5输出:6符合条件的数组:[1,2,3,4,5][1,2,3,5,4][1,2,4,3,5][1,2,4,5,3][

  7. ruby-on-rails - 在 Rails 和 ActiveRecord 中查询时忽略某些字段 - 2

    我知道我可以指定某些字段来使用pluck查询数据库。ids=Item.where('due_at但是我想知道,是否有一种方法可以指定我想避免从数据库查询的某些字段。某种反拔?posts=Post.where(published:true).do_not_lookup(:enormous_field) 最佳答案 Model#attribute_names应该返回列/属性数组。您可以排除其中一些并传递给pluck或select方法。像这样:posts=Post.where(published:true).select(Post.attr

  8. ruby - 即使失败也继续进行多主机测试 - 2

    我已经构建了一些serverspec代码来在多个主机上运行一组测试。问题是当任何测试失败时,测试会在当前主机停止。即使测试失败,我也希望它继续在所有主机上运行。Rakefile:namespace:specdotask:all=>hosts.map{|h|'spec:'+h.split('.')[0]}hosts.eachdo|host|begindesc"Runserverspecto#{host}"RSpec::Core::RakeTask.new(host)do|t|ENV['TARGET_HOST']=hostt.pattern="spec/cfengine3/*_spec.r

  9. ruby - 是否可以覆盖 gemfile 进行本地开发? - 2

    我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI

  10. ruby - 在 Windows 机器上使用 Ruby 进行开发是否会适得其反? - 2

    这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby​​-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub

随机推荐