Java模擬rank/over函數(shù)實現(xiàn)獲取分組排名的方法詳解
背景
考試批次 | 班級 | 姓名 | 語文 |
---|---|---|---|
202302 | 三年一班 | 張小明 | 130.00 |
202302 | 三年一班 | 王二小 | 128.00 |
202302 | 三年一班 | 謝春花 | 136.00 |
202302 | 三年二班 | 馮世杰 | 129.00 |
202302 | 三年二班 | 馬功成 | 130.00 |
202302 | 三年二班 | 魏翩翩 | 136.00 |
假設(shè)我們有如上數(shù)據(jù),現(xiàn)在有一個需求需要統(tǒng)計各學(xué)生語文單科成績在班級中的排名和全年段排名,你會如何實現(xiàn)?
很容易的我們想到了 rank() over() 實現(xiàn)
over()是分析函數(shù),可以和 rank()、 dense_rank() 、 row_number() 配合使用。
使用語法如下:
RANK() OVER(PARTITION BY COLUMN ORDER BY COLUMN) dense_rank() OVER(PARTITION BY COLUMN ORDER BY COLUMN) ROW_NUMBER() OVER(PARTITION BY COLUMN ORDER BY COLUMN)
解釋:partition by用于給結(jié)果集分組,如果沒有指定那么它把整個結(jié)果集作為一個分組。
- rank()涵數(shù)主要用于排序,并給出序號 ,對于排序并列的數(shù)據(jù)給予相同序號,并空出并列所占的名次。
- dense_rank() 功能同rank()一樣,區(qū)別在于不空出并列所占的名次
- row_number()涵數(shù)則是按照順序依次使用 ,不考慮并列
rank 結(jié)果為 1,2,2,4 dense_rank 結(jié)果為 1,2,2,3 row_number 結(jié)果為 1,2,3,4
實際應(yīng)用中,會存在數(shù)據(jù)從其他外部系統(tǒng)接入且數(shù)據(jù)量不大等多種情況,那么使用Java代碼的方式實現(xiàn)分組排名的功能則顯得更加方便。
詳細設(shè)計及實現(xiàn)
排序定義類 OrderBy
public class OrderBy { private String orderByEL; /** * 是否升序 */ private boolean ascend; public OrderBy(){ //默認(rèn)升序 this.ascend = true; } public String orderByEL(){ return this.orderByEL; } public OrderBy orderByEL(String orderByEL){ this.orderByEL = orderByEL; return this; } public OrderBy ascend(boolean ascend){ this.ascend = ascend; return this; } public boolean ascend(){ return this.ascend; } }
該類定義了如下屬性:
- 排序的fileld
- 是否升序
獲取排名方法
該方法定義如下:
<T> void rankOver(List<T> dataList, String[] partitionByFields, List<OrderBy> orderByList, String resultField, int rankType);
該方法提供了5個入?yún)ⅲ?/p>
dataList 排序的數(shù)據(jù)集
partitionByFields 分組field的數(shù)組
orderByList 排序字段集合
resultField 排名結(jié)果存放的字段
rankType 排名方式
- 1:不考慮并列(row_number 結(jié)果為 1,2,3,4)
- 2:考慮并列,空出并列所占的名次(rank 結(jié)果為 1,2,2,4)
- 3:考慮并列,不空出并列所占的名次(dense_rank 1,2,2,3)
該方法具體實現(xiàn)如下
public static <T> void rankOver(List<T> dataList, String[] partitionByFields, List<OrderBy> orderByList, String resultField, int rankType) { if (CollectionUtils.isEmpty(orderByList)) { return; } //STEP_01 剔除掉不參與排名的數(shù)據(jù) List<T> tempList = new ArrayList<>(); for (T data : dataList) { boolean part = true; for (OrderBy rptOrderBy : orderByList) { Object o1 = executeSpEL(rptOrderBy.orderByEL(), data); if (o1 == null) { //參與排序的值為null的話則不參與排名 part = false; break; } } if (part) { tempList.add(data); } } if (CollectionUtils.isEmpty(tempList)) { return; } //STEP_02 分組 Map<String, List<T>> groupMap = group(tempList, null, partitionByFields); for (List<T> groupDataList : groupMap.values()) { order(orderByList, groupDataList); if (rankType == 1) { int rank = 1; for (T temp : groupDataList) { setFieldValue(temp, resultField, rank); rank++; } } else { int prevRank = Integer.MIN_VALUE; int size = groupDataList.size(); for (int i = 0; i < size; i++) { T current = groupDataList.get(i); if (i == 0) { //第一名 setFieldValue(current, resultField, 1); prevRank = 1; } else { T prev = groupDataList.get(i - 1); boolean sameRankWithPrev = true;//并列排名 for (OrderBy rptOrderBy : orderByList) { Object o1 = executeSpEL(rptOrderBy.orderByEL(), current); Object o2 = executeSpEL(rptOrderBy.orderByEL(), prev); if (!o1.equals(o2)) { sameRankWithPrev = false; break; } } if (sameRankWithPrev) { setFieldValue(current, resultField, getFieldValue(prev, resultField)); if (rankType == 2) { ++prevRank; } } else { setFieldValue(current, resultField, ++prevRank); } } } } } }
使用案例
定義一個學(xué)生類:
public class Student { private String batch; private String banji; private String name; private Double yuwen; //extra private Integer rank1; private Integer rank2; public Student(String batch, String banji, String name, Double yuwen) { this.batch = batch; this.banji = banji; this.name = name; this.yuwen = yuwen; } }
我們寫一個方法,返回如下數(shù)據(jù):
public List<Student> getDataList() { List<Student> dataList = new ArrayList<>(); dataList.add(new Student("202302", "三年一班", "張小明", 130.0)); dataList.add(new Student("202302", "三年一班", "王二小", 128.0)); dataList.add(new Student("202302", "三年一班", "謝春花", 136.0)); dataList.add(new Student("202302", "三年二班", "馮世杰", 129.0)); dataList.add(new Student("202302", "三年二班", "馬功成", 130.0)); dataList.add(new Student("202302", "三年二班", "魏翩翩", 136.0)); return dataList; }
獲取學(xué)生語文成績的班級排名和年段排名,排名采用并列并空出并列所占用名次的方式。
List<Student> dataList = getDataList(); List<OrderBy> orderByList = new ArrayList<>(); orderByList.add(new OrderBy().orderByEL("yuwen").ascend(false)); //獲取全校排名 DataProcessUtil.rankOver(dataList, new String[]{"batch"}, orderByList, "rank1", 2); //獲取班級排名 DataProcessUtil.rankOver(dataList, new String[]{"batch", "banji"}, orderByList, "rank2", 2); log("語文單科成績排名情況如下:"); Map<String, List<Student>> groupMap = DataProcessUtil.group(dataList, null, new String[]{"batch"}); for (Map.Entry<String, List<Student>> entry : groupMap.entrySet()) { log("考試批次:" + entry.getKey()); for (Student s : entry.getValue()) { log(String.format("班級:%s 學(xué)生:%s 語文成績:%s 班級排名:%s 全校排名:%s", s.getBanji(), s.getName(), s.getYuwen(), s.getRank2(), s.getRank1())); } log(""); }
結(jié)果如下:
語文單科成績排名情況如下:
考試批次:202302
班級:三年一班 學(xué)生:張小明 語文成績:130.0 班級排名:2 全校排名:3
班級:三年一班 學(xué)生:王二小 語文成績:128.0 班級排名:3 全校排名:6
班級:三年一班 學(xué)生:謝春花 語文成績:136.0 班級排名:1 全校排名:1
班級:三年二班 學(xué)生:馮世杰 語文成績:129.0 班級排名:3 全校排名:5
班級:三年二班 學(xué)生:馬功成 語文成績:130.0 班級排名:2 全校排名:3
班級:三年二班 學(xué)生:魏翩翩 語文成績:136.0 班級排名:1 全校排名:1
可以看到全校排名中 有兩個并列第一名 兩個并列第三名,且空出了并列所占用的名次2 和 名次4
到此這篇關(guān)于Java模擬rank/over函數(shù)實現(xiàn)獲取分組排名的方法詳解的文章就介紹到這了,更多相關(guān)Java獲取分組排名內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot讀取yml文件中的list列表、數(shù)組、map集合和對象方法實例
在平時的yml配置文件中,我們經(jīng)常使用到配置基本數(shù)據(jù)類型的字符串,下面這篇文章主要給大家介紹了關(guān)于springboot讀取yml文件中的list列表、數(shù)組、map集合和對象的相關(guān)資料,需要的朋友可以參考下2023-02-02SpringBoot發(fā)現(xiàn)最新版Druid重大問題(坑)
這篇文章主要介紹了SpringBoot發(fā)現(xiàn)最新版Druid重大問題(坑),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09SpringBoot基于Minio實現(xiàn)分片上傳、斷點續(xù)傳的實現(xiàn)
本文主要介紹了SpringBoot基于Minio實現(xiàn)分片上傳、斷點續(xù)傳的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-08-08SpringBoot+MyBatisPlus中樂觀鎖的實現(xiàn)示例
樂觀鎖是一種用于解決并發(fā)沖突的機制,在數(shù)據(jù)庫中用于保護數(shù)據(jù)的一致性,本文主要介紹了SpringBoot+MyBatisPlus中樂觀鎖的實現(xiàn)示例,具有一定的參考價值,感興趣的可以了解一下2023-08-08CompletableFuture創(chuàng)建及功能使用全面詳解
這篇文章主要為大家介紹了CompletableFuture創(chuàng)建及功能使用全面詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-07-07SpringBoot 項目添加 MDC 日志鏈路追蹤的執(zhí)行流程
日志鏈路追蹤就是將一個標(biāo)志跨線程進行傳遞,在一般的小項目中也就是在你新起一個線程的時候,或者使用線程池執(zhí)行任務(wù)的時候會用到,比如追蹤一個用戶請求的完整執(zhí)行流程,本文給大家介紹SpringBoot MDC 日志鏈路追蹤的代碼,感興趣的朋友一起看看吧2021-06-06java并發(fā)編程中實現(xiàn)可見性的四種可行方案解析
這篇文章主要介紹了java并發(fā)編程中實現(xiàn)可見性的四種可行方案解析,使用關(guān)鍵字volatile和使用鎖(如synchronized關(guān)鍵字或者java.util.concurrent包中的鎖)來確保對共享變量的修改在多線程環(huán)境中能夠正確地被其他線程所觀察到,需要的朋友可以參考下2023-08-08