草庐IT

mongodb - MongoDB高级聚合

coder 2023-10-29 原文

我是MongoDB的新手。我为我的高尔夫俱乐部做了一个私人项目来分析这一轮。
我对应用程序使用meteorjs,并在命令行上尝试了一些聚合。但我不确定我是否对这项任务有正确的看法
示例文档:

{
    "_id" : "2KasYR3ytsaX8YuoT",
    "course" : {
        "id" : "rHmYJBhRtSt38m68s",
        "name" : "CourseXYZ"
    },
    "player" : {
        "id" : "tdaYaSvXJueDq4oTN",
        "firstname" : "Just",
        "lastname" : "aPlayer"
    },
    "event" : "Training Day",
    "tees" : [
        {
            "tee" : 1,
            "par" : 4,
            "fairway" : "straight",
            "greenInRegulation" : true,
            "putts" : 3,
            "strokes" : 5
        },
        {
            "tee" : 2,
            "par" : 5,
            "fairway" : "right",
            "greenInRegulation" : true,
            "putts" : 2,
            "strokes" : 5
        },
        {
            "tee" : 3,
            "par" : 5,
            "fairway" : "right",
            "greenInRegulation" : false,
            "shotType": "bunker",
            "putts" : 2,
            "strokes" : 5
        }
    ]
}

到目前为止我的尝试是:
db.analysis.aggregate([
   {$unwind: "$tees"}, 
   {$group: { 
       _id:"$player.id",
       strokes: {$sum: "$tees.strokes"},
       par: {$sum: "$tees.par"},
       putts: {$sum: "$tees.putts"},
       teesPlayed: {$sum:1}
   }}
])

我想要的结果
{ 
    "_id" : "tdaYaSvXJueDq4oTN", 
    "strokes" : 15, 
    "par" : 14, 
    "putts" : 7, 
    "teesPlayed" : 3 
    // here comes what I want to add:
    "fairway.straight": 1 // where tees.fairway equals "straight"
    "fairway.right": 2 // where tees.fraiway equals "right" (etc.)
    "shotType.bunker": 1 // where shotType equals "bunker" etc.
}

最佳答案

根据您的总体需求以及项目的目标MongoDB服务器版本,有几种方法可以实现这一点。
虽然“Meteor”安装和默认项目设置不会“捆绑”MongoDB 3.2实例,但您的项目不必将此类实例用作外部目标。如果这是一个新的项目起步,那么我强烈建议与最新版本的可用工作。甚至可能反对最新的开发版本,这取决于您自己的目标发布周期。用最新鲜的东西工作,你的申请也会很新鲜。
因此,我们从最新的开始。
MongoDB 3.2路-快速
MongoDB 3.2的一大特点是$sum操作方式的改变,这使得它在性能方面非常突出。以前,作为$group的累加器运算符,这将处理单个数值以生成总计。
大的改进隐藏在添加的$project阶段用法中,其中$sum可以直接应用于值数组。即{ "$sum": [1,2,3] }导致6。因此,现在可以使用从源代码生成值数组的任何内容来“嵌套”操作。最值得注意的是:

db.analysis.aggregate([
    { "$group": {
        "_id": "$player.id",
        "strokes": {
            "$sum": { 
                "$sum": {
                    "$map": {
                        "input": "$tees",
                        "as": "tee",
                        "in": "$$tee.strokes"
                    }
                }
            }
        },
        "par": {
            "$sum": {
                "$sum": {
                    "$map": {
                        "input": "$tees",
                        "as": "tee",
                        "in": "$$tee.par"
                    }
                }
            }
        },
        "putts": {
            "$sum": {
                "$sum": {
                    "$map": {
                        "input": "$tees",
                        "as": "tee",
                        "in": "$$tee.putts"
                    }
                }
            }
         },
        "teesPlayed": { "$sum": { "$size": "$tees" } },
        "shotsRight": {
            "$sum": {
                "$size": {
                    "$filter": {
                        "input": "$tees",
                        "as": "tee",
                        "cond": { "$eq": [ "$$tee.fairway", "right" ] }
                    }
                }
            }
        },
        "shotsStraight": {
            "$sum": {
                "$size": {
                    "$filter": {
                        "input": "$tees",
                        "as": "tee",
                        "cond": { "$eq": [ "$$tee.fairway", "straight" ] }
                    }
                }
            }
        },
        "bunkerShot": {
            "$sum": {
                "$size": {
                    "$filter": {
                        "input": "$tees",
                        "as": "tee",
                        "cond": { "$eq": [ "$$tee.shotType", "bunker" ] }
                    }
                }
            }
        }
    }}
])

因此,这里通过对数组项中的单个字段值执行double$map技巧来分割每个字段,或者相反,使用$sum对数组进行处理,以仅限于匹配项,并使用$filter对结果字段的匹配长度进行处理,而结果字段更希望“co”UNTS“。
尽管这在管道建设中看起来有些冗长,但它将产生快速的结果。尽管您需要指定所有键以使用关联的逻辑生成结果,但是没有什么可以阻止“生成”作为数据集上其他查询的结果的管道所必需的数据结构。
另一种聚合方式-稍微慢一点
当然,并不是每个项目都能实际使用最新版本的东西。因此,在MongoDB 3.2版本引入上述一些运算符之前,处理数组数据并有条件地处理不同元素和和和的唯一实际方法是先处理$size
所以本质上我们从您开始构造的查询开始,然后添加对不同字段的处理:
db.analysis.aggregate([
    { "$unwind": "$tees" },
    { "$group": {
        "_id": "$player.id",
        "strokes": { "$sum": "$tees.strokes" },
        "par": { "$sum": "$tees.par" },
        "putts": { "$sum": "$tees.putts" },
        "teedsPlayed": { "$sum": 1 },
        "shotsRight": {
            "$sum": {
                "$cond": [
                    { "$eq": [ "$tees.fairway", "right" ] },
                    1,
                    0
                ]
            }
        },
        "shotsStraight": {
            "$sum": {
                "$cond": [
                    { "$eq": [ "$tees.fairway", "straight" ] },
                    1,
                    0
                ]
            }
        },
        "bunkerShot": {
            "$sum": {
                "$cond": [
                    { "$eq": [ "$tees.shotType", "bunker" ] },
                    1,
                    0
                ]
            }
        }
    }}
])

因此,您应该注意到,与第一个列表仍然有“一些”相似之处,在$unwind语句中都有$filter参数中的一些逻辑的地方,该逻辑在这里被转换为"cond"运算符。
作为一个“三元”运算符(if/then/else),它的工作是计算一个逻辑条件(if),然后返回下一个条件为$cond(then)的参数,或者返回最后一个条件为true(else)的参数。在这种情况下,要么false要么1取决于测试条件是否匹配。这将根据需要为0提供“计数”。
无论哪种说法,结果都是这样的:
{
        "_id" : "tdaYaSvXJueDq4oTN",
        "strokes" : 15,
        "par" : 14,
        "putts" : 7,
        "teesPlayed" : 3,
        "shotsRight" : 2,
        "shotsStraight" : 1,
        "bunkerShot" : 1
}

因为这是一个带$sum的聚合语句,所以一个规则是“键”(除了需要在构造的语句中指定之外)必须在结构的“顶层”中。因此,$group中不允许有“嵌套”结构,因此每个键都有完整的名称。
如果确实必须转换,则可以在每个示例中的$group后面添加$project阶段:
{ "$project": {
    "strokes": 1,
    "par": 1,
    "putts": 1,
    "teesPlayed": 1,
    "fairway": {
        "straight": "$shotsStraight",
        "right": "$shotsRight"
    },
    "shotType": {
        "bunker": "$bunkerShot"
    }
}}

所以可以做一些“重新塑造”,但是当然所有的名字和结构都必须被指定,尽管理论上你可以只在代码中生成这些。毕竟它只是一个数据结构。
这里的底线是$group增加了成本,而且成本相当高。它基本上是要为管道中的每个文档添加一个副本,以便处理每个文档中包含的每个数组元素。因此,不仅有加工所有这些生产出来的东西的成本,而且还有一个首先“生产”它们的成本。
MapReduce-速度更慢,但按键更灵活
最后作为一种方法
db.analysis.mapReduce(
    function() {

        var data = { "strokes": 0 ,"par": 0, "putts": 0, "teesPlayed": 0, "fairway": {} };

        this.tees.forEach(function(tee) {
            // Increment common values
            data.strokes += tee.strokes;
            data.par += tee.par;
            data.putts += tee.putts;
            data.teesPlayed++;

            // Do dynamic keys
            if (!data.fairway.hasOwnProperty(tee.fairway))
                data.fairway[tee.fairway] = 0;
            data.fairway[tee.fairway]++;

            if (tee.hasOwnProperty('shotType')) {
                if (!data.hasOwnProperty('shotType'))
                    data.shotType = {};
                if (!data.shotType.hasOwnProperty(tee.shotType))
                    data.shotType[tee.shotType] = 0;
                data.shotType[tee.shotType]++
            }

        });

        emit(this.player.id,data);
    },
    function(key,values) {
        var data = { "strokes": 0 ,"par": 0, "putts": 0, "teesPlayed": 0, "fairway": {} };

        values.forEach(function(value) {
            // Common keys
            data.strokes += value.strokes;
            data.par += value.par;
            data.putts += value.putts;
            data.teesPlayed += value.teesPlayed;

            Object.keys(value.fairway).forEach(function(fairway) {
                if (!data.fairway.hasOwnProperty(fairway))
                    data.fairway[fairway] = 0;
                data.fairway[fairway] += value.fairway[fairway];
            });

            if (value.hasOwnProperty('shotType')) {
                if (!data.hasOwnProperty('shotType'))
                    data.shotType = {};
                Object.keys(value.shotType).forEach(function(shotType) {
                    if (!data.shotType.hasOwnProperty(shotType))
                        data.shotType[shotType] = 0;
                    data.shotType[shotType] += value.shotType[shotType];
                });
            }
        });

        return data;

    },
    { "out": { "inline": 1 } }
)

这个输出可以用嵌套结构立即完成,但当然是在“key/value”的mapreduce输出形式中,因为“key”是分组$unwind,“value”包含所有输出:
            {
                    "_id" : "tdaYaSvXJueDq4oTN",
                    "value" : {
                            "strokes" : 15,
                            "par" : 14,
                            "putts" : 7,
                            "teesPlayed" : 3,
                            "fairway" : {
                                    "straight" : 1,
                                    "right" : 2
                            },
                            "shotType" : {
                                    "bunker" : 1
                            }
                    }
            }

mapreduce的_id选项可以是"out",如图所示,您可以将所有结果放入内存(并在16mb bson限制内),也可以是另一个集合,稍后可以从中读取。对于"inline",有一个类似的$out,但这通常是否定的,因为聚合输出可以作为“游标”使用,除非您确实希望它位于集合中。
结束语
所以这一切都取决于你真的想怎么做。如果速度是最重要的,那么.aggregate()通常会产生最快的结果。另一方面,如果您想“动态地”使用生成的“键”,那么.aggregate()允许逻辑通常是自包含的,而不需要另一个检查过程来生成所需的聚合管道语句。

关于mongodb - MongoDB高级聚合,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35606546/

有关mongodb - MongoDB高级聚合的更多相关文章

  1. ruby - Rails Elasticsearch 聚合 - 2

    不知何故,我似乎无法获得包含我的聚合的响应...使用curl它按预期工作:HBZUMB01$curl-XPOST"http://localhost:9200/contents/_search"-d'{"size":0,"aggs":{"sport_count":{"value_count":{"field":"dwid"}}}}'我收到回复:{"took":4,"timed_out":false,"_shards":{"total":5,"successful":5,"failed":0},"hits":{"total":90,"max_score":0.0,"hits":[]},"a

  2. c# - Ruby 等效于 C# Linq 聚合方法 - 2

    什么是Linq聚合方法的ruby​​等价物。它的工作原理是这样的varfactorial=new[]{1,2,3,4,5}.Aggregate((acc,i)=>acc*i);每次将数组序列中的值传递给lambda时,变量acc都会累积。 最佳答案 这在数学以及几乎所有编程语言中通常称为折叠。它是更普遍的变形概念的一个实例。Ruby从Smalltalk中继承了这个特性的名称,它被称为inject:into:(像aCollectioninject:aStartValueinto:aBlock一样使用。)所以,在Ruby中,它称为inj

  3. ruby - 高级语言是否使用数据结构? - 2

    我目前还在上学,正在上一门关于用C++实现数据结构的类(class)。在业余时间,我喜欢使用“高级”语言(主要是Ruby和一些c#)进行编程。既然这些高级语言为你管理内存,你会用数据结构做什么?我可以理解对队列和堆栈的需求,但是您需要在Ruby中使用二叉树吗?还是2-3-4树?为什么?谢谢。 最佳答案 Sosincethesehigherlevellanguagesmanagethememoryforyou,whatwouldyouusedatastructuresfor?使用数据结构的主要原因与垃圾收集无关。但它是以某种方式有效的

  4. sql - Arel 导致聚合无限循环 - 2

    我在使用Arel聚契约(Contract)一查询中的2列时遇到了问题。当我运行它时,在railsdev-server崩溃之前,整个服务器会卡住一分钟。我怀疑是无限循环:)。也许我误解了Arel的整个概念,如果有人能看一下,我将不胜感激。这个查询的预期结果是这样的:[{:user_id=>1,:sum_account_charges=>300,:sum_paid_debts=>1000},...]a_account_charges=Table(:account_charges)a_paid_debts=Table(:paid_debts)a_participants=Table(:exp

  5. ruby-on-rails - 如何在 Rails/ActiveRecord 中同时使用多个聚合函数? - 2

    我想同时执行多个聚合函数,例如获取按状态分组的最大和最小id:Model.maximum(:id).minimum(:id).group(:status)这行不通(至少对于Rails3.1.1是这样)——你在最小调用时收到一个错误,说它没有在Fixnum上定义。NoMethodError:undefinedmethod`minimum'for22377:Fixnum我可以为它做原始sql-但只是想知道是否有更高级别/Rails选项...谢谢,克里斯 最佳答案 我有一个类似的问题,我在Rails4中使用groupwithpluck解决

  6. ruby-on-rails - 在一个 Rails 应用程序中使用 PostgreSQL 的 MongoDB - 2

    我可以在一个Rails应用程序中同时使用MongoDB和PostgreSQL吗?具体来说,我最终会想要使用像MongoHQ这样的东西。到目前为止,我未能在实验中进行这项工作。令我担心的是,MongoDB文档特别指出我必须禁用ActiveRecord。任何建议将不胜感激。 最佳答案 您无需禁用ActiveRecord即可使用MongoDB。查看Mongoid只需将gem加上任何模型与您现有的任何ActiveRecord模型一起添加。您应该注意到MongoHQ只是MongoDB的托管服务,可以与任何对象文档映射器(ODM)一起使用。更多

  7. ruby - 使用 mongodb/mongoid 运行时更改模型 - 2

    我必须在mongoid模型中添加几个字段,我知道MongoDB没有迁移,但如果我继续而不删除数据库,使rails完全“重新生成”数据库,它不会显示或使用新的领域!去这里最好的方法是什么?有比删除/重新打开mongodb更软的东西吗?提前致谢卢卡 最佳答案 一般来说,应该可以在运行时用新字段更新旧文档。MongoDB中不需要迁移。您可能想编写rake任务以使用新字段和默认值更新旧文档。您可以通过检查那些默认值为nil的新字段来找到这些文档。更新简单风格:如果您使用默认值定义一个新字段,只要您设置了一个新值,就应该始终使用该值:应用程序

  8. ruby-on-rails - 我如何从 Ruby 代码连接到 mongodb? - 2

    我如何从Ruby代码连接到mongodb? 最佳答案 首先,您必须安装MongoDbgem:geminstallmongo然后运行代码:require'rubygems'#notnecessaryforRuby1.9require'mongo'db=Mongo::Connection.new.db("mydb")#ORdb=Mongo::Connection.new("localhost").db("mydb")#ORdb=Mongo::Connection.new("localhost",27017).db("mydb")

  9. ruby - MongoDB:无法从 BSON 类型 EOO 转换为 Date - 2

    我正在尝试使用聚合框架(使用ruby​​)并像这样投影日期:db['requests'].aggregate([{"$project"=>{_id:0,method:'$method',user:'$user',year:{'$year'=>'$timestamp'}}}])文档是这样的:{_id:ObjectId("5177d7d7df26358289da7dfd"),timestamp:ISODate("2013-04-12T03:58:05+00:00"),method:"POST",status:"200",inputsize:"874",outputsize:"4981",u

  10. ruby - 在 Ruby 中从 MongoDB 中检索字段的子集 - 2

    我试图通过在Ruby中进行的查询从MongoDB获取字段的子集,但它似乎不起作用。它不返回任何结果这是ruby代码:coll.find("title"=>'Halo',:fields=>["title","isrc"])#thisdoesn'twork如果我删除字段散列,它会工作,返回包含所有字段的结果coll.find("title"=>'Halo')#thisworks查看mongodb控制台,第一个查询在mongodb服务器上结束,如下所示:{title:"Halo",fields:["title","isrc"]}如果我尝试从mongo客户端控制台进行查询,它会工作,我会得到结

随机推荐