Java内存对象实现聚合查询

文章目录

  • 什么是聚合查询
    • excel表格演示
      • 插入透视表
      • 透视表操作
    • sql聚合查询
      • 创建表和插入数据
      • 按照国家业务类型设备类型统计总销量
      • 按设备类型统计总销量
  • Java内存对象聚合查询
    • 普通对象方式
      • 创建对象
      • 聚合查询条件
      • 查询方法
      • 调用方式
      • 结果
    • Record对象方式
      • Recor对象
      • 创建对象
      • 聚合查询条件
      • 查询方法
      • 调用方法
      • 结果
  • 完整代码示例
    • DynamicAggregationDemo5.java
    • SalesStats.java
    • QueryReq
    • 相关jar包

什么是聚合查询

聚合查询的核心是​​对数据集进行分组​​,然后​​对每个分组应用聚合函数​​,最终得到汇总结果。
在数据类开发中经常用到。

excel表格演示

举一个最为常见的excel例子
有一份统计报表如下图所示

excel聚合查询示例

csv文件内容如下

销售年份,销售月份,国家,设备类型,业务类型,销售数量
2025,1,中国,手机,经销,15
2025,1,中国,穿戴终端,代销,8
2025,1,中国,电脑,直营,5
2025,1,美国,手机,经销,12
2025,1,美国,穿戴终端,代销,6
2025,1,美国,电脑,直营,4
2025,1,日本,手机,经销,10
2025,1,日本,穿戴终端,代销,5
2025,1,日本,电脑,直营,3
2025,2,中国,手机,经销,50
2025,2,美国,穿戴终端,代销,60
2025,2,日本,电脑,直营,16
2025,3,中国,手机,经销,48
2025,3,美国,穿戴终端,代销,30
2025,3,日本,电脑,直营,63
2025,4,中国,手机,经销,45
2025,4,美国,穿戴终端,代销,100
2025,4,日本,电脑,直营,22

插入透视表

插入透视表,不显示分类汇总,以表格形式展示

聚合查询透视表

就能很清晰的看到每个分组的销量。

透视表操作

顺序和分组聚合可以任意调整。
比如交换分组顺序

聚合查询透视表-列顺序

比如只用设备类型分组。

聚合查询透视表-设备类型

sql聚合查询

创建表和插入数据

-- 创建销售统计表  
CREATE TABLE salesStats (  id INT AUTO_INCREMENT PRIMARY KEY COMMENT '自增主键ID',  defaultYear INT NOT NULL COMMENT '销售年份',  defaultMon INT NOT NULL COMMENT '销售月份',  country VARCHAR(50) NOT NULL COMMENT '国家',  deviceFamilyName VARCHAR(50) NOT NULL COMMENT '设备类型',  erpBusinessType VARCHAR(50) NOT NULL COMMENT '业务类型',  amount INT NOT NULL COMMENT '销售数量',  INDEX idx_year_month (defaultYear, defaultMon) COMMENT '年份月份联合索引',  INDEX idx_country (country) COMMENT '国家索引',  INDEX idx_device (deviceFamilyName) COMMENT '设备类型索引'  
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='销售统计表';  -- 插入销售统计数据  
INSERT INTO salesStats (defaultYear, defaultMon, country, deviceFamilyName, erpBusinessType, amount) VALUES  (2025, 1, '中国', '手机', '经销', 15),  
(2025, 1, '中国', '穿戴终端', '代销', 8),  
(2025, 1, '中国', '电脑', '直营', 5),  
(2025, 1, '美国', '手机', '经销', 12),  
(2025, 1, '美国', '穿戴终端', '代销', 6),  
(2025, 1, '美国', '电脑', '直营', 4),  
(2025, 1, '日本', '手机', '经销', 10),  
(2025, 1, '日本', '穿戴终端', '代销', 5),  
(2025, 1, '日本', '电脑', '直营', 3),  (2025, 2, '中国', '手机', '经销', 50),  
(2025, 2, '美国', '穿戴终端', '代销', 60),  
(2025, 2, '日本', '电脑', '直营', 16),  (2025, 3, '中国', '手机', '经销', 48),  
(2025, 3, '美国', '穿戴终端', '代销', 30),  
(2025, 3, '日本', '电脑', '直营', 63),  (2025, 4, '中国', '手机', '经销', 45),  
(2025, 4, '美国', '穿戴终端', '代销', 100),  
(2025, 4, '日本', '电脑', '直营', 22);

上面的两个查询就可以表示为

按照国家业务类型设备类型统计总销量

-- 按照国家业务类型设备类型统计总销量
SELECT  country as '国家',  deviceFamilyName AS '设备类型',  erpBusinessType as '业务类型',  SUM(amount) AS '销量'  
FROM salesStats  
WHERE 1 = 1  
GROUP BY country,erpBusinessType,deviceFamilyName  
ORDER BY country,erpBusinessType,deviceFamilyName;# 中国,电脑,直营,5  
# 中国,手机,经销,158  
# 日本,穿戴终端,代销,5  
# 日本,电脑,直营,104  
# 日本,手机,经销,10  
# 美国,穿戴终端,代销,196  
# 美国,电脑,直营,4  
# 美国,手机,经销,12

按设备类型统计总销量

-- 2. 按设备类型统计总销量  
SELECT  deviceFamilyName AS '设备类型',  SUM(amount) AS '总销量'  
FROM salesStats  
GROUP BY deviceFamilyName  
ORDER BY SUM(amount) DESC;  # 穿戴终端,209  
# 手机,180  
# 电脑,113

但是sql实现方法存在一定局限性。
如果数据对象还需要经过清洗和其他关系映射等复杂的业务逻辑sql就很难用。
如果需要对一批数据频繁查询就会造成数据库的压力,而且不能保留聚合的过程数据。
好在使用java内存对象聚合查询就可以覆盖上述场景。

Java内存对象聚合查询

普通对象方式

创建对象

package org.example.aggquery;  import lombok.Data;  @Data  
public class SalesStats {  /** 设备类型 */  private String deviceFamilyName;  /** 国家 */  private String country;  /** 数量 */  private Integer amount;  /** 销售日期年 */  private Integer defaultYear;  /** 销售日期月 */  private Integer defaultMon;  /** 业务类型 */  private String erpBusinessType;  // 构造方法  public SalesStats(int defaultYear,int defaultMon, String country, String deviceFamilyName,  String erpBusinessType, int amount) {  this.defaultYear = defaultYear;  this.defaultMon = defaultMon;  this.country = country;  this.deviceFamilyName = deviceFamilyName;  this.erpBusinessType = erpBusinessType;  this.amount = amount;  }  }

聚合查询条件

@Data  
public class QueryReq {  List<String> groupByDim ;  
}

查询方法

public List<Map<String, Object>> aggQueryByObj(QueryReq req) {  List<SalesStats> dataList = mockObjData();  if (req.getGroupByDim() == null || req.getGroupByDim().size() == 0) {  // 无聚合条件,只按年月聚合  Map<String, Double> result = dataList.stream()  .collect(Collectors.groupingBy(  it -> it.getDefaultYear() * 100 + it.getDefaultMon() + "",  Collectors.summingDouble(SalesStats::getAmount)  ));  return Collections.singletonList(new DynamicAggregationDemo5.AggregationResult(result).toMap());  }  // 定义分组条件  List<Collector<SalesStats, ?, ?>> collectors = new ArrayList<>();  for (String dim : req.getGroupByDim()) {  if (dim.equals("country")) {  collectors.add(Collectors.groupingBy(SalesStats::getCountry));  }  if (dim.equals("deviceFamilyName")) {  collectors.add(Collectors.groupingBy(SalesStats::getDeviceFamilyName));  }  // 修改点4  if (dim.equals("erpBusinessType")) {  collectors.add(Collectors.groupingBy(SalesStats::getDeviceFamilyName));  }  }  // 动态构建分组  Collector<SalesStats, ?, Map<String, Map<String, Double>>> groupedCollector =  Collectors.groupingBy(  data -> {  List<String> keys = new ArrayList<>();  if (req.getGroupByDim().contains("country")) {  keys.add(data.getCountry());  }  if (req.getGroupByDim().contains("deviceFamilyName")) {  keys.add(data.getDeviceFamilyName());  }  // 修改点5  if (req.getGroupByDim().contains("erpBusinessType")) {  keys.add(data.getErpBusinessType());  }  return String.join("|", keys);  },  Collectors.groupingBy(  it -> it.getDefaultYear() * 100 + it.getDefaultMon() + "",  Collectors.summingDouble(SalesStats::getAmount)  )  );  // 执行分组  Map<String, Map<String, Double>> groupedData = dataList.stream()  .collect(groupedCollector);  // 转换为结果格式  return groupedData.entrySet().stream()  .map(entry -> {  String[] keys = entry.getKey().split("\\|");  DynamicAggregationDemo5.AggregationResult result = new DynamicAggregationDemo5.AggregationResult(entry.getValue());  int keyIndex = 0;  if (req.getGroupByDim().contains("country")) {  result.setCountry(keys[keyIndex++]);  }  if (req.getGroupByDim().contains("deviceFamilyName")) {  result.setDeviceFamilyName(keys[keyIndex++]);  }  // 修改点6  if (req.getGroupByDim().contains("erpBusinessType")) {  result.setErpBusinessType(keys[keyIndex++]);  }  return result.toMap();  })  .collect(Collectors.toList());  }// 聚合结果类  
static class AggregationResult {  private Map<String, Double> monthlyAmounts;  private String country;  private String deviceFamilyName;  // 修改点1  private String erpBusinessType;  public AggregationResult(Map<String, Double> monthlyAmounts) {  this.monthlyAmounts = monthlyAmounts;  }  public void setCountry(String country) {  this.country = country;  }  public void setDeviceFamilyName(String deviceFamilyName) {  this.deviceFamilyName = deviceFamilyName;  }  // 修改点2  public void setErpBusinessType(String erpBusinessType) {  this.erpBusinessType = erpBusinessType;  }  public Map<String, Object> toMap() {  Map<String, Object> result = new LinkedHashMap<>();  result.putAll(monthlyAmounts);  if (country != null) {  result.put("country", country);  }  if (deviceFamilyName != null) {  result.put("deviceFamilyName", deviceFamilyName);  }  // 修改点3  if (erpBusinessType != null) {  result.put("erpBusinessType", erpBusinessType);  }  return result;  }  
}private static String assemblyObjResp(List<Map<String, Object>> resultDetail) {  // 模拟jrpc 返回结果  Map<String, Object> result = new HashMap<>();  result.put("jsonrpc", 2.0);  result.put("id", "5691388858018585");  Map<String, Object> resultData = new HashMap<>();  resultData.put("total", resultDetail.size());  resultData.put("data", resultDetail);  result.put("result", resultData);  String jsonString = JSONObject.toJSONString(result);  return jsonString;  
}

调用方式

public static void main(String[] args) {  DynamicAggregationDemo5 dynamicAggregationDemo5 = new DynamicAggregationDemo5();  QueryReq req = new QueryReq();  // 修改这里的顺序和元素就可以动态聚合查询req.setGroupByDim(Arrays.asList("erpBusinessType", "country", "deviceFamilyName"));  // 对象方式  List<Map<String, Object>> resultDetailByObj = dynamicAggregationDemo5.aggQueryByObj(req);  String jsonObj = assemblyObjResp(resultDetailByObj);  System.out.println(jsonObj);   
}

结果

json格式化后

{  "result": {  "total": 9,  "data": [  {  "202501": 12,  "country": "美国",  "deviceFamilyName": "手机",  "erpBusinessType": "经销"  },  {  "202501": 3,  "202502": 16,  "202503": 63,  "202504": 22,  "country": "日本",  "deviceFamilyName": "电脑",  "erpBusinessType": "直营"  },  {  "202501": 6,  "202502": 60,  "202503": 30,  "202504": 100,  "country": "美国",  "deviceFamilyName": "穿戴终端",  "erpBusinessType": "代销"  },  {  "202501": 5,  "country": "日本",  "deviceFamilyName": "穿戴终端",  "erpBusinessType": "代销"  },  {  "202501": 10,  "country": "日本",  "deviceFamilyName": "手机",  "erpBusinessType": "经销"  },  {  "202501": 5,  "country": "中国",  "deviceFamilyName": "电脑",  "erpBusinessType": "直营"  },  {  "202501": 15,  "202502": 50,  "202503": 48,  "202504": 45,  "country": "中国",  "deviceFamilyName": "手机",  "erpBusinessType": "经销"  },  {  "202501": 4,  "country": "美国",  "deviceFamilyName": "电脑",  "erpBusinessType": "直营"  },  {  "202501": 8,  "country": "中国",  "deviceFamilyName": "穿戴终端",  "erpBusinessType": "代销"  }  ]  },  "id": "5691388858018585",  "jsonrpc": 2  
}

Record对象方式

普通对象方式还是存在以下问题
1.需要六个改动点才能新增出一个聚合属性
2.每次都需要新增一个统计对象类,如果项目这类查询对象很多会造成太多类需要维护。
那么有没有更灵活一点的方法呢?
有的兄弟,有的。

Recor对象

对象源码如下所示

public class Record implements Map<String, Object>, Serializable, Cloneable, Comparable<Record> {  private static final long serialVersionUID = -7753504263747912181L;  protected static Callable<Record> factory;  // 属性所在位置private Map<String, Object> map = new LinkedHashMap();  private List<String> keys = new ArrayList();// ...略
}

我之前有写过一篇 [[一文告诉你如何做数据库技术选型#一、对象的本质 —— 内存中]]有这类对象的介绍。
这里再简单重复一下。
所有java对象其实都可以看做一个固定key值的Map,

public class Order {private int orderId;private String orderName;
}
Order order = new Order();
order.setOrdreId(1);
order.setOrderName("订单A");

如果key值也不固定,key的数量也不固定他会变成,一个纯粹的Map键值对。

Map<String, Object> orde = new HashMap<>();
order.put("orderId", 1);
order.put("orderName", "订单A");

这就是Record对象原理,使用这个对象需要再pom文件引入如下依赖

<dependency>  <groupId>org.nutz</groupId>  <artifactId>nutz</artifactId>  <version>1.r.70-SNAPSHOT</version>  
</dependency>

创建对象

直接new Record();

聚合查询条件

@Data  
public class QueryReq {  List<String> groupByDim ;  
}

查询方法

public List<Record> aggQueryByRecord(QueryReq req) {  List<Record> records = mockRecordData();  List<String> groupByDim = req.getGroupByDim();  Map<String, List<Record>> collect = new LinkedHashMap<>();  collect = records.stream().collect(Collectors.groupingBy(e -> {  String key = "";  for (String dim : groupByDim) {  key = key + "&" + e.getString(dim);  }  return key;  }  ));  // 多线程处理分组后的数据  List<Future<List<Record>>> future = new ArrayList<>();  // 多线程处理分组后的数据  for (String dim : collect.keySet()) {  List<Record> recordsByDim = collect.get(dim);  future.add(threadPoolExecutor.submit(() -> {  List<Record> oneCake = dealProcess(req, recordsByDim, dim);  return oneCake;  }));  }  List<Record> result = new ArrayList<>();  // 获取结果  for (Future<List<Record>> listFuture : future) {  try {  result.addAll(listFuture.get());  } catch (Exception e) {  throw new RuntimeException("计算异常,原因:" + e.getMessage());  }  }  return result;  
}private List<Record> dealProcess(QueryReq req, List<Record> recordsByDim, String dim) {  List<Record> resultAll = new ArrayList<>();  Map<String, Double> collect = recordsByDim.stream().collect(  Collectors.groupingBy(  record -> record.getInt("defaultyear") * 100 + record.getInt("defaultmon") + "",  // 分组键  LinkedHashMap::new,                              // 使用LinkedHashMap保持顺序  Collectors.summingDouble(record -> record.getDouble("amount"))  // 求和操作  )  );  Set<String> keys = collect.keySet();  Record item = new Record();  for (String key : keys) {  // records 按月聚合  item.put(key, collect.getOrDefault(key, 0.0));  // 补充分组数据用于前端聚合  List<String> groupByDim = req.getGroupByDim();  String[] split = dim.split("&");  // 补充分组数据用于前端聚合  for (int i = 0; i < groupByDim.size(); i++) {  item.put(groupByDim.get(i), split[i + 1]);  }  }  resultAll.add(item);  return resultAll;  
}private static String assemblyRecordResp(List<Record> resultDetail) {  // 模拟jrpc 返回结果  Map<String, Object> result = new HashMap<>();  result.put("jsonrpc", 2.0);  result.put("id", "5691388858018585");  Map<String, Object> resultData = new HashMap<>();  resultData.put("total", resultDetail.size());  resultData.put("data", resultDetail);  result.put("result", resultData);  String jsonString = JSONObject.toJSONString(result);  return jsonString;  
}

调用方法

public static void main(String[] args) {  DynamicAggregationDemo5 dynamicAggregationDemo5 = new DynamicAggregationDemo5();  QueryReq req = new QueryReq(); // 修改这里的顺序和元素就可以动态聚合查询 req.setGroupByDim(Arrays.asList("erpBusinessType", "country", "deviceFamilyName"));  // Record方式  List<Record> resultDetailByRecord = dynamicAggregationDemo5.aggQueryByRecord(req);  String jsonRecord = assemblyRecordResp(resultDetailByRecord);  System.out.println(jsonRecord);  
}

结果

{  "result": {  "total": 9,  "data": [  {  "202501": 5,  "erpbusinesstype": "代销",  "country": "日本",  "devicefamilyname": "穿戴终端"  },  {  "202501": 8,  "erpbusinesstype": "代销",  "country": "中国",  "devicefamilyname": "穿戴终端"  },  {  "202501": 4,  "erpbusinesstype": "直营",  "country": "美国",  "devicefamilyname": "电脑"  },  {  "202501": 6,  "202502": 60,  "202503": 30,  "202504": 100,  "erpbusinesstype": "代销",  "country": "美国",  "devicefamilyname": "穿戴终端"  },  {  "202501": 3,  "202502": 16,  "202503": 63,  "202504": 22,  "erpbusinesstype": "直营",  "country": "日本",  "devicefamilyname": "电脑"  },  {  "202501": 10,  "erpbusinesstype": "经销",  "country": "日本",  "devicefamilyname": "手机"  },  {  "202501": 15,  "202502": 50,  "202503": 48,  "202504": 45,  "erpbusinesstype": "经销",  "country": "中国",  "devicefamilyname": "手机"  },  {  "202501": 5,  "erpbusinesstype": "直营",  "country": "中国",  "devicefamilyname": "电脑"  },  {  "202501": 12,  "erpbusinesstype": "经销",  "country": "美国",  "devicefamilyname": "手机"  }  ]  },  "id": "5691388858018585",  "jsonrpc": 2  
}

完整代码示例

DynamicAggregationDemo5.java

package org.example.aggquery;  import com.alibaba.fastjson.JSONObject;  
import org.nutz.dao.entity.Record;  import java.util.*;  
import java.util.concurrent.Future;  
import java.util.concurrent.LinkedBlockingQueue;  
import java.util.concurrent.ThreadPoolExecutor;  
import java.util.concurrent.TimeUnit;  
import java.util.stream.Collector;  
import java.util.stream.Collectors;  /**  * 聚合查询   */  
public class DynamicAggregationDemo5 {  ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(4, 10, 60, TimeUnit.SECONDS,  new LinkedBlockingQueue<Runnable>(50), new ThreadPoolExecutor.CallerRunsPolicy());  public List<Map<String, Object>> aggQueryByObj(QueryReq req) {  List<SalesStats> dataList = mockObjData();  if (req.getGroupByDim() == null || req.getGroupByDim().size() == 0) {  // 无聚合条件,只按年月聚合  Map<String, Double> result = dataList.stream()  .collect(Collectors.groupingBy(  it -> it.getDefaultYear() * 100 + it.getDefaultMon() + "",  Collectors.summingDouble(SalesStats::getAmount)  ));  return Collections.singletonList(new DynamicAggregationDemo5.AggregationResult(result).toMap());  }  // 定义分组条件  List<Collector<SalesStats, ?, ?>> collectors = new ArrayList<>();  for (String dim : req.getGroupByDim()) {  if (dim.equals("country")) {  collectors.add(Collectors.groupingBy(SalesStats::getCountry));  }  if (dim.equals("deviceFamilyName")) {  collectors.add(Collectors.groupingBy(SalesStats::getDeviceFamilyName));  }  // 修改点4  if (dim.equals("erpBusinessType")) {  collectors.add(Collectors.groupingBy(SalesStats::getDeviceFamilyName));  }  }  // 动态构建分组  Collector<SalesStats, ?, Map<String, Map<String, Double>>> groupedCollector =  Collectors.groupingBy(  data -> {  List<String> keys = new ArrayList<>();  if (req.getGroupByDim().contains("country")) {  keys.add(data.getCountry());  }  if (req.getGroupByDim().contains("deviceFamilyName")) {  keys.add(data.getDeviceFamilyName());  }  // 修改点5  if (req.getGroupByDim().contains("erpBusinessType")) {  keys.add(data.getErpBusinessType());  }  return String.join("|", keys);  },  Collectors.groupingBy(  it -> it.getDefaultYear() * 100 + it.getDefaultMon() + "",  Collectors.summingDouble(SalesStats::getAmount)  )  );  // 执行分组  Map<String, Map<String, Double>> groupedData = dataList.stream()  .collect(groupedCollector);  // 转换为结果格式  return groupedData.entrySet().stream()  .map(entry -> {  String[] keys = entry.getKey().split("\\|");  DynamicAggregationDemo5.AggregationResult result = new DynamicAggregationDemo5.AggregationResult(entry.getValue());  int keyIndex = 0;  if (req.getGroupByDim().contains("country")) {  result.setCountry(keys[keyIndex++]);  }  if (req.getGroupByDim().contains("deviceFamilyName")) {  result.setDeviceFamilyName(keys[keyIndex++]);  }  // 修改点6  if (req.getGroupByDim().contains("erpBusinessType")) {  result.setErpBusinessType(keys[keyIndex++]);  }  return result.toMap();  })  .collect(Collectors.toList());  }  public List<Record> aggQueryByRecord(QueryReq req) {  List<Record> records = mockRecordData();  List<String> groupByDim = req.getGroupByDim();  Map<String, List<Record>> collect = new LinkedHashMap<>();  collect = records.stream().collect(Collectors.groupingBy(e -> {  String key = "";  for (String dim : groupByDim) {  key = key + "&" + e.getString(dim);  }  return key;  }  ));  // 多线程处理分组后的数据  List<Future<List<Record>>> future = new ArrayList<>();  // 多线程处理分组后的数据  for (String dim : collect.keySet()) {  List<Record> recordsByDim = collect.get(dim);  future.add(threadPoolExecutor.submit(() -> {  List<Record> oneCake = dealProcess(req, recordsByDim, dim);  return oneCake;  }));  }  List<Record> result = new ArrayList<>();  // 获取结果  for (Future<List<Record>> listFuture : future) {  try {  result.addAll(listFuture.get());  } catch (Exception e) {  throw new RuntimeException("计算异常,原因:" + e.getMessage());  }  }  return result;  }  public static void main(String[] args) {  DynamicAggregationDemo5 dynamicAggregationDemo5 = new DynamicAggregationDemo5();  QueryReq req = new QueryReq();  req.setGroupByDim(Arrays.asList("erpBusinessType", "country", "deviceFamilyName"));  // 对象方式  List<Map<String, Object>> resultDetailByObj = dynamicAggregationDemo5.aggQueryByObj(req);  String jsonObj = assemblyObjResp(resultDetailByObj);  System.out.println(jsonObj);  // Record方式  List<Record> resultDetailByRecord = dynamicAggregationDemo5.aggQueryByRecord(req);  String jsonRecord = assemblyRecordResp(resultDetailByRecord);  System.out.println(jsonRecord);  }  private static String assemblyRecordResp(List<Record> resultDetail) {  // 模拟jrpc 返回结果  Map<String, Object> result = new HashMap<>();  result.put("jsonrpc", 2.0);  result.put("id", "5691388858018585");  Map<String, Object> resultData = new HashMap<>();  resultData.put("total", resultDetail.size());  resultData.put("data", resultDetail);  result.put("result", resultData);  String jsonString = JSONObject.toJSONString(result);  return jsonString;  }  private static String assemblyObjResp(List<Map<String, Object>> resultDetail) {  // 模拟jrpc 返回结果  Map<String, Object> result = new HashMap<>();  result.put("jsonrpc", 2.0);  result.put("id", "5691388858018585");  Map<String, Object> resultData = new HashMap<>();  resultData.put("total", resultDetail.size());  resultData.put("data", resultDetail);  result.put("result", resultData);  String jsonString = JSONObject.toJSONString(result);  return jsonString;  }  private List<Record> dealProcess(QueryReq req, List<Record> recordsByDim, String dim) {  List<Record> resultAll = new ArrayList<>();  Map<String, Double> collect = recordsByDim.stream().collect(  Collectors.groupingBy(  record -> record.getInt("defaultyear") * 100 + record.getInt("defaultmon") + "",  // 分组键  LinkedHashMap::new,                              // 使用LinkedHashMap保持顺序  Collectors.summingDouble(record -> record.getDouble("amount"))  // 求和操作  )  );  Set<String> keys = collect.keySet();  Record item = new Record();  for (String key : keys) {  // records 按月聚合  item.put(key, collect.getOrDefault(key, 0.0));  // 补充分组数据用于前端聚合  List<String> groupByDim = req.getGroupByDim();  String[] split = dim.split("&");  // 补充分组数据用于前端聚合  for (int i = 0; i < groupByDim.size(); i++) {  item.put(groupByDim.get(i), split[i + 1]);  }  }  resultAll.add(item);  return resultAll;  }  public static List<SalesStats> mockObjData() {  return generateSalesStats();  }  public static List<Record> mockRecordData() {  return generateSalesStatsRecord();  }  public static List<SalesStats> generateSalesStats() {  List<SalesStats> statsList = new ArrayList<>();  // 中国数据  statsList.add(new SalesStats(2025, 1, "中国", "手机", "经销", 15));  statsList.add(new SalesStats(2025, 1, "中国", "穿戴终端", "代销", 8));  statsList.add(new SalesStats(2025, 1, "中国", "电脑", "直营", 5));  // 美国数据  statsList.add(new SalesStats(2025, 1, "美国", "手机", "经销", 12));  statsList.add(new SalesStats(2025, 1, "美国", "穿戴终端", "代销", 6));  statsList.add(new SalesStats(2025, 1, "美国", "电脑", "直营", 4));  // 日本数据  statsList.add(new SalesStats(2025, 1, "日本", "手机", "经销", 10));  statsList.add(new SalesStats(2025, 1, "日本", "穿戴终端", "代销", 5));  statsList.add(new SalesStats(2025, 1, "日本", "电脑", "直营", 3));  statsList.add(new SalesStats(2025, 2, "中国", "手机", "经销", 50));  statsList.add(new SalesStats(2025, 2, "美国", "穿戴终端", "代销", 60));  statsList.add(new SalesStats(2025, 2, "日本", "电脑", "直营", 16));  statsList.add(new SalesStats(2025, 3, "中国", "手机", "经销", 48));  statsList.add(new SalesStats(2025, 3, "美国", "穿戴终端", "代销", 30));  statsList.add(new SalesStats(2025, 3, "日本", "电脑", "直营", 63));  statsList.add(new SalesStats(2025, 4, "中国", "手机", "经销", 45));  statsList.add(new SalesStats(2025, 4, "美国", "穿戴终端", "代销", 100));  statsList.add(new SalesStats(2025, 4, "日本", "电脑", "直营", 22));  return statsList;  }  public static List<Record> generateSalesStatsRecord() {  List<Record> statsList = new ArrayList<>();  statsList.add(newRecord(2025, 1, "中国", "手机", "经销", 15));  statsList.add(newRecord(2025, 1, "中国", "穿戴终端", "代销", 8));  statsList.add(newRecord(2025, 1, "中国", "电脑", "直营", 5));  statsList.add(newRecord(2025, 1, "美国", "手机", "经销", 12));  statsList.add(newRecord(2025, 1, "美国", "穿戴终端", "代销", 6));  statsList.add(newRecord(2025, 1, "美国", "电脑", "直营", 4));  statsList.add(newRecord(2025, 1, "日本", "手机", "经销", 10));  statsList.add(newRecord(2025, 1, "日本", "穿戴终端", "代销", 5));  statsList.add(newRecord(2025, 1, "日本", "电脑", "直营", 3));  statsList.add(newRecord(2025, 2, "中国", "手机", "经销", 50));  statsList.add(newRecord(2025, 2, "美国", "穿戴终端", "代销", 60));  statsList.add(newRecord(2025, 2, "日本", "电脑", "直营", 16));  statsList.add(newRecord(2025, 3, "中国", "手机", "经销", 48));  statsList.add(newRecord(2025, 3, "美国", "穿戴终端", "代销", 30));  statsList.add(newRecord(2025, 3, "日本", "电脑", "直营", 63));  statsList.add(newRecord(2025, 4, "中国", "手机", "经销", 45));  statsList.add(newRecord(2025, 4, "美国", "穿戴终端", "代销", 100));  statsList.add(newRecord(2025, 4, "日本", "电脑", "直营", 22));  return statsList;  }  private static Record newRecord(int defaultYear, int defaultMon, String country, String deviceFamilyName, String erpBusinessType, int amount) {  Record record = new Record();  record.put("defaultyear", defaultYear);  record.put("defaultmon", defaultMon);  record.put("amount", amount);  record.put("country", country);  record.put("deviceFamilyName", deviceFamilyName);  record.put("erpBusinessType", erpBusinessType);  return record;  }  // 聚合结果类  static class AggregationResult {  private Map<String, Double> monthlyAmounts;  private String country;  private String deviceFamilyName;  // 修改点1  private String erpBusinessType;  public AggregationResult(Map<String, Double> monthlyAmounts) {  this.monthlyAmounts = monthlyAmounts;  }  public void setCountry(String country) {  this.country = country;  }  public void setDeviceFamilyName(String deviceFamilyName) {  this.deviceFamilyName = deviceFamilyName;  }  // 修改点2  public void setErpBusinessType(String erpBusinessType) {  this.erpBusinessType = erpBusinessType;  }  public Map<String, Object> toMap() {  Map<String, Object> result = new LinkedHashMap<>();  result.putAll(monthlyAmounts);  if (country != null) {  result.put("country", country);  }  if (deviceFamilyName != null) {  result.put("deviceFamilyName", deviceFamilyName);  }  // 修改点3  if (erpBusinessType != null) {  result.put("erpBusinessType", erpBusinessType);  }  return result;  }  }  }

SalesStats.java

package org.example.aggquery;  import lombok.Data;  @Data  
public class SalesStats {  /** 设备类型 */  private String deviceFamilyName;  /** 国家 */  private String country;  /** 数量 */  private Integer amount;  /** 销售日期年 */  private Integer defaultYear;  /** 销售日期月 */  private Integer defaultMon;  /** 业务类型 */  private String erpBusinessType;  // 构造方法  public SalesStats(int defaultYear,int defaultMon, String country, String deviceFamilyName,  String erpBusinessType, int amount) {  this.defaultYear = defaultYear;  this.defaultMon = defaultMon;  this.country = country;  this.deviceFamilyName = deviceFamilyName;  this.erpBusinessType = erpBusinessType;  this.amount = amount;  }  }

QueryReq

package org.example.aggquery;  import lombok.Data;  import java.util.List;  @Data  
public class QueryReq {  List<String> groupByDim ;  
}

相关jar包

<dependency>  <groupId>com.alibaba</groupId>  <artifactId>fastjson</artifactId>  <version>1.2.62</version>  
</dependency>  
<!-- Record对象需引入 -->  
<dependency>  <groupId>org.nutz</groupId>  <artifactId>nutz</artifactId>  <version>1.r.70-SNAPSHOT</version>  
</dependency>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/79315.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VSCode开发调试Python入门实践(Windows10)

我的Windows10上的python环境是免安装直接解压的Python3.8.x老版本&#xff0c;可参见《Windows下Python3.8环境快速安装部署。 1. 安装VSCode 在Windows 10系统上安装Visual Studio Code&#xff08;VS Code&#xff09;是一个简单的过程&#xff0c;以下是详细的安装方法与…

Tomcat DOS漏洞复现(CVE-2025-31650)

免责申明: 本文所描述的漏洞及其复现步骤仅供网络安全研究与教育目的使用。任何人不得将本文提供的信息用于非法目的或未经授权的系统测试。作者不对任何由于使用本文信息而导致的直接或间接损害承担责任。如涉及侵权,请及时与我们联系,我们将尽快处理并删除相关内容。 前…

使用Qt QAxObject解决Visual Fox Pro数据库乱码问题

文章目录 使用Qt QAxObject解决Visual Fox Pro数据库乱码问题一、问题背景&#xff1a;ODBC读取DBF文件的编码困境二、核心方案&#xff1a;通过QAxObject调用ADO操作DBF1. 技术选型&#xff1a;为什么选择ADO&#xff1f;2. 核心代码解析&#xff1a;QueryDataByAdodb函数3. 连…

HTTP知识速通

一.HTTP的基础概念 首先了解HTTP协议&#xff0c;他是目前主要使用在应用层的一种协议 http被称为超文本传输协议 而https则是安全的超文本传输协议 本章节的内容首先就是对http做一个简单的了解。 HTTP是一种应用层协议&#xff0c;是基于TCP/IP协议来传递信息的。 其中…

制作一款打飞机游戏26:精灵编辑器

虽然我们基本上已经重建了Axel编辑器&#xff0c;但我不想直接使用它。我想创建一个真正适合我们当前目的的编辑器&#xff0c;那就是编辑精灵&#xff08;sprites&#xff09;。这将是今天的一个大目标——创建一个基于模板的编辑器&#xff0c;用它作为我们实际编辑器的起点。…

mac下载homebrew 安装和使用git

mac下载homebrew 安装和使用git 本人最近从windows换成mac&#xff0c;记录一下用homebrew安装git的过程 打开终端 command 空格&#xff0c;搜索终端 安装homebrew 在终端中输入下面命令&#xff0c;来安装homebrew /bin/bash -c "$(curl -fsSL https://raw.githu…

【LeetCode Hot100】图论篇

前言 本文用于整理LeetCode Hot100中题目解答&#xff0c;因题目比较简单且更多是为了面试快速写出正确思路&#xff0c;只做简单题意解读和一句话题解方便记忆。但代码会全部给出&#xff0c;方便大家整理代码思路。 200. 岛屿数量 一句话题意 求所有上下左右的‘1’的连通块…

《社交类应用开发:React Native与Flutter的抉择》

社交类应用以令人目不暇接的速度更新迭代。新功能不断涌现&#xff0c;从更智能的算法推荐到多样化的互动形式&#xff0c;从增强的隐私保护到跨平台的无缝体验&#xff0c;每一次更新都旨在满足用户日益增长且多变的需求。面对如此高频的更新需求&#xff0c;选择合适的跨端框…

关于3D的一些基础知识

什么是2D/3D? 2D&#xff08;二维&#xff09;和3D&#xff08;三维&#xff09;是描述空间维度的概念&#xff0c;它们的核心区别在于空间维度、视觉表现和应用场景。以下是详细对比&#xff1a; 1. 定义与维度 • 2D&#xff08;二维&#xff09; • 定义&#xff1a;仅包…

大连理工大学选修课——机器学习笔记(7):集成学习及随机森林

集成学习及随机森林 集成学习概述 泛化能力的局限 每种学习模型的能力都有其上限 限制于特定结构受限于训练样本的质量和规模 如何再提高泛化能力&#xff1f; 研究新结构扩大训练规模 提升模型的泛化能力 创造性思路 组合多个学习模型 集成学习 集成学习不是特定的…

嵌入式产品运行中数据丢失怎么办?

目录 1、数据丢失现象与根源分析 2、硬件层优化 3、系统/驱动层优化 4、应用软件层优化 5、文件系统选型深度解析 5.1、NAND Flash 适用文件系统 5.2、eMMC 适用文件系统 6、系统挂载选项优化实践 嵌入式系统在运行过程中&#xff0c;尤其是在涉及频繁数据写入&#xf…

第十一节:性能优化高频题-响应式数据深度监听问题

解决方案&#xff1a;watch的deep: true选项或watchEffect自动追踪依赖 Vue响应式数据深度监听与性能优化指南 一、深度监听的核心方案 watch的deep: true模式 • Vue2实现&#xff1a;需显式声明深度监听配置 watch: {obj: {handler(newVal) { /* 处理逻辑 */ },deep: tru…

【Linux实践系列】:进程间通信:万字详解命名管道实现通信

&#x1f525; 本文专栏&#xff1a;Linux Linux实践项目 &#x1f338;作者主页&#xff1a;努力努力再努力wz &#x1f4aa; 今日博客励志语录&#xff1a; 与其等待完美的风&#xff0c;不如学会在逆风中调整帆的角度——所有伟大航程都始于此刻出发的勇气 ★★★ 本文前置知…

权力结构下的人才价值重构:从 “工具论” 到 “存在论” 的转变​

引言​ 在现在的公司管理里&#xff0c;常常能听到这样一种说法&#xff1a;“我用你&#xff0c;你才是人才&#xff1b;不用你&#xff0c;你啥都不是。” 这其实反映了一种很常见的评判人才价值的标准&#xff0c;就是只看公司的需求&#xff0c;把人才当作实现公司目标的工…

UE实用地编插件Physical Layout Tool

免费插件 https://www.fab.com/zh-cn/listings/a7fb6fcf-596f-48e9-83cc-f584aea316b1 可以通过物理模拟批量放置物体 不用再一个个摆放了 装饰环境从未如此简单&#xff0c;您不必再考虑对齐物体。 物理地放置物体&#xff0c;移动它们&#xff0c;在移动或在地图上放置物体…

Nerfstudio 环境配置与自有数据集(图片和视频)测试全方位全流程实战【2025最新版!!】

一、引言 神经辐射场(Neural Radiance Fields&#xff0c;简称NeRF)是近年来计算机视觉和图形学领域的一项革命性技术&#xff0c;它能够从2D图像中学习复杂的3D场景表示。然而&#xff0c;NeRF技术的实现和应用门槛较高&#xff0c;需要较为专业的计算机视觉和深度学习知识。…

Transformer:颠覆深度学习的架构革命与技术演进

2017年&#xff0c;谷歌团队在论文《Attention Is All You Need》中提出的Transformer架构&#xff0c;彻底改变了人工智能对序列数据的处理范式。它不仅解决了传统循环神经网络&#xff08;RNN&#xff09;的长期依赖和并行化难题&#xff0c;更催生了BERT、GPT等划时代模型&a…

原型模式(Prototype Pattern)详解

文章目录 1. 什么是原型模式&#xff1f;2. 为什么需要原型模式&#xff1f;3. 原型模式的结构4. 原型模式的基本实现4.1 基础示例&#xff1a;简单的原型模式4.2 使用Java的Cloneable接口 5. 深拷贝与浅拷贝5.1 浅拷贝&#xff08;Shallow Copy&#xff09;5.2 深拷贝&#xf…

掉馅饼,八分之一到二分之一:《分析模式》漫谈59

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 “Analysis Patterns”的第6章“存货和会计”原文&#xff1a; The transactions creation would then be the only place that could create entries. ... Providing only the trans…

使用Python和Pandas实现的Amazon Redshift权限检查与SQL生成用于IT审计

import pandas as pd import psycopg2 from psycopg2 import sql# 连接Redshift conn psycopg2.connect(hostyour-cluster.endpoint.redshift.amazonaws.com,port5439,dbnamedev,useradmin,passwordyour-password )# 权限检查函数 def check_redshift_permissions(conn):"…