【问题标题】:Mongodb aggregate on filter like subdocumentMongodb在过滤器上聚合,如子文档
【发布时间】:2013-06-08 06:53:36
【问题描述】:

首先,对不起我的英语不好。

如果我们在 MongoDB 中有以下文档,

测试数据

{id:1, filter:{f1:'v1-1', f2:'v2-1', f3:['v3-1', 'v3-3']}}
{id:2, filter:{f1:'v1-1', f2:'v2-2', f3:['v3-2', 'v3-3']}}
{id:3, filter:{f1:'v1-1', f2:'v2-2', f3:['v3-1', 'v3-3']}}

准备收藏

db.test.drop()
db.test.insert({id:1, filter:{f1:'v1-1', f2:'v2-1', f3:['v3-1', 'v3-3']}})
db.test.insert({id:2, filter:{f1:'v1-1', f2:'v2-2', f3:['v3-2', 'v3-3']}})
db.test.insert({id:3, filter:{f1:'v1-1', f2:'v2-2', f3:['v3-1', 'v3-3']}})

您可以将过滤字段视为在许多购物网站上使用的过滤功能,例如,他们会告诉您网站上有多少 LED 电视和多少液晶电视。

我想使用 MongoDB 计算每个过滤选项有多少文档(包括数组字段中的每个项目),预期结果如下。

预期结果

[
    {
       _id : { key: 'f1', value: 'v1-1' }, count: 3
    },
    {
       _id : { key: 'f2', value: 'v2-1' }, count: 1
    },
    {
       _id : { key: 'f2', value: 'v2-2' }, count: 2
    },
    {
       _id : { key: 'f3', value: 'v3-1' }, count: 2
    },
    {
       _id : { key: 'f3', value: 'v3-2' }, count: 1
    },
    {
       _id : { key: 'f3', value: 'v3-3' }, count: 3
    }
]

使用map/reduce很容易得到结果,

Map/Reduce 解决方案

map = function () { 
    for (k in this.filter) { 
        if (this.filter[k] instanceof Array) {
            for (j in this.filter[k]) {
                emit( { key: k, value: this.filter[k][j]}, 1 ); 
            }
        } else {
            emit( { key: k, value: this.filter[k]}, 1 ); 
        }
    } 
}

reduce = function (k, values) { 
    result = 0;  
    values.forEach( function(v) { result += v; } ); 
    return result; 
}

db.test.mapReduce(map, reduce, {out:{inline:1}})

但是由于map/reduce的性能问题,它不能用于实时查询。如果我添加一些查询条件,结果集可能会改变,所以我无法将map/reduce结果保存到另一个集合中进行实时查询。

我可以使用聚合框架来计算一个过滤器的计数,

只有一个过滤器的聚合解决方案

db.test.aggregate(
{$project: {"filter.f2":1, "_id":0}},
{$group:   {"_id": {"key": {$ifNull: [null, "f2"]}, "value":"$filter.f2"}, "count" : {$sum: 1}}}
)

[
    {
        "_id" : { "key" : "f2", "value" : "v2-2" }, "count" : 2
    },
    {
        "_id" : { "key" : "f2", "value" : "v2-1" }, "count" : 1
    }
]

但我不知道如何为所有过滤器选项执行此操作。有什么想法吗?

【问题讨论】:

    标签: mongodb mapreduce aggregation-framework


    【解决方案1】:

    如果您将数据结构更改为类似这样,请注意所有值都是数组,即使是具有单个值的值:

    {
      _id: 1,
      filters: [{
        key: 'f1',
        values: ['v1-1']
      },{
        key: 'f2',
        values: ['v2-1']
      },{
        key: 'f3',
        values: ['v3-1', 'v3-3']
      }]
    }
    {
      _id: 2,
      filters: [{
        key: 'f1',
        values: ['v1-1']
      },{
        key: 'f2',
        values: ['v2-2']
      },{
        key: 'f3',
        values: ['v3-2', 'v3-3']
      }]
    }
    {
      _id: 3,
      filters: [{
        key: 'f1',
        values: ['v1-1']
      },{
        key: 'f2',
        values: ['v2-2']
      },{
        key: 'f3',
        values: ['v3-1', 'v3-3']
      }]
    }
    

    你可以像这样做一个聚合函数:

    db.test.aggregate({
      $unwind: "$filters"
    },{
      $project: {
        _id: 1,
        key: "$filters.key",
        values: "$filters.values"
      }
    },{
      $unwind: "$values"
    },{
      $group: {
        _id: {
          $concat: ["$key","|","$values"]
        },
        count: { $sum: 1 }
      }
    })
    

    如果你愿意,你可以跳过项目步骤,我只是把它放在那里。无论如何,你都需要两次放松。

    【讨论】:

    • 很好的答案,谢谢!我使用的是 mongodb 2.2,所以我需要在 $group 部分使用_id: { key: "$key", values: "$values" }
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-29
    • 2016-12-19
    • 1970-01-01
    • 1970-01-01
    • 2019-10-20
    • 2020-10-16
    相关资源
    最近更新 更多