协会限制猫鼬
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.
匹配所有a
或b
的父母。
按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同步发送)