【问题标题】:Aggregation to calculate number of each tag, where there are two types of tags聚合计算每个标签的数量,其中有两种类型的标签
【发布时间】:2014-05-26 05:27:17
【问题描述】:

我有一个可以简化为元素的集合:

{
  t1 : [1, 3, 6],
  t2 : [8, 9]
}

t1t2 中可以是1 到10 个没有重复的正整数。我需要计算集合中所有文档的 t1 和 t2 中每个数字有多少。

例如,如果我的收藏包含 3 个文档:

{
  t1 : [1, 3, 6],
  t2 : [8, 9]
}, {
  t1 : [1, 2],
  t2 : [8]
}, {
  t1 : [6],
  t2 : [8, 1]
}

我应该有类似的东西

t1 : {
   1 : 2,   // there are 2 elements of 1 in t1
   3 : 1,   // there is 1 element of 3 in t1
   6 : 2,
   2 : 1
}

t2 : {
  8 : 3,    // there are 3 elements of 8 in t2
  9 : 1,
  1 : 1
}

我目前正在做的事情是这样的:

var t1 = {}, t2 = {};
db.coll.find().forEach(function(e){
   // where I am iterating through each element in t1 and t2 to
   // and increase/populate values in t1 and t2
})

虽然这种方法没有错,但我认为聚合框架有更好的方法。是否可以只使用一次聚合来做到这一点?

P.S. 我在示例中显示的输出只是一个示例。任何可以为我提供所需信息的输出都是合适的。

【问题讨论】:

    标签: mongodb mapreduce aggregation-framework


    【解决方案1】:

    聚合的单一形式:

    db.tags.aggregate([
        { "$project": {
            "_id": 0,
            "t1": 1,
            "t2": 2,
            "type": { "$literal": ["t1","t2"] }
        }},
        { "$unwind": "$type" },
        { "$project": {
            "type": 1,
            "value": { 
                "$cond": [
                    { "$eq": [ "$type", "t1" ] },
                    "$t1",
                    "$t2"
                ]
            } 
        }},
        { "$unwind": "$value" },
        { "$group": {
            "_id": {
                "type": "$type",
                "value": "$value"
            },
            "count": { "$sum": 1 }
        }},
        { "$sort": { "_id.type": 1, "_id.value": 1 } }
    ])
    

    然后输出:

    { "_id" : { "type" : "t1", "value" : 1 }, "count" : 2 }
    { "_id" : { "type" : "t1", "value" : 2 }, "count" : 1 }
    { "_id" : { "type" : "t1", "value" : 3 }, "count" : 1 }
    { "_id" : { "type" : "t1", "value" : 6 }, "count" : 2 }
    { "_id" : { "type" : "t2", "value" : 1 }, "count" : 1 }
    { "_id" : { "type" : "t2", "value" : 8 }, "count" : 3 }
    { "_id" : { "type" : "t2", "value" : 9 }, "count" : 1 }
    

    或者,如果您更喜欢单个文档,只需将结束阶段替换为 $group$project

        { "$group": {
            "_id": null,
            "t1": {
                "$push": {
                    "$cond": [
                        { "$eq": [ "$_id.type", "t1" ] },
                        { "value": "$_id.value", "count": "$count" },
                        false
                    ]
                }
            },
            "t2": {
                "$push": {
                    "$cond": [
                        { "$eq": [ "$_id.type", "t2" ] },
                        { "value": "$_id.value", "count": "$count" },
                        false
                    ]
                }
            },
        }},
        { "$project": {
            "_id": 0,
            "t1": { "$setDifference": [ "$t1", [false] ] },
            "t2": { "$setDifference": [ "$t2", [false] ] }
        }}
    

    结果:

    { 
        "t1" : [ 
            { "value" : 2, "count" : 1 }, 
            { "value" : 6, "count" : 2 }, 
            { "value" : 3, "count" : 1 }, 
            { "value" : 1, "count" : 2 } 
        ], 
        "t2" : [ 
            { "value" : 1, "count" : 1 },
            { "value" : 9, "count" : 1 },
            { "value" : 8, "count" : 3 } 
        ] 
    }
    

    在不使用 MongoDB 2.6 中的新运算符的情况下,这些都是可能的,只是需要更多的工作。


    mapReduce 方法看起来相当简单。由于 mapReduce 的限制,输出当然不是您的格式,但它无需迭代查询即可获得结果:

    db.collection.mapReduce(
        function () {
          delete this["_id"];
    
          for ( var k in this ) {
            var list = this[k];
            list.forEach(function(v) {
              emit( { k: k , v: v }, 1 );
            });
          }
        },
        function (key,values) {
          return values.length;
        },
        { "out": { "inline": 1 } }
    )
    

    输出将是:

    { "_id" : { "k" : "t1", "v" : 1 }, "value" : 2 }
    { "_id" : { "k" : "t1", "v" : 2 }, "value" : 1 }
    { "_id" : { "k" : "t1", "v" : 3 }, "value" : 1 }
    { "_id" : { "k" : "t1", "v" : 6 }, "value" : 2 }
    { "_id" : { "k" : "t2", "v" : 1 }, "value" : 1 }
    { "_id" : { "k" : "t2", "v" : 8 }, "value" : 3 }
    { "_id" : { "k" : "t2", "v" : 9 }, "value" : 1 }
    

    还取决于您是否需要灵活使用“关键”名称。

    【讨论】:

    • 谢谢。输出对我没有任何影响。我没有得到你关于灵活键名的最后一句话。
    • @SalvadorDali 终于醒了。我想错了方向,但现在也有汇总答案
    【解决方案2】:
    db.nr.aggregate([ { $unwind: "$t1" }, { $group: { '_id': '$t1','count' : { '$sum':1 } } }, {    $project : {_id: 0, t1: '$_id', count:1}}, { $sort: { t1:1 } } ])
    "count" : 2, "t1" : 1 }
    "count" : 1, "t1" : 2 }
    "count" : 1, "t1" : 3 }
    "count" : 2, "t1" : 6 }
    
    db.nr.aggregate([ { $unwind: "$t2" }, { $group: { '_id': '$t2','count' : { '$sum':1 } } }, { $project : { _id: 0, t2: '$_id', count:1 } }, { $sort: { t2:1 } } ])
    "count" : 1, "t2" : 1 }
    "count" : 3, "t2" : 8 }
    "count" : 1, "t2" : 9 }
    

    【讨论】:

    • 谢谢。但我不确定这会比我的 foreach 语句更好(因为这里会有两次扫描)。这正是我告诉我希望使用一次聚合的原因。尽管如此,请接受我的 +1。
    • 不幸的是,展开 t1 和 t2 最终会得到笛卡尔积,因此对于单个聚合查询而言,独立计数并不是一件容易的事。
    • @VladMihalcea 如果您将它们组合到同一个字段中,则不会。看我的回答。
    • @NeilLunn 非常巧妙的解决方案。通常我会使用一个额外的业务关键字段来组合双未缠绕的管道,我从来没有想过像你那样添加文字。非常聪明!
    猜你喜欢
    • 1970-01-01
    • 2019-06-18
    • 1970-01-01
    • 1970-01-01
    • 2020-11-26
    • 1970-01-01
    • 2019-09-24
    • 1970-01-01
    • 2023-03-21
    相关资源
    最近更新 更多