3

我在 mongodb 中有一组文档,每个文档都有一个“组”字段,该字段指的是拥有该文档的组。文件如下所示:

{
  group: <objectID>
  name: <string>
  contents: <string>
  date: <Date>
}

我想构建一个查询,该查询返回每个组的最新 N 个文档。例如,假设有 5 个组,每个组有 20 个文档。我想编写一个查询,它将返回每个组的前 3 个,它将返回 15 个文档,每个组 3 个。每组得到 3 个,即使另一组有最近的第 4 个。

在 SQL 世界中,我相信这种类型的查询是通过“分区依据”和计数器完成的。mongodb中是否有这样的事情,没有对N组进行N + 1个单独的查询?

4

2 回答 2

6

您还不能使用聚合框架执行此操作 - 您可以获得每个组的 $max 或顶部日期值,但聚合框架还没有办法累积前 N 个,而且无法将整个文档推送到结果集中(仅个别字段)。

所以你必须依靠 MapReduce。这是可行的方法,但我确信有很多变体(所有变体都需要基于特定属性以某种方式对对象数组进行排序,我从这个问题的一个答案中借用了我的解决方案。

Map 函数 - 将组名作为键输出,将文档的整个其余部分作为值输出 - 但它将其输出为包含数组的文档,因为我们将尝试为每个组累积一个结果数组:

map = function () { 
    emit(this.name, {a:[this]}); 
}

reduce 函数会将属于同一组的所有文档累积到一个数组中(通过 concat)。请注意,如果您通过检查日期优化 reduce 以仅保留前五个数组元素,那么您将不需要 finalize 函数,并且在运行 mapreduce 期间将使用更少的内存(它也会更快)。

reduce = function (key, values) {
    result={a:[]};
    values.forEach( function(v) {
        result.a = v.a.concat(result.a);
    } );
    return result;
}

由于我保留了每个键的所有值,因此我需要一个 finalize 函数来仅提取每个键的最新五个元素。

final = function (key, value) {
      Array.prototype.sortByProp = function(p){
       return this.sort(function(a,b){
       return (a[p] < b[p]) ? 1 : (a[p] > b[p]) ? -1 : 0;
      });
    }

    value.a.sortByProp('date');
    return value.a.slice(0,5);
}

使用类似于您提供的模板文档,您可以通过调用 mapReduce 命令来运行它:

> db.top5.mapReduce(map, reduce, {finalize:final, out:{inline:1}})
{
    "results" : [
        {
            "_id" : "group1",
            "value" : [
                {
                    "_id" : ObjectId("516f011fbfd3e39f184cfe13"),
                    "name" : "group1",
                    "date" : ISODate("2013-04-17T20:07:59.498Z"),
                    "contents" : 0.23778377776034176
                },
                {
                    "_id" : ObjectId("516f011fbfd3e39f184cfe0e"),
                    "name" : "group1",
                    "date" : ISODate("2013-04-17T20:07:59.467Z"),
                    "contents" : 0.4434165076818317
                },
                {
                    "_id" : ObjectId("516f011fbfd3e39f184cfe09"),
                    "name" : "group1",
                    "date" : ISODate("2013-04-17T20:07:59.436Z"),
                    "contents" : 0.5935856597498059
                },
                {
                    "_id" : ObjectId("516f011fbfd3e39f184cfe04"),
                    "name" : "group1",
                    "date" : ISODate("2013-04-17T20:07:59.405Z"),
                    "contents" : 0.3912118375301361
                },
                {
                    "_id" : ObjectId("516f011fbfd3e39f184cfdff"),
                    "name" : "group1",
                    "date" : ISODate("2013-04-17T20:07:59.372Z"),
                    "contents" : 0.221651989268139
                }
            ]
        },
        {
            "_id" : "group2",
            "value" : [
                {
                    "_id" : ObjectId("516f011fbfd3e39f184cfe14"),
                    "name" : "group2",
                    "date" : ISODate("2013-04-17T20:07:59.504Z"),
                    "contents" : 0.019611883210018277
                },
                {
                    "_id" : ObjectId("516f011fbfd3e39f184cfe0f"),
                    "name" : "group2",
                    "date" : ISODate("2013-04-17T20:07:59.473Z"),
                    "contents" : 0.5670706110540777
                },
                {
                    "_id" : ObjectId("516f011fbfd3e39f184cfe0a"),
                    "name" : "group2",
                    "date" : ISODate("2013-04-17T20:07:59.442Z"),
                    "contents" : 0.893193120136857
                },
                {
                    "_id" : ObjectId("516f011fbfd3e39f184cfe05"),
                    "name" : "group2",
                    "date" : ISODate("2013-04-17T20:07:59.411Z"),
                    "contents" : 0.9496864483226091
                },
                {
                    "_id" : ObjectId("516f011fbfd3e39f184cfe00"),
                    "name" : "group2",
                    "date" : ISODate("2013-04-17T20:07:59.378Z"),
                    "contents" : 0.013748752186074853
                }
            ]
        },
        {
            "_id" : "group3",
                        ...
                }
            ]
        }
    ],
    "timeMillis" : 15,
    "counts" : {
        "input" : 80,
        "emit" : 80,
        "reduce" : 5,
        "output" : 5
    },
    "ok" : 1,
}

每个结果都将 _id 作为组名,将值作为该组名集合中最近五个文档的数组。

于 2013-04-17T20:42:46.400 回答
-1

您需要在 $limit 阶段通过管道传输的聚合框架 $group 阶段...您还希望以某种方式对记录进行 $sort ,否则限制将具有未定义的行为,返回的文档将是伪随机的(内部使用的顺序由蒙哥)

类似的东西: db.collection.aggregate([{$group:...},{$sort:...},{$limit:...}])

如果您想了解更多信息,这里有文档

于 2013-04-17T18:26:00.497 回答