75

I use Laravel 5.3.

I have 2 tables :

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

And

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

I have defined my relations in my models :

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

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

Is there an easy way using Eloquent to have a list a categories with count of articles. The difficulty is that I want to group categories where id_parent = 0, i.e. I want to display only parent categories with count of articles in children.

I tried something like that :

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

But I am lost...

2
  • How many levels of hierarchy will be there in the table category? Commented Dec 15, 2016 at 13:51
  • Only 2 max, not more Commented Dec 15, 2016 at 13:51

7 Answers 7

125

you can use withCount(). It is available from 5.3 version

for more info about eloquent visit : https://laravel.com/docs/5.3/eloquent-relationships

Sign up to request clarification or add additional context in comments.

3 Comments

Ah nice ! it works but I have 2 relations here. It works with the relation article -> category, but I need to include the children too.
Yeah! Thanks! After we can access this values in the attribute of Entity ex: $category->articles_count
withCount is n+1 pretty much everytime I try it - fine for a small 10 row DB but just bump your rows to 1000 and suddently it does 1000 queries to count each item
40

Define a articles() relation in your Category model as:

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

Then you can try it as:

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

2 Comments

But how to have the count of articles in category parent with children ?
Yes, but I will need the count of children's categories too. I mean if an article belongs to a children category, I need to count it.
21

You can use the hasManyThrough() Eloquent method to fetch all of the childrens' Articles, then add the article counts together in a nice little getter. I added the getter to the $appends array on the model to help illustrate it in the Tinker output.

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

Here's the Tinker output:

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,
   ]
>>> 

If you want to restrict your Category query to ones that have children that have articles, you could do that using the has() method:

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

Here's more on the has() method:

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

And the hasManyThrough() method:

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

Comments

2
  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');
        }

Comments

2

In regards to Carlos_E.'s answer:

You can improve the query by using whereHas instead of whereIn:

$agents = Agents::whereHas('schedule')
  ->with('schedules')
  ->get();

Comments

1

This should work:

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

The above query will get you category IDs and count of all articles that belong to the category.

After reading your question and comments again, if I understand correctly you want to get the count of all articles that belong to those categories (with parent_id = 0) + the count of articles that belong to sub categories (those with parent_id = (id of some category)).

Now I have no way of testing this easily, but I think something along these lines should work for that.

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

That beign said, I think you're better of having a column named count in categories and update it each time a new article gets added. For performance.

Comments

0

I am sure somebody is still going through this, I was able to solve it the following way, suppose I have an Agent model and a Schedule model, i.e. one agent may have many schedules:

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

Well some agents may not necessarily have schedules assigned, thus, I filtered those before calling the with() method, like this:

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

Hope this helps!.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.