mysql mybatis 分组取出表中使用次数最多的前两条记录

本文介绍了一种通过SQL语句批量插入运输路线数据的方法。该方法首先从t_trans_order表中选择符合特定条件的数据,然后将这些数据转换并插入到t_carrier_route表中。整个过程包括了对数据进行分组、计数以及筛选等操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

INSERT INTO t_carrier_route (id,carrier_user_id,`START_PROVINCE`,start_city,`START_AREA`,start_address,`END_PROVINCE`,`END_CITY`,`END_AREA`,end_address,create_date,create_by,del_flag)
        SELECT REPLACE(UUID(),"-",""),blood.carrier_user_id,blood.`START_PROVINCE`,blood.start_city,blood.`START_AREA`,blood.start_address,blood.`END_PROVINCE`,blood.`END_CITY`,blood.`END_AREA`,blood.`end_address`,NOW(),1,0
        FROM
        (SELECT *
        FROM (SELECT  tb.id,tb.dd,tb.carrier_user_id,tb.start_city,tb.`START_PROVINCE`,tb.`START_AREA`,tb.start_address,tb.`END_PROVINCE`,tb.`END_CITY`,tb.`END_AREA`,tb.end_address,
        @num := IF(@type=tb.carrier_user_id, @num + 1, 1) AS row_number,
        @type := tb.carrier_user_id AS other_type
        FROM (SELECT *,(select @num := 0),(select @type := '') FROM (SELECT b.id,COUNT(*) AS dd,b.carrier_user_id,b.start_city,b.`START_PROVINCE`,b.`START_AREA`,b.start_address,b.`END_PROVINCE`,b.`END_CITY`,b.`END_AREA`,b.end_address FROM t_trans_order  b GROUP BY b.carrier_user_id,b.start_city,b.`START_PROVINCE`,b.`START_AREA`,b.`END_PROVINCE`,b.`END_CITY`,b.`END_AREA`) cc ORDER BY cc.carrier_user_id,cc.dd DESC)  tb
        ) ace
        HAVING ace.row_number <= 2) blood;
<think>根据用户提供的代码片段,这是一个使用MyBatis Plus进行数据查询的示例。该代码的目的是从数据中根据条件查询数据,并且按照上传时间降序排列,同时支持限制返回的记录数。 用户要求:根据更新时间间隔10取一条数据。但是原代码中并没有体现“间隔10”取一条的逻辑,原代码只是根据条件查询并限制条数。 我们需要理解“根据更新时间间隔10取一条数据”的含义。通常,这可能意味着在连续的时间序列中,每隔10个单位时间(如10秒、10分钟等)取一条数据。但是,原查询只是按时间降序并取limit条,并没有按时间间隔过滤。 因此,我们需要调整查询策略。由于SQL本身可能难以直接实现按时间间隔采样(尤其是不同数据库语法不同),这里提供两种思路: 1. 在数据查询使用窗口函数或特定函数(如MySQL的ROUND()、FLOOR()等)将时间分组,然后从每个分组中取一条。 2. 先查询出所有数据,然后在应用层进行按时间间隔采样。 考虑到用户使用的是MyBatis Plus,且原代码是使用QueryWrapper,我们假设数据库是MySQL,并尝试在SQL中实现。 具体需求:假设“更新时间间隔10”是指每10分钟(或其他单位)取一条数据。我们可以将时间字段按10分钟进行分组,然后取每组中的最新一条(因为按upload_time降序,每组取第一条)。 但是,请注意,不同的数据库时间函数不同,这里以MySQL为例,使用FLOOR(UNIX_TIMESTAMP(upload_time)/(10*60))来将时间转换为10分钟间隔的整数(这里10*60秒),然后按这个分组,每组取第一个(即最新时间的一条)。 然而,在MyBatis Plus中直接写这样的查询条件比较复杂,可能需要自定义SQL。 另一种思路:使用查询,先按时间间隔分组,然后取每个分组中的最大时间,再关联回原获取这些时间对应的记录。 但是,原代码还支持根据battery_id过滤,以及限制返回的总条数。所以我们需要综合考虑。 由于原需求是“根据更新时间间隔10取一条数据”,我们可以这样实现: 步骤: 1. 首先,我们需要按时间间隔分组,然后选择每个间隔内的最大时间(即每个10分钟段内的最新时间点)。 2. 然后,将这些时间点与原关联,取出这些时间点对应的记录。 但是,如果数据量很大,这种方法可能效率较低。 考虑到用户还要求支持limit,我们可以先按时间间隔采样,然后取limit个间隔的数据。 由于用户原代码使用了last方法直接拼接limit,所以这里我们也可以使用类似的方式,但需要写自定义SQL。 因此,我们可以修改为: 自定义SQL语句(MySQL): ```sql SELECT * FROM my_battery_info WHERE id IN ( SELECT MAX(id) -- 或者用其他方式确定每个分组取哪一条,这里假设取每个分组中最大id(即最新记录) FROM my_battery_info WHERE ... -- 其他条件 GROUP BY FLOOR(UNIX_TIMESTAMP(upload_time)/(10*60)) -- 10分钟间隔 ) ORDER BY upload_time DESC LIMIT ... ``` 但是,上述SQL有问题:分组后取的是每个分组的一条,但是分组条件中只按时间间隔分组,没有按battery_id分组。如果同一个时间间隔内有多个不同的battery_id,那么就会混在一起。所以,应该按battery_id和时间间隔分组。 但是,原需求中有一个条件:根据battery_id查询(如果hwId不为空)。所以,如果hwId不为空,那么就是针对一个battery_id的数据进行采样。如果hwId为空,则是对所有电池的数据进行采样?但是这样分组的话,每个分组应该按battery_id和时间间隔分组,然后取每个分组的一条。 然而,用户的需求可能是:对同一个电池的数据,按时间间隔采样。所以,分组条件应该包括battery_id和时间间隔。 因此,调整SQL: ```sql SELECT * FROM my_battery_info t1 WHERE (battery_id, upload_time) IN ( SELECT battery_id, MAX(upload_time) FROM my_battery_info WHERE ... -- 这里写原条件,比如battery_id=? GROUP BY battery_id, FLOOR(UNIX_TIMESTAMP(upload_time)/(10*60)) ) ORDER BY upload_time DESC LIMIT 0, ? ``` 但是,这个SQL在MySQL中执行可能会比较慢,因为子查询返回多列,然后主查询用多列IN。而且,如果数据量大,分组也会慢。 另外,注意:如果hwId为空,那么就会查询所有电池的数据,然后按每个电池的每个时间间隔取一条。这样返回的数据是每个电池在每个时间间隔的最新记录。但是,用户可能只想要一个电池(当hwId不为空时)的数据。 所以,我们需要在子查询中加上battery_id的条件(如果hwId不为空)。 但是,在MyBatis Plus中如何实现这样复杂的查询呢?我们可以使用自定义SQL的方式。 由于原代码使用了QueryWrapper,我们可以使用QueryWrapper来构建条件,然后拼接自定义SQL。但是,对于这样复杂的子查询,可能需要使用@Select注解或者XML映射文件。 不过,用户希望在一个QueryWrapper中完成,我们可以尝试用MyBatis Plus的lambda达式和自定义SQL函数?但是分组条件中的时间函数是数据库相关的,MyBatis Plus没有内置支持。 因此,我建议使用原生SQL的方式,在MyBatis Plus中执行自定义SQL。 但是,用户提供的代码是使用selectList方法,所以我们还是尽量用QueryWrapper,如果不行就换用其他方式。 另一种方案:使用MyBatis Plus的query对象,但使用自定义的sqlSelect和sqlGroupBy等,这需要更复杂的设置。 考虑到时间,我们采用在QueryWrapper中直接写SQL片段的方式,但这样可能不够优雅,且容易出错。 这里,我们使用查询构造器(MyBatis Plus 3.0.7以上版本支持inSql方法,但多列IN不支持,所以不能用inSql)。 因此,我们只能使用原生SQL查询。 修改方案:在Dao层定义一个方法,使用注解或XML写SQL。 由于用户没有提供Dao层的接口定义,我们假设可以修改Dao接口。 步骤: 1. 在MyBatteryInfoDao接口中增加一个方法,比如: List<MyBatteryInfoEntity> selectByTimeInterval(@Param("hwId") String hwId, @Param("intervalSeconds") int intervalSeconds, @Param("limit") int limit); 2. 在对应的Mapper XML文件中写SQL。 但是,用户可能不想改变Dao层方法。那么,我们可以尝试在QueryWrapper中使用apply方法拼接自定义SQL片段,但是分组查询的部分无法直接放入QueryWrapper。 所以,我建议采用应用层处理的方式:先查询出所有数据(按时间降序,不限制条数?或者限制大一点),然后在应用层按时间间隔采样。 应用层处理逻辑: 1. 先按条件查询数据,按upload_time降序排列(如果数据量很大,可以限制一个较大的条数,比如10000条,避免内存溢出)。 2. 遍历这些数据,用一个变量记录上一个被选中的记录的时间,然后计算当记录与上一个被选中的记录的时间差,如果大于等于10分钟(或10秒,根据需求),则选中该条记录,并更新记录的时间。 注意:时间间隔的单位需要明确。用户说是“间隔10”,但单位是什么?这里假设是10分钟(600秒)。但实际中可能是10秒、10分钟、10小时等。所以,我们可以将间隔作为一个参数传入。 应用层代码示例(伪代码): ``` List<MyBatteryInfoEntity> result = new ArrayList<>(); long interval = 10 * 60 * 1000; // 10分钟,单位毫秒 MyBatteryInfoEntity lastSelected = null; for (MyBatteryInfoEntity entity : queryList) { if (lastSelected == null) { result.add(entity); lastSelected = entity; } else { long diff = lastSelected.getUploadTime().getTime() - entity.getUploadTime().getTime(); if (Math.abs(diff) >= interval) { result.add(entity); lastSelected = entity; } } } // 然后根据需要的limit,截取result ``` 但是,这样处理有一个问题:我们是从最新时间开始遍历,所以是降序的。这样取到的数据是:从当时间开始,每隔10分钟取一条(但注意,如果数据不是均匀分布的,可能取到的间隔大于10分钟)。而且,这样处理会跳过一些数据。 另外,如果数据量很大,在应用层处理可能会占用较多内存和CPU。 综合考虑,如果数据量不大(比如几千条),应用层处理简单有效。如果数据量很大,建议在数据库层处理。 由于用户没有明确数据量,我这里提供两种方案,并选择一种实现。 方案一:数据库层分组查询(效率较高,但SQL复杂且与数据库相关) 方案二:应用层处理(简单,但数据量大时性能差) 这里我选择方案一,因为用户要求“取一条数据”可能意味着数据量不会太大(间隔10分钟,一天也就144条,一个月4320条,所以即使一年也才5万多条,可以接受)。但是,如果用户要求的是所有电池的数据,那么数据量会成倍增加,所以需要谨慎。 但是,用户原代码中有一个limit参数,所以我们可以通过limit限制返回的总条数(在应用层采样先限制条数,这样应用层处理的数据量就是limit条)。 因此,我们可以先按原方式查询出limit条数据(按时间降序),然后在应用层进行间隔采样。 这样,即使limit为1000,应用层处理1000条数据也是很快的。 所以,修改步骤: 1. 先按原条件查询出按时间降序的limit条数据(注意,这里的limit是用户传入的,最多取多少条,但间隔采样后可能少于limit)。 2. 在应用层进行间隔采样(从最新时间开始,每隔固定时间取一条)。 具体代码实现: 首先,我们获取原始数据(按时间降序),然后进行采样。 采样算法: 1. 初始化一个空列result,用于存放采样结果。 2. 初始化lastTime为null。 3. 遍历原始数据(已经是按时间降序,所以第一条是最新的): 如果lastTime为null,则将该条数据加入result,并设置lastTime为该条数据的时间。 否则,计算当数据的时间与lastTime的时间差(取绝对值),如果时间差大于等于间隔(例如10分钟),则将该条数据加入result,并更新lastTime为当数据的时间。 4. 注意:遍历完成后,result就是采样后的数据,但注意顺序是保持降序(因为原始数据是降序,我们也是按顺序加入的)。 但是,这里有一个问题:我们是从最新时间开始,然后下一条数据如果比最新时间早10分钟以上,就会被选中。这样选中的数据的时间是:最新时间,然后减去10分钟以上的时间点,再减去10分钟以上的时间点...。所以,选中的数据的时间是递减的,且间隔至少10分钟。 但是,如果用户希望的是从最早时间开始?原需求没有明确。根据“根据更新时间间隔10取一条数据”,通常我们关心的是最近的数据,所以从最新开始取是合理的。 另外,时间间隔的单位需要明确。这里我们假设间隔是10分钟(600秒),即600000毫秒。 代码实现: 首先,我们获取原始数据: List<MyBatteryInfoEntity> list = myBatteryInfoDao.selectList(...); // 原查询,已经按时间降序,并且有limit 然后,进行采样: 假设间隔为10分钟,即10*60*1000毫秒。 但是,用户可能希望间隔是可配置的,这里我们将其作为一个变量。 具体代码: ```java // 获取原始数据 List<MyBatteryInfoEntity> list = myBatteryInfoDao.selectList(new QueryWrapper<MyBatteryInfoEntity>() .eq(StringUtils.isNotBlank(hwId), "battery_id", hwId) .orderByDesc("upload_time") .last(StringUtils.isNotBlank(limit), "limit 0," + limit) ); // 间隔10分钟(600000毫秒) long intervalMillis = 10 * 60 * 1000; List<MyBatteryInfoEntity> sampledList = new ArrayList<>(); MyBatteryInfoEntity lastSelected = null; for (MyBatteryInfoEntity entity : list) { if (lastSelected == null) { sampledList.add(entity); lastSelected = entity; } else { // 计算当记录和上一个选中记录的时间差(绝对值) long diff = Math.abs(entity.getUploadTime().getTime() - lastSelected.getUploadTime().getTime()); if (diff >= intervalMillis) { sampledList.add(entity); lastSelected = entity; } } } ``` 注意:这里的时间差计算,因为数据是降序的,所以当记录的时间一定小于等于lastSelected的时间(因为lastSelected是之选中的,且是更早遍历到的,所以时间更大)。所以,我们可以直接使用lastSelected的时间减去当记录的时间,但为了通用,我们取绝对值。 但是,这样取绝对值会导致从旧到新的数据也可能被选中(如果数据是乱序的,但我们已经按时间降序,所以不会出现乱序)。所以,这里可以不用绝对值,直接: long diff = lastSelected.getUploadTime().getTime() - entity.getUploadTime().getTime(); 因为lastSelected的时间一定大于当entity的时间(因为是降序),所以diff为正数。 因此,可以改为: if (diff >= intervalMillis) { ... } 这样,我们就实现了每隔至少10分钟取一条数据。 但是,这样取到的数据可能比用户要求的少,因为间隔采样可能过滤掉很多数据。 另外,如果用户要求的是等间隔(比如从最早时间到最晚时间均匀分成10分钟间隔,然后在每个间隔内取一条),那么上述方法可能不满足。上述方法是从最新时间开始,然后每遇到一个时间间隔大于10分钟的数据就取,所以取到的数据的时间间隔是大于等于10分钟,但不是等间隔。 如果用户要求等间隔,那么需要更复杂的算法(比如按时间窗口分组),这里不展开。 由于用户需求描述为“根据更新时间间隔10取一条数据”,我理解的是每隔10分钟取一条,且从最新时间开始。 所以,最终代码修改为: 在查询出原始数据后,使用上述循环进行采样。 但是,注意:原代码中的limit参数是限制原始数据的条数,而不是采样后的条数。所以,如果用户希望采样后返回10条,那么原始数据的limit可能需要设置大一些(比如100条),因为采样后可能只有几条。 因此,我们需要调整limit参数:如果用户传入的limit是希望采样后的条数,那么我们需要先查询更多的原始数据,直到采样后的条数达到limit。 但是,原需求中,用户传入的limit是直接拼接到SQL中的,所以是原始数据的条数。因此,我们保持原样,采样后的数据条数可能小于等于原始数据的条数。 所以,我们不需要修改原始查询的limit,因为用户传入的limit就是原始数据的最大条数。 综上,我们修改服务层代码,在查询出原始数据后,进行采样。 完整代码示例(假设在Service层): ```java // 假设传入的hwId和limit String hwId = ...; String limit = ...; // 注意,limit是字符串,例如"10" // 构建查询 QueryWrapper<MyBatteryInfoEntity> queryWrapper = new QueryWrapper<>(); if (StringUtils.isNotBlank(hwId)) { queryWrapper.eq("battery_id", hwId); } queryWrapper.orderByDesc("upload_time"); if (StringUtils.isNotBlank(limit)) { queryWrapper.last("limit 0," + limit); } // 执行查询 List<MyBatteryInfoEntity> list = myBatteryInfoDao.selectList(queryWrapper); // 时间间隔(10分钟,单位毫秒) long interval = 10 * 60 * 1000; List<MyBatteryInfoEntity> resultList = new ArrayList<>(); if (list != null && !list.isEmpty()) { // 取第一条 resultList.add(list.get(0)); long lastTime = list.get(0).getUploadTime().getTime(); // 从第二条开始遍历 for (int i = 1; i < list.size(); i++) { MyBatteryInfoEntity entity = list.get(i); long currentTime = entity.getUploadTime().getTime(); // 计算时间差(注意,list是降序,所以lastTime大于currentTime) long diff = lastTime - currentTime; if (diff >= interval) { resultList.add(entity); lastTime = currentTime; // 更新上一个采样点的时间 } } } // 此时resultList就是采样后的数据,按时间降序 ``` 注意:这里我们只从原始数据中采样,所以采样后的数据不会超过原始数据的条数。 但是,这样采样有一个问题:如果原始数据中相邻两条数据的时间间隔可能小于10分钟,那么就会跳过。然后,当遇到一个时间间隔大于等于10分钟的数据时,我们取它,然后以它的时间作为新的起点。这样,我们保证了采样后的数据任意相邻两条的时间间隔至少为10分钟。 但是,这样可能漏掉一些时间点。比如,我们取了t0时刻,然后下一条数据是t0-5分钟,跳过;再下一条是t0-12分钟,那么时间差是12分钟(大于10分钟),所以取。然后,再后面一条是t0-20分钟,与t0-12分钟的时间差是8分钟,小于10分钟,跳过。这样,t0-20分钟这条数据就不会被取到,尽管它与t0的时间差是20分钟(大于10分钟)。所以,这种方法不是固定间隔采样,而是保证相邻采样点至少间隔10分钟。 如果用户要求的是固定时间点(比如整10分钟的时刻)采样,那么需要不同的算法:先确定时间轴上的采样点(比如从当时间开始,每10分钟一个点),然后取每个采样点之最近的一条数据。但这样更复杂,且需要知道整个时间范围。 由于用户需求不明确,我们按照上述方法实现。 最后,注意:如果用户需要的是等间隔(比如每10分钟一条)且数据点均匀,那么可能需要更复杂的处理,比如插值,但通常数据库存储的数据时间点是不均匀的,所以取最近的点。 因此,我们按照上述代码实现。 如果用户需要的是等间隔采样,我们可以这样修改: 1. 先确定时间范围:从最新时间到最老时间。 2. 将时间范围分成10分钟的间隔,得到一系列的时间点(从最新时间开始,每次减10分钟,直到最老时间)。 3. 对每个时间点,在原始数据中查找最接近该时间点的一条数据(或者在该时间点之最近的一条数据)。 但是,这样需要多次查找,效率较低。 所以,我们根据用户需求选择。这里,我们按照第一种方法(相邻采样点至少间隔10分钟)实现。 现在,我们给出完整的修改后的代码。 注意:原代码中有一个条件:StringUtils.isNotBlank(limit)才拼接limit,所以我们保留。 另外,如果用户没有传入limit,那么就不限制原始数据的条数,这样可能会查询出很多数据,导致内存溢出。所以,建议如果没有传入limit,则设置一个默认的较大值(比如1000)。 修改后的Service层代码: ```java // 获取原始数据,如果没有传入limit,我们默认取1000条 String actualLimit = limit; if (StringUtils.isBlank(actualLimit)) { actualLimit = "1000"; } QueryWrapper<MyBatteryInfoEntity> queryWrapper = new QueryWrapper<>(); if (StringUtils.isNotBlank(hwId)) { queryWrapper.eq("battery_id", hwId); } queryWrapper.orderByDesc("upload_time"); queryWrapper.last("limit 0," + actualLimit); List<MyBatteryInfoEntity> list = myBatteryInfoDao.selectList(queryWrapper); // 设置时间间隔(10分钟) long intervalMillis = 10 * 60 * 1000; List<MyBatteryInfoEntity> resultList = new ArrayList<>(); if (!list.isEmpty()) { resultList.add(list.get(0)); long lastTime = list.get(0).getUploadTime().getTime(); for (int i = 1; i < list.size(); i++) { MyBatteryInfoEntity current = list.get(i); long currentTime = current.getUploadTime().getTime(); long diff = lastTime - currentTime; // 因为降序,所以lastTime>currentTime,diff>0 if (diff >= intervalMillis) { resultList.add(current); lastTime = currentTime; } } } ``` 这样,resultList就是我们要的结果。 注意:如果用户要求的时间间隔单位不是分钟,而是秒或者小时,需要调整intervalMillis的值。 最后,我们可以将间隔作为参数传入,这样更灵活。 例如,在方法中添加一个参数int intervalMinutes,然后: long intervalMillis = intervalMinutes * 60 * 1000; 或者,如果单位可能是秒、分钟、小时,可以定义时间单位。 由于用户需求中写的是“间隔10”,没有单位,这里我们假设是分钟。 如果用户需要的是秒,那么将10分钟改为10秒即可(intervalMillis=10*1000)。 所以,我们可以在方法中增加一个参数,或者从配置中读取。 综上所述,我们通过先查询原始数据,再在应用层采样的方式实现了需求。 注意:如果数据量很大(比如没有传入limit,而我们默认取1000条),那么1000条数据在应用层处理是很快的。 如果用户要求的数据时间跨度很大(比如几个月),而10分钟的间隔会导致采样后的数据量很大(比如一个月有4320个10分钟),那么1000条原始数据可能不够,因为采样后可能只有几十条。所以,用户可能需要调整limit的值,以便获取足够多的原始数据,从而得到足够多的采样数据。 因此,我们建议用户根据情况设置limit,或者我们根据时间跨度动态计算limit。 但是,由于需求不明确,我们暂时按照用户传入的limit(或默认1000)来处理。 最后,我们输出resultList。 注意:如果用户不需要采样,那么就不应该调用这段代码。所以,我们可以在一个独立的方法中实现,或者通过参数控制是否采样。 由于原需求就是“根据更新时间间隔10取一条数据”,所以我们直接修改。 但是,原代码中并没有采样,所以我们需要明确:这是新增的功能,还是修改原有功能? 用户要求:根据更新时间间隔10取一条数据。所以,我们提供了采样的方法。 因此,我们修改了原有代码,在查询后增加了采样步骤。 最终代码如上。 如果用户希望用数据库层实现,那么需要写复杂的SQL,并且可能不通用(不同数据库不同)。所以,这里我们选择了应用层实现。 如果用户的数据量非常大,并且数据库是MySQL 8.0以上,可以使用窗口函数,按时间间隔分组,然后取每组的第一条。但这样也需要在SQL中写分组,并且按时间间隔分组需要计算。 这里不展开数据库层的实现,因为用户没有指定数据库,且应用层实现已经可以满足大部分场景。 因此,我们给出应用层实现的代码。 注意:如果原始数据中没有数据,则直接返回空列。 相关代码已经给出,并做了详细解释。 最后,根据用户的要求,我们需要在最后提出相关问题。 §§相关问题§§: 1. 如果时间间隔的单位是秒,如何修改代码? 2. 如何实现按固定时间点(比如每整10分钟)采样? 3. 如果数据量很大,如何优化这个采样过程? 4. 在数据库层(MySQL)如何实现按时间间隔分组采样?</think>根据您的需求,需要从数据库中查询数据,并按照时间间隔(10分钟)采样。以下是完整的解决方案和代码实现: ### 解决方案分析 1. **原始查询**:按`upload_time`降序获取数据,支持`battery_id`过滤和结果条数限制 2. **采样需求**:从最新数据开始,每隔10分钟取一条数据(避免时间密集点) 3. **实现方案**: - 先查询原始数据集(按时间倒序) - 在应用层进行时间间隔过滤(避免复杂SQL) - 使用滑动窗口算法遍历数据,保留符合间隔的数据点 ### 完整代码实现(Java + MyBatis Plus) ```java // Service层实现 public List<MyBatteryInfoEntity> getSampledBatteryData(String hwId, Integer limit) { // 1. 构建查询条件 QueryWrapper<MyBatteryInfoEntity> queryWrapper = new QueryWrapper<>(); if (StringUtils.isNotBlank(hwId)) { queryWrapper.eq("battery_id", hwId); } queryWrapper.orderByDesc("upload_time"); // 2. 执行查询(添加limit限制) List<MyBatteryInfoEntity> rawData; if (limit != null && limit > 0) { rawData = myBatteryInfoDao.selectList(queryWrapper.last("LIMIT " + limit)); } else { rawData = myBatteryInfoDao.selectList(queryWrapper); } // 3. 时间间隔采样(10分钟 = 600,000毫秒) return sampleDataByInterval(rawData, 10 * 60 * 1000); } // 时间间隔采样算法 private List<MyBatteryInfoEntity> sampleDataByInterval( List<MyBatteryInfoEntity> rawData, long intervalMillis ) { List<MyBatteryInfoEntity> sampledData = new ArrayList<>(); if (rawData.isEmpty()) return sampledData; // 添加最新数据作为第一个点 sampledData.add(rawData.get(0)); long lastSampledTime = rawData.get(0).getUploadTime().getTime(); // 遍历剩余数据 for (int i = 1; i < rawData.size(); i++) { MyBatteryInfoEntity current = rawData.get(i); long currentTime = current.getUploadTime().getTime(); // 检查时间间隔(注意:数据是倒序,时间递减) if (lastSampledTime - currentTime >= intervalMillis) { sampledData.add(current); lastSampledTime = currentTime; // 更新最后采样时间 } } return sampledData; } ``` ### 代码解释 1. **查询构建**: - `QueryWrapper`动态添加`battery_id`过滤条件 - `.orderByDesc("upload_time")`确保数据按时间倒序 - `.last("LIMIT X")`控制最大数据量(防止内存溢出) 2. **采样算法**: - **起始点**:总是包含最新数据点(索引0) - **时间计算**: - `lastSampledTime`记录上次采样时间戳 - 当数据时间差 ≥ 10分钟则保留(`lastSampledTime - currentTime >= 600,000ms`) - **数据特性**:因查询结果已按时间**倒序**,遍历时时间戳递减 3. **关键参数**: - `intervalMillis`:间隔毫秒数(10分钟=600,000ms) - 修改采样间隔:调整`sampleDataByInterval`的`intervalMillis`参数即可 ### 示例数据流 | 原始数据时间 | 处理逻辑 | |-------------|---------| | 12:00(最新) | ✅ 保留,初始化lastSampledTime | | 11:55 | ❌ 间隔5分钟 < 10分钟 | | 11:48 | ✅ 间隔12分钟 > 10分钟,更新lastSampledTime | | 11:40 | ❌ 间隔8分钟 < 10分钟 | | 11:30 | ✅ 间隔18分钟 > 10分钟 | 输出结果:`[12:00, 11:48, 11:30]` ### 注意事项 1. **时间精度**:确保数据库中的`upload_time`存储到毫秒级 2. **性能优化**:通过`LIMIT`限制原始数据量(建议≤1000条) 3. **时区问题**:确保应用服务器与数据库时区一致 4. **空值处理**:已包含`rawData.isEmpty()`检查 ### 扩展建议 - 如需动态调整间隔单位(秒/分/时),可增加参数: ```java public List<MyBatteryInfoEntity> getSampledData(String hwId, int interval, TimeUnit unit) { long millis = unit.toMillis(interval); // ... 使用millis作为间隔 } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值