这是我发现有趣的 MongoDB 挑战。
给定一个带时间戳的集合 events
和一个特定的输入选择器 _object
,我们如何聚合所关注的 event
文档列表 输入?
例如,Mongoose 中的模式:
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) ) }
]
在这种情况下,选择我们想要的结果集的第一个元素很容易:
Event.find({ _object: ObjectId('123abc...1' }).limit(2).exec(function(err, events) {
// select the _second_ element of our result set
console.log(events[1];);
});
...但是在给定输入 _object
的情况下,我们如何聚合第二个元素的列表,它可能有很多条目?
奖励:我们可以选择第 *n* 个元素吗?
最佳答案
不确定这是否真的是这样的最佳用法,但您似乎想要某种方式来跳过每个游标结果。真的,这可能是您应该做的,尽管这当然意味着实际上检索所有结果,即使您丢弃了不需要的结果。
如果您真的坚持要让服务器执行此操作,那么一种可能的方法是使用 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
键对结果进行排序。这是设计使然,因为其目的是确保事情按顺序“减少”。但是您可以使用该值来影响您的结果,您也可以对输入进行“排序”:
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 }
}
)
因此向后计数并通过使发出的键为负值来对输出进行排序:
{ "_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" } }
为了以其他方式“跳过”到一个选择点,那么您可以更改逻辑
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" } }
请记住,所有这些都会“扫描”对输入查询有效的所有结果,因此您实际上只是在服务器端而非客户端“跳过”光标。
关于javascript - 挑战 : Aggregate Second Element from Input Element,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24471046/