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) ただし、上記と同じ結果で totalResponses、totalResponsesCount、avgResponse、minResponse、および maxResponse を出力できるように、上記の同じ集計で「pathStats_xxx_api_get_response」(コレクションから) でアンワインドを実行する必要があります。したがって、私の結果は次のようになります。

{
"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

おそらく最も簡単な解決策は、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 クックブック ( http://cookbook.mongodb.org/ ) には、Map Reduce の優れた例がいくつかあります。

クックブックの記事「バージョニングされたドキュメントで最大値と最小値を見つける」 http://cookbook.mongodb.org/patterns/finding_max_and_min/の「Extras」セクションには、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 を実行し、reduce 出力オプションを指定して map reduce 操作を実行します。

上記が、統計を計算する最良の方法に関する考察の材料になることを願っています。ドキュメントに必要な情報を含めることが望ましいですが、それが不可能な場合は、Aggregation Framework を使用する方が Map Reduce よりも効率的です。

2番目のコメントへの応答として、集約フレームワークとpymongoに関するメモを次に示します。

集計フレームワークは、データベース オブジェクトのコマンド メソッドを使用して pymongo で使用できます。
コマンド メソッドに関するドキュメントは、http: //api.mongodb.org/python/current/api/pymongo/database.html#pymongo.database.Database.commandにあります。

集計操作を実行するには、2 つのキーを使用してドキュメントをコマンド メソッドに渡します。「集約」と「パイプライン」。「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 に答える