Mongodb:聚合子文档数组的值

Mongodb: aggregate values on array of subdocuments

本文关键字:数组 文档 Mongodb      更新时间:2023-09-26

我有一个文档集合:

{
    "_id": ObjectId("55dc62647cda24224372e308"),
    "last_modified": ISODate("2015-07-01T15:57:26.874Z"),
    "services": [
        {"last_modified": ISODate("2015-05-08T07:10:11.250Z")},
        {...}
    ]
}

我需要通过查找其服务的最大last_updated值来刷新文档的last_modified字段:

>db.documents.find().map(function(d){
    db.documents.update(
        {_id: d._id},
        {$set: {last_updated: Math.max(d.services.last_updated)}}
    )
})
Tue Aug 25 16:01:20.536 TypeError: Cannot read property 'last_modified' of undefined

如何在数组中访问子文档的聚合属性?

这里的基本过程是,您需要从数组中获取最大排序日期并从中获取值。当然,您需要一个循环,并且您不能在更新语句中直接访问文档的值。所以你需要先读它,但是Bulk操作在这里有帮助:

var bulk = db.documents.initializeOrderedBulkOp(),
    count = 0;
db.documents.find().forEach(function(doc) {
  var last_modified = doc.services.sort(function(a,b) {
    return a.last_modified < b.last_modified;
  }).slice(-1)[0].last_modified;
  bulk.find({ "_id": doc._id }).updateOne({
    "$set": { "last_modified": last_modified }
  });
  count++;
  if ( count % 1000 == 0 ) {
    bulk.execute();
    bulk = db.documents.initializeOrderedBulkOp();
  }
});
if ( count % 1000 != 0 )
  bulk.execute();

更好的是,考虑在添加新项时对数组本身进行排序。这基本上是通过 $sort 修饰符到 $push

来完成的
 db.documents.update(
     { "_id": id },
     { "$push": { 
         "services": {
             "$each": [{ "last_modified": date }],
             "$sort": { "last_modified": 1 }
     }}
)

或者甚至忘记$sort,因为所有数组值无论如何都被附加到末尾,除非您告诉操作以其他方式。

那么你基本上可以使用 $slice 来缩短这个过程。

var bulk = db.documents.initializeOrderedBulkOp(),
    count = 0;
db.documents.find(
    {},
    { 
        "last_modified": { "$slice": -1}
    }
).forEach(function(doc) {
  bulk.find({ "_id": doc._id }).updateOne({
    "$set": { "last_modified": doc.last_modified[0] }
  });
  count++;
  if ( count % 1000 == 0 ) {
    bulk.execute();
    bulk = db.documents.initializeOrderedBulkOp();
  }
});
if ( count % 1000 != 0 )
  bulk.execute();

聚合框架可以在这里使用,但考虑到从每个文档的对象中获取最大日期值是多么简单,这真的不是必要的。

var bulk = db.documents.initializeOrderedBulkOp(),
    count = 0;
db.documents.aggregate([
    { "$unwind": "$services" },
    { "$group": {
        "_id": "$_id",
        "last_modified": { "$max": "$services.last_modified" }
    }}
]).forEach(function(doc) {
  bulk.find({ "_id": doc._id }).updateOne({
    "$set": { "last_modified": doc.last_modified }
  });
  count++;
  if ( count % 1000 == 0 ) {
    bulk.execute();
    bulk = db.documents.initializeOrderedBulkOp();
  }
});
if ( count % 1000 != 0 )
  bulk.execute();

由于 $unwind 的使用,这实际上比必要的代价要大得多。