Laravel查询构造器不是ORM,它生成原生SQL并绑定参数;查不到数据常因未调用get()或first()等终止方法;子查询须用fromSub()或joinSub(),禁用DB::raw()直接传字符串;条件拼接应使用when()/unless(),whereRaw()等需用?占位符防注入。

DB::table() 不是 ORM,它生成原生 SQL 并绑定参数,查不到数据时大概率是因为忘了调用 get()、first() 或其他终止方法——查询根本没执行。
子查询嵌套必须用 fromSub() 或 joinSub(),别手写 DB::raw('(SELECT ...)')
直接传字符串给 DB::table() 做子查询(比如 DB::table(DB::raw('(SELECT ...) as t')))在 Laravel 10+ 会报错或行为异常,因为无法自动处理参数绑定和表别名解析。
正确做法是把子查询构造器实例传进去:
-
fromSub()用于 FROM 子句中的派生表,第二个参数是别名 -
joinSub()用于 JOIN 子句,第三个参数是 on 条件闭包 - 子查询内部不能用外部查询的字段名,需显式 alias 或用
whereColumn()对齐列名
例如获取每个用户的最新订单:
$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', function ($join) {
$join->on('users.id', '=', 'latest.user_id');
})
->get();
when() 和 unless() 是条件拼接的唯一安全方式,别再写 if
手动用 if 判断后追加 where() 容易漏掉变量作用域、重复构建实例,还可能因闭包捕获问题导致条件错位。
when() 只在第一个参数为 truthy 时执行回调,unless() 是反向逻辑;两者都返回 builder 实例,天然支持链式:
- 回调函数接收当前 builder 实例,可安全调用任意查询方法
- 第三个参数(
when($cond, $then, $else))是“否则”分支,适合默认筛选 - 注意:不要在
when()回调里 return 新的 builder,而是 return $query(即原实例)
示例:
$query = DB::table('posts');
$query->when(request('q'), function ($q, $v) {
return $q->where('title', 'like', "%{$v}%")
->orWhere('content', 'like', "%{$v}%");
});
$query->when(request('status'), function ($q, $s) {
return $q->where('status', $s);
})->get(); // 这里才真正执行
selectRaw / whereRaw 必须配 ? 占位符,别拼字符串
selectRaw('DATE(created_at)') 没问题,但一旦带用户输入,就必须用占位符,否则绕过绑定机制,等于裸奔:
-
whereRaw('name LIKE ?', ["%{$request->q}%"])✅ 安全 -
whereRaw("name LIKE '%{$request->q}%'")❌ SQL 注入高危 -
havingRaw()同理,聚合后的过滤也要绑定,尤其涉及AVG()、SUM()等计算字段 - MySQL 的
DATE()、PostgreSQL 的EXTRACT()等函数可放心用,但时间范围值仍要绑定
按月统计并筛选销售额超 1000 的记录:
DB::table('orders')
->selectRaw('YEAR(created_at) as year, MONTH(created_at) as month, SUM(amount) as total')
->groupBy('year', 'month')
->havingRaw('SUM(amount) > ?', [1000])
->get();
where() 参数顺序容易混淆,优先用三参数形式
where('price', '>', 100) 看似简单,但 where('status', 'active') 实际是两参数等值匹配,而 where('score', null) 会生成 = NULL(永远 false),得用 whereNull('score')。
为避免歧义,建议统一用三参数形式明确操作符:
-
where('score', '>=', 80)—— 显式声明操作符 -
where('deleted_at', '=', null)仍错,应改用whereNull('deleted_at') -
whereIn('id', $ids)数组超 1000 项时 MySQL 会报 placeholder 错误,需分批或改用临时表 - 复杂 AND/OR 混合逻辑,必须用闭包包裹,如
where(function ($q) { $q->where(...)->orWhere(...); })
最常被忽略的一点:所有 where* 方法默认是 AND 连接,orWhere() 会破坏括号层级,除非你真需要全局 OR。











