我在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"
]
}
}
}
}
}}
])
所以这真的很狡猾。在完成最初的"分组"以确定每天的平均值后,您每天在结果文档中得到两个字段,分别是_id
和avg
。$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 )
因此,这实际上可以归结为从任何方式进行数组操作,但日期操作技巧也应该为您节省一些工作,以获得预期的时间戳值。