问题描述
我有一个简单报表的查询,当前端查询没有维度字段时,也就是groupList为空。实际查询sql为:SELECT IFNULL( ROUND( SUM( highprice_sum ), 2 ), 0 ) AS highPrice FROM dsp_basic_report;
这个查询使用了PageHelper( pagehelper-spring-boot-starter 2.0.0版本)进行了分页查询,使用方法正确,进行实际查询时,计算总数的sql和真正执行查询的sql不一致。
最终查询总数的sql为:select count(*) from dsp_basic_report;
这样就导致的问题是查询的条数是所有的数据8条,而真正查询出来的数据是1条,导致分页查询错误。
查找原因
从现象来看,sql中IFNULL( ROUND( SUM( highprice_sum ), 2 ), 0 ) AS highPrice被替换成count(*),既然我没有进行任何更改,使用形式也完全正确,那就只能去源码中寻找答案了。
PageInterceptor
先找到PageInterceptor,然后再找到执行查询总数的位置,在intercept方法中调用的count方法。
count方法
此时会执行到ExecutorUtil.executeAutoCount方法。
ExecutorUtil
executeAutoCount方法
执行完getCountSql方法后执行发现count sql已经变成错误的了,再进入到getCountSql方法中。
AbstractHelperDialect
getCountSql方法
最后会进入到AbstractHelperDialect.getCountSql方法中,countColumn为0不是空会执行第一个getSmartCountSql方法。
DefaultCountSqlParser
getSmartCountSql方法
最终会进入到DefaultCountSqlParser.getSmartCountSql方法中,DefaultCountSqlParser是
CountSqlParser的实现类。此时入参里的sql还是正确的查询语句。
执行完sqlToCount方法后,count sql变成错误的了,再进入当前方法。
sqlToCount方法
发现进入了第一个分支。
执行完第一个分支后,发现count sql变成错误的了。此时大概知道是什么问题了,if语句里有两个判断条件,第一个selectBody instanceof PlainSelect只是简单判断了下类型,没有其它更多的信息;第二个this.isSimpleCount((PlainSelect)selectBody),从名称大概能看出当前查询是简单计算,然后select语句就会直接替换为count(*)。
我看了之后也很蒙,明明我查询使用了各种函数,最主要使用了SUM聚合函数,怎么也不能是简单查询,只能继续看isSimpleCount的执行逻辑。
isSimpleCount方法(根本原因)
可以看到当前方法有四个分支,返回true为简单查询计算,会被count(0)代替,返回false则还是原sql进行计数。
- 语句存在group by,返回false
- 语句存在distinct,返回false
- 语句存在having,返回false
前3个可以清晰的知道不包含这些关键字会就可能是简单计数,第4个逻辑较为复杂分开说明。
第4个分支
具体的debug过程不再记录了,只写下大致执行逻辑。
当前语句有5个循环,1个while循环和4个do while循环。以我sqlSELECT IFNULL( ROUND( SUM( highprice_sum ), 2 ), 0 ) AS highPrice FROM dsp_basic_report;为例,都是大体逻辑不能保证完全正确。
首先会执行到第5个循环。item不包含?,并且是SelectExpressionItem会跳出当前循环,退到第4个循环。
然后进入到第4个循环。获取到当前表达式,并且表达式为Function,再获取其name就是IFNULL,会跳到第3个循环;如果不是会执行到while判断,当前表达式存在()括号并且别名不为空,就会直接返回false。
在第3个循环中,因为name为IFNULL不为空,退到第2个循环。
第2个循环中,skipFunctions为空,会退到第1个循环。falseFunctions也为空,继续向下执行。
获取到AGGREGATE_FUNCTIONS,是一个set包含SUM函数但是不包含IFNULL函数,
Set<String> AGGREGATE_FUNCTIONS = new HashSet(Arrays.asList("APPROX_COUNT_DISTINCT,ARRAY_AGG,AVG,BIT_,BOOL_,CHECKSUM_AGG,COLLECT,CORR,COUNT,COVAR,CUME_DIST,DENSE_RANK,EVERY,FIRST,GROUP,JSON_,LAST,LISTAGG,MAX,MEDIAN,MIN,PERCENT_,RANK,REGR_,SELECTIVITY,STATS_,STD,STRING_AGG,SUM,SYS_OP_ZONE_ID,SYS_XMLAGG,VAR,XMLAGG".split(",")));,此时会将IFNULL函数添加到skipFunctions中,然后就是处理下个查询参数,然而当前sql只有一个参数返回true。
总结
我个人认为当前处理逻辑是存在bug的,不应该只拿最外层的函数来确认是否是AGGREGATE_FUNCTIONS,正确的来说应该是一层层解析嵌套函数,有一个存在,当前sql就不是简单计算。
上述逻辑在包含group by,distinct,having,参数包含?,参数包含()和别名,第一个函数在AGGREGATE_FUNCTIONS中,都会返回false。
如果存在当前情况,就根据以上条件按实际情况修改。
我想保留我的函数,最终将sql修改为:select (1) as "skip",IFNULL( ROUND( SUM( highprice_sum ),2), 0 ) as highPrice from dsp_basic_report;,计数查询执行正确。
提交了issue,等待作者最终的回复吧。