正确做法是SQL层用field()提取年月标识(如FROM_UNIXTIME(create_time, "%Y-%m") as ym),PHP层按该标识归类为二维数组,模板层用两层{volist}输出;严禁在group()中直接写函数表达式或在模板中判断月份。

ThinkPHP 5.1 按月份分段循环的常见错误
直接用 foreach 或 {volist} 遍历数据,再在模板里用 date('Y-m', $time) 判断月份——这会导致同一月份的数据被反复拆开、无法聚合,且模板层逻辑臃肿、难以复用。
更糟的是,有人试图在控制器里用 array_chunk() 按固定条数切分,结果发现“同月数据被割裂到不同块”,根本不是按自然月分段。
- 数据库时间字段是
int类型时间戳(如create_time),没转成datetime格式就 group,MySQL 会按数值截断,导致 2026-01 和 2026-10 被归为同一组 - 没用
FROM_UNIXTIME或DATE_FORMAT提前提取年月,却在 PHP 层做date('Y-m', $v['create_time']),既无法利用索引,又让分组逻辑脱离 SQL 层 - TP5.1 的
group()不支持直接对表达式分组(如group('FROM_UNIXTIME(create_time, "%Y-%m")')),会报语法错误或静默失败
必须在 SQL 层完成月份提取与分组
ThinkPHP 5.1 不允许在 group() 中直接写函数表达式,但可以通过 field() + 原生 SQL 函数构造分组键,再用 PHP 合并结果。
正确做法是:先查出带「年月标识」的原始数据,再用 PHP 按该标识归类。关键不是“怎么循环”,而是“怎么把数据组织成按月分好的数组”。
立即学习“PHP免费学习笔记(深入)”;
- 用
Db::name('article')->field('id,title,create_time,FROM_UNIXTIME(create_time, "%Y-%m") as ym')->order('create_time desc')->select()—— 注意ym是计算字段,不是数据库真实列 - 避免写
GROUP BY FROM_UNIXTIME(...),TP5.1 会把它当字符串字面量处理,不执行函数 - 如果
create_time是datetime类型,改用DATE_FORMAT(create_time, "%Y-%m") - 结果集出来后,用
array_reduce()或简单foreach归类:$byMonth = []; foreach ($list as $item) { $ym = $item['ym']; if (!isset($byMonth[$ym])) { $byMonth[$ym] = []; } $byMonth[$ym][] = $item; }
模板里怎么安全输出按月分段内容
别在模板里写嵌套 {volist}{volist},也别用 {foreach} 套 {if} 判断月份变化——TP5.1 模板引擎不支持变量作用域穿透,$prev_ym 类变量无法跨迭代维持状态。
必须把已分好组的二维数组($byMonth)直接 assign 进模板,然后用两层 {volist} 输出。
- 控制器中:
$this->assign('byMonth', $byMonth); - 模板中:
{volist name="byMonth" id="monthList" key="ym"} <h3>{$ym} 月</h3> {volist name="monthList" id="item"} {$item.title} - {$item.id} {/volist} {/volist} - 注意:
key="ym"是必须的,否则取不到月份标识;name="monthList"是当前月份下的数据列表,不是原始$list - 如果需要按时间倒序排月份(如 2026-07 在前,2026-06 在后),PHP 层归类后要调用
ksort($byMonth, SORT_DESC)
性能与边界情况提醒
按月分段本质是内存聚合操作,数据量大时容易 OOM——这不是模板问题,是设计层面没控制查询范围。
线上必须加时间范围约束,不能查全表再分月。
- 务必在 SQL 中加
where('create_time', '>=', strtotime('2026-01-01')),避免拉取数年前的冷数据 - 如果月份跨度大(如查三年数据),建议前端分页按「年份」切换,后端每次只查单年,再按月分段,而非一次查三年
- TP5.1 的
select()默认返回Collection,但toArray()才能确保array_reduce()稳定工作;不确定时先调$list->toArray() - 日期字段为空(
null或0)时,FROM_UNIXTIME(0, ...)返回'1970-01',需提前过滤或单独归入“未知月份”组
真正卡住人的从来不是“怎么写模板标签”,而是没想清楚:月份分段是数据组织问题,不是渲染问题。SQL 层提取、PHP 层归类、模板层消费,三层职责必须切清。



















