Kaminari gem按关联表排序时返回记录不一致问题排查
你遇到的这个情况其实不是Kaminari的锅,而是Active Record在处理关联表排序+includes时的查询行为导致的,咱们一步步拆解:
为什么会出现计数混乱?
当你只写Hippy.includes(:flowers).page(1).per(50)时,Active Record会用预加载(也就是执行两条SQL:先查50个Hippies,再批量查它们关联的Flowers),这时候count统计的是主表Hippies的数量,结果是正常的。
但一旦你加上order("flowers.id asc"),Active Record就会把includes转换成LEFT OUTER JOIN(因为要基于关联表的字段排序,必须把两张表连起来)。这时候如果一个Hippy对应多个Flowers,查询结果里就会出现多条同一个Hippy的记录——而Kaminari的count默认统计的是所有返回的行数,不是去重后的Hippy数量,这就导致分页计数完全乱掉了。
你看到不同排序方向结果不同,只是因为关联记录的排序顺序改变了重复记录的分布,但本质原因都是没去重。
解决方法
只需要在查询里加上distinct,让Active Record只统计主表的唯一记录:
方案1:全局去重(推荐)
Hippy.includes(:flowers).order("flowers.id asc").distinct.page(1).per(50).count
这样会强制SQL里加上DISTINCT,确保每条Hippy只被统计一次,分页的count和total_pages都会恢复正常。
方案2:指定主表字段去重(更明确)
如果担心其他字段影响,也可以明确指定主表的主键字段:
Hippy.includes(:flowers).order("flowers.id asc").distinct(:id).page(1).per(50).count
补充说明
如果你用joins(:flowers)代替includes,默认是INNER JOIN,会过滤掉没有Flowers的Hippy,如果你不需要保留这些Hippy,也可以用joins加distinct,但如果要保留所有Hippy,还是用includes(本质是LEFT JOIN)加distinct更合适。
测试一下,加上distinct后,你应该就能得到正确的每页50条Hippy记录,total_pages也会回到和无排序时一致的数值啦。
内容的提问来源于stack exchange,提问作者offwhite

