GroupBy from relations using laravel eloquent - laravel

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

Related

Laravel, eloquent, query: problem with retriving right data from DB

I'm trying to get the right data from the database, I'm retriving a model with a media relation via eloquent, but I want to return a photo that contains the 'main' tag stored in JSON, if this tag is missing, then I would like to return the first photo assigned to this model.
how i assign tags to media
I had 3 ideas:
Use orWhere() method, but i want more likely 'xor' than 'or'
$models = Model::with(['media' => function ($query) {
$query->whereJsonContains('custom_properties->tags', 'main')->orWhere();
}]);
return $models->paginate(self::PER_PAGE);
Raw SQL, but i don't really know how to do this i tried something with JSON_EXTRACT and IF/ELSE statement, but it was to hard for me and it was a disaster
Last idea was to make 2 queries and just add media from second query if there is no tag 'main'
$models = Model::with(['media' => function ($query) {
$query->whereJsonContains('custom_properties->tags', 'main');
}]);
$models_all_media = Model:: with(['media']);
return $models->paginate(self::PER_PAGE);
but i tried something like
for($i=0; $i<count($models); $i++) {
$models->media = $models_all_media
}
but i can't do this without get() method, beacuse i don't know how to change this to LengthAwarePaginator class after using get()
try using whereHas https://laravel.com/docs/9.x/eloquent-relationships
Model::with('media')
->whereHas('media',fn($media)=>$media->whereJsonContains('custom_properties->tags', 'main'))
->paginate(self::PER_PAGE);
as per your comment you can use
$models = Model::with(['media' => function ($query) {
$query->whereJsonContains('custom_properties->tags', 'main');
}])
->leftJoin('media', function ($join) {
$join->on('models.id', '=', 'media.model_id')
->whereNull('media.custom_properties->tags->main');
})
->groupBy('models.id')
->paginate(self::PER_PAGE);
return $models;

Eloquent accessing array inside object

I have a get formule that returns some nested relationships in an array. I was wondering how to access them in a where statement.
The initial get
$taken = UserWork::with('work.place')
->with('user')
->with('work.timeslot')
->get();
I tried something like this
$taak = $taken->where('work.timeslot[0].start_hour',"17:00:00")->first();
json result from $taken
Using with will endup with two queries. if you want to bring the user with timeslot null then there no need to add whereHas
$callback = function($query) {
$query->where('start_hour',"17:00:00");
};
$taken = UserWork::whereHas('work.timeslot', $callback)
->with(
['work.place', 'user', 'work.timeslot' => $callback]
)->get();

How to group records in year and month? laravel

I have been already to this link but its different in my case.
I have record which has a column created_at. My objective is to group the record by year, and inside each year, records should be group in month also.
My expectations:
I have tried this code below, but it is working only for yearly only.
$records = \App\VehicleRental::query()
->where('operator_id', $operator->id)
->get()
->groupBy(function($val) { return \Carbon\Carbon::parse($val->created_at)->format('Y'); });
So I have tried to put another group for month but error Property [created_at] does not exist on this collection instance. returned
$record = \App\VehicleRental::query()
->where('operator_id', $operator->id)
->get()
->groupBy(function($val) { return \Carbon\Carbon::parse($val->created_at)->format('M'); })
->groupBy(function($val) { return \Carbon\Carbon::parse($val->created_at)->format('Y'); });
Someone knows how to achieve this?
You could also pass an array of elements to the groupBy() collection method, for nested grouping. Try this:
$record = \App\VehicleRental::query()
->where('operator_id', $operator->id)
->get()
->groupBy([
function ($val) { return $val->created_at->format('Y'); },
function ($val) { return $val->created_at->format('m'); },
]);
As a side note, the created_at could be directly casted as a Carbon instance. If the above doesn't work, define the attribute date mutator in your model:
# VehicleRental.php
protected $dates = ['created_at', 'updated_at'];

Laravel simplePaginate() for Grouped Data

I have the following query.
$projects = Project::orderBy('created_at', 'desc');
$data['sorted'] = $projects->groupBy(function ($project) {
return Carbon::parse($project->created_at)->format('Y-m-d');
})->simplePaginate(5);
When I try to paginate with the simplePaginate() method I get this error.
stripos() expects parameter 1 to be string, object given
How can I paginate grouped data in this case?
The created_at attribute is already casted as a Carbon Object (by default in laravel models). that's why you are getting that error. Try this:
$projects = Project::orderBy('created_at', 'desc')->get();
$data['sorted'] = $projects->groupBy(function ($project) {
return $project->created_at->format('Y-m-d');
})->simplePaginate(5);
this answer is just for the error you're getting. now if you want help with the QueryBuilder, can you provide an example of the results you're expecting to have and an example of the database structure ?
The pagination methods should be called on queries instead of collection.
You could try:
$projects = Project::orderBy('created_at', 'desc');
$data['sorted'] = $projects->groupBy('created_at');
The problem was solved. I was create custom paginator via this example:
https://stackoverflow.com/a/30014621/6405083
$page = $request->has('page') ? $request->input('page') : 1; // Use ?page=x if given, otherwise start at 1
$numPerPage = 15; // Number of results per page
$count = Project::count(); // Get the total number of entries you'll be paging through
// Get the actual items
$projects = Project::orderBy('created_at', 'desc')
->take($numPerPage)->offset(($page-1)*$numPerPage)->get()->groupBy(function($project) {
return $project->created_at->format('Y-m-d');
});
$data['sorted'] = new Paginator($projects, $count, $numPerPage, $page, ['path' => $request->url(), 'query' => $request->query()]);
simplePaginate Method is exist in the path below:
Illuminate\Database\Eloquent\Builder.php::simplePaginate()

Laravel pluck an array from nested relationship

I need to get only the roomnumber arrays returned from the following query:
$roomnumbers = Room::with(['floorroomcount' => function($query){
$query->with('roomnumber')->get();
}])->where('roomtype_id', $roomtype_id)->get();
Tried:
The follow pluck is returning floorroomcount
$roomnumbers->pluck('floorroomcount');
but i need roomnumber array, how can i get?
This gives you all roomnumber results in one collection:
$roomnumbers->pluck('floorroomcount')->collapse()->pluck('roomnumber')->collapse();
You may shorten #Jonas Staudenmeir's answer like so:
$roomnumbers->pluck('floorroomcount.*.roomnumber.*')->collapse();
pluck('*') is essentially the same as collapse() in this particular context.
This is working, but with many loop and echoing directly, if anything can be simplified please let me know :
$roomnumbers = Room::with(['floorroomcount.roomnumber'])->where('roomtype_id', $roomtype_id)->get();
$floorroomcounts = $roomnumbers->pluck('floorroomcount');
$records = $floorroomcounts->map(function($floorroomcount, $value){
return $floorroomcount->pluck('roomnumber')->flatten();
})->values()->all();
foreach($records as $record){
foreach($record as $row){
echo '<option value='.$row->id.'>'.$row->roomnumber.'</option>';
}
}
//return response()->json($roomnumbers);
Try,
$roomnumbers = Room::with(['floorroomcount' => function($query){
$query->with('roomnumber')->get();
}])
->where('roomtype_id', $roomtype_id)
->get();
$records = $roomnumbers->map(function($element, $value){
return $element->map(function($e, $v){
return $e->roomnumber;
});
})->values()->all();
map() is a Laravel collection method so you need to import the collection facade on the top of the controller like: use Illuminate\Support\Collection;
In Laravel 5.1 and + you can use flatten() on collection.
method flattens a multi-dimensional collection into a single dimension:
$roomnumbers->flatten()->pluck('floorroomcount');

Resources