Propel 1.2中PropelPager搭配HAVING子句时总记录数统计异常
Propel 1.2 带HAVING子句查询搭配PropelPager时总记录数统计异常
问题表现
- 运行环境为Propel 1.2,使用Propel Pager组件实现列表分页
- 构造的包含LEFT JOIN、GROUP BY、HAVING条件的Criteria对象,直接调用
EfEventsPeer::doSelect($c)可以返回预期的200条左右结果,手动执行生成的SQL结果也符合预期 - 把同一个Criteria传入PropelPager初始化后,调用
getTotalRecordCount()固定返回1,getTotalPages()固定返回1,但调用getResult()仍然能正常取出当前页的10条数据
复现代码片段
构造查询Criteria
$c = new Criteria(); $criterion = $c->getNewCriterion(EfEventsPeer::EF_STAMMDATEN_ID, $stamm_id); $criterion->addAnd($c->getNewCriterion(EfEventsPeer::GELOESCHT, 1, Criteria::NOT_EQUAL)); $criterion->addAnd($c->getNewCriterion(EfEventDatenPeer::ID, null, Criteria::ISNOTNULL)); $criterion->addAnd($c->getNewCriterion(EfEventsPeer::IMPORTIERT, 1, Criteria::NOT_EQUAL)); $c->addJoin(EfEventDatenPeer::EF_EVENTS_ID, EfEventsPeer::ID, Criteria::LEFT_JOIN); $c->addGroupByColumn(EfEventsPeer::ID); $c->addHaving($c->getNewCriterion(EfEventDatenPeer::DATUM, "MAX(".EfEventDatenPeer::DATUM.") < '" . date('Y-m-d') . "'", Criteria::CUSTOM)); $c->add($criterion); $c->addDescendingOrderByColumn(EfEventsPeer::GESPERRT); $c->addDescendingOrderByColumn(EfEventsPeer::EINGESTELLT_AM); $c->setDistinct();
直接查询结果正常
$results = EfEventsPeer::doSelect($c);
PropelPager统计异常
$pager = new PropelPager($c, 'EfEventsPeer', 'doSelect', 1, 10); $pager->getTotalRecordCount(); // 错误返回1 $pager->getTotalPages(); // 错误返回1 // 但当前页结果可以正常遍历 foreach($pager->getResult() as $single_event){ // 业务处理逻辑 }
根因分析
这个问题和addHaving()的写法没有关系,核心是Propel 1.2版本PropelPager的count统计逻辑存在硬伤:
- 计算总记录数时,组件会自动克隆传入的Criteria,清空原有的查询字段、排序规则,试图生成
SELECT COUNT(*)格式的统计SQL,但旧版本逻辑没有保留原Criteria中的GROUP BY、HAVING、多表JOIN关联条件,最终生成的统计SQL只带主表的基础WHERE过滤规则,统计结果完全失真。 - 你贴出的自动生成SQL片段
SELECT DISTINCT FROM ef_events WHERE ...就是直接证据:不仅丢失了所有JOIN、分组、HAVING逻辑,连SELECT后应该拼接的字段列表都为空,说明这个版本在处理带DISTINCT、多表关联的复杂查询时,本身就存在SQL拼接bug,自动生成的count结果完全不可信。
修复方案
优先选择手动统计总记录数的方案,不需要升级框架,改动成本最低:
- 克隆原查询Criteria,手动编写正确的count统计逻辑,清空分页参数、排序参数,指定统计主表主键的去重计数,避免多表JOIN产生的重复数据影响结果:
// 克隆查询条件,避免修改原Criteria $countCrit = clone $c; $countCrit->clearSelectColumns(); // 按主表主键去重统计,解决多表JOIN重复问题 $countCrit->addSelectColumn(sprintf('COUNT(DISTINCT %s) as total', EfEventsPeer::ID)); $countCrit->setLimit(0); $countCrit->setOffset(0); $countCrit->clearOrderByColumns(); // 执行查询拿到正确总记录数 $stmt = EfEventsPeer::doSelectStmt($countCrit); $total = (int)$stmt->fetchColumn(0);
- 初始化PropelPager后,手动把正确的总记录数设置进去,覆盖组件自动生成的错误统计值:
$pager = new PropelPager($c, 'EfEventsPeer', 'doSelect', 1, 10); $pager->setTotalRecordCount($total);
后续分页参数、结果遍历逻辑不需要改动,即可正常使用总页数计算、分页跳转等功能。
如果后续有框架升级计划,Propel 1.3及以上版本已经修复了复杂Criteria下的count统计逻辑,但对于1.2的老项目,手动统计的方案兼容性最好。
内容的提问来源于stack exchange,提问作者Andreas Neger
相关产品推荐
相关产品推荐

