4

私はバックエンドとしてmongodbを使用して小さなアプリケーションを実装しています。このアプリケーションでは、サブドキュメントの配列を含むフィールドがドキュメントに含まれるデータ構造があります。

私は次のユースケースを基礎として使用します:http: //docs.mongodb.org/manual/use-cases/inventory-management/

例からわかるように、各ドキュメントには、サブドキュメントの配列であるcartedというフィールドがあります。

{
    _id: 42,
    last_modified: ISODate("2012-03-09T20:55:36Z"),
    status: 'active',
    items: [
        { sku: '00e8da9b', qty: 1, item_details: {...} },
        { sku: '0ab42f88', qty: 4, item_details: {...} }
    ]
}

これは、1つの問題を除いて、私にぴったりです。コレクション全体で各一意のアイテム(一意の識別子キーとして「sku」を使用)をカウントし、各ドキュメントでカウントを1ずつ追加します(同じ「sku」の複数のインスタンス同じドキュメントでも1)がカウントされます。たとえば、この結果が欲しいです:

{sku: '00e8da9b'、doc_count:1}、{sku: '0ab42f88'、doc_count:9}

MongoDBを読んだ後、上記のような複雑なスキーマがある場合に、これを(高速に)行う方法についてかなり混乱しています。他の点では優れたドキュメントが正しいことを理解している場合、そのような操作はおそらく集約フレームワークまたはmap / reduceフレームワークのいずれかを使用して達成される可能性がありますが、ここでいくつかの入力が必要です。

  • 構造の複雑さを考えると、私が探している結果を達成するのに適したフレームワークはどれですか?
  • 選択したフレームワークから可能な限り最高のパフォーマンスを得るには、どのような種類のインデックスが推奨されますか?
4

2 に答える 2

14

MapReduceは低速ですが、非常に大きなデータセットを処理できます。一方、アグリゲーションフレームワークは少し高速ですが、大量のデータに苦労します。

示されている構造の問題は、データをクラックして開くために配列を「$unwind」する必要があることです。これは、すべての配列アイテムに対して新しいドキュメントを作成することを意味し、集約フレームワークを使用して、メモリ内でこれを行う必要があります。したがって、100個の配列要素を持つ1000個のドキュメントがある場合、groupByしてそれらをカウントするには、100,000個のドキュメントのストリームを構築する必要があります。

クエリをより適切にサーバー化するスキーマレイアウトがあるかどうかを確認することを検討することもできますが、Aggregationフレームワークを使用して実行する場合は、次の方法で実行できます(サンプルデータを使用して、スクリプト全体をシェルにドロップします) ;

db.so.remove();
db.so.ensureIndex({ "items.sku": 1}, {unique:false});
db.so.insert([
    {
        _id: 42,
        last_modified: ISODate("2012-03-09T20:55:36Z"),
        status: 'active',
        items: [
            { sku: '00e8da9b', qty: 1, item_details: {} },
            { sku: '0ab42f88', qty: 4, item_details: {} },
            { sku: '0ab42f88', qty: 4, item_details: {} },
            { sku: '0ab42f88', qty: 4, item_details: {} },
    ]
    },
    {
        _id: 43,
        last_modified: ISODate("2012-03-09T20:55:36Z"),
        status: 'active',
        items: [
            { sku: '00e8da9b', qty: 1, item_details: {} },
            { sku: '0ab42f88', qty: 4, item_details: {} },
        ]
    },
]);


db.so.runCommand("aggregate", {
    pipeline: [
        {   // optional filter to exclude inactive elements - can be removed    
            // you'll want an index on this if you use it too
            $match: { status: "active" }
        },
        // unwind creates a doc for every array element
        { $unwind: "$items" },
        {
            $group: {
                // group by unique SKU, but you only wanted to count a SKU once per doc id
                _id: { _id: "$_id", sku: "$items.sku" },
            }
        },
        {
            $group: {
                // group by unique SKU, and count them
                _id: { sku:"$_id.sku" },
                doc_count: { $sum: 1 },
            }
        }
    ]
    //,explain:true
})

SKUはドキュメントごとに1回しかカウントできないとおっしゃっていたため、$ groupを2回行ったことに注意してください。そのため、最初に一意のdoc / skuペアを分類してから、カウントアップする必要があります。

出力を少し変えたい場合(つまり、サンプルとまったく同じように)、それらを$projectすることができます。

于 2012-10-25T20:02:31.327 に答える
2

最新のmongoビルド(他のビルドにも当てはまる可能性があります)では、わずかに異なるバージョンのCirrusの回答の方がパフォーマンスが速く、メモリの消費量が少ないことがわかりました。理由の詳細はわかりませんが、このバージョンでは、mongoがパイプラインを最適化する可能性が高いようです。

db.so.runCommand("aggregate", {
    pipeline: [
        { $unwind: "$items" },
        {
            $group: {
                // create array of unique sku's (or set) per id
                _id: { id: "$_id"},
                sku: {$addToSet: "$items.sku"}
            }
        },
        // unroll all sets
        { $unwind: "$sku" },
        {
            $group: {
                // then count unique values per each Id
                _id: { id: "$_id.id", sku:"$sku" },
                count: { $sum: 1 },
            }
        }
    ]
})

問題の質問とまったく同じ形式に一致させるには、「_id」によるグループ化をスキップする必要があります

于 2014-03-05T15:37:54.367 に答える