协会限制猫鼬

Posted

技术标签:

【中文标题】协会限制猫鼬【英文标题】:Mongoose limit by association 【发布时间】:2015-03-08 02:21:13 【问题描述】:

我有一个这样的收藏:

[
   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); 来进行聚合部分吗?我怎么会 $limit 呢?用手?它的文档很少。

无论如何,这里的一些方向会很棒。我正在使用mongoose@latest

【问题讨论】:

jira.mongodb.org/browse/SERVER-9377 【参考方案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] 
])

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

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 返回。选择权在您手中,具体取决于适合您的应用程序。

【讨论】:

w 实际上是一个日期,而不仅仅是一个层次结构级别 没关系。 “级别”只是一个有助于识别***记录的字段。当您插入一个文档时,您需要确保它是根据您想要排序的字段进行设置的。【参考方案2】:

读完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);
  

【讨论】:

这种方法不会在数据库服务器上切掉父级的顶部 n 记录,并且链接的答案有太多阶段并且涉及大量开销,这可能会导致非常低如果您的数据集很大,性能。如果您想要最高的n 记录,您最终会执行n 组和n 展开阶段,不用管项目阶段之间的数量。 它确实分割了顶部的n 记录,它基本上是将链接的答案制成一个模块。当然,我担心开销。我想我应该衡量一下 如果我采用您的第一种方法,我必须在每次插入时修改 parent 的每个文档。 是的,我同意,您需要批量更新。因此,如果您采用第一种方法,则需要考虑您的数据集和应用程序代码(这将需要一些返工)来选择最佳解决方案。

以上是关于协会限制猫鼬的主要内容,如果未能解决你的问题,请参考以下文章

中国半导体协会发表严正声明!

浅析Redis的BigKey(阿里巴巴技术协会ATA同步发送)

中国语文报刊协会的组织章程

比特币协会发布2020年度报告,重点展示了协会推动Bitcoin SV发展的工作及成果

[航海协会]基因切割

即墨区个体协会与私营企业协会主席孙宗栋生平简介