应使用 date(p.createdat) 提取日期再分组,避免按完整 datetime 分组;需用 expr()->date() 或 addselect('date(p.createdat) as day') 后 groupby('day');统计访问频次应基于 accesslog 实体并注意时区转换。

直接用 Doctrine QueryBuilder 做日期分组统计,别碰 groupBy('p.createdAt') 这种写法——它会按完整 datetime 分组,不是“每日”,而是“每秒/每分钟”。必须先截断时间精度。
用 DATE() 函数提取日期做分组
MySQL 和 PostgreSQL 都支持 DATE() 提取年月日部分。Doctrine DQL 不直接识别该函数,得用 expr()->date() 或原生 SQL 片段。推荐前者,兼容性好:
-
DATE(p.createdAt)在 MySQL 中等价于DATE_FORMAT(p.createdAt, '%Y-%m-%d') - PostgreSQL 需用
DATE(p.createdAt),但 Doctrine 默认不注册该函数,得手动注册或改用expr()->date() - 在 QueryBuilder 中写:
$qb->addSelect('DATE(p.createdAt) AS day'),再$qb->groupBy('day') - 注意:不能写
$qb->groupBy('DATE(p.createdAt)'),Doctrine 会报 “Invalid parameter number” 错误
关联路由名字段需 JOIN 日志表而非主实体
访问频次统计通常基于请求日志表(比如 access_log),不是业务实体。假设你有 AccessLog 实体,含 routeName 和 createdAt 字段:
开箱即用的技能链路由引擎。13 条预定义链覆盖搜索、开发、审查、MLOps、法律、创意等场景,三层路由架构(触发词→SAD反馈→DAG编排),recall@10=96.97%。配置驱动(chains.yaml),零代码扩展。pip install skill-weave-chains 一键安装。
- 别在
User或ProductRepository 里查,建独立的AccessLogRepository - 如果
routeName是字符串字段,直接select('p.routeName')+groupBy('p.routeName', 'day') - 若要关联
Route实体(比如带中文名称),必须leftJoin('p.route', 'r'),且确保r别名不与主表冲突 - 多字段分组时,
groupBy参数顺序必须和select一致,否则 SQLite 会报错,MySQL 可能返回非预期聚合结果
避免 COUNT(*) 导致空日期缺失
默认 GROUP BY 只返回有数据的日期。要补全最近 7 天、哪怕某天没访问也显示 0,Doctrine 不支持 RIGHT JOIN 生成日期序列,得换思路:
- 先查出有数据的日期+计数,存为 PHP 数组
- 用
new \DateTime('-6 days')循环生成 7 天日期数组 - 用
array_key_exists($dateStr, $result)合并补零 - 或者改用原生 SQL:用
UNION ALL拼接日期序列(MySQL 8.0+ 支持 CTE,但 Symfony 默认不启用) - 千万别在 DQL 里写
COUNT(p.id)而不是COUNT(*)——万一p.id允许 NULL(虽然不该),结果就错了
日期分组最易忽略的是时区。数据库存的是 UTC,但你要统计“中国用户每日”,就得在查询前 $qb->setParameter('timezone', 'Asia/Shanghai'),并在 DQL 里用 CONVERT_TZ(p.createdAt, '+00:00', :timezone) ——这个函数只在 MySQL 开启时区表后才可用,本地开发常漏配。










