【问题标题】:Mongodb : Mapreduce query filter positional operatorMongodb:Mapreduce 查询过滤器位置运算符
【发布时间】:2014-04-09 10:16:25
【问题描述】:

我有一个庞大的数据集(以百万计),格式如下:

{
  "userid" : "codejammer",
  "data" : [       
   {"type" : "number", "value" : "23748"},
   {"type" : "message","value" : "one"}
  ]
}

我想得到 message 的计数值 oneuserid - codejammer

以下是我正在使用的 mapreduce 函数: 地图:

var map = function(){
   emit(this.data[0].value,1);
}

减少

var reduce = function(key,values){
    return Array.sum(values);
}

选项

var options = {
             "query":{"userid" : "codejammer",
                 "data.type" : "message"},
             "out" : "aggregrated"
            }

mapreduce 函数成功执行,输出如下:

{
 "_id" : 23748,
  "value" : 1
}

但是,我期待以下输出:

{
 "_id" : one,
 "value" : 1
}

选项中的query 过滤器将整个数组发送到映射函数,即使我特别要求data.type : "message"

有没有办法在query 过滤器中使用projection 运算符来仅获取数组中所需的项目?

非常感谢您的帮助。

【问题讨论】:

    标签: javascript node.js mongodb mapreduce aggregation-framework


    【解决方案1】:

    实际上,您最好使用aggregate 执行此操作。在这种情况下不需要 mapReduce,聚合框架作为本机代码运行,并且比通过 JavaScript 解释器运行要快得多:

    db.collection.aggregate([
        // Still makes sense to match the documents to reduce the set
        { "$match": {
            "userid": "codejammer",
            "data": { "$elemMatch": { 
                "type": "message", "value": "one" 
            }}
        }},
    
        // Unwind to de-normalize the array content
        { "$unwind": "$data" },
    
        // Filter the content of the array
        { "$match": {
            "data.type": "message",
            "data.value": "one"
        }},
    
        // Count all the matching entries
        { "$group": {
            "_id": null,
            "count": { "$sum": 1 }
        }}
    ])
    

    当然,如果您实际上在“数据”数组中只有一个“消息”,这将变得非常简单:

    db.collection.aggregate([
        // Match the documents you want
        { "$match": {
            "userid": "codejammer",
            "data": { "$elemMatch": { 
                "type": "message", "value": "one" 
            }}
        }},
    
        // Simply count the documents
        { "$group": {
            "_id": null,
            "count": { "$sum": 1 }
        }}
    ])
    

    当然,这实际上与此没有什么不同:

    db.collection.find({
        "userid": "codejammer",
        "data": { "$elemMatch": { 
            "type": "message", "value": "one" 
        }}
    }).count()
    

    因此,虽然有一种方法可以使用 mapReduce 来做到这一点,但显示的其他方法要好得多。特别是在新发布的 2.6 及更高版本中。在较新的版本中,聚合管道可以利用磁盘存储来处理非常大的集合。

    但是要使用mapReduce 进行计数,您基本上是以错误的方式进行的。投影不能用作输入,因此您需要将元素从结果中取出。即使不是这种情况,我仍然会考虑您的数组中可能有多个匹配值:

    db.collection.mapReduce(
        function() {
            var userid = this.userid;
            this.data.forEach(function(doc) {
                if ( doc == condition )
                    emit( userid, 1 ); 
            });
        },
        function(key,values) {
            return values.length;
        },
        {
            "query": { 
                "userid": "codejammer",
                "data": { "$elemMatch": { 
                    "type": "message", "value": "one" 
                }}
            },
            "scope": {
               "condition": {"type" : "message", "value" : "one"}
            },
            "out": { "inline": 1 }
        }
    )
    

    因此,当在数据数组中找到符合您的条件的文档时,这与此“发出”公共键的值大致相同。所以你不能只投影匹配的元素,你会得到所有的元素,然后用这种方式过滤。

    由于您只期望一个结果,因此实际输出到集合是没有意义的,因此只需将其作为一个发送出去。

    但基本上,如果必须这样做,请使用聚合方法。

    【讨论】:

    • 有一个limitation 可以按大小进行聚合。如果这次尝试成功,我需要做更复杂的 mapreduc :)
    • @Neil,第二个$match 阶段不是多余的吗?
    • @AnandJayabalan 不,不是。目的是实际过滤数组的元素。但是正如我在扩展它时所说的那样,如果实际上只有一个元素,那么只需计算来自 .find() 的结果
    • @codejammer 你会发现这里有足够多的信息来完成你想要的。如前所述,从 2.6 版(昨天发布)开始,限制不再存在。
    • @codejammer 几乎所有你不需要硬编码的东西,它只是代码中的数据结构,所以即使是函数部分实际上也可以写成字符串。但如果它更适合您的感受,我将列表更改为使用范围内定义的变量。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-10-31
    • 2011-09-10
    • 2020-07-20
    • 1970-01-01
    • 2017-01-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多