这是一个后端查询折线图数据需求,前端会给出不同的时间段,(本月、本年、自定义),当是本月时则把截至今天本月的每天和对应的数量输出,并且如果前端传了地区参数,则不但要输出某一天对应的数量总数,还要把所选地区对应的数量也一并输出;对于时间跨度大的时间,后端适量调整查询的时间跨度,使用java采用springboot架构给出后端代码,查询的总数是受地区参数限制,如果选了合肥、安庆,输出的总数是这两个地区为条件查到的,并要把安庆对应多少数量,合肥对应多少数量也输出,sql脚本使用mybatis

时间: 2025-03-25 10:08:39 浏览: 35
<think>好的,我需要解决用户的问题,即如何用Spring Boot和MyBatis实现后端查询折线图数据的需求。首先,我得理清用户的需求。 用户提到前端会传不同的时间段:本月、本年、自定义。当选择本月时,需要输出截止到当天的每天数据,并且如果传了地区参数,每个天的总数要包括所选地区的总和,同时每个地区单独的数量也要输出。时间跨度大时,后端需要调整查询粒度,比如按周或月分组。 首先,我需要设计一个数据表结构。假设有一个记录表,包含时间、地区和数量。可能的结构是:记录表(record)有字段record_date(日期)、region(地区)、count(数量)。但用户没有给出具体表结构,可能需要根据常规设计假设。 接下来考虑接口设计。用户需要接收时间类型(type)、开始和结束时间(自定义时使用)、地区列表。返回的数据结构应该包含日期、总数量和各个地区的数量。例如,一个每日的数据对象包含日期、总数量,以及一个地区数量的映射。 然后,处理不同的时间类型。例如,当类型是本月时,自动设置开始时间为本月1号,结束时间为今天。对于本年,可能从年初到当前日期。自定义则直接使用传入的开始和结束时间。 对于SQL部分,需要根据时间跨度和地区参数动态生成查询。如果时间跨度大,比如超过一个月,可能需要按周或月分组,而不是每天。这里需要根据时间范围动态选择分组方式,比如使用CASE语句或动态SQL。 在MyBatis的XML中,需要构建一个查询,按时间分组(可能是天、周、月),并统计总数以及各地区的数量。如果传入了地区参数,需要在WHERE条件中过滤这些地区,并且使用SUM和CASE WHEN来分别统计每个地区的数量。 例如,SQL可能像这样: SELECT DATE_FORMAT(record_date, '%Y-%m-%d') AS date, SUM(count) AS total, SUM(CASE WHEN region = '安庆' THEN count ELSE 0 END) AS anqing, SUM(CASE WHEN region = '合肥' THEN count ELSE 0 END) AS hefei FROM record WHERE record_date BETWEEN #{start} AND #{end} AND region IN ('安庆', '合肥') GROUP BY date; 但这里的问题是地区参数是动态的,用户可能传多个地区,所以需要动态生成CASE WHEN部分。这可能需要使用MyBatis的动态SQL功能,比如用foreach循环生成每个地区的统计列。 不过,MyBatis在处理动态列名时比较麻烦,可能需要使用注解或拼接SQL,但这样可能存在SQL注入风险。或者,可以在Java代码中构建这些CASE语句,作为字符串传递给XML。但需要确保参数安全。 另一个方法是,先查询所有记录,然后在Java中进行分组和统计。但这样如果数据量大,性能可能不佳。所以最好在SQL中处理。 因此,可能需要使用动态SQL来构建每个地区的SUM CASE语句。例如,在MyBatis的XML中,遍历地区列表,为每个地区生成一个SUM(CASE WHEN region = #{region} THEN count ELSE 0 END) AS #{region}。 但MyBatis的XML中,列别名不能直接使用参数,需要用${},但这会有注入风险。如果地区参数是用户输入的,可能会有问题。但这里地区参数是前端传的可选值,可能从固定列表中选择,比如下拉框,所以可以认为是安全的。 因此,在XML中,可以这样处理: <foreach collection="regions" item="region" separator=","> SUM(CASE WHEN region = #{region} THEN count ELSE 0 END) AS ${region} </foreach> 然后,在查询结果中,每个地区的别名就是地区名,例如“合肥”、“安庆”。这样,在Java的DTO中,可以用一个Map<String, Integer>来存储这些动态的地区数量。 但MyBatis的结果映射如何处理动态列呢?通常,结果映射需要明确的字段名,但这里列名是动态的。可能需要使用@ResultMap或者Map类型来接收结果,然后在Service层转换为所需的DTO结构。 或者,可以将查询结果映射为一个包含日期、总数量和地区数量Map的对象。例如,每个返回的DailyData对象包含日期、总数量和一个Map,其中键是地区名,值是对应的数量。 但MyBatis默认不支持直接映射到Map中的动态键。可能的解决方案是,在查询时返回一个包含所有地区的列,然后在Java中将这些列转换为Map。例如,DTO对象有属性date、total,以及各个地区的字段,如String hefei, String anqing等,但这需要预先知道所有可能的地区,不灵活。 所以,更好的方法是在查询时,将地区作为行的一部分,然后使用Java进行分组。例如,查询结果按日期和地区分组,然后在Service层按日期合并,计算总和和各地区的数量。但这样可能需要更复杂的SQL和Java处理。 或者,可以使用MyBatis的ResultHandler来自定义结果处理,将动态的列存入Map中。但这样可能比较复杂。 权衡之下,可能还是需要在SQL中动态生成每个地区的列,然后在DTO中使用一个Map来存储这些动态的列。例如,DTO如下: public class DailyData { private String date; private Integer total; private Map<String, Integer> regionCounts; } 然后,在MyBatis的结果映射中,可以使用@Result注解来映射固定字段date和total,而动态的地区列则需要特殊处理。可能需要使用@Results和@Result来手动映射,或者使用拦截器处理。 这可能比较复杂。另一种方法是,在SQL查询中使用JSON对象来聚合地区数据。例如,在MySQL中可以使用JSON_OBJECTAGG函数: SELECT DATE_FORMAT(record_date, '%Y-%m-%d') AS date, SUM(count) AS total, JSON_OBJECTAGG(region, SUM(count)) AS region_counts FROM record WHERE record_date BETWEEN #{start} AND #{end} AND region IN <foreach ...> GROUP BY date; 但这样需要数据库支持JSON函数,并且处理JSON结果。在Java中解析JSON字符串到Map中。 这可能是一个可行的方法,但需要数据库支持,例如MySQL 5.7以上。 假设数据库支持,那么可以在SQL中生成一个JSON对象,包含每个地区的数量,然后在Java中将这个JSON字符串转换为Map。 这样,MyBatis的ResultMap可以这样写: <result property="regionCounts" column="region_counts" typeHandler="com.example.JsonToMapTypeHandler"/> 然后自定义一个TypeHandler,将JSON字符串转换为Map<String, Integer>。 这可能是一个可行的方法,但需要处理TypeHandler和JSON转换。 不过,用户的问题中提到使用MyBatis,所以需要确保这种方法的可行性。 另一种方法是,在Service层处理,先查询所有满足条件的记录,然后按日期和地区进行分组统计。例如,查询所有在时间范围内且地区在参数列表中的记录,然后在内存中按日期分组,计算总和和各地区的数量。这可能适用于数据量不大的情况。 例如,SQL查询为: SELECT record_date, region, SUM(count) AS count FROM record WHERE record_date BETWEEN #{start} AND #{end} AND region IN <foreach collection="regions" item="region" open="(" separator="," close=")">#{region}</foreach> GROUP BY record_date, region 然后,在Java中,遍历结果,按日期分组,累积总和和各地区的数量。 这种方法可能更简单,不需要动态SQL生成多个列,但缺点是如果数据量大,查询结果的行数会很多,可能影响性能。 用户提到时间跨度大时,后端调整查询的时间跨度,比如按周或月分组。所以,可能需要根据时间范围的长短,动态决定按天、周或月分组。 例如,如果时间跨度超过30天,按周分组;超过3个月,按月分组。这需要在SQL中动态调整日期格式函数,比如按天:DATE_FORMAT(record_date, '%Y-%m-%d'),按周:CONCAT(YEAR(record_date), '-', WEEK(record_date)),按月:DATE_FORMAT(record_date, '%Y-%m')。 因此,在Java代码中,需要根据时间范围计算天数差,决定分组方式。然后在SQL中使用相应的日期格式。 综上,后端处理步骤可能如下: 1. 接收前端参数:时间类型(type)、开始结束时间(自定义时需要)、地区列表。 2. 根据时间类型计算实际的开始和结束时间。 3. 根据时间范围的长度决定分组粒度(天、周、月)。 4. 构建SQL查询,按分组粒度聚合数据,统计总数量和各个地区的数量。 5. 执行查询,处理结果,返回给前端。 现在考虑代码实现: 首先,定义请求参数DTO: public class ChartQueryParam { private String type; // month, year, custom private LocalDate startDate; private LocalDate endDate; private List<String> regions; } 在Controller中,处理请求: @PostMapping("/chart/data") public Result getChartData(@RequestBody ChartQueryParam param) { // 处理时间范围 LocalDate startDate; LocalDate endDate = LocalDate.now(); if ("month".equals(param.getType())) { startDate = LocalDate.now().withDayOfMonth(1); } else if ("year".equals(param.getType())) { startDate = LocalDate.now().withDayOfYear(1); } else if ("custom".equals(param.getType())) { startDate = param.getStartDate(); endDate = param.getEndDate(); } else { throw new IllegalArgumentException("Invalid type"); } // 调整结束时间为今天,如果结束时间超过今天 endDate = endDate.isAfter(LocalDate.now()) ? LocalDate.now() : endDate; // 计算时间跨度,决定分组粒度 long days = ChronoUnit.DAYS.between(startDate, endDate); String groupBy; if (days <= 30) { groupBy = "day"; } else if (days <= 90) { groupBy = "week"; } else { groupBy = "month"; } // 查询数据 List<DailyData> data = recordService.getChartData(startDate, endDate, param.getRegions(), groupBy); return Result.success(data); } 在Service层: public List<DailyData> getChartData(LocalDate start, LocalDate end, List<String> regions, String groupBy) { return recordMapper.selectChartData(start, end, regions, groupBy); } 在MyBatis的Mapper接口: List<DailyData> selectChartData(@Param("start") LocalDate start, @Param("end") LocalDate end, @Param("regions") List<String> regions, @Param("groupBy") String groupBy); 在XML映射文件中,需要动态构建分组表达式和地区统计: <select id="selectChartData" resultType="com.example.DailyData"> SELECT <choose> <when test="groupBy == 'day'"> DATE_FORMAT(record_date, '%Y-%m-%d') AS date, </when> <when test="groupBy == 'week'"> CONCAT(YEAR(record_date), '-', WEEK(record_date)) AS date, </when> <when test="groupBy == 'month'"> DATE_FORMAT(record_date, '%Y-%m') AS date, </when> </choose> SUM(count) AS total <if test="regions != null and regions.size() > 0"> <foreach collection="regions" item="region"> , SUM(CASE WHEN region = #{region} THEN count ELSE 0 END) AS ${region} </foreach> </if> FROM record WHERE record_date BETWEEN #{start} AND #{end} <if test="regions != null and regions.size() > 0"> AND region IN <foreach collection="regions" item="region" open="(" separator="," close=")"> #{region} </foreach> </if> GROUP BY date ORDER BY date </select> 这里使用了动态SQL来根据groupBy参数选择不同的日期格式,同时遍历地区列表生成每个地区的SUM CASE语句。结果列的名称是地区名,例如“合肥”、“安庆”。 然后,DailyData类需要能够接收这些动态列。一种方法是使用Map<String, Object>来接收额外的地区数量,但MyBatis默认会将查询结果映射到对象的属性中。因此,DailyData类应该有date、total属性,以及一个Map<String, Integer>来保存地区数量。但MyBatis无法直接映射动态列到Map中,除非使用@MapKey注解或其他方式。 另一种方法是,让DailyData类包含所有可能的地区字段,但这不现实。因此,可能需要使用MyBatis的结果处理器(ResultHandler)或者反射来动态设置字段。 不过,这可能比较复杂。另一种解决方法是让DailyData类包含一个泛型的属性,例如: public class DailyData { private String date; private Integer total; private Map<String, Integer> regionCounts; } 然后在MyBatis中,将每个地区的列值映射到regionCounts中。这需要自定义结果映射,可能不可行。 或者,可以使用一个通用的对象,如LinkedHashMap,作为结果类型,然后在Service层转换为DailyData对象。例如,Mapper接口返回List<Map<String, Object>>,每个Map代表一行数据,包含date、total以及各个地区的列名和值。然后,在Service层遍历这些Map,构建DailyData对象: public List<DailyData> getChartData(...) { List<Map<String, Object>> rows = recordMapper.selectChartData(...); List<DailyData> result = new ArrayList<>(); for (Map<String, Object> row : rows) { DailyData data = new DailyData(); data.setDate((String) row.get("date")); data.setTotal((Integer) row.get("total")); Map<String, Integer> regionCounts = new HashMap<>(); for (String region : regions) { regionCounts.put(region, (Integer) row.get(region)); } data.setRegionCounts(regionCounts); result.add(data); } return result; } 这样,Mapper接口返回的是List<Map<String, Object>>,然后在Service层进行转换。这种方法可能更灵活,但需要确保地区列表在Service层可用。 但此时,在Mapper的XML中,动态生成的列名必须与region参数一致,这样在Map中才能正确获取。 例如,当regions是["合肥", "安庆"]时,SQL生成的列名为合肥、安庆,对应的Map的键就是“合肥”、“安庆”,然后Service层根据传入的regions列表遍历这些键,从Map中获取值。 这样,即使某些日期没有某个地区的数量,也会显示0,因为SQL中的CASE WHEN已经处理了。 因此,调整Mapper接口返回类型为List<Map<String, Object>>,然后在Service中处理: 在Mapper接口中: List<Map<String, Object>> selectChartData(...); 在Service中: public List<DailyData> getChartData(LocalDate start, LocalDate end, List<String> regions, String groupBy) { List<Map<String, Object>> rows = recordMapper.selectChartData(start, end, regions, groupBy); List<DailyData> result = new ArrayList<>(); for (Map<String, Object> row : rows) { DailyData data = new DailyData(); data.setDate((String) row.get("date")); data.setTotal(((Number) row.get("total")).intValue()); Map<String, Integer> regionCounts = new HashMap<>(); if (regions != null) { for (String region : regions) { Object value = row.get(region); regionCounts.put(region, value != null ? ((Number) value).intValue() : 0); } } data.setRegionCounts(regionCounts); result.add(data); } return result; } 这样,即使某些地区在某个时间段没有数据,SUM会返回null,但CASE WHEN中ELSE 0,所以应该为0。不过需要确认数据库的SUM行为,当没有匹配时是否返回0还是null。例如,如果所有行的region都不匹配,SUM(CASE...)会是0吗?在MySQL中,如果没有匹配的行,SUM返回NULL。因此,可能需要使用COALESCE函数将NULL转换为0: SUM(COALESCE(CASE WHEN region = #{region} THEN count ELSE 0 END, 0)) AS ${region} 或者在Java代码中处理null值,将其视为0。 综上,调整SQL中的每个地区列为: SUM(COALESCE(CASE WHEN region = #{region} THEN count ELSE 0 END, 0)) AS ${region} 或者,在MyBatis的XML中: <foreach collection="regions" item="region"> , COALESCE(SUM(CASE WHEN region = #{region} THEN count ELSE 0 END), 0) AS ${region} </foreach> 这样可以确保即使没有数据,也会返回0。 现在,关于SQL注入的问题,因为地区名是用户输入的,但这里使用${region}作为列别名,存在风险。但假设地区参数是前端从固定选项中选择的,例如下拉框,而不是自由输入,那么可以认为是安全的。否则,需要验证地区参数是否在允许的列表中,防止SQL注入。 因此,在Service层,调用Mapper之前,应该验证regions参数是否都是有效的地区,例如查询数据库中的有效地区列表,或者有一个预定义的地区列表。例如: // 在Service方法中 if (regions != null) { List<String> validRegions = regionService.getValidRegions(); for (String region : regions) { if (!validRegions.contains(region)) { throw new IllegalArgumentException("Invalid region: " + region); } } } 这样可以确保传入的region参数都是合法的,避免SQL注入。 最后,关于时间跨度的调整,用户在问题中提到“时间跨度大的时间,后端适量调整查询的时间跨度”。例如,当时间跨度较大时,按周或月分组。前面的代码已经根据天数判断groupBy的类型,可能还需要进一步调整,比如当groupBy是周或月时,返回的date字段格式需要是周或月的标识,前端需要根据这个格式显示。 总结: - 控制器处理参数,计算时间范围和分组粒度。 - Service层调用Mapper查询数据,处理结果转换。 - Mapper使用动态SQL生成按不同时间粒度分组的查询,并统计总数量和各个地区的数量。 - 结果以Map形式返回,Service层转换为结构化的DTO。 现在给出示例代码: 1. 请求参数DTO: public class ChartQueryParam { private String type; private LocalDate startDate; private LocalDate endDate; private List<String> regions; // getters and setters } 2. 返回结果DTO: public class DailyData { private String date; private Integer total; private Map<String, Integer> regionCounts; // getters and setters } 3. Controller: @RestController @RequestMapping("/api/chart") public class ChartController { @Autowired private RecordService recordService; @PostMapping("/data") public ResponseEntity<Result> getChartData(@RequestBody ChartQueryParam param) { List<DailyData> data = recordService.getChartData(param); return ResponseEntity.ok(Result.success(data)); } } 4. Service: @Service public class RecordService { @Autowired private RecordMapper recordMapper; public List<DailyData> getChartData(ChartQueryParam param) { // 处理时间范围 LocalDate startDate; LocalDate endDate = LocalDate.now(); switch (param.getType()) { case "month": startDate = LocalDate.now().withDayOfMonth(1); break; case "year": startDate = LocalDate.now().withDayOfYear(1); break; case "custom": startDate = param.getStartDate(); endDate = param.getEndDate(); break; default: throw new IllegalArgumentException("Invalid type"); } // 确保结束日期不超过今天 endDate = endDate.isAfter(LocalDate.now()) ? LocalDate.now() : endDate; // 计算时间跨度,决定分组粒度 long days = ChronoUnit.DAYS.between(startDate, endDate); String groupBy = "day"; if (days > 90) { groupBy = "month"; } else if (days > 30) { groupBy = "week"; } // 查询数据 List<Map<String, Object>> rows = recordMapper.selectChartData(startDate, endDate, param.getRegions(), groupBy); // 转换为DailyData列表 return convertToDailyData(rows, param.getRegions()); } private List<DailyData> convertToDailyData(List<Map<String, Object>> rows, List<String> regions) { List<DailyData> result = new ArrayList<>(); for (Map<String, Object> row : rows) { DailyData data = new DailyData(); data.setDate((String) row.get("date")); data.setTotal(((Number) row.get("total")).intValue()); Map<String, Integer> regionCounts = new HashMap<>(); if (regions != null) { for (String region : regions) { Object value = row.get(region); regionCounts.put(region, value != null ? ((Number) value).intValue() : 0); } } data.setRegionCounts(regionCounts); result.add(data); } return result; } } 5. Mapper接口: public interface RecordMapper { List<Map<String, Object>> selectChartData(@Param("start") LocalDate start, @Param("end") LocalDate end, @Param("regions") List<String> regions, @Param("groupBy") String groupBy); } 6. MyBatis XML映射: <select id="selectChartData" resultType="map"> SELECT <choose> <when test="groupBy == 'day'"> DATE_FORMAT(record_date, '%Y-%m-%d') AS date, </when> <when test="groupBy == 'week'"> CONCAT(YEAR(record_date), '-', WEEK(record_date, 3)) AS date, </when> <when test="groupBy == 'month'"> DATE_FORMAT(record_date, '%Y-%m') AS date, </when> <otherwise> DATE_FORMAT(record_date, '%Y-%m-%d') AS date, </otherwise> </choose> COALESCE(SUM(count), 0) AS total <if test="regions != null and regions.size() > 0"> <foreach collection="regions" item="region"> , COALESCE(SUM(CASE WHEN region = #{region} THEN count ELSE 0 END), 0) AS `${region}` </foreach> </if> FROM record WHERE record_date BETWEEN #{start} AND #{end} <if test="regions != null and regions.size() > 0"> AND region IN <foreach collection="regions" item="region" open="(" separator="," close=")"> #{region} </foreach> </if> GROUP BY date ORDER BY date </select> 注意:在MyBatis的XML中,使用`${region}`作为列别名时,需要用反引号包裹,防止地区名中有特殊字符,如空格等。但假设地区名是简单的汉字,可能不需要,但安全起见可以加上反引号。 此外,WEEK函数的第二个参数用于指定周的开始,这里使用3表示ISO周(周一作为开始)。 最后,确保数据库中的record表有record_date(日期类型)、region(字符串)、count(整数)字段。 测试用例: 假设查询本月数据,地区参数是合肥和安庆。 SQL会生成类似: SELECT DATE_FORMAT(record_date, '%Y-%m-%d') AS date, COALESCE(SUM(count), 0) AS total, COALESCE(SUM(CASE WHEN region = '合肥' THEN count ELSE 0 END), 0) AS `合肥`, COALESCE(SUM(CASE WHEN region = '安庆' THEN count ELSE 0 END), 0) AS `安庆` FROM record WHERE record_date BETWEEN '2023-10-01' AND '2023-10-20' AND region IN ('合肥', '安庆') GROUP BY date ORDER BY date 返回结果中,每个日期有总数量、合肥和安庆的数量。 在Java中,Service层将这些结果转换为DailyData对象,包含日期、总数量和地区数量映射。 这样,前端收到的数据结构大致是: [ { "date": "2023-10-01", "total": 100, "regionCounts": { "合肥": 60, "安庆": 40 } }, ... ] 总结,这个方案通过动态SQL处理不同的时间粒度和地区参数,确保查询效率和数据的准确性。同时,Service层进行结果转换,保证返回结构清晰。需要注意地区参数的验证和SQL注入的防范,确保安全性。</think>### 分步解决方案及代码示例 #### 1. 数据库表结构设计 假设有记录表 `record`,包含以下字段: - `record_date` DATE COMMENT '记录日期', - `region` VARCHAR(20) COMMENT '地区', - `count` INT COMMENT '数量' ```sql CREATE TABLE record ( record_date DATE, region VARCHAR(20), count INT ); ``` #### 2. DTO类定义 - **请求参数类** `ChartQueryParam.java` ```java import lombok.Data; import java.time.LocalDate; import java.util.List; @Data public class ChartQueryParam { private String type; // month, year, custom private LocalDate startDate; private LocalDate endDate; private List<String> regions; } ``` - **返回数据类** `DailyData.java` ```java import lombok.Data; import java.util.Map; @Data public class DailyData { private String date; // 日期 private Integer total; // 总数 private Map<String, Integer> regionCounts; // 地区对应数量 } ``` #### 3. Controller层 处理请求参数,调用Service层 ```java import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/api/chart") public class ChartController { @Autowired private RecordService recordService; @PostMapping("/data") public ResponseEntity<Result> getChartData(@RequestBody ChartQueryParam param) { List<DailyData> data = recordService.getChartData(param); return ResponseEntity.ok(Result.success(data)); } } ``` #### 4. Service层 处理业务逻辑,调用Mapper查询数据并转换结果 ```java import org.springframework.stereotype.Service; import java.time.LocalDate; import java.time.temporal.ChronoUnit; import java.util.*; @Service public class RecordService { @Autowired private RecordMapper recordMapper; public List<DailyData> getChartData(ChartQueryParam param) { // 处理时间范围 LocalDate startDate = calculateStartDate(param.getType()); LocalDate endDate = validateEndDate(param.getEndDate()); // 计算分组粒度 String groupBy = calculateGroupBy(startDate, endDate); // 查询数据 List<Map<String, Object>> rows = recordMapper.selectChartData( startDate, endDate, param.getRegions(), groupBy ); // 转换为DailyData列表 return convertToDailyData(rows, param.getRegions()); } private LocalDate calculateStartDate(String type) { switch (type) { case "month": return LocalDate.now().withDayOfMonth(1); case "year": return LocalDate.now().withDayOfYear(1); default: throw new IllegalArgumentException("Invalid type"); } } private LocalDate validateEndDate(LocalDate endDate) { return endDate.isAfter(LocalDate.now()) ? LocalDate.now() : endDate; } private String calculateGroupBy(LocalDate start, LocalDate end) { long days = ChronoUnit.DAYS.between(start, end); if (days > 90) return "month"; else if (days > 30) return "week"; else return "day"; } private List<DailyData> convertToDailyData(List<Map<String, Object>> rows, List<String> regions) { List<DailyData> result = new ArrayList<>(); for (Map<String, Object> row : rows) { DailyData data = new DailyData(); data.setDate((String) row.get("date")); data.setTotal(((Number) row.get("total")).intValue()); data.setRegionCounts(extractRegionCounts(row, regions)); result.add(data); } return result; } private Map<String, Integer> extractRegionCounts(Map<String, Object> row, List<String> regions) { Map<String, Integer> counts = new HashMap<>(); if (regions != null) { regions.forEach(region -> { Object value = row.get(region); counts.put(region, value != null ? ((Number) value).intValue() : 0); }); } return counts; } } ``` #### 5. Mapper接口及XML映射 - **Mapper接口** `RecordMapper.java` ```java import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import java.time.LocalDate; import java.util.List; import java.util.Map; @Mapper public interface RecordMapper { List<Map<String, Object>> selectChartData( @Param("start") LocalDate start, @Param("end") LocalDate end, @Param("regions") List<String> regions, @Param("groupBy") String groupBy ); } ``` - **XML映射** `RecordMapper.xml` ```xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.mapper.RecordMapper"> <select id="selectChartData" resultType="map"> SELECT <choose> <when test="groupBy == 'day'"> DATE_FORMAT(record_date, '%Y-%m-%d') AS date, </when> <when test="groupBy == 'week'"> CONCAT(YEAR(record_date), '-', WEEK(record_date, 3)) AS date, </when> <when test="groupBy == 'month'"> DATE_FORMAT(record_date, '%Y-%m') AS date, </when> </choose> COALESCE(SUM(count), 0) AS total <if test="regions != null and regions.size() > 0"> <foreach collection="regions" item="region"> , COALESCE(SUM(CASE WHEN region = #{region} THEN count ELSE 0 END), 0) AS `${region}` </foreach> </if> FROM record WHERE record_date BETWEEN #{start} AND #{end} <if test="regions != null and regions.size() > 0"> AND region IN <foreach collection="regions" item="region" open="(" separator="," close=")"> #{region} </foreach> </if> GROUP BY date ORDER BY date </select> </mapper> ``` ### 关键点说明 1. **动态时间处理**:根据前端传入的`type`自动计算时间范围,支持本月、本年、自定义 2. **智能分组策略**:根据时间跨度动态调整分组粒度(天/周/月) 3. **地区过滤统计**:使用动态SQL生成多个地区的统计列,保证查询效率 4. **结果集处理**:通过Map接收动态列结果,在Service层转换为结构化数据 5. **空值处理**:使用`COALESCE`确保空值返回0,保持数据一致性 ### 使用示例 **请求示例(本月+地区过滤)**: ```json { "type": "month", "regions": ["合肥", "安庆"] } ``` **响应示例**: ```json [ { "date": "2023-10-01", "total": 150, "regionCounts": { "合肥": 100, "安庆": 50 } }, { "date": "2023-10-02", "total": 200, "regionCounts": { "合肥": 120, "安庆": 80 } } ] ```
阅读全文

最新推荐

recommend-type

使用java实现各种数据统计图(柱形图,饼图,折线图)

在Java编程中,生成数据统计图是一种常见的需求,特别是在数据分析、报表展示以及数据挖掘等领域。JFreeChart是一个强大的Java库,它可以用来创建多种类型的图表,包括柱形图、饼图和折线图。本篇文章将详细介绍如何...
recommend-type

【Python】豆瓣电影TOP250数据规律分析(Pearson相关系数、折线图、条形图、直方图)

在本篇【Python】豆瓣电影TOP250数据规律分析中,我们将探讨如何利用Python进行数据分析,特别是关于Pearson相关系数、折线图、条形图和直方图的运用,来揭示豆瓣电影评分背后的一些有趣规律。这篇文章的作者Vivid-...
recommend-type

flask+layui+echarts实现前端动态图展示数据效果

在这个场景中,`Flask`作为后端服务器,负责接收前端发送的请求,处理业务逻辑,比如查询数据库获取数据,然后将数据以JSON或其他格式返回给前端。 `LayUI`是一个基于HTML5的国产前端UI框架,提供了丰富的组件,如...
recommend-type

python matplotlib折线图样式实现过程

本文将深入讲解如何使用matplotlib绘制不同样式的折线图,包括简单的单条折线、多条折线,以及设置折线的颜色、样式和宽度,还有在折线图上添加注解。 1. **简单的折线图** 在Python中绘制一个简单的折线图,需要...
recommend-type

详解Android图表 MPAndroidChart折线图

完成上述步骤后,你将在Android应用中看到一个自定义的折线图。MPAndroidChart库提供了许多高级特性,如动画效果、触摸交互、自定义标记和图例等,可以根据需求进一步探索和定制。 总之,MPAndroidChart是一个功能...
recommend-type

谭浩强C语言电子教案第三版权威教程下载

《C语言电子教案第三版(谭浩强)》是一本面向C语言学习者的权威电子教材,由知名计算机教育家谭浩强教授编著。此书内容详实,结构清晰,深受广大师生和自学者的青睐。该教材不仅适合大学计算机相关专业的学生使用,也为编程初学者提供了很好的学习材料。以下是对该教材内容的知识点总结。 首先,C语言作为一门高级编程语言,其电子教案的设计和内容涵盖应包括以下几个基础知识点: 1. C语言概述:电子教案会介绍C语言的历史背景,其在程序设计语言中的地位,以及它在当今社会的应用范围。同时,讲解C语言的基本特点,如简洁、灵活、功能强大等。 2. 环境配置与开发工具:为了让学生能够顺利开始C语言编程,电子教案中会有专门的部分来指导学生如何搭建C语言的开发环境,包括编译器的安装,编辑器的使用等。常用编译器如GCC、Clang等,以及集成开发环境(IDE)如Code::Blocks、Visual Studio Code等会作为内容介绍。 3. 基本语法:这是学习C语言的核心部分,包括数据类型(基本类型、构造类型、指针类型、空类型)、变量和常量、运算符和表达式、控制语句(分支结构和循环结构)等内容,这些都是编程的基础元素。 4. 函数:函数是C语言中实现程序模块化的主要工具。教案中会详细讲解如何定义和声明函数、函数的参数传递、函数的返回值以及递归函数等。 5. 指针:指针是C语言中的高级特性,也是其难点之一。电子教案将介绍指针的概念、指针与数组的关系、指针与函数的关系,以及指针的高级用法,例如指向指针的指针和指针数组等。 6. 结构体与联合体:在C语言中,结构体和联合体允许我们定义可以包含不同类型数据的复合数据类型。电子教案中将引导学生学习结构体的定义、使用以及与函数的关系,联合体的概念和特点。 7. 文件操作:电子教案会讲解C语言中文件操作的基础知识,包括文件的打开、关闭、读写以及随机访问等操作,这些都是程序与外部数据进行交互的基本方法。 8. 预处理和动态内存分配:预处理命令是C语言编译前的处理步骤,包括宏定义、文件包含、条件编译等。动态内存分配涉及到的内存管理函数如malloc、calloc、realloc和free等也会被详细介绍。 9. 错误处理:良好的错误处理机制是编写健壮程序的关键。因此,电子教案将教会学生如何使用C语言中的错误检测与处理机制,包括标准错误函数如perror、strerror等。 10. 高级编程技巧:随着学习的深入,电子教案还会涉及一些高级编程技巧,包括位操作、数据结构在C语言中的实现等。 由于《C语言电子教案第三版(谭浩强)》是作为教材使用的,因此除了知识点的介绍之外,电子教案通常还包括习题、案例、实验指导等内容,旨在帮助学生加深理解,提高编程能力。习题部分可能包含选择题、填空题、编程题等多种形式,覆盖了各个章节的知识点,有助于学生巩固所学内容。案例分析则能帮助学生理解如何将理论应用到实际问题的解决中。实验指导则提供了实验环境的搭建方法和实验步骤,让学生通过实践来提升编程技能。 综合来看,《C语言电子教案第三版(谭浩强)》是一份内容全面、结构清晰、实用性高的教学材料,适合用于C语言的课堂教学和自学,能够帮助学生系统地掌握C语言的核心概念和编程技能。
recommend-type

【性能测试基准】:为RK3588选择合适的NVMe性能测试工具指南

# 1. NVMe性能测试基础 ## 1.1 NVMe协议简介 NVMe,全称为Non-Volatile Memory Express,是专为固态驱动器设计的逻辑设备接口规范。与传统的SATA接口相比,NVMe通过使用PCI Express(PCIe)总线,大大提高了存储设备的数据吞吐量和IOPS(每秒输入输出操作次数),特别适合于高速的固态存储设备。
recommend-type

centos 修改密码失败ERROR 1820 (HY000): You must reset your password using ALTER USER statement before executing this statement.

<think>我们被要求解决MySQL在CentOS系统中出现的ERROR1820(HY000)问题,即需要重置密码才能执行其他语句。根据引用内容,这个问题通常发生在安装MySQL后第一次登录时,系统要求用户必须修改初始密码。我们参考了多个引用,其中引用[4]提供了详细的解决步骤(包括修改密码策略),引用[5]则提供了另一种重置密码并授权的方法。解决步骤:1.使用初始密码登录MySQL(初始密码通常可以在/var/log/mysqld.log中找到)。2.登录后,执行任何命令都会报错ERROR1820,此时必须重置密码。3.重置密码时可能会遇到密码策略问题(如密码太简单),这时需要调整密码策略
recommend-type

50万吨原油常压塔设计与改造分析

根据给定文件信息,以下是对标题“年处理量为50万吨的常压塔的设计图”和描述中包含知识点的详细说明: 1. 常压塔的功能与设计: 常压塔是石油炼制过程中用来分离原油为不同组分的设备,如汽油、煤油、柴油等。设计常压塔时需要考虑其处理能力,即每天可以加工多少原油。本设计案例针对年处理量为50万吨的常压塔,这是一个相对较大的处理规模,意味着设计要满足高标准的工艺需求和技术参数。 2. 工艺计算与物料衡算: 工艺计算涉及塔内流体流动的动态特性,包括温度、压力、流量等参数的计算。物料衡算是基于物质守恒定律,确定在给定条件下塔内各组分的流率和组成。这些计算对塔的性能和效率至关重要。 3. 操作弹性: 操作弹性指的是设备在保证产品质量的前提下所能适应的运行条件变化范围,包括进料量、压力和温度的波动。一个高操作弹性的常压塔能够在更宽的操作范围内维持稳定运行,这对于保证生产的连续性和可靠性是非常重要的。 4. 强度及稳定校核: 设备的机械强度是指在正常运行和极端条件下的结构强度,而稳定校核则关注设备在各种负载下的稳定性,包括抗风、抗震等。设计常压塔时,这两个方面都是必须严格检验的,确保在高压和高温等苛刻条件下不会发生结构损坏或安全事故。 5. 板式塔的结构与特点: 板式塔是一种常见的塔设备,塔盘(板)上设有一定数量的开口,以便液体流动和蒸汽交换。塔盘的类型对塔的分离效率和处理能力有很大影响。本设计采用了浮阀塔盘和固舌塔盘兼用的形式,这意味着结合了两种塔盘的特点,能够实现更高效的分离和更大的处理能力。 6. 局部改造: 在塔的局部改造方面,本设计特别提到了塔顶的防腐和提高塔盘操作稳定性。塔顶防腐是因为长期暴露在外部环境中,塔顶部分更易受到腐蚀;而提高塔盘操作稳定性则能确保在各种运行条件下塔盘的高效稳定运行。 7. 常压塔与大庆2#原油: 大庆油田是中国著名的油田之一,大庆2#原油是该油田的一种原油品种。针对特定类型的原油进行塔设计时,需要考虑到原油的物理和化学特性,比如沸点分布、热稳定性等,这些特性将直接影响到塔设计的关键参数。 8. 毕业设计图的重要性: 毕业设计图作为本案例中提及的文件,通常反映了学生在大学期间所学知识的综合应用,包括工程制图、设计原理和工程实践等方面。图纸中会详细展示塔的结构尺寸、塔板布局、相关设备接口位置等重要信息,对于理解整个常压塔的设计至关重要。 以上内容详细解析了常压塔的设计要点,涉及到的工艺、材料、结构设计以及特定应用场景等方面的知识点。在实际工程实践中,这些知识点是保证常压塔安全、稳定、高效运行的基础。
recommend-type

【固态硬盘寿命延长】:RK3588平台NVMe维护技巧大公开

# 1. 固态硬盘寿命延长的基础知识 ## 1.1 固态硬盘的基本概念 固态硬盘(SSD)是现代计算设备中不可或缺的存储设备之一。与传统的机械硬盘(HDD)相比,SSD拥有更快的读写速度、更小的体积和更低的功耗。但是,SSD也有其生命周期限制,主要受限于NAND闪存的写入次数。 ## 1.2 SSD的写入次数和寿命 每块SSD中的NAND闪存单元都有有限的写入次数。这意味着,随着时间的推移,SSD的