Laravel:如何平均嵌套 hasMany 关系(hasManyThrough)
Posted
技术标签:
【中文标题】Laravel:如何平均嵌套 hasMany 关系(hasManyThrough)【英文标题】:Laravel: how to get average on nested hasMany relationships (hasManyThrough) 【发布时间】:2015-02-26 05:15:27 【问题描述】:我有三张桌子:
products: id|name|description|slug|category_id|...
reviews: id|product_id|review_text|name|email|...
review_rows id|review_id|criteria|rating
评论表存储评论文本,评论作者,并有一个外部product_id键。 review_rows 表存储不同标准的评分,例如:
----------------------------------------
| id | criteria | rating | review_id |
----------------------------------------
| 1 | price | 9 | 12 |
----------------------------------------
| 2 | service | 8 | 12 |
----------------------------------------
| 3 | price | 6 | 54 |
----------------------------------------
| 4 | service | 10 | 54 |
----------------------------------------
评论行通过 review_id 外键链接到评论表。我已经建立了这样的模型关系:
Product -> hasMany -> Review
Review -> belongsTo -> Product
Review -> hasMany -> ReviewRow
ReviewRow -> belongsTo -> Review
现在我想在我的类别和产品页面上显示产品的平均评分。我怎样才能做到这一点?
我需要对每条评论的所有 reviewRows 求和和平均,然后对每条评论的所有这些求和并求平均,最终得出该产品的总体评分。这是否可以通过 Eloquent 实现,还是我需要不同的解决方案或不同的数据库设计/结构?
提前致谢!
【问题讨论】:
【参考方案1】:您需要类似 http://softonsofa.com/tweaking-eloquent-relations-how-to-get-hasmany-relation-count-efficiently/ 的东西,只需稍作调整即可满足您的需求:
public function reviewRows()
return $this->hasManyThrough('ReviewRow', 'Review');
public function avgRating()
return $this->reviewRows()
->selectRaw('avg(rating) as aggregate, product_id')
->groupBy('product_id');
public function getAvgRatingAttribute()
if ( ! array_key_exists('avgRating', $this->relations))
$this->load('avgRating');
$relation = $this->getRelation('avgRating')->first();
return ($relation) ? $relation->aggregate : null;
那么就这么简单:
// eager loading
$products = Product::with('avgRating')->get();
$products->first()->avgRating; // '82.200' | null
// lazy loading via dynamic property
$product = Product::first()
$product->avgRating; // '82.200' | null
【讨论】:
哇,非常巧妙的解决方案!这非常符合 Laravel 的语法和意识形态!我有一个问题。如果我运行查询,它会返回评分以及第一个 review_row 上的所有其他信息,但我只想返回 avg(rating) 值。 它只返回aggregate
和 product_id
- 我刚刚编辑了答案,之前我忘记了后者。无论如何,它不应该包括除了两者之外的任何东西。【参考方案2】:
也许你可以尝试 Eloquent 关系和 php 函数 array_reduce 的一点帮助
//model/Reviews.php
public function sum()
return array_reduce($this->hasMany('ReviewRows')->lists('rating'), "sumItems");
public function sumItems ($carry, $item)
$carry += $item;
return $carry;
或者使用 Eloquent RAW 查询,例如:
//model/Reviews.php
public function avg()
$result = $this->hasMany('ReviewRows')
->select(DB::raw('avg(rating) average'))
->first();
return $result->average;
【讨论】:
【参考方案3】:见https://github.com/faustbrian/laravel-commentable
public function comments(): MorphMany
return $this->morphMany($this->commentableModel(), 'commentable');
public function avgRating()
return $this->comments()->avg("rating");
$products = \App\Models\Products::with(
[
"comments" => function ($q)
$q->with(["children" => function ($qch)
$qch->take(2);
])->withCount("children")->where("parent_id", '=', null);
,]
)->take(5)->get();
foreach ($products as &$product)
$product["avgRating"] = $product->avgRating();
dd($products);
【讨论】:
【参考方案4】:使用 laravel 官方文档 here 中提到的 withAvg()
【讨论】:
【参考方案5】:简单易行的解决方案。将此添加到产品模型中
protected $appends = ["avg_rating"];
public function reviewRows()
return $this->hasManyThrough('App\ReviewRow','App\Review','product_id','review_id');
public function getAvgRatingAttribute()
return round($this->reviewRows->average('rating'),2);
【讨论】:
您好,感谢您的回答。对你们每个人都非常有帮助,可以附加一个关于你的代码如何工作的解释!以上是关于Laravel:如何平均嵌套 hasMany 关系(hasManyThrough)的主要内容,如果未能解决你的问题,请参考以下文章
Laravel Eloquent - 使用过滤数据获取嵌套关系