Laravel:如何获得嵌套的hasMany关系的平均值(hasManyThrough)

时间:2014-12-30 02:01:17

标签: php mysql laravel eloquent relationship

我有三张桌子:

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

现在,我想在我的类别和产品页面上显示产品的平均评分。我怎样才能做到这一点?

我需要对每次审核中的所有评论进行求和并对其进行平均,然后对每个评论的所有评分进行求和并平均,最后得出该产品的总体评分。这是通过Eloquent实现的,还是需要不同的解决方案或不同的数据库设计/结构?

提前致谢!

3 个答案:

答案 0 :(得分:13)

您需要稍微调整一下 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

答案 1 :(得分: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;
}

答案 2 :(得分:0)

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);