日期问题-试图通过PHP在Highcharts中绘制MongoDB数据


Issue with Dates - trying to plot MongoDB data in Highcharts via PHP

我在MongoDB中有这个数据:

{
  "_id": ObjectId("542bxxxxxxxxxxxx"),
  "TEMP_C": 13,
  "time": ISODate("2014-08-21T05:30:00Z")
}

我想把它按天分组,并把它输入到Highcharts,以显示每天的平均温度。像这样:http://jsfiddle.net/tw7n6wxb/3/

使用MongoDB聚合管道,我能够根据其他一些例子和这篇很棒的文章进行分组:http://www.kamsky.org/stupid-tricks-with-mongodb/stupid-date-tricks-with-aggregation-framework

附带问题:为什么在MongoDB中按日期分组如此复杂???最烦人的部分是在将日期对象拆分为"$dayOfMonth"、"$month"answers"$year"后,必须重新组合日期对象。有什么更简单的方法吗?

不管怎样,我已经把这个部分做好了(我想)。这就是结果:

{
  "_id" : {"sec":1409346800,"usec":0},
  "avg" : 12
},
{
  "_id" : {"sec":1409356800,"usec":0},
  "avg" : 15
},

但是,Highcharts系列采用值对数组作为输入:示例:数据:[[5,2],[6,3],[8,2]]。每对上的第一个值是X值,该值必须是一个数字(当X轴配置为日期时间时,X值以毫秒为单位)。

我遇到的问题是,MongoDB将日期作为MongoDate对象返回,其中包含两个值,"sec"answers"usec",而Highcharts需要一个数字。

在管道中是否有将MongoDate对象转换为整数的方法?例如使用$project?我使用的是PHP,但我希望避免在应用程序中进行后期处理(比如PHP日期格式)。

或者,关于如何解决这个问题,还有其他想法吗?

谢谢,

您似乎只想要从结果中返回时间戳值。在聚合框架中确实有一种简单的方法可以做到这一点,而无需使用日期聚合运算符。您可以使用基本的"日期数学",并使用一个技巧从日期对象中提取"时间戳"值并对其进行操作:

db.collection.aggregate([
    { "$group": {
        "_id": {
            "$subtract": [
                { "$subtract": [ "$time", new Date("1970-01-01") ] },
                { "$mod": [
                    { "$subtract": [ "$time", new Date("1970-01-01") ] },
                    1000 * 60 * 60 * 24    
                ]}
            ]
        },
        "avg": { "$avg": "$TEMP_C" }
    }}
])

因此,基本的"技巧"是,当从另一个日期对象(或类似的操作)中减去一个日期时,返回的结果是两者之间时间差的"毫秒"。因此,通过使用"1970-01-01"的"epoch"日期,可以获得日期对象的"epoch-tempstamp"值作为数字。

然后,通过从这个值中减去一天中毫秒的模(或余数)来应用基本的日期数学。这将对值进行"四舍五入",以表示记录条目的"日期"。

我喜欢发布JSON,因为它可以在任何地方进行解析,但以更PHP的方式,然后是这样的:

$collection->aggregate(array(
    array( '$group' => array(
        '_id' => array(
            '$subtract' => array(
                array( '$subtract' =>  array( 
                    '$time', new MongoDate(strtotime("1970-01-01 00:00:00"))
                ) ),
                array(  '$mod' => array(
                    array( '$subtract' =>  array( 
                        '$time', new MongoDate(strtotime("1970-01-01 00:00:00"))
                    ) ),
                    1000 * 60 * 60 * 24    
                ))
            )
        ),
        "avg" => array( '$avg' => '$TEMP_C' )
    ))
))

因此,这比使用日期聚合运算符来获得预期结果要干净一些。当然,这仍然不能"完全"实现您希望数据在客户端中使用的方式。

这里真正要做的是操作结果,以便获得所需的输出格式。这可能更适合您的服务器代码在返回响应之前进行操作,但如果您有MongoDB 2.6或更高版本,则可以在聚合管道本身中进行操作:

db.collection.aggregate([
    { "$group": {
        "_id": {
            "$subtract": [
                { "$subtract": [ "$time", new Date("1970-01-01") ] },
                { "$mod": [
                    { "$subtract": [ "$time", new Date("1970-01-01") ] },
                    1000 * 60 * 60 * 24    
                ]}
            ]
        },
        "avg": { "$avg": "$TEMP_C" }
    }},
    { "$group": {
        "_id": null,
        "data": {
            "$push": {
                "$map": {
                    "input": { "$literal": [ 1,2 ] },
                    "as": "el",
                    "in": {
                        "$cond": [
                            { "$eq": [ "$$el", 1 ] },
                            "$$_id",
                            "$avg"
                        ]
                    }
                }
            }
        }
    }}
])

所以这真的很狡猾。在完成最初的"分组"以确定每天的平均值后,您每天在结果文档中得到两个字段,分别是_idavg$map运算符在这里所做的是将和数组作为输入(在这种情况下,只是一个带有一对值的编号模板来标识位置),并处理每个元素以返回一个等于原始元素的数组。

这里的$cond运算符允许您查看该数组的当前元素的值,并将其与当前文档中的另一个值"交换"。因此,对于每个文档,结果都包含一个成对的数组,如:

[ 1409346800000, 12 ]

然后,所有结果都被推送到一个带有"数据"数组的单个文档中,该数组显示如下:

{ "_id": null, "data": [ [..,..], [..,..], (...) ] }

现在,这一结果中的数据元素是一个数组对数组,表示您想要的点。

当然,像$map这样的操作符只在MongoDB 2.6及以后版本中可用,所以如果您有可用的操作符,那么您可以使用它们,但在其他方面,只需使用类似的"映射"操作在代码中处理结果:

function my_combine($v) {
    return array($v["_id"],$v["avg"])
}
$newresult = array_map( "my_combine", $result )

因此,这实际上可以归结为从任何方式进行数组操作,但日期操作技巧也应该为您节省一些工作,以获得预期的时间戳值。