通过关联限制猫鼬限制

时间:2015-01-09 17:33:07

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

我有一个这样的集合:

[
  { parent: 'a', d1: '1', d2: '2', d3: '3', w: 10 },
  { parent: 'a', d1: '1', d2: '2', d3: '3', w: 20 },
  { parent: 'a', d1: '1', d2: '2', d3: '3', w: 30 },
  { parent: 'a', d1: '1', d2: '2', d3: '3', w: 40 },
  { parent: 'a', d1: '1', d2: '2', d3: '3', w: 50 },
  { parent: 'a', d1: '1', d2: '2', d3: '3', w: 60 },
  { parent: 'b', d1: '1', d2: '2', d3: '3', w: 10 },
  { parent: 'b', d1: '1', d2: '2', d3: '3', w: 13 },
  { parent: 'b', d1: '1', d2: '2', d3: '3', w: 14 },
  { parent: 'b', d1: '1', d2: '2', d3: '3', w: 15 },
  { parent: 'c', d1: '1', d2: '2', d3: '3', w: 10 },
  { parent: 'c', d1: '1', d2: '2', d3: '3', w: 100 },
  { parent: 'c', d1: '1', d2: '2', d3: '3', w: 200 },
  { parent: 'c', d1: '1', d2: '2', d3: '3', w: 300 }
]

如果查询了相关的父ID ['b','c'],我需要找回每个父亲的前3个结果,希望按w进行DESC排序:

[
  { parent: 'b', d1: '1', d2: '2', d3: '3', w: 15 },
  { parent: 'b', d1: '1', d2: '2', d3: '3', w: 14 },
  { parent: 'b', d1: '1', d2: '2', d3: '3', w: 13 },
  { parent: 'c', d1: '1', d2: '2', d3: '3', w: 300 },
  { parent: 'c', d1: '1', d2: '2', d3: '3', w: 200 },
  { parent: 'c', d1: '1', d2: '2', d3: '3', w: 100 }
]

使用.find().limit()将返回前N个结果,而不是每个parent的前N个结果。使用.aggregate()我想出了如何按parent进行汇总,但我无法弄清楚如何{父} $limit,也不知道如何将整个文件归还{parent: 'b', items: [{..}, {..}] }而不仅仅是组数据。我可以使用parent获得parent,或者使用$push得到.mapReduce和某个字段上的数组,但这仍然不行。

最后我还试过了emit(this.project, this);,但这似乎有点矫枉过正,对于聚合部分我不会mongoose@latest吗?我怎么会限制它呢?用手?它完全没有记录。

无论如何,在这方面要走哪条路会很棒。我使用{{1}}。

2 个答案:

答案 0 :(得分:1)

作为pointed,遗憾的是,使用当前存在的MongoDB聚合框架无法实现这一点,正如您所提到的那样,map-reduce将是一种过度杀伤。

但是还有其他方法:

方法A:

  • 根据w维护一个表示层次结构级别的变量 字段,或您要对结果集进行排序的字段。一旦 在插入过程中将变量添加到每个文档。
  • 您的文档将包含一个名为level的新字段 一个单值的数组。我们将讨论为什么这需要成为一个 数组而不是简单的字段。

插入脚本:

db.collection.insert([
  { parent: 'a', d1: '1', d2: '2', d3: '3', w: 10,level:[6] },
  { parent: 'a', d1: '1', d2: '2', d3: '3', w: 20,level:[5] },
  { parent: 'a', d1: '1', d2: '2', d3: '3', w: 30,level:[4] },
  { parent: 'a', d1: '1', d2: '2', d3: '3', w: 40,level:[3] },
  { parent: 'a', d1: '1', d2: '2', d3: '3', w: 50,level:[2] },
  { parent: 'a', d1: '1', d2: '2', d3: '3', w: 60,level:[1] },
  { parent: 'b', d1: '1', d2: '2', d3: '3', w: 10,level:[4] },
  { parent: 'b', d1: '1', d2: '2', d3: '3', w: 13,level:[3] },
  { parent: 'b', d1: '1', d2: '2', d3: '3', w: 14,level:[2] },
  { parent: 'b', d1: '1', d2: '2', d3: '3', w: 15,level:[1] },
  { parent: 'c', d1: '1', d2: '2', d3: '3', w: 10,level:[4] },
  { parent: 'c', d1: '1', d2: '2', d3: '3', w: 100,level:[3] },
  { parent: 'c', d1: '1', d2: '2', d3: '3', w: 200,level:[2] },
  { parent: 'c', d1: '1', d2: '2', d3: '3', w: 300,level:[1] }
])

假设您希望根据每个父级的3字段的排序顺序获得最高w结果。您可以轻松汇总如下:

var levels = [1,2,3];  // indicating the records in the range that we need to pick up,
                       // from each parent. 
  • 匹配ab
  • 的所有父母
  • w字段对记录进行排序。
  • parent分组。分组后,为父母提供所有文件 成为分组记录的子文档,因此允许您 应用$redact阶段。
  • 现在应用$redact阶段来编辑那些子文档 等级不是我们寻求的等级的子集。我们保持level为 一个数组,因为它可以更容易地应用$setIsSubset 操作员就可以了。否则我们需要$in,而不是 支持$cond表达式。

代码:

Model.aggregate(
{$match:{"parent":{$in:["a","b"]}}},
{$sort:{"w":-1}},
{$group:{"_id":"$parent",
         "rec":{$push:"$$ROOT"}}},
{$redact:{$cond:[{$setIsSubset:[{$ifNull:["$levels",[1]]},
                               inp]},
                 "$$DESCEND","$$PRUNE"]}},
,function(err,resp){
 // handle response
})

获得的输出是完美的,正如我们想要的那样:(仅显示b组,以保持缩短)

{
        "_id" : "b",
        "rec" : [
                {
                        "_id" : ObjectId("54b030a3e4eae97f395e5e89"),
                        "parent" : "b",
                        "d1" : "1",
                        "d2" : "2",
                        "d3" : "3",
                        "w" : 15,
                        "level" : [
                                1
                        ]
                },
                {
                        "_id" : ObjectId("54b030a3e4eae97f395e5e88"),
                        "parent" : "b",
                        "d1" : "1",
                        "d2" : "2",
                        "d3" : "3",
                        "w" : 14,
                        "level" : [
                                2
                        ]
                },
                {
                        "_id" : ObjectId("54b030a3e4eae97f395e5e87"),
                        "parent" : "b",
                        "d1" : "1",
                        "d2" : "2",
                        "d3" : "3",
                        "w" : 13,
                        "level" : [
                                3
                        ]
                }
        ]
}

方法B:

子文档的编辑在客户端完成:

var result = db.collection.aggregate([
{$match:{"parent":{$in:["a","b"]}}},
{$sort:{"w":-1}},
{$group:{"_id":"$parent","rec":{$push:"$$ROOT"}}}
]).map(function(doc){
    doc.rec.splice(0,3);
    return doc;
})

这相当慢,因为MongoDB会返回每个父级的所有记录。您的选择取决于您的应用程序。

答案 1 :(得分:0)

在阅读this answer to a similar question之后,我决定沿着这条道路走下去,我写了a module that builds the aggregate query for you,具有一定的灵活性。

基于我的初始问题的示例代码:

var _ = require('lodash');
var limited = require('limited');
var D = require('./models/D');

function getLastDsByParent (ids, done) {
  var options = {
    model: D,
    field: 'parent',
    query: { parent : { $in: ids } },
    limit: 3,
    sort: { w: -1 }
  };
  limited(options, find);

  function find (err, result) {
    if (err) {
      done(err); return;
    }

    D
      .find({ _id: { $in: _.flatten(result, 'documents') } })
      .lean()
      .exec(done);
  }
}