最近遇到一个问题,需要根据两张表(一对一)的查询结果进行分组并按照时间倒序排序,取每组最新的一条记录;问题是分组的字段和排序的字段还不在一张表,而且因为执行顺序group by是在order by之前的,先group by分组再order by排序发现记录不是每组排序的第一条;把查询结果作为子查询先order by排序,最后在对整体记录group by分组也得不到想要的结果,搞得很头疼
通过对官方文档的查询、网上资料的研究以及自己的验证,我总结了几种解决方案,既可以作为解决类似问题的快速回忆,又可以帮助其他遇到这类问题的人节省时间。以下是几种方案:
方案一:利用distinct关键字,这里distinct其实起到了一个固定排序的作用
SELECT
*
FROM
(
SELECT DISTINCT
t1.*,
t2.type,
t2.price
FROM
t1
LEFT JOIN t2 ON t1.type_id = t2.id
ORDER BY
t1.end_time DESC
) t
GROUP BY
t.type
方案二:利用limit关键字,这里limit其实也是起到了一个固定排序的作用,这种因为要尽可能的使limit的数量比较大,所以不是很推荐,如果是对一些已知且数据量小的表的话也是可以用的
SELECT
*
FROM
(
SELECT
t1.*,
t2.type,
t2.price
FROM
t1
LEFT JOIN t2 ON t1.type_id = t2.id
ORDER BY
t1.end_time DESC
LIMIT 99999(超过表记录数或者可预知的最大记录数)
) t
GROUP BY
t.type
方案三:先对一个唯一列分组排序,一般是主键,最后再对查询的记录进行实际字段的分组,其实原理还是固定排序,防止最后分组的时候破坏原有的排序
SELECT
*
FROM
(
SELECT
t1.*,
t2.type,
t2.price
FROM
t1
LEFT JOIN t2 ON t1.type_id = t2.id
GROUP BY
t1.id
ORDER BY
t1.end_time DESC
) t
GROUP BY
t.type
方案四:利用SUBSTRING_INDEX函数截取主键然后再分组,跟方案三其实思路一样,实现方式不同
SELECT
t1.*,
t2.type,
t2.price
FROM
t1
LEFT JOIN t2 ON t1.type_id = t2.id
WHERE
t1.id IN (
SELECT
SUBSTRING_INDEX(GROUP_CONCAT(t.id ORDER BY t.end_time DESC), ',', 1)
FROM
(
SELECT
t3.*,
t4.type,
t4.price
FROM
t3
LEFT JOIN t4 ON t3.type_id = t4.id
ORDER BY
t3.end_time DESC
) t
GROUP BY
t.type
)
方案五:sql不分组,在代码利用循环中比较结束时间,取出每种分组最后一条记录放入新集合中,这种是最容易理解的方案,但是代码略显臃肿
方案六:sql不分组,在代码中利用stream的sorted排序然后groupingBy分组,这样的结果是分组的字段为key,分组的数据为list的一个map,在声明返回结果类型和中间类型类型时注意要返回一个有序的map,TreeMap或者LinkedHashMap,这样你根据key取每一组的第一条数据就是你最开始sorted排序的第一条,下边简单写一个例子:
List studentVOList = new ArrayList<>();
LinkedHashMap> studentVOListMap = studentVOList.stream()
.sorted(Comparator.comparing(StudentVO::getEndTime).reversed())
.collect(Collectors.groupingBy(s -> s.getType(), LinkedHashMap::new, Collectors.toList()));
studentVOListMap.forEach((type, list) -> {
StudentVO studentVO = list.get(0);
});
方案七:sql不分组,在代码中利用stream的groupingBy分组,然后再对返回的map中的value进行排序,这个对返回的map类型没有具体要求,其实思路都差不多,看个人习惯和偏好吧,下边也是一个简单的例子:
List studentVOList = new ArrayList<>();
HashMap map = list.stream()
.collect(Collectors.groupingBy(StudentVO::getType, HashMap::new,
Collectors.collectingAndThen(Collectors.toList(), list -> list.stream()
.sorted(Comparator.comparing(StudentVO::getEndTime).reversed())
.findFirst().get())));
map.forEach((key, value) -> {
StudentVO studentVO = value;
});
这个其实理解起来很好理解,但是代码读起来有点费劲,需要对Java的steam流有一定的了解,也可以直接套用自己检验,嘿嘿~~
以上的方案我都实际试过,最后采用的是第五种,复杂度和可读性我个人理解相对好一些,sql的写法被老大噶了,说数据量大的时候性能不行,各位根据自身情况采用或者自己研究,我上边的例子是阉割版的,如果是理解不深入的话,套用的时候最好多试试,别出问题,总结不易,觉得有用请点赞支持下,有其他好的解决方案的也可以评论留言,互相交流!