1. 高级查询
对基础语法还不了解的,可以去此网站MongoDB语法进行学习,语法比较全面。
1.1 复杂find
1.2 复杂aggregate
示例一:对匹配到的数据分组
示例二:先match匹配,再project处理数据格式,最后根据多个条件group聚合
db.deviceDataCollection.aggregate([{
"$match": {
"deviceId": "232424",
"deviceType": "2",
"commonDeviceData.commonDeviceExtraData": {
"$exists": true
},
"$or": [{
"commonDeviceData.commonDeviceExtraData.identityNo": "2455266x"
}, {
"commonDeviceData.commonDeviceExtraData.identityNo": "2455266X"
}]
}
}, {
"$project": {
"date": {
"$dateToString": {
"format": "%Y-%m-%d",
"date": "$ts"
}
},
"commonDeviceData": 1
}
}, {
"$group": {
"_id": {
"date": "$date",
"extraCode": "$commonDeviceData.extraCode"
},
"checkTime": {
"$last": "$date"
},
"commonDeviceData": {
"$last": "$commonDeviceData"
}
}
}])
1.3 格式转换
$convert 是 MongoDB 中的一个聚合管道运算符,用于将一个数据类型转换为另一个数据类型。转换类型包括数字类型、日期类型、字符串类型等。
基本语法:
{ $convert: { input: <expression>, to: <type>, onError: <expression>, onNull: <expression> } }
其中,input 参数表示要转换的值;to 参数表示要转换的目标类型,可以为以下类型之一:double、string、objectId、bool、date、int、long;onError 和 onNull 参数是可选参数,分别表示在转换过程中遇到错误或输入为 null 时返回的值
"heartRate": {
"$convert": {
"input": "$commonDeviceData.commonDeviceExtraData.heartRate",
"to": "int",
"onError": 0,
"onNull": 0
}
}
java代码
Aggregation.project()
.and(ConvertOperators.Convert.convertValueOf("commonDeviceData.commonDeviceExtraData.heartRate")
.to("int")
.onErrorReturn(0)
.onNullReturn(0))
.as("heartRate")
1.4 日期处理
1.4.1 add、subtract、multiply、divide、mod
基础语法:加、减、乘、除、取模是类似的
{ $add : [ < expression1 > , < expression2 > , ... ] }
{ $subtract: [ <expression1>, <expression2> ] }
{ $multiply : [ < expression1 > , < expression2 > , ... ] }
{ $divide: [ <expression1>, <expression2> ] }
{ $mod: [ <expression1>, <expression2> ] }
主要是嵌套,需求不可能只是简单的加减乘除。如果函数是最内层的,就用[]包裹,如果不是最内层,则哪一项中有函数,就加一层{}包裹。
{
"$mod": [{
"$subtract": [
"$ts",
ISODate("1970-01-01T00:00:00Z")
]
}, 600000]
}
1.4.2 日期计算
上述公式在用于日期计算的时候,会自动将日期转换成时间戳进行计算。就比如上面的示例,我们将ts这个时间字段减去1970年,得到的便是这段时间对应的毫秒值。
比如我们有个需求,我们需要每10分钟聚合一次数据,求这十分钟的呼吸、心率的平均值。关键在于如何按每10分钟进行分组,如果是按每分钟分组,我们可以用MongoDB中的日期运算符或者对时间进行截取,去除分钟之后的部分,按处理后的时间进行分组。简单的来想,现在有一个小时的数据,从1分钟到60分钟,如果分成6份,我们将分钟与10取mod值,然后用自身减去这个模值就可以了,得到的便是0、10、20、30、40、50,然后我们根据这个计算值分组就可以了。我们常用1970-01-01 00:00:00作为起始时间,方便我们取值。
关键一:处理分组字段,((数据时间 - 1970)-(数据时间 - 1970)/ 10分钟 )= 当前这组数据起始十分钟的时间戳。
"date": {
"$subtract": [{
"$subtract": [
"$ts",
ISODate("1970-01-01T00:00:00Z")
]
}, {
"$mod": [{
"$subtract": [
"$ts",
ISODate("1970-01-01T00:00:00Z")
]}
, 600000]
}]
}
关键二:将时间戳再转成日期格式,直接与起始时间1970年相加即可。
"date": {
"$add": [
ISODate("1970-01-01T00:00:00Z")
, "$date"]
}
完整sql
db.deviceDataCollection_1537267276171177984.aggregate([{
"$match": {
"deviceId": "xxxxxxx",
"ts": {
"$gte": ISODate("2023-09-12T01:21:37Z"),
"$lte": ISODate("2023-09-13T01:21:37Z")
}
}
}, {
"$project": {
"date": {
"$subtract": [{
"$subtract": [
"$ts",
ISODate("1970-01-01T00:00:00Z")
]
}, {
"$mod": [{
"$subtract": [
"$ts",
ISODate("1970-01-01T00:00:00Z")
]}
, 600000]
}]
},
"status": "$commonDeviceData.commonDeviceExtraData.status",
"heartRate": {
"$convert": {
"input": "$commonDeviceData.commonDeviceExtraData.heartRate",
"to": "int",
"onError": 0,
"onNull": 0
}
},
"respiratoryRate": {
"$convert": {
"input": "$commonDeviceData.commonDeviceExtraData.respiratoryRate",
"to": "int",
"onError": 0,
"onNull": 0
}
}
}
}, {
"$group": {
"_id": "$date",
"heartRateAvg": {
"$avg": "$heartRate"
},
"breathAvg": {
"$avg": "$respiratoryRate"
},
"status": {
"$last": "$status"
},
"date": {
"$last": "$date"
}
}
}, {
"$project": {
"date": {
"$add": [
ISODate("1970-01-01T00:00:00Z")
, "$date"]
},
"heartRateAvg": 1,
"breathAvg": 1,
"status": 1
}
}, {
"$sort": {
"date": - 1
}
}])
结果:其中_id 则是date对应的时间戳。
java代码
// 聚合
Aggregation agg = Aggregation.newAggregation(
Aggregation.match(criteria),
Aggregation.project()
.andExpression("{$subtract:{{$subtract:{'$ts',new java.util.Date(0L)}},{$mod:{{$subtract: {'$ts',new java.util.Date(0L)}},1000 * 60 * 10}}}}").as("date")
.and("commonDeviceData.commonDeviceExtraData.status").as("status")
.and(ConvertOperators.Convert.convertValueOf("commonDeviceData.commonDeviceExtraData.heartRate")
.to("int")
.onErrorReturn(0)
.onNullReturn(0))
.as("heartRate")
.and(ConvertOperators.Convert.convertValueOf("commonDeviceData.commonDeviceExtraData.respiratoryRate")
.to("int")
.onErrorReturn(0)
.onNullReturn(0))
.as("respiratoryRate"),
Aggregation.group("date")
.avg("heartRate").as("heartRateAvg")
.avg("respiratoryRate").as("breathAvg")
.last("status").as("status")
.last("date").as("date"),
Aggregation.project()
.andExpression("{$add:{new java.util.Date(0L),'$date'}}").as("date")
.and("heartRateAvg").as("heartRateAvg")
.and("breathAvg").as("breathAvg")
.and("status").as("status")
).withOptions(allowDiskOptions);
代码注意点:
- 代码用的andExpression表达式,表达式里面不支持中括号,我们需要将其转变成大括号
- 表示1970-01-01 00:00:00时,需要用到new Date()函数,表达式中的方法一定要用全类名表示,比如这里的new java.util.Date()
- new java.util.Date(0L)中的参数是long类型的,否则会出现本地运行没问题,但是Liunx系统中报错。