【问题标题】:Challenge: Aggregate Second Element from Input Element挑战:从输入元素聚合第二个元素
【发布时间】:2014-06-28 21:19:24
【问题描述】:

这是我觉得有趣的 MongoDB 挑战。

给定一个时间戳集合events 和一个特定的输入选择器_object,我们如何聚合一个跟随输入的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) ) }
]

在这种情况下,选择我们预期结果集中的 first 元素很容易:

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* 个元素吗?

【问题讨论】:

  • 你使用什么作为你的隐含顺序? limit(2) 没有排序并不能保证它会返回你想要的。您按 _id 顺序列出文档 - 这是您期望的顺序吗?
  • @AsyaKamsky 在这种情况下,只有 _id 字段中隐含的日期。更准确地说,意图是使用timestamp 字段。

标签: javascript node.js mongodb mapreduce aggregation-framework


【解决方案1】:

不确定这真的是这样的最佳用法,但您似乎想要某种方式跳过每个光标结果。确实,这可能是您应该做的,尽管这当然意味着实际上检索所有结果,即使您丢弃了您不想要的结果。

如果您确实坚持要让服务器执行此操作,那么一种可能的方法是使用 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"  } }

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

【讨论】:

  • 很好的答案!我打算让它打开一段时间,看看是否还有其他建议(可能不使用 MapReduce?)。
  • @martindale 只是为了解释 mapReduce 的用法,目的是引入一个顺序计数器,它将响应执行此操作所需的模运算。除非您的数据中有一些自然字段可以响应“每个n 记录”的要求,否则没有其他方法可以这样做。您可以每分钟左右执行类似时间戳的最后一个值的操作,但这与您似乎在询问的n 记录跳过不同。例如,聚合框架无法引入此序列。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-11-16
  • 2013-09-25
  • 1970-01-01
  • 1970-01-01
  • 2018-01-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多