Laravel嵌套查询去重需将distinct()置于子查询内部并显式select字段,再通过from()和mergeBindings()安全执行;关联查询用join+distinct+exists替代whereHas;pluck前必须调用distinct()确保ID唯一。

要在Laravel中对嵌套查询结果做去重,不能直接在子查询外套一层distinct()——那样会因语法嵌套错误导致SQL解析失败,尤其在含CASE WHEN或JOIN的复杂场景下必报MariaDB/MySQL 42000错误。
用Query Builder写安全的嵌套去重
方法一:把distinct()放在子查询内部,且必须显式select字段
先构造子查询对象,再在其上调用distinct()和select(),最后作为from子句使用:
第一步:【必须先定义子查询变量,不能链式直接from(DB::table(...)->distinct())】
$sub = DB::table('messages')->selectRaw("CASE WHEN message_from = ? THEN message_to ELSE message_from END as contact", [auth()->id()])->where(function ($q) { $q->where('message_from', auth()->id())->orWhere('message_to', auth()->id()); });
第二步:将子查询传入from(),并再次select该字段
$contacts = DB::table(DB::raw("({$sub->toSql()}) as sub"))->mergeBindings($sub)->select('contact')->distinct()->get();
这一步关键在于mergeBindings()——它把子查询中的?占位符绑定值同步到外层,否则参数丢失,查不到数据。
关联模型中避免N+1的同时去重
当需要从User出发,查出所有发过消息的Contact用户(去重),但又不想加载全部Message记录时:
用join + distinct + exists替代whereHas:
$users = User::join('messages as m1', function ($join) { $join->on('m1.message_from', '=', 'users.id')->where('m1.message_to', auth()->id()); })->orWhere(function ($q) { $q->join('messages as m2', function ($join) { $join->on('m2.message_to', '=', 'users.id')->where('m2.message_from', auth()->id()); }); })->select('users.*')->distinct()->get();
注意:orWhere()必须包在闭包里,否则SQL逻辑错乱;distinct()必须在select()之后、get()之前调用。
用Eloquent子查询+distinct获取唯一ID列表
方法一:pluck前必须distinct,否则返回带重复ID的集合
$uniqueIds = Message::selectRaw("CASE WHEN message_from = ? THEN message_to ELSE message_from END", [auth()->id()])->where('message_from', auth()->id())->orWhere('message_to', auth()->id())->distinct()->pluck(DB::raw("CASE WHEN message_from = ? THEN message_to ELSE message_from END"), [auth()->id(), auth()->id()]);
方法二:更稳妥——先distinct再pluck指定别名字段
$uniqueIds = Message::selectRaw("CASE WHEN message_from = ? THEN message_to ELSE message_from END as contact", [auth()->id()])->where(function ($q) { $q->where('message_from', auth()->id())->orWhere('message_to', auth()->id()); })->distinct()->pluck('contact');
这一步操作起来很简单,直接调用pluck('contact')就能拿到去重后的ID数组,无需toArray()二次处理。











