mongodb aggregate按日期分組統計及spring mongo實現
如需轉載請註明出處: 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實現