Laravel Eloquent预加载时如何获取关联模型列的求和值?
如何在Laravel Eloquent预加载时获取关联模型的求和值
嘿,这个思路真的很赞——直接在预加载阶段就计算关联数据的聚合值,确实能大幅减少数据库返回的数据量,比加载所有关联记录要高效太多了!不过你给出的示例写法没法直接生效,因为with()方法里的字段选择语法不支持直接写聚合函数。别担心,我们有两种靠谱的解决方案来实现这个需求:
方法一:通过闭包定制预加载查询
你可以在with()的关联闭包里,用DB::raw()来编写聚合逻辑,同时要记得包含关联外键并分组,这样Eloquent才能正确把聚合结果和对应的Website模型关联起来:
use Illuminate\Support\Facades\DB; $websites = Website::with([ // 处理valid_click_ads的求和 'valid_click_ads' => function ($query) { $query->select( 'website_id', // 必须包含外键,用于关联匹配 DB::raw('COALESCE(SUM(clicks), 0) as total_clicks'), // COALESCE确保无数据时返回0而非null DB::raw('COALESCE(SUM(revenue), 0) as total_revenue') )->groupBy('website_id'); // 按网站分组计算总和 }, // 处理facebook_ads的求和 'facebook_ads' => function ($query) { $query->select( 'website_id', DB::raw('COALESCE(SUM(clicks), 0) as total_clicks'), DB::raw('COALESCE(SUM(impressions), 0) as total_impressions') )->groupBy('website_id'); }, // 处理google_ads的求和 'google_ads' => function ($query) { $query->select( 'website_id', DB::raw('COALESCE(SUM(clicks), 0) as total_clicks'), DB::raw('COALESCE(SUM(impressions), 0) as total_impressions') )->groupBy('website_id'); } ])->get();
使用时,你可以通过关联属性访问聚合值:
foreach ($websites as $website) { echo $website->valid_click_ads->first()->total_clicks; echo $website->facebook_ads->first()->total_impressions; }
方法二:使用withCount()扩展实现聚合
如果你不需要保留关联模型的结构,只是想把聚合值直接作为Website模型的属性,withCount()是更简洁的选择——它不仅能计数,还支持自定义聚合函数:
use Illuminate\Support\Facades\DB; $websites = Website::withCount([ // 给valid_click_ads的clicks求和,命名为valid_clicks_total 'valid_click_ads as valid_clicks_total' => function ($query) { $query->select(DB::raw('COALESCE(SUM(clicks), 0)')); }, // 给valid_click_ads的revenue求和,命名为valid_revenue_total 'valid_click_ads as valid_revenue_total' => function ($query) { $query->select(DB::raw('COALESCE(SUM(revenue), 0)')); }, // facebook_ads的clicks求和 'facebook_ads as fb_clicks_total' => function ($query) { $query->select(DB::raw('COALESCE(SUM(clicks), 0)')); }, // facebook_ads的impressions求和 'facebook_ads as fb_impressions_total' => function ($query) { $query->select(DB::raw('COALESCE(SUM(impressions), 0)')); }, // google_ads的clicks求和 'google_ads as g_clicks_total' => function ($query) { $query->select(DB::raw('COALESCE(SUM(clicks), 0)')); }, // google_ads的impressions求和 'google_ads as g_impressions_total' => function ($query) { $query->select(DB::raw('COALESCE(SUM(impressions), 0)')); } ])->get();
这种方式下,聚合值会直接成为Website实例的属性,使用起来更直观:
foreach ($websites as $website) { echo $website->valid_clicks_total; echo $website->fb_impressions_total; }
注意事项
- 用
COALESCE包裹SUM是为了避免没有关联记录时返回null,确保值始终是数字类型,方便后续页面渲染。 - 如果关联表使用了软删除,记得在闭包里加上
->withTrashed()(如果需要包含已删除记录),否则会自动排除软删除的数据。 - 对于数据量较大的关联表,建议给
website_id和聚合字段(比如clicks、impressions)创建复合索引,提升聚合查询的性能。
内容的提问来源于stack exchange,提问作者LucyTurtle
相关产品推荐
相关产品推荐

