近日,在开发博客的时候,遇到了一个问题,归档不显示,安装后页面看不见,找了很多原因,都无法解决,最后终于从列的一致性上找到了原因。问题解决了之后,博客归档页面完美呈现。
一、正确代码:
sql
$archives = $db->fetchAll("
SELECT
DATE_FORMAT(create_time, '%Y年%m月') as month,
COUNT(*) as count
FROM article
WHERE status = 1
GROUP BY DATE_FORMAT(create_time, '%Y年%m月')
ORDER BY MIN(create_time) DESC
LIMIT 10
");
二、错误代码:
sql
$archives = $db->fetchAll("
SELECT DATE_FORMAT(create_time, '%Y年%m月') as month, COUNT(*) as count
FROM article
WHERE status = 1
GROUP BY YEAR(create_time), MONTH(create_time)
ORDER BY YEAR(create_time) DESC, MONTH(create_time) DESC
LIMIT 10
");
三、代码分析
1、这段代码的作用
这段代码的作用是:从数据库的文章表中,按月份统计文章数量,生成归档列表。
比如:
2025年05月 → 3篇文章
2025年04月 → 1篇文章
2025年03月 → 5篇文章
2、正确代码解析
sql
SELECT
DATE_FORMAT(create_time, '%Y年%m月') as month, -- 将日期格式化为"2025年05月"
COUNT(*) as count -- 统计该月份有多少篇文章
FROM article -- 从文章表查询
WHERE status = 1 -- 只统计已发布的文章
GROUP BY DATE_FORMAT(create_time, '%Y年%m月') -- 按格式化后的月份分组
ORDER BY MIN(create_time) DESC -- 按月份倒序排列(最新的在前)
LIMIT 10 -- 只取最近10个月
3、原来的错误代码
sql
SELECT
DATE_FORMAT(create_time, '%Y年%m月') as month,
COUNT(*) as count
FROM article
WHERE status = 1
GROUP BY YEAR(create_time), MONTH(create_time) -- ❌ 问题在这里
ORDER BY YEAR(create_time) DESC, MONTH(create_time) DESC
四、错误原因:ONLY_FULL_GROUP_BY
1、什么是 ONLY_FULL_GROUP_BY?
这是 MySQL 的一种严格模式,它要求:
在
GROUP BY中出现的列,必须和SELECT中的非聚合列完全一致。
2、错误分析
sql
SELECT
DATE_FORMAT(create_time, '%Y年%m月') as month, -- 非聚合列(格式化后的)
COUNT(*) as count -- 聚合列(没问题)
FROM article
GROUP BY YEAR(create_time), MONTH(create_time) -- 按年、月分组
问题:
-
SELECT中的非聚合列是DATE_FORMAT(create_time, '%Y年%m月') -
GROUP BY中的列是YEAR(create_time), MONTH(create_time)
这两者不相等! MySQL 无法确定 DATE_FORMAT(create_time, '%Y年%m月') 是否和 YEAR(...), MONTH(...) 一一对应。
3、举例说明
假设有两条记录:
-
记录1:
create_time = '2025-05-01 10:00:00' -
记录2:
create_time = '2025-05-15 14:30:00'
按 YEAR(create_time), MONTH(create_time) 分组,它们属于同一组(都是2025年5月)。
但 DATE_FORMAT(create_time, '%Y年%m月') 对两条记录都返回 "2025年05月",这没问题。
但问题是 :MySQL 的严格模式不信任这种转换,它要求 SELECT 中的非聚合列必须直接 出现在 GROUP BY 中,不能通过函数转换。
五、修复方法的原理
sql
GROUP BY DATE_FORMAT(create_time, '%Y年%m月')
为什么这样就能工作?
因为现在 SELECT 中的非聚合列和 GROUP BY 中的列完全一致了:
| SELECT 中的非聚合列 | GROUP BY 中的列 |
|---|---|
DATE_FORMAT(create_time, '%Y年%m月') |
DATE_FORMAT(create_time, '%Y年%m月') |
MySQL 可以明确知道:按这个表达式分组,每个组的值是唯一的。
六、关于 ORDER BY MIN(create_time) DESC
sql
ORDER BY MIN(create_time) DESC
因为我们是按月份 分组,不是按具体时间。MIN(create_time) 取每个组中最早的日期:
-
2025年05月组 →
MIN(create_time)=2025-05-01(组内最早的) -
2025年04月组 →
MIN(create_time)=2025-04-03
按 MIN(create_time) 倒序排列,结果就是:最新月份在前面。
七、对比两种写法
| 写法 | 优点 | 缺点 |
|---|---|---|
GROUP BY YEAR(...), MONTH(...) |
性能稍好(使用索引) | 严格模式下报错 |
GROUP BY DATE_FORMAT(...) |
兼容严格模式 | 可能无法使用索引 |
总结
| 问题 | 原因 | 解决 |
|---|---|---|
| 归档页面报错 | MySQL 严格模式要求 GROUP BY 和 SELECT 中的非聚合列一致 |
改用 GROUP BY DATE_FORMAT(...) |
一句话:严格模式下,GROUP BY 后面跟什么,SELECT 中非聚合列就必须是什么,不能有函数转换的差异。 ✅