您好,登錄后才能下訂單哦!
這篇文章主要介紹mysql統計訂單收益的案例,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
在如何優雅統計訂單收益(一)中已經詳細說明,大概就是些日/月/年的收益統計.
建立在已經通過canal異構出收益日統計表的情況下:
這樣看來日統計表的異構是有價值的,至少可以解決當前的所有需求.
如果需要今日/昨日/上月/本月的收益統計,用SQL直接聚合查詢,則需要分別查詢今日,昨日以及跨度為整月的數據集然后通過SUM聚合
實現.
CREATE TABLE `t_user_income_daily` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵', `user_id` int(11) NOT NULL COMMENT '用戶id', `day_time` date NOT NULL COMMENT '日期', `self_purchase_income` int(11) DEFAULT '0' COMMENT '自購收益', `member_income` int(11) DEFAULT '0' COMMENT '一級分銷收益', `affiliate_member_income` int(11) DEFAULT '0' COMMENT '二級分銷收益', `share_income` int(11) DEFAULT '0' COMMENT '分享收益', `effective_order_num` int(11) DEFAULT '0' COMMENT '有效訂單數', `total_income` int(11) DEFAULT '0' COMMENT '總收益', `update_time` datetime DEFAULT NULL COMMENT '更新時間', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8 COMMENT='用戶收益日統計'
這種寫法如果接口需要返回今日/昨日/上月/本月的收益統計時,就需要查詢4次SQL
才可以實現.寫法沒問題,但是不是最優解?可以用更少的SQL查詢么?
通過觀察分析,今日/昨日/上月/本月統計存在共同的交集
,它們都處于同一個時間區間(上月一號-本月月末),那我們可以通過SQL直接查出這兩個月的數據,再通過程序聚合就可以輕松得出我們想要的數據.
補充一下收益日統計表設計
select * from t_user_income_daily where day_time BETWEEN '上月一號' AND '本月月末' and user_id=xxx
查詢出兩個月的收益
select * from t_user_income
為了減少表的數據量,如果當日沒有收益變動是不會創建當日的日統計數據的,所以這里只能查詢出某時間區間用戶有收益變動的收益統計數據.如果處理某一天數據為空的情況則還需要再程序中特殊處理.此處有小妙招,在數據庫中生成一張時間輔助表
.以天為單位,存放各種格式化后的時間數據,輔助查詢詳細操作可見這篇博文Mysql生成時間輔助表.有了這張表就可以進一步優化這條SQL.時間輔助表的格式如下,也可修改存儲過程,加入自己個性化的時間格式.
SELECT a.DAY_ID day_time, a.MONTH_ID month_time, a.DAY_SHORT_DESC day_time_str, CASE when b.user_id is null then #{userId} else b.user_id end user_id, CASE when b.self_purchase_income is null then 0 else b.self_purchase_income end self_purchase_income, CASE when b.member_income is null then 0 else b.member_income end member_income, CASE when b.affiliate_member_income is null then 0 else b.affiliate_member_income end affiliate_member_income, CASE when b.share_income is null then 0 else b.share_income end share_income, CASE when b.effective_order_num is null then 0 else b.effective_order_num end effective_order_num, CASE when b.total_income is null then 0 else b.total_income end total_income FROM t_day_assist a LEFT JOIN t_user_income_daily b ON b.user_id = #{userId} AND a.DAY_SHORT_DESC = b.day_time WHERE STR_TO_DATE( a.DAY_SHORT_DESC, '%Y-%m-%d' ) BETWEEN #{startTime} AND #{endTime} ORDER BY a.DAY_ID DESC
思路很簡單,用時間輔助表左關聯需要查詢的收益日統計表,關聯字段就是day_time時間,如果沒有當天的收益數據,SQL中也會有日期為那一天但是統計數據為空的數據,用casewhen判空賦值給0,最后通過時間倒序,便可以查詢出一套完整時間區間統計
.
以SQL查詢出的數據為基礎.在程序中用stream進行聚合. 舉例說明一些例子,先從簡單的開始
/** * @description: 本月的第一天 * @author: chenyunxuan */ public static LocalDate getThisMonthFirstDay() { return LocalDate.of(LocalDate.now().getYear(), LocalDate.now().getMonthValue(), 1); } /** * @description: 本月的最后一天 * @author: chenyunxuan */ public static LocalDate getThisMonthLastDay() { return LocalDate.now().with(TemporalAdjusters.lastDayOfMonth()); } /** * @description: 上個月第一天 * @author: chenyunxuan */ public static LocalDate getLastMonthFirstDay() { return LocalDate.of(LocalDate.now().getYear(), LocalDate.now().getMonthValue() - 1, 1); } /** * @description: 上個月的最后一天 * @author: chenyunxuan */ public static LocalDate getLastMonthLastDay() { return getLastMonthFirstDay().with(TemporalAdjusters.lastDayOfMonth()); } /** * @description: 今年的第一天 * @author: chenyunxuan */ public static LocalDate getThisYearFirstDay() { return LocalDate.of(LocalDate.now().getYear(), 1, 1); } /** * @description: 分轉元,不支持負數 * @author: chenyunxuan */ public static String fenToYuan(Integer money) { if (money == null) { return "0.00"; } String s = money.toString(); int len = s.length(); StringBuilder sb = new StringBuilder(); if (s != null && s.trim().length() > 0) { if (len == 1) { sb.append("0.0").append(s); } else if (len == 2) { sb.append("0.").append(s); } else { sb.append(s.substring(0, len - 2)).append(".").append(s.substring(len - 2)); } } else { sb.append("0.00"); } return sb.toString(); }
public ResponseResult selectIncomeDetailThisMonth(int userId, Integer year, Integer month) { ResponseResult responseResult = ResponseResult.newSingleData(); String startTime; String endTime; //不是指定月份 if (null == year && null == month) { //如果時間為當月則只顯示今日到當月一號 startTime = DateUtil.getThisMonthFirstDay().toString(); endTime = LocalDate.now().toString(); } else { //如果是指定年份月份,用LocalDate.of構建出需要查詢的月份的一號日期和最后一天的日期 LocalDate localDate = LocalDate.of(year, month, 1); startTime = localDate.toString(); endTime = localDate.with(TemporalAdjusters.lastDayOfMonth()).toString(); } //查詢用通用的SQL傳入用戶id和開始結束時間 List<UserIncomeDailyVO> userIncomeDailyList = selectIncomeByTimeInterval(userId, startTime, endTime); /給前端的數據需要把數據庫存的分轉為字符串,如果沒有相關需求可跳過直接返回 List<UserIncomeStatisticalVO> userIncomeStatisticalList = userIncomeDailyList.stream() .map(item -> UserIncomeStatisticalVO.builder() .affiliateMemberIncome(Tools.fenToYuan(item.getAffiliateMemberIncome())) .memberIncome(Tools.fenToYuan(item.getMemberIncome())) .effectiveOrderNum(item.getEffectiveOrderNum()) .shareIncome(Tools.fenToYuan(item.getShareIncome())) .totalIncome(Tools.fenToYuan(item.getTotalIncome())) .dayTimeStr(item.getDayTimeStr()) .selfPurchaseIncome(Tools.fenToYuan(item.getSelfPurchaseIncome())).build()).collect(Collectors.toList()); responseResult.setData(userIncomeStatisticalList); return responseResult; }
public Map<String, String> getPersonalIncomeMap(int userId) { Map<String, String> resultMap = new HashMap<>(4); LocalDate localDate = LocalDate.now(); //取出上個月第一天和這個月最后一天 String startTime = DateUtil.getLastMonthFirstDay().toString(); String endTime = DateUtil.getThisMonthLastDay().toString(); //這條查詢就是上面優化過的SQL.傳入開始和結束時間獲得這個時間區間用戶的收益日統計數據 List<UserIncomeDailyVO> userIncomeDailyList = selectIncomeByTimeInterval(userId, startTime, endTime); //因為這里需要取的都是總收益,所以封裝了returnTotalIncomeSum方法,用于傳入條件返回總收益聚合 //第二個參數就是篩選條件,只保留符合條件的部分.(此處都是用的LocalDate的API) int today = returnTotalIncomeSum(userIncomeDailyList, n -> localDate.toString().equals(n.getDayTimeStr())); int yesterday = returnTotalIncomeSum(userIncomeDailyList, n -> localDate.minusDays(1).toString().equals(n.getDayTimeStr())); int thisMonth = returnTotalIncomeSum(userIncomeDailyList, n -> n.getDayTime() >= Integer.parseInt(DateUtil.getThisMonthFirstDay().toString().replace("-", "")) && n.getDayTime() <= Integer.parseInt(DateUtil.getThisMonthLastDay().toString().replace("-", ""))); int lastMonth = returnTotalIncomeSum(userIncomeDailyList, n -> n.getDayTime() >= Integer.parseInt(DateUtil.getLastMonthFirstDay().toString().replace("-", "")) && n.getDayTime() <= Integer.parseInt(DateUtil.getLastMonthLastDay().toString().replace("-", ""))); //因為客戶端顯示的是兩位小數的字符串,所以需要用Tools.fenToYuan把數值金額轉換成字符串 resultMap.put("today", Tools.fenToYuan(today)); resultMap.put("yesterday", Tools.fenToYuan(yesterday)); resultMap.put("thisMonth", Tools.fenToYuan(thisMonth)); resultMap.put("lastMonth", Tools.fenToYuan(lastMonth)); return resultMap; } //傳入收益集合以及過濾接口,返回對應集合數據,Predicate接口是返回一個boolean類型的值,用于篩選 private int returnTotalIncomeSum(List<UserIncomeDailyVO> userIncomeDailyList, Predicate<UserIncomeDailyVO> predicate) { return userIncomeDailyList.stream() //過濾掉不符合條件的數據 .filter(predicate) //把流中對應的總收益字段取出 .mapToInt(UserIncomeDailyVO::getTotalIncome) //聚合總收益 .sum(); }
擴展returnTotalIncomeSum函數,mapToInt支持傳入ToIntFunction參數的值.
private int returnTotalIncomeSum(List<UserIncomeDailyVO> userIncomeDailyList, Predicate<UserIncomeDailyVO> predicate,ToIntFunction<UserIncomeDailyVO> function) { return userIncomeDailyList.stream() //過濾掉不符合條件的數據 .filter(predicate) //把流中對應的字段取出 .mapToInt(function) //聚合收益 .sum(); 例如: 今日分享的金額,function參數傳入`UserIncomeDailyVO::getShareIncome` 今日自購和分享的金額,funciton參數傳入`userIncomeDailyVO->userIncomeDailyVO.getShareIncome()+userIncomeDailyVO.getSelfPurchaseIncome()` }
我們先來了解一下stream的聚合 語法糖:
list.stream().collect( Collectors.groupingBy(分組字段, Collectors.collectingAndThen(Collectors.toList(), list -> {分組后的操作}) ));
流程圖:代碼實例:
public ResponseResult selectIncomeDetailThisYear(int userId) { ResponseResult responseResult = ResponseResult.newSingleData(); List<UserIncomeStatisticalVO> incomeStatisticalList = new LinkedList<>(); //開始時間為今年的第一天 String startTime = DateUtil.getThisYearFirstDay.toString(); //區間最大時間為今日 String endTime = LocalDate.now().toString(); //通用SQL List<UserIncomeDailyVO> userIncomeDailyList = selectIncomeByTimeInterval(userId, startTime, endTime); //運用了stream的聚合,以月份進行分組,分組后用LinkedHashMap接收防止分組后月份順序錯亂,完畢后再把得到的每個月的收益集合流進行聚合并組裝成最終的實體返回 Map<Integer, UserIncomeStatisticalVO> resultMap = userIncomeDailyList.parallelStream() .collect(Collectors.groupingBy(UserIncomeDailyVO::getMonthTime, LinkedHashMap::new, Collectors.collectingAndThen(Collectors.toList(), item -> UserIncomeStatisticalVO.builder() .affiliateMemberIncome(Tools.fenToYuan(item.stream().mapToInt(UserIncomeDailyVO::getAffiliateMemberIncome).sum())) .memberIncome(Tools.fenToYuan(item.stream().mapToInt(UserIncomeDailyVO::getMemberIncome).sum())) .effectiveOrderNum(item.stream().mapToInt(UserIncomeDailyVO::getEffectiveOrderNum).sum()) .shareIncome(Tools.fenToYuan(item.stream().mapToInt(UserIncomeDailyVO::getShareIncome).sum())) .totalIncome(Tools.fenToYuan(item.stream().mapToInt(UserIncomeDailyVO::getTotalIncome).sum())) .monthTimeStr(item.stream().map(time -> { String timeStr = time.getMonthTime().toString(); return timeStr.substring(0, timeStr.length() - 2).concat("-").concat(timeStr.substring(timeStr.length() - 2)); }).findFirst().get()) .selfPurchaseIncome(Tools.fenToYuan(item.stream().mapToInt(UserIncomeDailyVO::getSelfPurchaseIncome).sum())).build())) ); resultMap.forEach((k, v) -> incomeStatisticalList.add(v)); responseResult.setData(incomeStatisticalList); return responseResult; }
以上是mysql統計訂單收益的案例的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。