1. 程式人生 > >mongodb aggregate按日期分組統計及spring mongo實現

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實現