wheredoesntexist() 是查“无关联订单用户”的唯一可靠方式,它生成 not exists 子查询,语义严谨、可下推执行;而 wheredoesnthave() 用 left join + is null,多条件时易漏过滤,且不支持跨库自动切换。

whereDoesntExist() 是唯一可靠的方式
想查“没有关联订单的用户”,不能用 whereDoesntHave() 替代,它底层走 LEFT JOIN + IS NULL,语义和性能都不同。尤其当关联条件含多个字段(比如 published = true AND status = 'active')时,whereDoesntHave() 可能漏过滤,而 whereDoesntExist() 生成标准 NOT EXISTS 子查询,逻辑严谨、可下推到数据库执行。
必须满足两个硬性条件:
- 子查询里要显式调用
select(\DB::raw(1)),不写会报QueryException: SQLSTATE[HY000]: General error - 关联字段要用
whereColumn(),例如whereColumn('posts.user_id', 'users.id');写成where('posts.user_id', 'users.id')就变成字符串匹配,永远查不到
低版本 Laravel(
Laravel 5.7 及更早版本不支持 whereDoesntExist(),必须用 whereRaw() 拼 NOT EXISTS。但这里极易出错:
- 参数必须通过第二个数组传入,不能直接拼进字符串,否则有 SQL 注入风险:
whereRaw('NOT EXISTS (SELECT 1 FROM posts WHERE posts.user_id = users.id AND posts.published = ?)', [true]) - 外层主表别名要和子查询里一致,比如主查询是
User::,默认表别名是users,子查询里就得写users.id,不能写成user.id或模型映射的表名 -
withTrashed()、作用域等 Eloquent 特性不会被带入whereRaw,需要手动补条件
跨库关联时 whereDoesntExist() 会失效
如果 User 在 mysql 连接,Post 在 mysql2 连接,whereDoesntExist() 的子查询仍会默认走主模型连接,导致“表不存在”错误——它不识别关联模型的 $connection 属性。
此时必须放弃 whereDoesntExist(),改用两步法:
- 先查出所有关联记录的主键(例如
Post::where('published', true)->pluck('user_id')) - 再用
whereNotIn('id', [...])筛主模型 - 注意:若关联数据量大,
pluck()可能爆内存,需分批或改用原生查询 +NOT IN (SELECT ...)手动指定库前缀
不要混淆 exists() 和 withExists() 的行为
exists() 返回布尔值,withExists() 是给主模型加一个 {relation}_exists 字段,两者目的完全不同。误用 withExists() 做“不存在”筛选,只会多查一列,无法过滤结果。
更隐蔽的坑是:withExists() 自动处理空关系,但它的字段值是 null 还是 false 取决于关联是否定义、是否为空集合,不是纯粹的存在性判断。真要筛“没有关联”的记录,还是得回到 whereDoesntExist() 或两步法。
最常被忽略的是子查询里表名与连接的耦合——哪怕你给 Post 模型设了 $connection = 'mysql2',whereDoesntExist() 也不会自动切库,它只认当前构建器的连接上下文。











