Laravel 5.5 查询构造器不支持任意复杂SQL(如CTE、窗口函数),但可通过DB::raw()、joinSub()和原生SQL组合实现主流复杂查询;关键在于安全嵌入、分页兼容与逻辑可维护,其中joinSub()是关联子查询(如“每个用户最新订单”)的正确写法,需传QueryBuilder实例、显式别名及on()条件闭包。

直接说结论:Laravel 5.5 的查询构造器本身不支持真正“任意复杂”的 SQL(比如嵌套多层子查询、窗口函数、CTE),但可以通过 DB::raw()、joinSub() 和原生 SQL 嵌入组合出绝大多数业务所需的复杂查询;关键不是“能不能写”,而是“怎么嵌得安全、分页不崩、逻辑可维护”。
用 joinSub() 做关联子查询(比如“每个用户的最新订单”)
这是最常被误写的点:很多人直接拼 SQL 字符串进 join(),结果分页报错或字段冲突。正确做法是把子查询构造成独立实例,再用 joinSub() 注入。
-
joinSub()第一个参数必须是查询构造器实例(不能是字符串),否则paginate()会因无法解析 count 语句而失败 - 子查询里必须显式
select()字段,且别名要唯一(比如latest_orders),避免和主表字段重名 - 关联条件里用
on()引用别名字段,如'users.id', '=', 'latest_orders.user_id',不能写成'latest_orders.user_id'直接当字符串传
示例:
php
$latest = DB::table('orders')
->select('user_id', DB::raw('MAX(created_at) as max_created'))
->groupBy('user_id');
$users = DB::table('users')
->joinSub($latest, 'latest_orders', function ($join) {
$join->on('users.id', '=', 'latest_orders.user_id');
})
->select('users.*', 'latest_orders.max_created')
->get();
把原生 SQL 当作临时表用(DB::raw('(...) as alias'))
当连 joinSub() 都不够用(比如要 UNION ALL 或带变量的复杂视图),就得走原生 SQL 临时表路线。但括号和别名是硬性要求,漏一个就 SQLSTATE[42000]。
- 整个原生 SQL 必须用
()包裹,例如(SELECT ... FROM ...) AS tmp,不能省略括号 -
AS alias不可省略,Laravel 分页时会自动加COUNT(*) FROM (your_sql) AS temp_table,没别名就语法错误 - 绑定参数只能在最外层
where()里用,子 SQL 内部若需动态值,必须提前用DB::raw()拼进去(注意 SQL 注入风险)
示例:
php
$sql = "(SELECT u.id, u.name, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id) AS user_orders";
$users = DB::table(DB::raw($sql))
->where('order_count', '>', 0)
->paginate(15);
whereRaw() 和闭包嵌套 where 的边界在哪
whereRaw() 看似万能,但一滥用就失去参数绑定保护;而纯闭包嵌套又容易写成“括号地狱”。实际该选哪个,取决于是否含用户输入。
- 含用户输入(如搜索关键词、时间范围)→ 必须用闭包 + 多个
where(),哪怕多写两行。例如:where('status', 'active')->where(function ($q) use ($min) { $q->where('score', '>=', $min)->orWhere('type', 'vip'); }) - 纯数据库函数或固定逻辑(如
DATE(created_at) = CURDATE())→ 才用whereRaw(),且参数必须用?占位并传数组,不能字符串拼接 - 千万别混用:
whereRaw("price > IF(state = 'TX', ?, 100)", [$price])是 OK 的;whereRaw("state = '$state'")是危险的
分页时 count() 失败的三个典型原因
复杂查询一加 paginate() 就报错,90% 出在这三处:
- 子查询没别名(
DB::table(DB::raw('SELECT ...'))缺少AS xxx) - SELECT 中用了聚合函数但没
GROUP BY,导致外层 count() 无法执行 - 用了
UNION或ORDER BY在子查询里——Laravel 默认 count SQL 会剥离这些,但某些 MySQL 版本会直接报错
临时绕过法:simplePaginate() 不依赖 count,但无法显示总页数;长期解法是把复杂逻辑拆到视图或存储过程里,让 PHP 层只做简单查询。


















