Laravel查询构建器实现一对多关联单条附件关联问题
问题描述
我有一个products表,通过model_has_attachments表与attachments表关联。需要通过查询构建器为每个产品记录关联其第一条附件,但当前查询仅返回少量带有model_has_attachments ID的记录,其余均为null。
原查询代码:
$products = DB::table('products')-> leftJoin(DB::raw('(select `model_id`, `attachment_id` from model_has_attachments where model_has_attachments.model_id = id) as model_has_attachments'), 'model_has_attachments.model_id', 'products.id')-> leftJoin('attachments', 'model_has_attachments.attachment_id', '=', 'attachments.id')-> select('products.id', 'products.square', 'products.height', 'products.address', 'products.rooms', 'products.title', 'products.description', 'model_has_attachments.model_id as id_model', 'model_has_attachments.attachment_id')-> where([ ['products.deleted_at', '=', null], ]);
尝试在DB::raw中添加limit 1,仅返回products表的第一条记录,而非关联后的结果。另外尝试直接关联model_has_attachments表的方式,会获取所有附件记录,导致产品有多个附件时出现重复的产品记录,末尾加->limit(1)也无效:
leftJoin('model_has_attachments', function ($join) { $join->on('products.id', '=', 'model_has_attachments.model_id')->where('model_has_attachments.model_type', '=', Product::class); })->
问题分析与解决方案
问题根源
- 子查询条件错误:子查询里的
where model_has_attachments.model_id = id未指定表前缀,数据库会将id解析为model_has_attachments表的id字段,而非外层products表的id,导致仅能匹配到model_has_attachments.id = model_has_attachments.model_id的极少记录。 - 全局limit错误:直接在子查询加
limit 1会让子查询仅返回单条记录,关联后只有该记录对应的产品能匹配,其他产品无法关联附件。 - 直接关联导致重复:未筛选单条附件就关联
model_has_attachments表,每个附件都会生成一条产品记录,造成产品重复。
正确实现方式
方式一:查询构建器子查询筛选单条附件
先通过子查询筛选出每个产品对应的第一条附件,再关联主查询:
$products = DB::table('products') ->leftJoin( DB::raw('( SELECT model_id, attachment_id FROM model_has_attachments WHERE model_type = ? ORDER BY id ASC -- 按附件ID排序取第一条,可替换为created_at等字段 GROUP BY model_id ) as first_attachments'), 'first_attachments.model_id', '=', 'products.id' ) ->leftJoin('attachments', 'first_attachments.attachment_id', '=', 'attachments.id') ->select( 'products.id', 'products.square', 'products.height', 'products.address', 'products.rooms', 'products.title', 'products.description', 'first_attachments.model_id as id_model', 'attachments.id as attachment_id', 'attachments.file_name' // 按需选择附件字段 ) ->whereNull('products.deleted_at') ->setBindings([Product::class]) ->get();
方式二:Laravel模型关联查询(更简洁)
先在Product模型中定义多态关联:
// app/Models/Product.php public function attachments() { return $this->morphToMany(Attachment::class, 'model', 'model_has_attachments'); }
查询时通过关联闭包限制只取第一条附件:
$products = Product::with(['attachments' => function ($query) { $query->take(1); }]) ->whereNull('deleted_at') ->get();
内容的提问来源于stack exchange,提问作者RedEclipse
相关产品推荐
相关产品推荐

