Laravel Eloquent 不支持原生 CTE(WITH 子句),直至 10.x 仍需通过 DB::select() 执行带绑定参数的原始 SQL;Query Builder 拼接易出错,第三方包兼容性有限,推荐拆分为视图或用 Service 封装。

PHP Laravel 中 Eloquent 不支持原生 CTE(WITH 子句)
Eloquent 本身不提供 with() 方法来定义 SQL 的 WITH 公用表表达式——这和 Eloquent 的 with() 关联预加载完全无关,纯属命名冲突带来的误解。Laravel 直到 10.x 仍未在 Query Builder 或 Eloquent 中内置 CTE 支持,所有“Eloquent 使用 CTE”的说法,本质都是绕过 Eloquent、退回到原生查询或 Query Builder 手动拼接 WITH。
必须用 DB::select() 或 DB::statement() 执行带 WITH 的原始 SQL
如果你确实需要递归层级查询(如组织架构树)、多步中间结果复用(如先算分组统计再排名),又不想写纯 PDO,最稳妥的方式是用 DB::select() 配合绑定参数:
- CTE 必须写在语句最前面,且紧邻
SELECT(不能有空行或注释隔开) -
DB::select()返回数组,不是 Eloquent Collection,无法链式调用map()或访问模型属性 - 参数绑定只支持 ? 占位符,不支持 :named 参数;若需动态表名/字段名,必须白名单校验后字符串拼接(严禁用户输入直插)
DB::select("WITH active_users AS (
SELECT id, name FROM users WHERE status = ?
)
SELECT u.name, COUNT(p.id) as post_count
FROM active_users u
LEFT JOIN posts p ON p.user_id = u.id
GROUP BY u.id", ['active']);
Query Builder 拼 CTE 很容易出错,慎用
有人尝试用 DB::table()->fromRaw('WITH ...') 或多次 ->fromSub() 嵌套模拟 CTE,但极易触发以下问题:
- MySQL 8.0+ 和 PostgreSQL 支持 CTE,但 SQLite、SQL Server(旧版)不支持 ——
fromRaw()会直接报错,无降级路径 -
fromSub()生成的子查询会被括号包裹,而 CTE 要求WITH cte AS (subquery)中的 subquery 不能有多余括号,否则语法错误 - 复杂 CTE(尤其递归)依赖
RECURSIVE关键字和UNION ALL结构,Query Builder 无法生成,硬拼易漏关键字或括号错位
真要“类 Eloquent”体验?选包或换思路
第三方包如 staudenmeir/laravel-cte 可让 Query Builder 支持 withExpression(),但它本质仍是封装原始 SQL,且:
立即学习“PHP免费学习笔记(深入)”;
- 不兼容 Laravel 11+ 的部分底层变更(截至 2024 年中)
- 递归 CTE 需手动写
UNION ALL和终止条件,没比手写简洁多少 - 调试时 dump 出来的 SQL 常含冗余括号,和你手写的不一致,反而更难排查
更实际的做法:把 CTE 部分拆成临时视图(MySQL 5.7+)或物化 CTE(PostgreSQL),然后用 Eloquent 查这个视图;或者接受“这部分逻辑不属于模型层”,用 Service 类封装原始查询,返回标准数组或 DTO。
CTE 不是银弹,它解决的是特定 SQL 层面的可读性与复用问题,强行塞进 Eloquent 模型,往往让边界变模糊、测试变困难、迁移变脆弱。真正卡住的通常不是语法,而是没想清楚——这段逻辑,到底该由数据库做,还是该由 PHP 分步做。



















