获得每个第N个项目

我有一个Mongodb模式,看起来大致如下:

[
  {
    "name" : "name1",
    "instances" : [ 
      {
        "value" : 1,
        "date" : ISODate("2015-03-04T00:00:00.000Z")            
      }, 
      {
        "value" : 2,
        "date" : ISODate("2015-04-01T00:00:00.000Z")
      }, 
      {
        "value" : 2.5,
        "date" : ISODate("2015-03-05T00:00:00.000Z")
      },
      ...
    ]
  },
  {
    "name" : "name2",
    "instances" : [ 
      ...
    ]
  }
]

每个元素的实例数量可能相当大。

我有时只想获取数据的一个样本,也就是说,每获得一个第三个实例,或者每获得一个第十个实例,就可以得到这张图片。

我可以通过获取所有实例并在服务器代码中对其进行过滤来实现此目标,但是我想知道是否有办法通过使用某个聚合查询来完成此操作。

有任何想法吗?


更新

假设数据结构如@SylvainLeroux所建议的那样是平坦的,即:

[
  {"name": "name1", "value": 1, "date": ISODate("2015-03-04T00:00:00.000Z")},
  {"name": "name2", "value": 5, "date": ISODate("2015-04-04T00:00:00.000Z")},
  {"name": "name1", "value": 2, "date": ISODate("2015-04-01T00:00:00.000Z")},
  {"name": "name1", "value": 2.5, "date": ISODate("2015-03-05T00:00:00.000Z")},
  ...
]

获得每一个第N项(具体name )的任务会更容易吗?


看起来你的问题清楚地问到“得到每一个实例”,这似乎是一个非常明确的问题。

诸如.find()类的查询操作实际上只能在投影和操作符(例如允许单个匹配数组元素的位置$ match操作符或$elemMatch的常规字段“选择”之外以“按原样”返回文档。

当然有$slice ,但是这只允许在阵列上进行“范围选择”,所以再次不适用。

.aggregate().mapReduce()可以修改服务器上的结果。 前者不会以“切分”阵列的方式“发挥得很好”,至少不会受到“n”个元素的影响。 然而,由于mapReduce的“function()”参数是基于JavaScript的逻辑,所以你有更多的空间来玩。

对于分析过程和“仅”分析目的,只需使用.filter()通过mapReduce过滤数组内容:

db.collection.mapReduce(
    function() {
        var id = this._id;
        delete this._id;

        // filter the content of "instances" to every 3rd item only
        this.instances = this.instances.filter(function(el,idx) {
            return ((idx+1) % 3) == 0;
        });
        emit(id,this);
    },
    function() {},
    { "out": { "inline": 1 } } // or output to collection as required
)

在这一点上,它实际上只是一个“JavaScript运行者”,但如果这只是为了分析/测试,那么这个概念没有任何问题。 当然,输出并不是“完全”如何构建文档,但它与mapReduce可以获得的接近传真一样。

我在这里看到的另一个建议需要创建一个包含所有项目“非规范化”的新集合,并将数组中的“索引”作为非常规_id键的一部分插入。 这可能会产生一些您可以直接查询的内容,但是您仍然必须执行“每第n个项目”:

db.resultCollection.find({
     "_id.index": { "$in": [2,5,8,11,14] } // and so on ....
})

因此,制定并提供“每第n个项目”的指标值以获得“每第n个项目”。 所以这似乎并不能解决问题。

如果输出形式对您的“测试”目的来说似乎更理想,那么对这些结果的更好的后续查询将使用聚合管道,其中$redact

db.newCollection([
    { "$redact": {
        "$cond": {
            "if": {
                "$eq": [ 
                    { "$mod": [ { "$add": [ "$_id.index", 1] }, 3 ] },
                0 ]
            },
            "then": "$$KEEP",
            "else": "$$PRUNE"
        }
    }}
])

至少使用一个“逻辑条件”,与之前使用.filter()应用的条件非常相似,只需选择“第n个索引”条目而不将所有可能的索引值列为查询参数。


不幸的是,对于聚合框架来说这是不可能的,因为这需要一个带有$unwind的选项来发出一个数组索引/位置,而目前的聚合无法处理。 这里有一个开放的JIRA票据SERVER-4588

然而,一个解决方法是使用MapReduce,但这会带来巨大的性能成本,因为获取数组索引的实际计算是使用嵌入式JavaScript引擎执行的(这很慢),并且仍然存在单个全局JavaScript锁,只允许一次一个JavaScript线程运行。

有了mapReduce,你可以尝试这样的事情:

映射功能:

var map = function(){
    for(var i=0; i < this.instances.length; i++){
        emit(
            { "_id": this._id,  "index": i },
            { "index": i, "value": this.instances[i] }
        );
    }
};

减少功能:

var reduce = function(){}

然后,您可以在集合上运行以下mapReduce函数:

db.collection.mapReduce( map, reduce, { out : "resultCollection" } );

然后,您可以使用map()游标方法,将结果集合查询到实例数组的每个第N个项目的geta list / array:

var thirdInstances = db.resultCollection.find({"_id.index": N})
                                        .map(function(doc){return doc.value.value})

或者只用一个查找块:

db.Collection.find({}).then(function(data) {
  var ret = [];
  for (var i = 0, len = data.length; i < len; i++) {
    if (i % 3 === 0 ) {
      ret.push(data[i]);
    }
  }
  return ret;
});

返回一个promise,你可以调用它来获取第N个模数据。

链接地址: http://www.djcxy.com/p/86445.html

上一篇: get every Nth item

下一篇: Is it required to close a Psycopg2 connection at the end of a script?