在编写报表功能时,我遇到了一个需求:需要根据用户ID分组查询最新的一条钱包明细数据。在编写SQL测试时,我遇到了一个有趣的问题:开始使用子查询根据时间倒序和GROUP BY customer_id,发现查询出来的数据一直是最旧的一条,而不是我需要的最新一条数据。尽管我已经对数据进行了倒序排列,但问题依然存在。经过总结,我总结出了三种解决方案。

一、注意事项

数据库版本:Mysql5.7+

执行GROUP BY语句时出现的sql_mode=only_full_group_by解决方法(这里是Mysql8的解决方案,Mysql5.7也差不多,自行百度即可)

1. 执行select @@sql_mode;查看sql模式

2. 将sql_mode中的only_full_group_by模式剔除,重新设置sql_mode值。如果是使用JDBC连接,需要重启项目才能生效。

二、准备SQL

这里模拟一个SQL语句:

```sql

SELECT * FROM (

SELECT wallet_detail.* FROM wallet_detail

WHERE user_id = '需要查询的用户ID'

ORDER BY create_time DESC

) AS temp

GROUP BY temp.user_id;

```

三、错误查询及错误原因

在MySQL5.7以及之后的版本中,如果GROUP BY的子查询中包含ORDER BY,但是GROUP BY不与LIMIT配合使用,ORDER BY会被忽略掉。因此,子查询在GROUP BY时排序不会生效。这可能是因为子查询大多数是作为一个结果给主查询使用,所以子查询不需要排序。

四、方法一(适用于自增ID和创建时间排序一致)

鉴于以上原因,我们可以添加上LIMIT条件来实现功能。PS:这个LIMIT的数量可以先自行COUNT出你要遍历的数据条数(这个数据条数是所有满足查询条件的数据总数,我这里共9条数据)。

五、方法二(适用于自增ID和创建时间排序一致)

方法一需要先COUNT查询然后将查询结果设置到LIMIT条件中,比较麻烦。这里还可以使用MAX()函数来实现该功能。PS:因为我这里的业务数据是有序插入的,使用主键自增id和create_time结果是一样的。而且使用id查询效率更高,如果没有唯一且有序的id可以替代create_time,那么就用方案一。不能直接使用SELECT id,MAX(create_time)这种操作来获取最新一条数据id,原因在总结中有详细描述。

六、方法三(适用于自增ID和创建时间排序一致)

方法三和方法二实现逻辑基本一致,只是将IN查询替换成了连接查询。本地20w条数据测试,方法三比方法二性能提升50%。有兴趣的可以增大数据集测试后续性能变化。

在经过多次业务测试后,我发现方案三是最合适的。相较于方案一和方案二,方案三的SQL语句简单,性能适中。然而,最终选择哪个方案主要取决于具体的业务需求。

在使用MAX()函数和MIN()这类函数时,如果与GROUP BY配合使用,可能会出现问题。当GROUP BY拿到的数据永远都是这个分组排序最上面的一条时,而MAX()函数和MIN()这类函数会将这个分组中最大或最小的值取出来,这样会导致查询出来的数据对应不上。

正确查询示例:

```sql

SELECT a.id, a.name, a.create_time

FROM table_a a

JOIN (

SELECT id, MAX(create_time) AS max_create_time

FROM table_a

GROUP BY id

) b ON a.id = b.id AND a.create_time = b.max_create_time;

```

错误查询示例:这里的确拿到了每个分组最新创建时间,但是拿的数据id还是排序的第一条。

```sql

SELECT a.id, a.name, a.create_time

FROM table_a a

JOIN (

SELECT id, MAX(create_time) AS max_create_time

FROM table_a

GROUP BY id

) b ON a.id = b.id;

```