laravel withcount changes relationship result - laravel

I'm trying to count relationship using a scope to count a filtered part of the relationship
Parent::
withWhereHas('children')
->withCount(['children', function (Builder $builder) {
$builder->where('status', 1);
}])
->get()
The Parent has 36 Children, 6 of them are status = 1 and the variable added is right, i retrieve childens_count = 6.
But i only retrieve the 6 children in the relationship array, whereas i didn't scoped the withWhereHas, so i guess i'm missing something. Is the withCount having any side effect on the whole request ? Didn't see this in the doc.
Here are the relationships in models :
// Programme Model
public function childrens(): HasMany
{
return $this->hasMany(Children::class);
}
// Children Model
public function parent(): BelongsTo
{
return $this->belongsTo(Parent::class);
}
Any suggestion appreciated !

From how I understand your question, this is the query that you need to use to retrieve all of the 36 children
$parents = Parent::with('children')->get();
And if you want to set a condition in to children relationship
$parents = Parent::withWhereHas('children', fn($q) => $q->where('status', 1))->get();
// notice i use here foreach looping , because get will return a colletion
foreach ($parents as $parent) {
// to count the children after this query
count($parent->children); // should return 36
}
And if you want to return both children and the counter of a single Parent
$parent = Parent::query()
->withCount('children')
->with('children')
->where('id', 20) // optional , but best to filter which parent
->first(); // this will return a single
// to access how many children
$parent->children_count;
// to access each children
foreach ($parent->children as $child) {
// do something here
}
And if you only want to count all of children, I suggest you use Children.
$children_count = Children::count(); // add filter when needed
EDIT:
To grab all children and yet in the same query showing how many children with status active (1), you can adjust your eloquent query into this:
// I grab only the first Parent with id 1
$parent = Parent::query()
->withWhereHas('children')
->withCount(['children' => fn ($q) => $q->where('status', 1)])
->where('id', 1)
->first();
// this will return all children with any kind of status
$this->assertTrue( $parent->children->count() === 36 );
// this will return counted children with only status active (1)
$this->assertTrue( $parent->children_count === 6 );
note: I have test it in my local test area and work perfectly.
The only difference here is using query() at the beginning.
And of course extra where() and return only the first Parent.
I hope this can help you out.

Related

Get the last item registered, in a one-to-many relationship, in scope search

I have this in Process model
public function tracing()
{
return $this->hasMany(Tracing::class, 'process_id');
}
public function lasttracing()
{
return $this->hasOne(Tracing::class);
}
I have this in Tracing model
public function process()
{
return $this->belongsTo(Process::class);
}
Finally in Process model I have scopesearch function:
public function scopesearch($query, $mode, $area_id)
{
return $query->when($mode, function ($q) use ($mode) {
$user = session()->get('personal');
$area_id = $user['area_id'];
switch ($mode) {
case 'entrada':
$q->whereHas('tracing', function ($q2) use ($area_id) {
return $q2->latest()->where('area_id', $area_id)
->whereIn('accion', ['SEND', 'TO REGISTER']);
// return $q2->where('area_id', $area_id)->whereNull('recieved');
})->whereIn('situation', ['REGISTERED', 'SENT']);
break;
case 'bandeja':
break;
case 'salida':
break;
case 'general':
break;
case 'archivos':
break;
case 'courier':
break;
}
})->orderby('number', 'asc');
}
My probem is that I need to obtain the last tracing, of each process within the scopesearch function, and from there filter it according to the cases.
EDITED
I get the last tracing of this area, I need to get the last tracing in general, and after that check if it belongs to that area (area_id) that I need or no. I have already tried to use the lasttracing function but without results.
There are two ways to limit a relationship's results:
If you're querying the relationship of a single model (a single Process in your case), you can use the limit() method inside a Closure inside the with() method.
Process::query()
->where('id', $id) // querying a single Process
->search(...)
->with(['tracing' => fn($tracing) => $tracing->limit(1)])
->get();
If you're querying the relationship of more than a single model (multiple Processes in your case), you will need to use the setRelation() method inside a Closure inside the map() method after returning the Collection. This won't prevent Eloquent from querying the entire relationship. It just filters the result at the end.
Process::query()
->search(...)
->with(['tracing'])
->get()
->map(fn($process) => $process->setRelation('tracing', $process->tracing->take(1));
or
Process::query()
->search(...)
->with(['tracing'])
->get()
->map(fn($process) => $process->setRelation('tracing', $process->tracing->first());
depending on your use case.

GroupBy from relations using laravel eloquent

I have TypeOfVehicle model that has many Vehicle.
How can I group all vehicles by type of vehicle name with counts to get something like this
[
'Sedan' => 10,
'SUV' => 25,
'Crossover' => 5
]
I assume you have eloquent relation type in Vehicle model, e.g.:
public function type()
{
return $this->belongsTo('\App\TypeOfVehicle');
}
So, you can get you data this way:
$result = Vehicle::select('vehicle_type_id', DB::raw('count(vehicle_type_id) as cnt'))
->with('type') // with your type relation
->groupBy('vehicle_type_id') // group by type of vehicle
->get() // get collection from database
->pluck('cnt', 'type.name') // get only needful data
->toArray(); // cast to array if necessary
You can use
$counts = DB::table('tablename')
->select('TypeOfVehicle', DB::raw('count(*) as total'))
->groupBy('TypeOfVehicle')
->get();
Counting Related Models
If you want to count the number of results from a relationship without actually loading them you may use the withCount method, which will place a {relation}_count column on your resulting models.
$typeOfVehicles = App\TypeOfVehicle::withCount('vehicles')->get();
foreach ($typeOfVehicles as $typeOfVehicle) {
echo $typeOfVehicle->vehicles_count;
}
Try this it gives you your solution
$vehicle=Vehicle::all();
$grouped = $vehicle->groupBy(function ($item, $key) {
return substr($item['that_coloumn_name_like_type'], 0);
});
$groupCount = $grouped->map(function ($item, $key) {
return collect($item)->count();
});
//dd($groupCount); to check it work correctly

How to get count and avg of results in Laravel 5.8

I have the below query returning all available fiction books from all libraries:
$results = Library::with('category.books')
->whereHas('category.books', function($query) {
$query->where('available', 1);
})
->whereHas('category', function($query) {
$query->where('name', 'fiction');
})
->get();
Now, what is the best way to get the total number of books and the average rating per book (book has field rating), per library?
I assume I have to create a collection of these results somehow, apply a custom function.
You can get all the libraries while made changes to them using map() function.
You can count number of items in the collection using count() function.
You can get average by a property of items in the collection using average() function.
$libraries = $results->map(function($library) {
// All the books of the library
$books = $library->category->flatMap->books;
// set book count and average rating to the library object
$library->book_count = $books->count();
$library->average_rating = $books->average('rating');
return $library;
});
Now every library object in this $libraries collection has those new two properties called book_count and average_rating.
This could be achieved in a number of ways. From the results that you have:
$libraries = Library::with('category.books')
->whereHas('category.books', function($query) {
$query->where('available', 1);
})
->whereHas('category', function($query) {
$query->where('name', 'fiction');
})
->get();
$books = $libraries->map(function ($library) {
return $library->category->books;
})
->collapse()
->filter(); // This is an optional step to remove NULL books, if there are any.
$count = $books->count();
$avg = $books->avg('rating'); // Or $books->average('rating');
But I think a better approach could be calculating the count and average from a query starting from Book, let's define a relationship named category in the model class if you haven't have one:
class Book
{
// This could be a different type of relationships.
public function category()
{
return $this->belongsTo('category');
}
}
Then write a query.
$query = Book::where('available', 1)
->whereHas('category', function($query) {
$query->where('name', 'fiction');
});
$count = $query->count();
$avg = $query->avg(); // Or $query->average();
You just need to define the relation in library model for count average of book and rating like below. I am expecting that you have a column library_id in books table.
In your library model.
public function TotalBooks()
{
return $this->hasMany('App\Books', 'library_id')
->selectRaw('SUM(id) as total_book');
}
public function AvgRating()
{
return $this->hasMany('App\Books', 'library_id')
->selectRaw('AVG(rating) as avg_arting');
}
$data = Library::with('TotalBooks')->with('AvgRating'); // Get record

Laravel Eloquent query order_by hasOne() parameter, then later by updated_at

I have a right now simple $ads query, but I need to add more complexity to it for a richer ordering result.
I have an index() method at AdsController, so I list a lot of ads ordered by last updated date. So, I need to order it first by a linked model called promo.promotype (which goes from 1 to 4 values), then later order by the updated_at parameter in Ad Model.
This is the actual query:
$query = Ad::query();
$query->where('category_id', $sub_category->category_id)->with(['description', 'resources', 'category.description', 'category.parent.description', 'promo', 'stats']);
//Bunch of conditions here? ...
//Minimal Price
if (null !== Input::get('min_price')) {
$query->when(request('min_price') >= 0, function ($q) {
return $q->where('price', '>=', request('min_price', 0));
});
}
//Maximum Price
if (null !== Input::get('max_price')) {
$query->when(request('max_price') >= 0, function ($q) {
return $q->where('price', '<=', request('max_price', 0));
});
}
//Order
$query->orderBy('updated_at', 'desc');
$ads = $query->get();
So, I need to order results first from the promotype column at AdPromo Model (set as hasOne in Ad Model)
I need these results:
ads.poromotype = 4 (random between them)
ads.poromotype = 3 (random between them)
ads.poromotype = 2 (random between them)
ads.poromotype = 1 (random between them)
ads.poromotype = null (orderred by updated_at)
This has a big importance for me, but I just don't know hoy to achieve, thanks!
You need to use join() to order by a relation column. Be caseu the "with" does not actually use join but add another query used to fill your relation data
Have a look there Order by relationship column, it is pretty straight forward :)

Verify belongsToMany for update function in controller laravel 5.4

I'm creating this sort of database with some movies information.
The logic structure is pretty simple one movie has many actors and one actor have done many movies so it's a many to many relationship.
I'm also using select2 library to have a simple input where write actors like tags, separating them with commas or spacebar (here the link to docs) and below a simple snapshot to better understand the result
In the create/store function I don't need to check if any relation exists yet because the movie it's new. So I just need to check if actors already exists in the database, if not save them.
My controller store() function looks like:
foreach ($request->input('actors') as $key => $new_actor) {
// if actors doesn't exist in the db it save it.
$check = Actor::where('name', '=', $new_actor)->count();
if ($check === 0) {
$actor = new Actor;
$actor->name = $new_actor;
$actor->save();
}
// Here it creates the relationship
$actor = Actor::where('name', '=', $new_actor)->first();
$film->actors()->attach($actor->id);
}
The problem
when I edit a movie changing the actors, for example deleting or adding a new actor. I need to check in the controller if there are new relationship or if I have to delete some. How can I do it?
This is my controller update() function and of course doesn't work at all
foreach ($request->input('actors') as $key => $new_actor) {
// if actors doesn't exist in the db it save it.
$check = Actor::where('name', '=', $new_actor)->count();
if ($check === 0) {
$actor = new Actor;
$actor->name = $new_actor;
$actor->save();
}
$actors = $film->actors()->get();
foreach ($actors as $key => $actor) {
if ($actor->name === $new_actor) {
$actor = Actor::where('name', '=', $new_actor)->first();
$film->actors()->attach($actor->id);
}
}
}
Thanks for your help.
There is not direct way to do this with using the eloquent it. But you can do with the using db facade like this
$actors_film_relation = \DB::table('actor_film')->pluck('actor_id')->unique();
By using this now you can get the list of the actors that are attached with atleast one film.
Before deleting user you can check that actor_id is not inside the $actor_film_relation list like this
if( !in_array( $id, $actors_film_realtion)) { $actor->delete(); }
So now, the actor that is related to atleast one film will be not deleted.
Ok I found my way. With the first part checks if there are new actors and if true, it creates a new row in the actors table. With the second part, check if there are changes from the input comparing the new array with the relationship saved in the db, so it can detect if some actors were deleted and then deletes the relationship too.
foreach ($request->input('actors') as $key => $new_actor) {
// if actors doesn't exist in the db it save it.
$check = Actor::where('name', '=', $new_actor)->count();
if ($check === 0) {
$actor = new Actor;
$actor->name = $new_actor;
$actor->save();
}
// If the relation doesn't exists it creates it.
if (!$film->hasActor($new_actor)) {
$actor = Actor::where('name', '=', $new_actor)->first();
$film->actors()->attach($actor->id);
}
// If the relation has been deleted, it deletes it.
$actors = $film->actors()->get();
$new_actors = $request->input('actors');
foreach ($actors as $key => $actor) {
$check = in_array($actor->name, $new_actors);
if ($check === false) {
$film->actors()->detach($actor->id);
}
}
}

Resources