有可能通过mongodb中的四分位数有效聚合吗?

时间:2015-01-05 02:19:19

标签: mongodb mapreduce mongodb-query aggregation-framework

例如,假设我有{10,000}个已排序的文档,我希望aggregate()开启。但是我想将它们分成四分位数:前25%,25% - 50%,50% - 75%,最低25%。在一个管道中有没有办法解决这个问题,而不必为每个四分位数做4个独立的管道?

类似的东西:

aggregate()
- Transform into {quartile1: [list of docs], quartile2: [list of docs], ...}
- Run other pipeline commands

或者我是否需要运行4个单独的aggregate()管道?

谢谢!

2 个答案:

答案 0 :(得分:2)

对于你所问的问题,“聚合框架可以吗?”,那么答案就是不可以。另一方面,你可以用mapReduce做这样的事情。但我想提出的真实情况是这种情况的可靠性,以及“有什么意义?”。

在这里表达怀疑主义的最佳方式是充分解释事情。

聚合框架不能做这种事情,因为它在处理10,000个文档的过程中没有“当前它在哪里”的概念。为此,您需要某种“变量”,当您处理每个“已排序”的项目时,该变量会递增。

您可以使用该方法根据您“排序”的值“标记”项目。但问题仍然是“你怎么知道”特定值在整个结果集中的排名。因此,除非有明确的方法,否则你不能投射这样一个领域。

只有当您准备使用不一定是所有结果的“四分之一”的“设定范围”时,您才能使用.aggregate()执行此操作:

db.collection.aggregate([
    { "$project": {
        "grouping": {
            "$cond": [
                { "$lt": [ "$score", 25 ]  },
                3,
                { "$cond": [
                    { "$lt": [ "$score", 50 ] },
                    2,
                    { "$cond": [
                        { "$lt": [ "$score", 75 ] },
                        1,
                        0
                    ]}
                ]}
            ]
        },
        "score": 1,
        "otherField": 1
    }},
    { "$sort": { "grouping"  1, "score": -1 }
])

另一方面,.mapReduce()可以访问这样的全局变量。因此,基本上可以检查计数器,以查看它是否在您预期的分组中。基本形式:

db.collection.mapReduce(
    function() {
        counter++;
        if ( counter % ( total / 4 ) == 0 )
            grouping++;

        var id = this._id;
        delete this._id;

        emit({ "grouping": grouping, "_id": id },this);
    },
    function() {}, // no need for a reducer
    {
        "out": { "replace": "results" },
        "scope": { "counter": 0, "grouping": 0, "total": 10000 },
        "sort": { "score": -1 }
    }
)

它基本上做你想要的。但不是以非常灵活的方式或非常可靠的方式。主要是因为在大多数现实情况下,并不能保证始终有10,000个结果,特别是如果运行一个带条件的查询来计算,另一个查询将结果“标记”到它们的分组中。

因此,考虑到这里根本没有发生真正的“聚合”,那么最好的方法就是简单地将数据查询到列表中:

var cursor = db.collection.find({}).sort({ "score": -1 });
var total = cursor.count();

var counter = 0,
    grouping = 0;

cursor.forEach(function(doc) {
    counter++;
    if ( counter % ( total / 4 ) == 0 )
        grouping++;
    doc._id = { "grouping": grouping, "_id": doc._id };

    // Do something with "doc"
});

不是很优雅,但指出了基本技术。

另外要注意,你建议的数组[]并不是一个好主意。即使在10,000文档场景中,在单个文档响应中产生的2,500个元素阵列和基本上10,000个项目,也可能“炸毁”16MB BSON限制。至少它不是很容易管理,用光标更好。

因此,您既可以选择服务器来“标记”这些项目,也可以在阅读时“标记”它们。至少在后一种情况下,您可以访问结果的“光标”

答案 1 :(得分:0)

我认为需要沿着mongo docs中找到的4条管道。

db.articles.aggregate( [
                        { $match : { score : { $gt : 70, $lte : 90 } } },
                        { $group: { _id: null, count: { $sum: 1 } } }
                       ] );

但是针对所有正常的数据库规则,请考虑双重浸入或两次输入数据。一次用于基数场,另一次用于四分位场。这种方法很难,但允许快速读取;一个人可以在索引字段上进行简单的查找,并执行单个聚合。

{name: cartman, score: 56, quartile: 3 }
{name: kenny, score: 36, quartile: 2 }
{name: kyle, score: 76, quartile: 4 }

db.scores.find( {"quartile" : 3 });

db.scores.aggregate( [
                        { $group: { _id: null, count: { $quartile: 1 } } }
                       ] );