Laravel Eloquent 获取关联数量

74

我使用的是Laravel 5.3。

我有2个表:

Articles
---------
id
cat_id
title

Category
---------
id
parent_id
title

我已经在我的模型中定义了关系:

// Article model
public function category()
{
    return $this->belongsTo(Category::class);
}

// Category model
public function children() 
{
    return $this->hasMany(Category::class, 'parent_id', 'id');
}   

使用Eloquent有没有一种简单的方法可以列出一个包含文章计数的类别列表。困难在于我想将id_parent = 0的类别分组,即我只想显示带有子文章计数的父类别。

我尝试了这样的代码:

    $category = new \App\Models\Category();
    $categoryTable = $category->getTable();

    return $category->leftJoin('article', 'article.cat_id', '=', 'category.id')
        ->whereIn('article.cat_id', function($query)
            {
                $query->select('cat_id')
                    ->from('categories')
                    ->where('categories.parent_id', ???)
                    ->orWhere($this->tableName .'.cat_id', $id);
            })
        ->groupBy('cat_id');

但是我迷失了...


category中会有多少层次的层级? - Dev
Only 2 max, not more - Vincent Decaux
7个回答

120

啊,太好了!它可以工作,但我这里有两个关系。它可以处理文章->类别的关系,但我还需要包括子级。 - Vincent Decaux
3
是的!谢谢!之后我们可以在实体属性中访问这些值,例如:$category->articles_count - Artur Mamedov
1
withCount 几乎每次都是 n+1 - 对于小型的 10 行数据库来说还好,但如果将行数增加到 1000,它会突然执行 1000 次查询以计算每个项目的数量。 - Erik Thiart

40

在您的Category模型中定义一个名为articles()的关系:

public function articles() 
{
    return $this->hasMany(Article::class, 'cat_id');
}

那么你可以尝试如下:

Category::where('parent_id', 0)->withCount('articles')->get();

但是如何获取具有子类别的父类别中文章的计数? - Vincent Decaux
是的,但我还需要子类别的数量。我的意思是,如果一篇文章属于儿童类别,我需要计算它。 - Vincent Decaux

21
您可以使用hasManyThrough() Eloquent方法获取所有子文章,然后在一个不错的getter中将文章计数相加。我将getter添加到模型上的$appends数组中,以帮助说明它在Tinker输出中的效果。
class Category extends Model
{

    protected $appends = [
        'articleCount'
    ];

    public function articles()
    {
        return $this->hasMany(Article::class);
    }

    public function children()
    {
        return $this->hasMany(Category::class, 'parent_id');
    }

    public function childrenArticles()
    {
        return $this->hasManyThrough(Article::class, Category::class, 'parent_id');
    }

    public function getArticleCountAttribute()
    {
        return $this->articles()->count() + $this->childrenArticles()->count();
    }
}

这是 Tinker 输出的结果:

Psy Shell v0.8.0 (PHP 7.0.6 — cli) by Justin Hileman
>>> $cat = App\Category::first();
=> App\Category {#677
     id: "1",
     name: "Cooking",
     parent_id: null,
     created_at: "2016-12-15 18:31:57",
     updated_at: "2016-12-15 18:31:57",
   }
>>> $cat->toArray();
=> [
     "id" => 1,
     "name" => "Cooking",
     "parent_id" => null,
     "created_at" => "2016-12-15 18:31:57",
     "updated_at" => "2016-12-15 18:31:57",
     "articleCount" => 79,
   ]
>>> 

如果您想将类别查询限制为具有文章子项的子项,则可以使用has()方法:

Category::has('children.articles')->get();

关于 has() 方法的更多信息:

https://laravel.com/docs/5.3/eloquent-relationships#querying-relationship-existence

以及 hasManyThrough() 方法:

https://laravel.com/docs/5.3/eloquent-relationships#has-many-through


2
关于Carlos_E.的回答,你可以通过使用whereHas来改进查询,而不是使用whereIn:
$agents = Agents::whereHas('schedule')
)->with('schedules')->get();

1
  public function NoOfStudent()
    {
        return $this->hasMany(UserAssignment::class,'assignment_id','id');
    }



$assignment = Assignment::select('id','batch_id','title','description','attachment','last_submission_date',DB::raw('(CASE WHEN type = 9 THEN "Quiz Type"  ELSE "Descriptive" END) AS assignment_type'),DB::raw('(CASE WHEN status = 1 THEN "Assigned"  ELSE "Not Assigned" END) AS status'))
                      ->with('assignmentBatch:id,batch_number')
                      ->where('assignments.instructor_id',auth('api')->user()->id)
                      ->orderBy('created_at','DESC');
        if(!$request->user_id){
            $assignment =$assignment->withCount('NoOfStudent');
        }

1
这应该可以工作:

$category
->where('categories.parent_id', 0)
->leftJoin('article', 'article.cat_id', '=', 'categories.id')
->select('categories.id', \DB::raw('COUNT(article.id)'))
->groupBy('categories.id')
->get();

上述查询将获取属于该类别的所有文章的类别ID和计数。

再次阅读您的问题和评论后,如果我理解正确,您想获取属于那些类别(具有parent_id = 0)的所有文章的计数+属于子类别(具有parent_id =(某个类别的id))的文章计数。

现在我无法轻松地测试这一点,但我认为以下内容应该可以胜任。

$category
->where('categories.parent_id', 0)
->leftJoin('article', 'article.cat_id', '=', 'categories.id')
->leftJoin('categories as c2', 'c2.parent_id', '=', 'categories.id')
->leftJoin('article as a2', 'a2.cat_id', '=', 'c2.id')
->select('categories.id', \DB::raw('(COUNT(article.id)) + (COUNT(a2.id)) as count'))
->groupBy('categories.id')
->get();

说到这一点,我认为最好在类别中有一个名为“count”的列,并在每次添加新文章时更新它。这样可以提高性能。

0

我相信有些人仍在经历这个问题,我能够用以下方法解决它,假设我有一个Agent模型和一个Schedule模型,即一个代理可以有多个日程:

class Schedule extends Model {
  public function agent() {
    return $this->belongsTo(Agent::class, 'agent_id');
  }
}

class Agent extends Model {
  public function user(){
    return $this->belongsTo(User::class);
  }

  public function schedules(){
    return $this->hasMany(Schedule::class);
  }
}

一些代理可能没有被分配时间表,因此在调用with()方法之前,我会对它们进行筛选,像这样:

$agents = Agents::whereIn(
    'id', 
    Schedule::distinct()->pluck('agent_id')
)->with('schedules')->get();

希望这能有所帮助!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接