通过协会的mongoose限制
我有这样一个集合:
[ { 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 } ]
给定一个查询与相关的父母ids, ['b','c']
,我需要得到每个父母的前3个结果,希望DESC – 按w
:
[ { 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);
为聚合部分? 我怎么会甚至限制呢? 用手? 这是相当的没有logging。
无论如何,在这条路上的某个方向将会很棒。 我使用mongoose@latest
。
正如所指出的那样,不幸的是,使用当前存在的MongoDB
的聚合框架是无法实现的,正如你所提到的, map-reduce
会是一个矫枉过正的事情。
但是还有其他的方法:
方法一:
- 维护一个表示基于
w
字段的层次结构的variables,或者要对结果集进行sorting的字段。 一旦在插入过程中将variables添加到每个文档。 - 您的文档将包含一个名为
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
字段的sorting顺序获取排名前3
结果。 你可以很容易地聚合如下:
var levels = [1,2,3]; // indicating the records in the range that we need to pick up, // from each parent.
- 匹配所有
a
或b
的父母。 - 按
w
字段sortinglogging。 - 由
parent
分组。 一旦你分组,父母的所有文件成为分组logging的子文件,因此允许你应用$ redact阶段。 - 现在应用
$redact
阶段来修改那些子级文档,其级别不是我们所寻求的级别的子集。 我们保持level
为一个数组,因为它更容易在其上应用$setIsSubset
运算符。 否则我们需要$in
,这在$cond
expression式中是不被支持的。
码:
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; })
这相当慢,因为每个父代的所有logging都将由MongoDB
返回。 select是你的,取决于你的应用程序是什么。
在阅读了一个类似问题的答案之后,我决定沿着这条路走下去,然后我写了一个模块,它为您提供了一定程度的灵活性。
基于我最初的问题的代码示例:
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); } }