MongoDB列表 - 获取每个第N项

时间:2015-07-05 15:20:01

标签: mongodb mongodb-query aggregation-framework

我的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" : [ 
      ...
    ]
  }
]

其中每个元素的实例数量可能非常大。

我有时只希望得到一个数据样本,即获取每个第三个实例,或者每第10个实例......你得到的图片。

我可以通过获取所有实例并在我的服务器代码中过滤它们来实现这一目标,但我想知道是否有一种方法可以通过使用一些聚合查询来实现。

有什么想法吗?

更新

假设数据结构持平,如下面的@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)的任务会更容易吗?

6 个答案:

答案 0 :(得分:6)

似乎你的问题明确要求"得到每个第n个实例"这似乎是一个非常明确的问题。

.find()这样的查询操作实际上只能返回文档""除了一般领域"选择"在投影和运算符中,例如positional $匹配运算符或$elemMatch,允许使用单个匹配的数组元素。

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

" only"可以在服务器上修改结果的内容是.aggregate().mapReduce()。前者并没有“发挥得非常好”#34;与"切片"任何方式的数组,至少不是" n"元素。但是因为" function()" mapReduce的参数是基于JavaScript的逻辑,那么你还有更多的空间可以玩。

用于分析过程,用于分析目的,仅用于#34;然后使用.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一样接近传真。

我在这里看到的另一个建议是创建一个包含所有项目的新集合"非规范化"并插入"索引"作为unqique _id键的一部分从数组中。这可能会产生一些你可以直接查询的内容,即每隔一个项目#34;你仍然需要这样做:

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()的内容大致相同,只需选择" nth索引"未将所有可能的索引值列为查询参数的项目。

答案 1 :(得分:4)

不幸的是,使用聚合框架是不可能的,因为这需要一个带有 $unwind 的选项来发出数组索引/位置,目前聚合可以&#39处理这里有一个开放的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列表/数组:

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

答案 2 :(得分:4)

这里不需要$unwind。您可以将$push$arrayElemAt结合使用,以将数组值投影到$group聚合内的请求索引处。

类似

db.colname.aggregate(
[
  {"$group":{
    "_id":null,
    "valuesatNthindex":{"$push":{"$arrayElemAt":["$instances",N]}
    }}
  },
  {"$project":{"valuesatNthindex":1}}
])

答案 3 :(得分:4)

您可能会喜欢使用$lookup聚合的这种方法。可能是没有任何聚合技巧的最方便,最快的方法。

使用以下架构创建集合 Names

[
  { "_id": 1, "name": "name1" },
  { "_id": 2, "name": "name2" }
]

,然后是其父ID为Instances

"nameId" 集合
[
  { "nameId": 1, "value" : 1, "date" : ISODate("2015-03-04T00:00:00.000Z") },
  { "nameId": 1, "value" : 2, "date" : ISODate("2015-04-01T00:00:00.000Z") },
  { "nameId": 1, "value" : 3, "date" : ISODate("2015-03-05T00:00:00.000Z") },
  { "nameId": 2, "value" : 7, "date" : ISODate("2015-03-04T00:00:00.000Z") }, 
  { "nameId": 2, "value" : 8, "date" : ISODate("2015-04-01T00:00:00.000Z") }, 
  { "nameId": 2, "value" : 4, "date" : ISODate("2015-03-05T00:00:00.000Z") }
]

现在使用$lookup聚合 3.6 语法,您可以在$sample $lookup内使用pipeline来获取每个 Nth 元素。

db.Names.aggregate([
  { "$lookup": {
    "from": Instances.collection.name,
    "let": { "nameId": "$_id" },
    "pipeline": [
      { "$match": { "$expr": { "$eq": ["$nameId", "$$nameId"] }}},
      { "$sample": { "size": N }}
    ],
    "as": "instances"
  }}
])

您可以here对其进行测试

答案 4 :(得分:3)

您可以使用以下汇总:

db.col.aggregate([
    {
        $project: {
            instances: {
                $map: {
                    input: { $range: [ 0, { $size: "$instances" }, N ] },
                    as: "index",
                    in: { $arrayElemAt: [ "$instances", "$$index" ] }
                }
            }
        }
    }
])

$range生成索引列表。第三个参数表示非零步长。对于N = 2,它将是[0,2,4,6...],对于N = 3,它将返回[0,3,6,9...],依此类推。然后,您可以使用$mapinstances数组中获取相应的项目。

答案 5 :(得分:2)

或仅使用查找块:

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,你可以调用then()来获取第N个模数据。