
本文详解如何将含 case when 和 distinct 的原始 sql 查询安全、高效地转换为 laravel query builder 语法,避免嵌套子查询误用与 sql 注入风险,并提供可直接运行的优化代码示例。
本文详解如何将含 case when 和 distinct 的原始 sql 查询安全、高效地转换为 laravel query builder 语法,避免嵌套子查询误用与 sql 注入风险,并提供可直接运行的优化代码示例。
在 Laravel 开发中,常需从消息表(如 messages)中提取当前用户的所有对话联系人——即所有与该用户有过收发消息交互的其他用户 ID。原始 SQL 使用 CASE WHEN 动态判断 message_from 或 message_to 中的“对方”身份,并配合 DISTINCT 去重和 WHERE ... IN (...) 筛选,看似简洁,但直接迁移到 Laravel Query Builder 时极易出错。
常见错误是将整个子查询(如 SELECT DISTINCT CASE...)强行塞入 selectRaw(),导致 Laravel 在其外层自动补全 FROM messages 和 ORDER BY,最终生成语法非法的嵌套结构(例如 SELECT (SELECT ... FROM ...) FROM messages),引发 MariaDB/MySQL 的 SQLSTATE[42000] 语法错误。
✅ 正确做法是:将 CASE WHEN 保留在 SELECT 投影层,而把过滤逻辑(WHERE 条件)和去重、排序交由 Query Builder 原生方法处理。以下是推荐写法:
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Auth;
$user_id = Auth::user()->user_id;
$contacts = DB::table('messages')
->selectRaw("CASE WHEN message_from = ? THEN message_to ELSE message_from END as contact", [$user_id])
->where('message_from', $user_id)
->orWhere('message_to', $user_id)
->distinct()
->latest('id') // 显式指定按 messages.id 降序(非默认 created_at)
->get(['contact']);? 关键要点说明:
- 参数化绑定替代字符串拼接:使用 ? 占位符 + 数组参数([$user_id])传递 $user_id,彻底规避 SQL 注入风险。切勿在 selectRaw() 中直接拼接变量(如 "message_from = '$user_id'"),即使 $user_id 来自认证用户,也应遵循最小信任原则。
- 拆分 WHERE 条件:原始 SQL 的 WHERE '$user_id' IN (message_from, message_to) 在 Query Builder 中应转为链式 ->where(...)->orWhere(...)。注意 orWhere 会改变逻辑优先级,此处因仅两个条件且为 OR 关系,可直接使用;若后续扩展更复杂条件,建议包裹 where(function ($q) { ... }) 以明确作用域。
- distinct() 必须置于 where 之后、get() 之前:Query Builder 的 distinct() 作用于最终结果集,位置错误将导致无效或报错。
- latest() 需指定字段:原始 SQL 使用 ORDER BY id DESC,因此应调用 ->latest('id') 而非无参 ->latest()(后者默认按 created_at 排序,可能不符合业务预期)。
- 显式选择字段:末尾的 ->get(['contact']) 限定只返回 contact 列,提升可读性与性能,避免冗余数据。
? 进阶提示:若需关联用户信息(如昵称、头像),可在上述查询基础上使用 join() 或通过 Eloquent 关系预加载;若数据量大,建议为 message_from 和 message_to 字段添加联合索引(如 INDEX idx_user_pair (message_from, message_to))以加速 WHERE 查找。
综上,理解 Query Builder 的执行流程(SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY → LIMIT)并合理分配各子句职责,是写出健壮、安全、可维护 Laravel 查询的关键。










