如需转载请注明出处: mongodb aggregate按日期分组统计及spring mongo实现
实现的需求
传入毫秒级开始时间戳和结束的时间戳,根据当前状态currentStatus.status和当前状态时间currentStatus.datetime进行按日统计,缺少数值自动补0.
访问方式如下:
http://localhost:9999/sample/release-count?start_time=1541006872000&end_time=1544117272000
返回结果
{"code":0,"msg":"成功","data":{"list":[{"date":1541865600000,"release":1},{"date":1543248000000,"release":3},{"date":1542729600000,"release":1},{"date":1541088000000,"release":1},{"date":1541433600000,"release":17},{"date":1541779200000,"release":2},{"date":1541347200000,"release":2},{"date":1541692800000,"release":1}]}}
实现例子
以下代码实现了aggregate按日期分组统计并且当天没有数值的情况下自动补0.
ReleaseCountResultVo.java
package com.biologic.vo;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
public class ReleaseCountResultVo {
private String date;
private int release;
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public int getRelease() {
return release;
}
public void setRelease(int release) {
this.release = release;
}
public String toString() {
DateFormat fmt =new SimpleDateFormat("yyyy-MM-dd");
try {
fmt.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
Date datetime = fmt.parse(date);
return "{date:"+datetime.getTime()+","+"release:"+release+"}";
} catch (ParseException e) {
e.printStackTrace();
return "{date:"+date+","+"release:"+release+"}";
}
}
}
SampleController.java
@GetMapping(value = "/sample/release-count")
@ResponseBody
Object queryReleaseCount(@RequestParam("start_time") String startTime, @RequestParam("end_time") String endTime) throws ParseException {
Date end = new Date(Long.parseLong(String.valueOf(endTime)));
Date start = new Date(Long.parseLong(String.valueOf(startTime)));
Criteria criteria = Criteria.where("currentStatus.status").is(RELEASED_STATUS).andOperator(
Criteria.where("currentStatus.datetime").lte(end), Criteria.where("currentStatus.datetime").gte(start));
long count = mongoTemplate.count(new Query(criteria), Person.class);
System.out.println(count);
List<ReleaseCountResultVo> resultVos = new ArrayList<ReleaseCountResultVo>();
if (count == 0) {
resultVos =repairEmptyDate(end, start, resultVos);
} else {
// 匹配查询
MatchOperation matchOperation = Aggregation.match(criteria);
// 返回参数
ProjectionOperation return1 = Aggregation.project("barCode")
.and(DateOperators.DateToString.dateOf("currentStatus.datetime").toString("%Y-%m-%d")).as("date");
// 按条件分组
GroupOperation go2 = Aggregation.group("date").count().as("release");
// 设置排序
SortOperation sortOperation = Aggregation.sort(Sort.Direction.ASC, "date");
// 返回参数2
ProjectionOperation return2 = Aggregation.project("release").and("_id").as("date");
// ProjectionOperation return3 =
// Aggregation.project("release").andExpression("dateFromString(date)")
// .as("date");
// 构建参数
Aggregation aggregation = Aggregation.newAggregation(matchOperation, return1, go2, sortOperation, return2);
// 分组聚合查询
AggregationResults<ReleaseCountResultVo> aggregate = mongoTemplate.aggregate(aggregation,
Person.class, ReleaseCountResultVo.class);
// 获取结果
resultVos = aggregate.getMappedResults();
resultVos =repairEmptyDate(end, start, resultVos);
}
JSONArray jSONArray = new JSONArray();
JSONObject jSONList = new JSONObject();
for (ReleaseCountResultVo resultVo : resultVos) {
System.out.println("toString: "+resultVo.toString());
jSONArray.add(resultVo.toString());
}
jSONList.put("list", jSONArray);
return jSONList;
}
private List<ReleaseCountResultVo> repairEmptyDate(Date end, Date start, List<ReleaseCountResultVo> resultVos) {
List<ReleaseCountResultVo> resultVoNews = new ArrayList<ReleaseCountResultVo>();
List<String> resultVoIds =new ArrayList<String>();
System.out.println("resultVos长度:"+resultVos.size());
for (ReleaseCountResultVo resultVo : resultVos) {
resultVoIds.add(resultVo.getDate());
resultVoNews.add(resultVo);
}
Calendar endAdd1 = Calendar.getInstance();
endAdd1.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
endAdd1.setTime(new Date(end.getTime()));
endAdd1.add(Calendar.DAY_OF_MONTH, +1);
Date endAdd1Date = endAdd1.getTime();
for (long i = start.getTime(); i <= endAdd1Date.getTime();) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
String startTimeFormat = sdf.format(i);
System.out.println("nowiString: "+String.valueOf(i));
System.out.println("nowformatString: "+startTimeFormat);
if(i<=end.getTime()&&!resultVoIds.contains(startTimeFormat)) {
resultVoIds.add(startTimeFormat);
ReleaseCountResultVo releaseCountResultVo = new ReleaseCountResultVo();
releaseCountResultVo.setDate(startTimeFormat);
releaseCountResultVo.setRelease(0);
resultVoNews.add(releaseCountResultVo);
System.out.println("addString: "+startTimeFormat);
}
Calendar rightNow = Calendar.getInstance();
rightNow.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
rightNow.setTime(new Date(i));
rightNow.add(Calendar.DAY_OF_MONTH, +1);
Date dt1 = rightNow.getTime();
i = dt1.getTime();
}
return resultVoNews;
}
细节和坑
Java中的时间戳有个需要注意的细节是通过new Date().getTime()获取的时间是毫秒级的
如果需要秒级的除以1000才能得到秒级的unix时间戳。
// pure java
(int) (System.currentTimeMillis() / 1000)
// joda
(int) (DateTime.now().getMillis() / 1000)
而接收到秒级的unix时间戳后也需要乘以1000才能用于转换为 Java的Date类型,否则会精度不对应,导致转换出来的时间是1970年。
所以在交互时一定要沟通好是使用毫秒级的时间戳还是秒级的。
更多参考
更多Spring Data MongoDB中的aggregate操作可查看
Spring Data MongoDB - Reference Documentation
注意事项
因为对aggregate的支持是新特性,所以需要注意版本问题。
一般要求mongodb版本3.6以上,spring的用法还需要注意引入的包版本,例如dateFromString就需要2.1的版本
关于Java中的DateOperators.DateFromString
Class DateOperators.DateFromString
关于mongo-js中的dateFromString
https://docs.mongodb.com/manual/reference/operator/aggregation/dateFromString/
关于mongo-js中的dateToString
https://docs.mongodb.com/manual/reference/operator/aggregation/dateToString/
aggregate可用表达式
Aggregation Pipeline Quick Reference
如需转载请注明出处: mongodb aggregate按日期分组统计及spring mongo实现