查询“有 userattr 关联且 job 为 null 的用户”须用 wherenull('job');查“无关联记录”用 wheredoesnthave();空值统计筛选应配合 withcount() 和 having();null 与空字符串混合判断需用 whereraw("trim(col) = ''") 或 wherenull()。

whereHas() 里不能直接查 NULL 字段
想查“有 userAttr 关联,且 job 字段为 NULL 的用户”,别在 whereHas() 闭包里写 where('job', null)——这会生成 WHERE job = NULL,永远不成立。MySQL 中判断 NULL 必须用 IS NULL。
正确做法是显式调用 whereNull('job'):
User::whereHas('userAttr', function ($query) {
$query->whereNull('job');
})->get();
-
whereHas()内部的$query操作的是关联表(如user_attrs),所以字段名直接写数据库列名,不用加表前缀 - 如果同时要查
job IS NULL或job = '',得用闭包组合:where(function ($q) { $q->whereNull('job')->orWhere('job', ''); }) - 注意:软删除模型下,
whereHas()默认不包含已软删除的关联记录;若需包含,得在关联定义里加withTrashed()
查“没有关联记录”的场景该用 whereDoesntHave()
业务中常遇到“找出所有没填联系方式的用户”,即主模型存在、但关联模型完全不存在。这时候不能用 whereNull()(它查的是关联存在但字段为 NULL),而要用 whereDoesntHave()。
例如:
User::whereDoesntHave('contact')->get();
-
whereDoesntHave('contact')翻译为 SQL 是NOT EXISTS (SELECT * FROM contacts WHERE users.id = contacts.user_id) - 它和
whereDoesntHave('contact', fn($q) => $q->where('type', 'phone'))不同:后者是“存在 contact 但 type 不是 phone”,语义更细,性能也更差 - 如果关联表数据量大,
whereDoesntHave()可能触发全表扫描;建议给外键字段(如user_id)加索引
统计型空值判断:用 withCount() + whereXxx() 组合
要查“发过 0 条公告的申请单”,别先 withCount('notices') 再 PHP 层过滤——那会把全部数据拉到内存。应该让数据库直接筛。
正确姿势是结合 withCount() 和 whereHas()/whereDoesntHave():
Apply::whereDoesntHave('notices')->get();
// 或带计数字段(用于后续展示)
Apply::withCount('notices')
->having('notices_count', '=', 0)
->get();
-
withCount()本身不筛选,只是加一个xxx_count字段;真要按数量筛选,得用having()(不是where()) -
having()必须配合groupBy()才生效,而withCount()自动帮你加了,所以可直接用 - 如果关联是多对多(比如
users和roles),whereDoesntHave('roles')查的是“完全没分配角色”,而whereDoesntHave('roles', fn($q) => $q->where('name', 'admin'))查的是“没分配 admin 角色”
空字符串和 NULL 混查时,小心字段类型和前后空格
用户输入可能带空格,数据库存的是 '',而业务上认为 ' '、''、NULL 都算“未填写”。这种混合判断不能靠 Eloquent 自动处理。
推荐在数据库层统一 trim 后比较:
User::where(function ($q) {
$q->whereNull('nickname')
->orWhereRaw("TRIM(nickname) = ''");
})->get();
-
whereRaw()是唯一能安全嵌入函数(如TRIM()、LENGTH())的方式;别手拼 SQL 字符串 - MySQL 中
TEXT类型字段用TRIM()没问题,但JSON字段得用JSON_LENGTH()判断是否为空对象 - PostgreSQL 用户注意:
TRIM()行为一致,但IS NULL查询在部分索引下才高效,建索引时记得加WHERE column IS NULL
实际项目里最容易漏掉的,是关联表字段允许 NULL 但主表外键没设索引——这时 whereDoesntHave() 或 whereHas() 一跑就慢,而且开发环境数据少看不出来。上线前务必用 EXPLAIN 看执行计划。











