【问题标题】:Mongodb aggregation pipeline new dateMongodb聚合管道新日期
【发布时间】:2014-10-16 17:56:45
【问题描述】:

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

对象表达式中不允许的字段类型 Date (at 'date') // date: new Date('$_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'
}];

【问题讨论】:

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


    【解决方案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 创建集合,然后让您的代码从读取的任何结果中转换日期。但是您不能操纵日期并以这种方式从聚合管道本身返回日期对象。使用最适合您的方法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-10-23
      • 1970-01-01
      • 2015-04-24
      • 2020-09-20
      • 2021-04-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多