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: {...} }
    ]
}

这非常适合我,除了一个问题:我想计算整个集合中的每个唯一项目(以“sku”作为唯一标识符键),其中每个文档将计数加 1(相同“sku”的多个实例)同一份文件仍将仅计入 1)。例如,我想要这个结果:

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

在阅读了 MongoDB 之后,当您有如上所述的复杂模式时,我对如何(快速)执行此操作感到非常困惑。如果我正确理解了其他出色的文档,则可以使用聚合框架或 map/reduce 框架来实现此类操作,但这是我需要一些输入的地方:

  • 考虑到结构的复杂性,哪个框架更适合实现我正在寻找的结果?
  • 为了从所选框架中获得最佳性能,首选哪种索引?
4

2 回答 2

14

MapReduce 很慢,但它可以处理非常大的数据集。另一方面,聚合框架要快一些,但会在处理大量数据时遇到困难。

显示的结构的问题是您需要“$unwind”数组来破解数据。这意味着为每个数组项创建一个新文档,并使用聚合框架在内存中执行此操作。因此,如果您有 1000 个包含 100 个数组元素的文档,则需要构建一个包含 100,000 个文档的流,以便对它们进行分组和计数。

您可能想考虑看看是否有一个架构布局可以更好地为您的查询提供服务,但是如果您想使用聚合框架来执行此操作,那么您可以这样做(使用一些示例数据,因此整个脚本将放入 shell) ;

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
})

请注意,我已经 $group'd 两次,因为您说 SKU 每个文档只能计数一次,所以我们需要先整理出唯一的 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 回答