SQL 取出UserID相同记录中的一条,并且取的是ID最大的。

本文介绍了一种SQL技巧,通过特定的查询语句确保在产品列表页面上为每位用户仅展示其最新的产品记录。该方法使用了子查询与聚合函数相结合的方式实现。

SELECT * FROM Product WHERE (id IN(SELECT MAX([id])   FROM Product  GROUP BY userid))   ORDER BY id DESC


实际应用:用户发布产品时,保证列表页每个用户只显示最新的一条。


<think>我们使用MyBatis-Plus进行分组统计,需要构建QueryWrapper,选择分组字段和聚合函数,然后分组、排序并限制结果数量。 目标:按user_id分组,统计每个用户的water_num总和,然后按总和倒序排列,前10条。 步骤: 1. 创建QueryWrapper对象。 2. 使用select方法指定选择的字段:user_id和SUM(water_num)的聚合,并为聚合结果指定别名(例如total_water)。 3. 使用groupBy方法按user_id分组。 4. 使用orderByDesc方法按总和别名(或表达式)倒序排序。 5. 使用last方法添加LIMIT 10(注意:last方法直接拼接SQL,有SQL注入风险,但这里参数固定,可接受)或者使用selectMapsPage进行分页(但这里只需要前10条,且是分组后的分页,可能比较复杂)。 6. 使用selectMaps方法执行查询,返回Map列表,其中每个Map对应一行结果,包含user_id和total_water。 注意:由于分组统计返回的字段与实体类字段不匹配,所以使用selectMaps获结果。 代码示例: ```java QueryWrapper<Order> wrapper = new QueryWrapper<>(); wrapper.select("user_id, SUM(water_num) AS total_water") // 选择分组字段和聚合函数,并给聚合结果起别名 .groupBy("user_id") // 按user_id分组 .orderByDesc("total_water") // 按总和倒序排序 .last("LIMIT 10"); // 限制10条,注意:直接拼接SQL,确保安全(这里参数固定) List<Map<String, Object>> resultList = orderMapper.selectMaps(wrapper); ``` 这样,resultList就是我们要的结果,每个Map包含两个键:user_id和total_water。 另一种方式(避免使用last,使用分页): 我们可以使用Page对象进行分页,但是分组后再分页,MyBatis-Plus的分页会自动在SQL中加入LIMIT,但是要注意:分页查询会先执行一次count查询(统计总记录数),而我们这里只需要前10条,且不需要总数,所以可以设置不查询count。 使用分页的写法: ```java Page<Map<String, Object>> page = new Page<>(1, 10); // 第一页,10条数据 QueryWrapper<Order> wrapper = new QueryWrapper<>(); wrapper.select("user_id, SUM(water_num) AS total_water") .groupBy("user_id") .orderByDesc("total_water"); // 执行查询,注意:这里使用selectMapsPage方法,它返回分页结果 IPage<Map<String, Object>> resultPage = orderMapper.selectMapsPage(page, wrapper); List<Map<String, Object>> resultList = resultPage.getRecords(); ``` 但是,注意:分组统计的分页,count查询是统计分组后的数量,所以它会执行类似下面的SQL: SELECT COUNT(1) FROM (SELECT user_id FROM order GROUP BY user_id) temp 如果数据量很大,这个count查询可能会比较慢。而我们只需要前10条,所以如果不想执行count查询,可以设置page.setSearchCount(false): ```java Page<Map<String, Object>> page = new Page<>(1, 10); page.setSearchCount(false); // 设置为false,表示不进行count查询 // ... 其他同上 ``` 这样,就只会执行分组查询并前10条,不会执行count查询。 根据需求选择:如果确定只需要前10条,且数据量很大,建议使用page.setSearchCount(false)避免额外的count查询。如果数据量不大,或者需要分页信息(如总页数等),则不需要设置。 推荐第一种直接last("LIMIT 10")的方式,简洁高效,但注意last方法直接拼接SQL,需确保安全(避免用户输入拼接)。 注意:在MyBatis-Plus中,last方法会直接拼接到SQL语句的最后,所以这里我们拼写的是"LIMIT 10",在MySQL中有效,如果使用其他数据库,可能需要调整(如Oracle的ROWNUM或TOP N等)。因此,请根据数据库类型调整。 由于问题中没有指定数据库,假设为MySQL。 因此,最终代码(MySQL)如下: ```java QueryWrapper<Order> wrapper = new QueryWrapper<>(); wrapper.select("user_id, SUM(water_num) AS total_water") .groupBy("user_id") .orderByDesc("total_water") .last("LIMIT 10"); List<Map<String, Object>> resultList = orderMapper.selectMaps(wrapper); ``` 结果处理:遍历resultList,每个map中有两个键:user_id和total_water。 如果希望将结果封装到自定义对象中,可以使用自定义ResultMap或者使用@Select注解写SQL,但这里要求使用MyBatis-Plus的Wrapper方式,所以用Map接收比较直接。 如果需要自定义对象接收,可以使用实体类或DTO,但需要在select中指定别名与DTO属性匹配,然后使用selectList方法,并传入DTO的Class。但请注意,由于我们使用了聚合函数,返回的字段与实体类不同,因此我们需要创建一个专用的DTO类。 例如,创建OrderSumDTO: ```java @Data public class OrderSumDTO { private Long userId; private BigDecimal totalWater; // 注意:根据water_num类型,这里用BigDecimal或Long等 } ``` 然后,在select中指定别名,使得数据库返回的字段名与DTO属性名匹配(注意数据库字段user_id对应userId,total_water对应totalWater): ```java QueryWrapper<Order> wrapper = new QueryWrapper<>(); wrapper.select("user_id AS userId, SUM(water_num) AS totalWater") // 使用别名匹配DTO属性 .groupBy("user_id") .orderByDesc("totalWater") // 这里排序字段用别名totalWater .last("LIMIT 10"); List<OrderSumDTO> resultList = orderMapper.selectList(wrapper, OrderSumDTO.class); ``` 注意:这里使用的是`selectList(wrapper, OrderSumDTO.class)`,这个方法需要MyBatis-Plus版本支持(3.4.0及以上)。如果版本较低,可以使用`orderMapper.selectList(wrapper)`返回Order实体列表,但这样不行,因为Order实体没有totalWater属性。所以低版本只能使用Map或者自己写XML。 因此,如果版本在3.4.0及以上,推荐使用这种方式。 总结:根据实际情况选择使用Map或DTO。 我这里给出两种方式,用户可根据自己的版本和需求选择。 注意:在使用DTO方式时,排序字段orderByDesc中应该使用别名(如totalWater)而不是数据库字段(如total_water)。因为查询结果已经用别名,而排序是在SELECT之后,所以用别名。但是在MySQL中,排序也可以使用聚合表达式(如SUM(water_num)),但使用别名更清晰。 因此,最终代码(DTO方式,MyBatis-Plus 3.4.0+): ```java QueryWrapper<Order> wrapper = new QueryWrapper<>(); wrapper.select("user_id AS userId, SUM(water_num) AS totalWater") .groupBy("user_id") .orderByDesc("totalWater") .last("LIMIT 10"); List<OrderSumDTO> resultList = orderMapper.selectList(wrapper, OrderSumDTO.class); ``` 如果版本低于3.4.0,则可以使用selectMaps然后手动转换成DTO: ```java List<Map<String, Object>> maps = orderMapper.selectMaps(wrapper); List<OrderSumDTO> dtos = maps.stream().map(map -> { OrderSumDTO dto = new OrderSumDTO(); dto.setUserId(Long.valueOf(map.get("userId").toString())); dto.setTotalWater(new BigDecimal(map.get("totalWater").toString())); return dto; }).collect(Collectors.toList()); ``` 根据实际情况选择。 以上就是完整的实现方案。 </think>### 订单表分组统计实现方案 使用MyBatis-Plus实现按`user_id`分组统计`water_num`总和,结果倒序前10条的代码如下: ```java // 创建查询包装器 QueryWrapper<Order> wrapper = new QueryWrapper<>(); wrapper.select("user_id, SUM(water_num) AS total_water") // 分组字段和聚合函数 .groupBy("user_id") // 按user_id分组 .orderByDesc("total_water") // 按总和降序 .last("LIMIT 10"); // 限制结果数量 // 执行查询(返回Map列表) List<Map<String, Object>> result = orderMapper.selectMaps(wrapper); // 处理结果 result.forEach(map -> System.out.println( "用户ID: " + map.get("user_id") + ", 总水量: " + map.get("total_water") )); ``` #### 关键点说明: 1. **聚合函数**:`SUM(water_num) AS total_water` 计算每个用户的水量总和 2. **分组字段**:`.groupBy("user_id")` 按用户ID分组 3. **排序方式**:`.orderByDesc("total_water")` 按总和降序排列 4. **结果限制**:`.last("LIMIT 10")` 限制返回前10条记录 5. **结果类型**:使用`selectMaps()`返回`List<Map>`,键为字段名或别名 > **性能建议**:当数据量较大时,建议在数据库建立索引: > ```sql > CREATE INDEX idx_user_water ON order_table(user_id, water_num); > ```[^1] #### 输出示例: ``` 用户ID: 1001, 总水量: 56840 用户ID: 2056, 总水量: 49210 用户ID: 3089, 总水量: 45120 ... ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值