Mongodb聚合管道新日期

时间:2014-08-22 19:41:58

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

我尝试使用聚合管道根据之前的管道值附加/创建新日期并保存到新集合中。(请参阅下面的管道)。但是,语法错误,我收到错误

  

禁止字段类型对象表达式中的日期(在'日期')//日期:新日期(' $ _ id.year',' $ _ id.month' ;,' $ _ id.day')

我想知道如何使用mongo聚合管道中的上一年,月,日值来创建日期?基本上,在将我的ISODate转换为分组的年,月和日之后,我想将它们转换回ISODate格式。

pipeline = [{
    $match: {
        event: 'sample_event',
    }
}, {
    $project: {
        _id: false,
        uuid: '$properties.distinct_id',
        time: '$properties.time'
    }
}, {
    $group: {
        _id: {
            year: {
                $year: '$time'
            },
            month: {
                $month: '$time'
            },
            day: {
                $dayOfMonth: '$time'
            },
            uuid: '$uuid'
        }
    }
}, {
    $group: {
        _id: {
            year: '$_id.year',
            month: '$_id.month',
            day: '$_id.day'
        },
        value: { $sum: 1 }
    }
}, {
    $sort: {
        '_id.year': 1,
        '_id.month': 1,
        '_id.day': 1
    }
}, {
    $project: {
        _id: {
            $concat: [
                { $substr: ['$_id.year', 0, 4] },
                '-',
                {
                    $cond: [
                        { $lte: [ '$_id.month', 9 ] },
                        { $concat: [
                            '0',
                            { $substr: [ '$_id.month', 0, 2 ] }
                        ]},
                        { $substr: [ '$_id.month', 0, 2 ] }
                    ]
                },
                '-',
                {
                    $cond: [
                        { $lte: [ '$_id.day', 9 ] },
                        { $concat: [
                            '0',
                            { $substr: [ '$_id.day', 0, 2 ] }
                        ]},
                        { $substr: [ '$_id.day', 0, 2 ] }
                    ]
                },
            ]
        },
        date: new Date('$_id.year', '$_id.month', '$_id.day'), // errorrrr
        value: 1
    }
}, {
    $out: 'output_collection'
}];

1 个答案:

答案 0 :(得分:1)

您无法在聚合管道中“投射”新数据类型。唯一真正允许的是对整数值(不是双精度)使用$substr并从日期中提取时间戳值作为整数。但字符串或数字不能成为字符串或日期对象的数字。

此外,在管道中实际上没有评估JavaScript,您可能看到的任何示例都纯粹是“单行程”,其中JavaScript函数在创建管道的文档中被“评估”。但是你不能以任何方式对管道内的数据采取行动。

编写新集合的最佳方法是处理游标结果并使用Bulk Operations API写出。因此,如果这是为了定期构建聚合结果,那么您甚至可以基本上附加或更新到目标集合。

这意味着使用底层本机驱动程序中的本机方法。找到那些使用mongojs的人看起来有点滑稽,但方法仍然存在:

var async = require('async'),
    mongojs = require('mongojs'),
    db = mongojs('mongodb://localhost/test',['sample']);


db._get(function(err,db) {
  if (err) throw err;

  var source = db.collection('source'),
      bulk = db.collection('target').initializeOrderedBulkOp(),
      count = 0;

  var cursor = source.aggregate(
    [
      { "$match": { "event" : "sample event" } },
      { "$group": {
        "_id": {
          "day": {
            "$subtract": [
              { "$subtract": [ "$properties.time", new Date("1970-01-01") ] },
              { "$mod": [ 
                { "$subtract": [ "$properties.time", new Date("1970-01-01") ] },
                1000 * 60 * 60 * 24 
              ]}
            ]
          },
          "uuid": "$properties.distinct_id"
        }
      }}.
      { "$group": { "_id": "$_id.day", "count": { "$sum": 1 } }}
    ],
    { "cursor": { "batchSize": 101 } }
  );

  cursor.on("data",function(data) {
    bulk.insert({ "date": new Date(data._id), "count": data.count });
    count++;

    if ( count % 1000 == 0 ) {
      cursor.pause();
      bulk.execute(function(err,result) {
        if (err) throw err;
        bulk = db.collection('target').initializeOrderedBulkOp();
        cursor.resume();
      });
    }

  });

  cursor.on("end",function() {
    if ( count % 1000 != 0 )
      bulk.execute(function(err,result) {
        console.log("done");
      });
  });

});

这样,您的聚合操作就会大大简化并且运行得更快。输出是表示“日期”的纪元时间戳值,通过从另一个日期对象中减去一个日期对象时获得的时间值得到的结果。这里的一般日期数学将数字四舍五入到表示从提供日期开始的“日期”的值。

这比强制字符串更有效,并且适合作为新日期对象的构造函数提供给Date()函数,这当然是在聚合管道的“外部”完成的。

此处的插入件是批量执行的,每千件物品只执行一次,因此非常有线。由于这实际上是一个流接口,因此您使用事件并使用.pause().resume()以避免过多的并发请求和/或过多的内存使用。根据需要调整,但无论大小如何,驱动程序实际上会分解为任何一个发送和返回的1000个请求。

或者当然只是在这个阶段没有将值转换为日期而只是使用 $out 来创建集合,然后使代码从任何结果中转换为日期读。但是,您无法操纵日期并以这种方式从聚合管道本身返回日期对象。使用最适合您的方法。