挑战:从input元素聚合第二元素

对MongoDB来说,这是一个挑战,我觉得很有趣。

给定一个时间戳集合events和一个特定的inputselect器_object ,我们如何汇总一个input后面event文档列表?

例如,Mongoose中的Schema:

 var EventSchema = new Schema({ _object: { type: ObjectId } , timestamp: { type: Date, default: Date.now } }); 

示例集合:

 [ { _id: ObjectId('1'), _object: ObjectId('123abc...1', timestamp: 'Sat Jun 21 2014 16:30:01 GMT-0400 (EDT) ) } , { _id: ObjectId('2'), _object: ObjectId('123abc...2', timestamp: 'Sat Jun 22 2014 16:30:00 GMT-0400 (EDT) ) } , { _id: ObjectId('3'), _object: ObjectId('123abc...1', timestamp: 'Sat Jun 23 2014 16:30:01 GMT-0400 (EDT) ) } , { _id: ObjectId('4'), _object: ObjectId('123abc...3', timestamp: 'Sat Jun 24 2014 16:30:01 GMT-0400 (EDT) ) } , { _id: ObjectId('5'), _object: ObjectId('123abc...1', timestamp: 'Sat Jun 25 2014 16:30:01 GMT-0400 (EDT) ) } , { _id: ObjectId('6'), _object: ObjectId('123abc...4', timestamp: 'Sat Jun 26 2014 16:30:02 GMT-0400 (EDT) ) } , { _id: ObjectId('7'), _object: ObjectId('123abc...1', timestamp: 'Sat Jun 27 2014 16:30:00 GMT-0400 (EDT) ) } , { _id: ObjectId('8'), _object: ObjectId('123abc...3', timestamp: 'Sat Jun 28 2014 16:30:01 GMT-0400 (EDT) ) } , { _id: ObjectId('9'), _object: ObjectId('124abc...1', timestamp: 'Sat Jun 29 2014 16:30:00 GMT-0400 (EDT) ) } , { _id: ObjectId('10'), _object: ObjectId('124abc...2', timestamp: 'Sat Jun 30 2014 16:30:00 GMT-0400 (EDT) ) } ] 

假设我们的预期目标是ObjectId('123abc...1') 。 我们将使用我们特殊的方法来查询我们的集合,提供序号1 (而不是0 ,元素本身)的参数:

 Events.mySpecialMethod( { _object: ObjectId('123abc...1') } , 1 ).exec(function(err, output) { console.log(output); // contains intended results (see below) }); 

这种查询的预期输出是:

 [ { _id: ObjectId('2'), _object: ObjectId('123abc...2', timestamp: 'Sat Jun 22 2014 16:30:00 GMT-0400 (EDT) ) } , { _id: ObjectId('4'), _object: ObjectId('123abc...3', timestamp: 'Sat Jun 24 2014 16:30:01 GMT-0400 (EDT) ) } , { _id: ObjectId('6'), _object: ObjectId('123abc...4', timestamp: 'Sat Jun 26 2014 16:30:02 GMT-0400 (EDT) ) } , { _id: ObjectId('8'), _object: ObjectId('123abc...3', timestamp: 'Sat Jun 28 2014 16:30:01 GMT-0400 (EDT) ) } ] 

在这种情况下,select我们想要的结果集的第一个元素很容易:

 Event.find({ _object: ObjectId('123abc...1' }).limit(2).exec(function(err, events) { // select the _second_ element of our result set console.log(events[1];); }); 

…但是我们如何汇总第二个元素的列表给定一个input_object ,它可能有很多条目?

奖金:我们可以select* n * th元素吗?

不知道这真的是这样的最好的用法,但你似乎想要跳过每一个游标结果的某种方式。 真的,这可能是你应该做的,虽然这当然意味着即使丢弃了你不想要的那些结果,也实际上检索了所有的结果。

如果你真的坚持要让服务器做到这一点,那么一种可能的方法是使用mapReduce的JavaScript评估来为你做这件事。

考虑一下这个例子:

 { _id: 1, oth: "A", grp: "A" }, { _id: 2, oth: "B", grp: "A" }, { _id: 3, oth: "C", grp: "A" }, { _id: 4, oth: "D", grp: "A" }, { _id: 5, oth: "E", grp: "B" }, { _id: 6, oth: "F", grp: "B" }, { _id: 7, oth: "G", grp: "B" }, { _id: 8, oth: "H", grp: "B" } 

为了获得每一秒或nth项目,你基本上都是模数:

 db.sequence.mapReduce( function () { counter++; var id = this._id; delete this._id; if ( counter % seq == 0 ) emit( id, this ); }, function() {}, // blank mapper { "scope": { "counter": 0, "seq": 2 }, "out": { "inline": 1 } } ) 

给你这样的结果:

 { "_id" : 2, "value" : { "oth" : "B", "grp" : "A" } }, { "_id" : 4, "value" : { "oth" : "D", "grp" : "A" } }, { "_id" : 6, "value" : { "oth" : "F", "grp" : "B" } }, { "_id" : 8, "value" : { "oth" : "H", "grp" : "B" } } 

如果你想从一个起始位置查询,那么你可以发出一个查询部分

 db.sequence.mapReduce( function () { counter++; var id = this._id; delete this._id; if ( counter % seq == 0 ) emit( id, this ); }, function() {}, // blank mapper { "query": { "oth": "B" }, "scope": { "counter": 0, "seq": 2 }, "out": { "inline": 1 } } ) 

然后,你只是从那个位置工作:

 { "_id" : 3, "value" : { "oth" : "C", "grp" : "A" } }, { "_id" : 5, "value" : { "oth" : "E", "grp" : "B" } }, { "_id" : 7, "value" : { "oth" : "G", "grp" : "B" } } 

Map-reduce总是通过发射的_id键sorting结果。 这是为了确保事物“减less”的目的。 但是您可以使用该值来影响结果,还可以对input进行“sorting”:

 db.sequence.mapReduce( function () { counter++; var id = this._id; delete this._id; if ( counter % seq == 0 ) emit( -id, this ); }, function() {}, // blank mapper { "sort": { "oth": -1 }, "scope": { "counter": 0, "seq": 2 }, "out": { "inline": 1 } } ) 

因此,通过将发出的键设置为负值来倒数和sorting输出:

 { "_id" : -7, "value" : { "oth" : "G", "grp" : "B" } }, { "_id" : -5, "value" : { "oth" : "E", "grp" : "B" } }, { "_id" : -3, "value" : { "oth" : "C", "grp" : "A" } }, { "_id" : -1, "value" : { "oth" : "A", "grp" : "A" } } 

为了以其他方式“跳过”到一个select点,那么你可以改变逻辑

 db.sequence.mapReduce( function () { counter++; var id = this._id; delete this._id; if ( counter % seq == 0 ) seen++; if ( seen == skip && counter % seq == 0 ) emit( id, this ); }, function() {}, // blank mapper { "scope": { "counter": 0, "seq": 2, "seen": 0, "skip": 3 }, "out": { "inline": 1 } } ) 

这将带来两个第三序列:

 { "_id" : 6, "value" : { "oth" : "F", "grp" : "B" } } 

请记住,所有这些都会扫描所有对input查询有效的结果,因此您只是在服务器端而不是客户端上“跳过”光标。