如何筛选出最新关联消息未读的Offer记录?
解决Laravel中筛选「最新消息未读」的Offer记录问题
你的问题核心在于:whereHas('latestMessage')只会检查关联的messages中是否存在未读消息,而非验证最新的那条消息是否未读。以下是几种可行的解决方案:
方法1:使用whereExists子查询精准匹配
直接通过子查询校验每个Offer对应的最新消息是否未读,逻辑清晰且性能较好:
Offer::whereExists(function ($query) { $query->select(DB::raw(1)) ->from('messages') ->whereColumn('messages.seller_id', 'offers.seller_id') ->whereNull('messages.read') ->whereRaw('messages.created_at = (SELECT MAX(created_at) FROM messages WHERE seller_id = offers.seller_id)'); })->paginate(100);
逻辑说明:对每个Offer,检查是否存在一条属于它的消息——这条消息既未读,同时又是该Offer下创建时间最晚的记录。
方法2:基于现有关联调整whereHas查询
如果你想保留whereHas的写法,可以在闭包内添加子查询,强制限定仅校验最新的那条消息:
Offer::whereHas('latestMessage', function ($query) { $query->whereNull('read') ->whereRaw('created_at = (SELECT MAX(created_at) FROM messages WHERE seller_id = offers.seller_id)'); })->paginate(100);
注:原
latestMessage关联的latest()仅在获取单条关联数据时生效,whereHas默认会遍历所有关联记录,因此必须通过子查询锁定最新的那条。
方法3:用JOIN关联最新消息记录
通过两次JOIN先定位每个Offer的最新消息,再筛选未读状态:
Offer::join(DB::raw('(SELECT seller_id, MAX(created_at) as max_created_at FROM messages GROUP BY seller_id) as latest_messages'), function ($join) { $join->on('offers.seller_id', '=', 'latest_messages.seller_id'); })->join('messages', function ($join) { $join->on('messages.seller_id', '=', 'offers.seller_id') ->on('messages.created_at', '=', 'latest_messages.max_created_at'); })->whereNull('messages.read') ->select('offers.*') ->distinct() ->paginate(100);
逻辑说明:先通过子查询获取每个seller_id对应的最新消息创建时间,再关联到messages表找到具体的最新消息,最后筛选出未读的Offer(用distinct避免重复)。
原写法失效原因
你之前的latestMessage关联用hasOne(...) ->latest(),仅在获取单个Offer的关联数据时会返回最新消息;但whereHas的逻辑是检查「该Offer是否存在任意符合条件的关联消息」,不会自动沿用关联定义中的latest()限制,因此会误判为只要有未读消息就返回。
内容的提问来源于stack exchange,提问作者Axel
相关产品推荐
相关产品推荐

