【问题标题】:Laravel: how to get average on nested hasMany relationships (hasManyThrough)Laravel:如何平均嵌套 hasMany 关系(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 实现,还是我需要不同的解决方案或不同的数据库设计/结构?

提前致谢!

【问题讨论】:

    标签: php mysql laravel eloquent relationship


    【解决方案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) 值。
    • 它只返回 aggregateproduct_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);
          }
          

          【讨论】:

          • 您好,感谢您的回答。对你们每个人都非常有帮助,可以附加一个关于你的代码如何工作的解释!
          猜你喜欢
          • 1970-01-01
          • 2018-10-18
          • 2023-03-09
          • 2018-07-24
          • 2020-10-14
          • 1970-01-01
          • 1970-01-01
          • 2021-12-27
          • 2018-03-08
          相关资源
          最近更新 更多