Java模擬rank/over函數(shù)實(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)在有一個(gè)需求需要統(tǒng)計(jì)各學(xué)生語文單科成績在班級中的排名和全年段排名,你會(huì)如何實(shí)現(xiàn)?
很容易的我們想到了 rank() over() 實(shí)現(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é)果集分組,如果沒有指定那么它把整個(gè)結(jié)果集作為一個(gè)分組。
- 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
實(shí)際應(yīng)用中,會(huì)存在數(shù)據(jù)從其他外部系統(tǒng)接入且數(shù)據(jù)量不大等多種情況,那么使用Java代碼的方式實(shí)現(xiàn)分組排名的功能則顯得更加方便。
詳細(xì)設(shè)計(jì)及實(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個(gè)入?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)
該方法具體實(shí)現(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);
}
}
}
}
}
}使用案例
定義一個(gè)學(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;
}
}
我們寫一個(gè)方法,返回如下數(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
可以看到全校排名中 有兩個(gè)并列第一名 兩個(gè)并列第三名,且空出了并列所占用的名次2 和 名次4
到此這篇關(guān)于Java模擬rank/over函數(shù)實(shí)現(xiàn)獲取分組排名的方法詳解的文章就介紹到這了,更多相關(guān)Java獲取分組排名內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MyBatis實(shí)現(xiàn)多表聯(lián)查的詳細(xì)代碼
這篇文章主要介紹了MyBatis如何實(shí)現(xiàn)多表聯(lián)查,通過實(shí)例代碼給大家介紹使用映射配置文件實(shí)現(xiàn)多表聯(lián)查,使用注解的方式實(shí)現(xiàn)多表聯(lián)查,需要的朋友可以參考下2022-08-08
SpringBoot與rabbitmq的結(jié)合的示例
這篇文章主要介紹了SpringBoot與rabbitmq的結(jié)合的示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-03-03
springboot?集成activemq項(xiàng)目配置方法
這篇文章主要介紹了springboot?集成activemq項(xiàng)目配置方法,e-car項(xiàng)目配置通過引入activemq依賴,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-04-04
本地編譯打包項(xiàng)目部署到服務(wù)器并且啟動(dòng)方式
這篇文章主要介紹了本地編譯打包項(xiàng)目部署到服務(wù)器并且啟動(dòng)方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02
使用ByteArrayOutputStream寫入字符串方式
這篇文章主要介紹了使用ByteArrayOutputStream寫入字符串方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
spring boot使用sharding jdbc的配置方式
這篇文章主要介紹了spring boot使用sharding jdbc的配置方式,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-12-12
SpringBoot使用Nacos進(jìn)行application.yml配置管理詳解
Nacos是阿里巴巴開源的一個(gè)微服務(wù)配置管理和服務(wù)發(fā)現(xiàn)的解決方案,下面我們來看看在SpringBoot中如何使用Nacos進(jìn)行application.yml配置管理吧2025-03-03

