Mongodb聚合在数组的子文档中

时间:2012-10-25 17:12:15

标签: mongodb mapreduce aggregation-framework

我正在使用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框架来实现这样的操作,但这是我需要输入的地方:

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

2 个答案:

答案 0 :(得分:14)

MapReduce很慢,但它可以处理非常大的数据集。另一方面,聚合框架更快一些,但是会对大量数据产生影响。

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

您可能需要考虑是否存在可以更好地为您的查询提供服务的架构布局,但是如果您想在聚合框架中执行此操作,那么您将如何做到这一点(使用一些示例数据以便整个脚本将落入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对,然后对它们进行计数。

如果您希望输出略有不同(换句话说,完全像您的样本中那样),我们可以将它们投射出来。

答案 1 :(得分: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”分组