5

(1) 我在集合中添加了以下内容:

{ "_id" : 1, "hitsPerOneSecond" : [ 2, 3, 5, 4, 1, 2, 3, 4, 1, 2 ], "startTime" :    ISODate("2012-04-07T10:41:33.380Z"), "returnCodeHits" : { "300" : 5, "200" : 12 }, "xxxServer" : "xxx:8100", "statsSummarizedToSeconds" : 10, "pathStats_xxx_api_get_version" : [ 0.2280779683225852, 0.030849283020361273, 0.9947690473370484 ], "pathStats_xxx_api_get_response" : [ 1.2163705612407407, 1.0602539963494662, 1.4853219936411421 ], "type" : "xxxType", "startTimeStr" : "07-04-2012:10AM" }

{ "_id" : 2, "hitsPerOneSecond" : [ 2, 3, 5, 4, 1, 2, 3, 4, 1, 2 ], "startTime" : ISODate("2012-04-07T10:41:43.380Z"), "returnCodeHits" : { "300" : 5, "200" : 12 }, "xxxServer" : "xxx:8100", "statsSummarizedToSeconds" : 10, "pathStats_xxx_api_get_version" : [ 0.2280779683225852, 0.030849283020361273, 0.9947690473370484 ], "pathStats_xxx_api_get_response" : [ 1.2163705612407407, 1.0602539963494662, 1.4853219936411421 ], "type" : "xxxType", "startTimeStr" : "07-04-2012:10AM" }

(2) 进行以下聚合时:

db.newStats.aggregate({$unwind: "$hitsPerOneSecond"},{$group:{_id:"$startTimeStr", totalHits: {$sum: "$hitsPerOneSecond"}, totalHitsCount: {$sum: 1}, avgHit: {$avg: "$hitsPerOneSecond"}, minHit: {$min:"$hitsPerOneSecond"}, maxHit:{$max: "$hitsPerOneSecond"}}});

(3) 结果正确:

{
"result" : [
    {
        "_id" : "07-04-2012:10AM",
        "totalHits" : 54,
        "totalHitsCount" : 20,
        "avgHit" : 2.7,
        "minHit" : 1,
        "maxHit" : 5
    }
],
"ok" : 1

}

(4) 但是,我需要在上面相同的聚合中对“pathStats_xxx_api_get_response”(来自集合)执行展开,以便在上面的相同结果中输出 totalResponses、totalResponsesCount、avgResponse、minResponse 和 maxResponse。因此,我的结果应该是这样的:

{
"result" : [
    {
        "_id" : "07-04-2012:10AM",
        "totalHits" : 54,
        "totalHitsCount" : 20,
        "avgHit" : 2.7,
        "minHit" : 1,
        "maxHit" : 5,
                    "totalResponses" : ??
                    "totalResponsesCount": ??
        "avgResponse" : 2.7,
        "minResponse" : 1,
        "maxResponse" : 5
    }
],
"ok" : 1

}

不确定如何在与我几乎相同的聚合中添加更多 $unwind !

4

2 回答 2

14

如何$unwind多于一个数组?你试过$unwinding多次吗?:)

db.newStats.aggregate([
    {$unwind: "$hitsPerOneSecond"},
    {$unwind: "$pathStats_xxx_api_get_response"},

    {$group:{
        _id:"$startTimeStr", 
        totalHits: {$sum: "$hitsPerOneSecond"}, 
        totalHitsCount: {$sum: 1}, 
        avgHit: {$avg: "$hitsPerOneSecond"}, 
        minHit: {$min:"$hitsPerOneSecond"}, 
        maxHit:{$max: "$hitsPerOneSecond"},

        totalResponses: {$sum: "$pathStats_xxx_api_get_response"},
        . . .
     }}
]);

请记住,聚合框架将数组作为输入(注意我添加了[, ])。在数组中,您可以根据需要向管道添加任意数量的聚合函数(需要引用),任何步骤的输出都将成为下一个步骤的输入!

笔记:

不要忘记,如果您尝试$unwind使用不存在的键或空数组,您最终会没有文档!这就像乘以0我猜......所以对于多个(可能很多)$unwind,处理鬼的机会增加:如果任何涉及的数组为空,则整个文档都会丢失,并且您的任何$group聚合都一无所获.. .

于 2012-12-03T20:32:27.680 回答
2

可能最简单的解决方案是使用两个单独的聚合操作来执行此操作,并将结果合并到您的应用程序中。

或者,您可以使用 Map Reduce 操作执行此操作:

以下 map 和 reduce 函数应提供您正在寻找的结果:

var map = function() {
  var totalHits = this.hitsPerOneSecond.map(function(a,b){return a+b;});
  var totalHitsCount = this.hitsPerOneSecond.length;
  var avgHit = totalHits / totalHitsCount;
  var minHit = Math.min.apply(Math, this.hitsPerOneSecond);
  var maxHit = Math.max.apply(Math, this.hitsPerOneSecond);
  var totalResponses = pathStats_xxx_api_get_response.map(function(a,b){return a+b;});
  var totalResponsesCount = this.pathStats_xxx_api_get_response.length;
  var avgResponse = totalResponses / totalResponsesCount;
  var minResponse = Math.min.apply(Math, this.pathStats_xxx_api_get_response);
  var maxResponse = Math.max.apply(Math, this.pathStats_xxx_api_get_response);
  emit(this.startTimeStr, {
    "totalHits": totalHits,
    "totalHitsCount": totalHitsCount,
    "avgHit": avgHit,
    "minHit": minHit,
    "maxHit": maxHit,
    "totalResponses": totalResponses,
    "totalResponsesCount": totalResponsesCount,
    "avgResponse": avgResponse,
    "maxResponse": maxResponse,
    "minResponse": minResponse
  })
}

var reduce = function(key, values) {
  var output = {
    "totalHits": 0,
    "totalHitsCount": 0,
    "avgHit": 0,
    "minHit": null,
    "maxHit": null,
    "totalResponses": 0,
    "totalResponsesCount": 0,
    "avgResponse": 0,
    "maxResponse": null,
    "minResponse": null
  };
  values.forEach(function(v) {
    output.totalHits += v.totalHits;
    output.totalHitsCount += v.totalHitsCount;
    output.avgHit = output.totalHits / output.totalHitsCount;
    if (output.minHit == null) {
      output.minHit = v.minHit;
    } else {
      if (v.minHit < output.minHit) {
        output.minHit = v.minHit
      }
    }
    if (output.maxHit == null) {
      output.maxHit = v.maxHit;
    } else {
      if (v.maxHit > output.maxHit) {
        output.maxHit = v.maxHit
      }
    }

    output.totalResponses += v.totalResponses;
    output.totalResponsesCount += v.totalResponsesCount;
    output.avgResponse = output.totalResponses / output.totalResponsesCount;
    if (output.minResponse == null) {
      output.minResponse = v.minResponse;
    } else {
      if (v.minResponse < output.minResponse) {
        output.minResponse = v.minResponse
      }
    }
    if (output.maxResponse == null) {
      output.maxResponse = v.maxResponse;
    } else {
      if (v.maxResponse > output.maxResponse) {
        output.maxResponse = v.maxResponse
      }
    }
  });
  return output;
}

> db.newStats.mapReduce(map, reduce, {out:{inline:1}})
{
    "results" : [
        {
            "_id" : "07-04-2012:10AM",
            "value" : {
                "totalHits" : 54,
                "totalHitsCount" : 20,
                "avgHit" : 2.7,
                "minHit" : 1,
                "maxHit" : 5,
                "totalResponses" : 7.523893102462698,
                "totalResponsesCount" : 6,
                "avgResponse" : 1.253982183743783,
                "maxResponse" : 1.4853219936411421,
                "minResponse" : 1.0602539963494662
            }
        }
    ],
    "timeMillis" : 0,
    "counts" : {
        "input" : 2,
        "emit" : 2,
        "reduce" : 1,
        "output" : 1
    },
    "ok" : 1,
}
> 

如果您不熟悉 Map Reduce,可以在此处找到文档: http ://www.mongodb.org/display/DOCS/MapReduce

此外,MongoDB Cookbook 中有一些很好的 Map Reduce 示例:http: //cookbook.mongodb.org/

食谱文章“使用版本化文档查找最大值和最小值” http://cookbook.mongodb.org/patterns/finding_max_and_min/的“附加”部分包含一个很好的 Map Reduce 操作的逐步演练,解释了如何功能被执行。

希望这将帮助您实现您想要的结果。如果您能够通过单个聚合操作找到一种方法,请分享您的解决方案,以便社区可以从您的经验中受益。谢谢。

以下是关于 Map Reduce 的一些注释,以回应您的评论:

MapReduce 在服务器上执行 JavaScript。因此,您可能会发现其他操作的性能会受到影响。Map Reduce 适用于可能在服务器未达到峰值流量时执行的偶尔操作。您可能会发现使用 Map Reduce 来获取大型集合中的动态统计数据并不是最优的。

另一方面,聚合框架依赖本机代码,不执行服务器端 JavaScript,比 Map Reduce 更快。

如果可能,最好的选择是为每个可查询的文档添加字段。这为每次插入或更新增加了一点额外的开销,但如果可以避免 Map Reduce 操作,结果将更快地返回。不幸的是,这对于最大值和最小值以及平均值来说是困难的。

如果 Map Reduce 操作是唯一的选择,则可以采取一些措施来减轻其对服务器的影响。首先,可以使用 SlaveOk 在辅助节点上运行 Map Reduce。但是,由于无法将数据写入辅助节点,因此必须以内联方式返回输出,因此限制为 16MB。一些用户将从副本集中取出一个辅助节点,将其作为独立的 mongod 进程重新启动,对其运行 map-reduce 操作,将输出集合复制到需要去的任何地方,然后将辅助节点重新加入副本集。

最后要考虑的一件事是增量 Map Reduce: http ://www.mongodb.org/display/DOCS/MapReduce#MapReduce-IncrementalMapreduce 您可以将查询传递给 map reduce 命令,该命令将仅匹配自最后一个 map reduce,并使用 reduce 输出选项运行 map reduce 操作。

希望以上内容能让您对计算统计数据的最佳方法有所思考。在文档中包含所需的信息是可取的,但如果这不可能,使用聚合框架将比 Map Reduce 更有效。

以下是关于聚合框架和 pymongo 的注释,以回应第二条评论:

聚合框架可以在 pymongo 中使用数据库对象的命令方法。
命令方法的文档可以在这里找到:http: //api.mongodb.org/python/current/api/pymongo/database.html#pymongo.database.Database.command

要执行聚合操作,请使用两个键将文档传递给 command 方法;“聚合”和“管道”。“aggregate”的值是要执行操作的集合的名称,“pipeline”的值是要执行的聚合操作的数组。管道在“聚合框架”文档中进行了解释: http ://www.mongodb.org/display/DOCS/Aggregation+Framework#AggregationFramework-Pipelines

以下是如何在 pymongo 中执行 $unwind 操作的示例:

In [1]: import pymongo

In [2]: conn = pymongo.Connection()

In [3]: db = conn.test

In [4]: result = db.command({"aggregate":"newStats", "pipeline":
                            [{"$unwind": "$hitsPerOneSecond"},
                             {"$group": {"_id":"$startTimeStr", 
                                          "totalHits": {"$sum": 
                                          "$hitsPerOneSecond"}, 
                              "totalHitsCount": {"$sum": 1}, 
                              "avgHit": {"$avg": "$hitsPerOneSecond"}, 
                              "minHit": {"$min":"$hitsPerOneSecond"}, 
                              "maxHit":{"$max": "$hitsPerOneSecond"}}}]})

In [5]: result
Out[5]: 
{u'ok': 1.0,
 u'result': [{u'_id': u'07-04-2012:10AM',
   u'avgHit': 2.7,
   u'maxHit': 5.0,
   u'minHit': 1.0,
   u'totalHits': 54.0,
   u'totalHitsCount': 20}]}
于 2012-04-16T22:45:21.807 回答