如何在mongodb中获取某个键的最大值、最小值

2014-11-04

最近,遇到这样一个需求,就是计算mongodb中一个集合里某个键的最大最小值,官方没有比较简洁的指令,只能想想其他办法。

MongoDB版本是2.4,在数据库s中test集合下,每个文档都是这样的形式(文档中省略了其他的键):

> db.test.findOne()
{
    "_id" : ObjectId("5456d5b9cc7a670d39a9ecdb"),
    "vid" : 6096,
}
> 

我的目标是获取vid的最大值和最小值。

第1种解决办法:使用MapReduce

我在MapReduce in MongoDB介绍过在MongoDB中如何使用mapreduce。官方的一篇文档Finding Max And Min Values for a given Key也介绍了如何在某种情况下如何使用mapreduce获取最大值/最小值。

对这次需求而言,需要使用下面的代码:

var map = function() {
    id = this.vid;
    emit("vid",{max:id, min:id})    
}
var reduce = function(key, values) {
    var min = values[0].min;
    var max = values[0].max;
    for ( var i=1; i<values.length; i++ ) {
        if (min > values[i].min) {
            min = values[i].min;
        }   
        if (max < values[i].max) {
            max = values[i].max;
        }   
    }
    return {"min":min, "max":max};
}

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

执行结果如下:

{
    "results" : [
        {
            "_id" : "vid",
            "value" : {
                "min" : 1,
                "max" : 6110
            }
        }
    ],
    "timeMillis" : 1078,
    "counts" : {
        "input" : 6110,
        "emit" : 6110,
        "reduce" : 62,
        "output" : 1
    },
    "ok" : 1,
}

对于小数据集,速度很快。然后我用了一个大数据集,一个有12000左右个文档的集合,文档数量不算多,但是每个文档很大,最终占用30GB左右的空间,在对vid进行索引的情况下,在10分钟内没有输出结果,我就把它中断了。

第2种解决办法:使用一个文档来记录最大最小值

在小数据集的情况下,将得到的最小值,最大值放入一个文档里。然后,当插入新的数据时,判断是否更新最小值、最大值。

还有更多方法

2015-05-23补充。
感谢@lhrkkk的提醒,可见自己对mongodb了解的还不够。

假设现在集合link下有以下三个文档:

{ "_id" : NumberLong(22), "status" : 1, "topic_id" : NumberLong(34)}
{ "_id" : NumberLong(23), "status" : 1, "topic_id" : NumberLong(35)}
{ "_id" : NumberLong(24), "status" : 1, "topic_id" : NumberLong(35)}

使用sort

> db.link.find().sort({"topic_id":1}).limit(1)   // topic_id最小的doc
{ "_id" : NumberLong(22), "status" : 1, "topic_id" : NumberLong(34)}
> db.link.find().sort({"topic_id":-1}).limit(1)  // topic_id最大的doc
{ "_id" : NumberLong(23), "status" : 1, "topic_id" : NumberLong(35)}
> db.link.find().sort({"topic_id":-1}).limit(2)  // topic_id最大两个的doc
{ "_id" : NumberLong(23), "status" : 1, "topic_id" : NumberLong(35)}
{ "_id" : NumberLong(24), "status" : 1, "topic_id" : NumberLong(35)}

使用aggregate

// 获取最小的_id
> db.link.aggregate(
   [
     {
       $group:
         {
           _id: "min_id",
           min_id: { $min: "$_id" }
         }
     }
   ]
) // Enter

{
    "result" : [
        {
            "_id" : "min_id",
            "min_id" : NumberLong(22)
        }
    ],
    "ok" : 1
}

// 按照topic_id分组,得到每组中的_id的最小值
> db.link.aggregate(
   [
     {
       $group:
         {
           _id: "$topic_id",
           min_id: { $min: "$_id" }
         }
     }
   ]
) // Enter

{
    "result" : [
        {
            "_id" : NumberLong(35),
            "min_id" : NumberLong(23)
        },
        {
            "_id" : NumberLong(34),
            "min_id" : NumberLong(22)
        }
    ],
    "ok" : 1
}

( 完 )

( 完 )