When using Group By at the database level, you must aggregate all the columns. But what if you want to group the data by a column for a more accessible display like this:
You can't do that with a database, but you can with Collections! Let's look at a few valuable methods of collections.
Group By in Collection - Set a Key For Related Items
The groupBy()
method on collections often groups data into a different format. And it's as easy as specifying a field:
$questions = Question::query() ->with(['topic', 'questionAnswers']) ->get() // Note that the groupBy is AFTER the database query is executed, meaning that it's a collection method ->groupBy('topic.name');
This allowed us to quickly group all of our questions within a topic.name
key for a quick display result:
@foreach($questions as $topicName => $topicQuestions) <div class="mb-4"> <h2 class="text-2xl">{{ $topicName }}</h2> <div> @foreach($topicQuestions as $question) <p>{{ $question->question }}</p> <small>Choices - {{ $question->questionAnswers->count() }}</small> @endforeach </div> </div>@endforeach
Giving us a result of grouped questions and amounts of choices that each question has:
This function also accepts a callback, where we can define a custom structure:
$questions = Question::query() ->with(['topic', 'questionAnswers']) ->get() ->groupBy(function(Question $question){ return \Str::of($question->question) ->append(' - ') ->append($question->questionAnswers->count()) ->toString(); });
Which will give us the following:
Key By - Use Any Field as Index
Another helpful method in collections is keyBy()
, which allows us to change the default keys of our array to be any other field (it has to be unique!). For example:
$topics = Topic::query() ->get() ->keyBy('name');
And now, when displaying the list, we can take a foreach and instantly have a name available:
@foreach($topics as $name => $topic) <div class="border-b py-4"> <h2 class="text-2xl">{{ $name }}</h2> {{-- Topic details inside --}} </div>@endforeach
It also accepts a callback in which we can define our field or mutate it:
$topics = Topic::query() ->with(['questions']) ->get() ->keyBy(function(Topic $topic){ return \Str::of($topic->name) ->append(' - ') ->append($topic->questions->count()); });
This gives a result where our names are appended with a questions count:
Counting How Many Times Something Repeats - Using Count By
Another great collection feature is countBy()
, which can count how many times a specific value occurs in your array. For example, we have an age
field on our Users table and want to see how many users we have per age:
$ageList = User::query() ->get() ->countBy('age') // We added a bonus to sort the age in ascending order ->sortKeys();
This allows us to display the list like this quickly:
<h2 class="text-2xl">Users count by Age</h2> @foreach($ageList as $age => $count) <p> {{ $age }} - {{ $count }} </p>@endforeach
And now, we have a count of people by their age:
This is great if you want to get a key
in an array of specific values and then count
how many times it has repeated in your dataset.
Other Methods - Sum, Avg, Min, Max
In cases when you have the data loaded and need to display a sum of values, average, min, or max, you can use the collection methods sum()
, avg()
, min()
, max()
to do so:
We have loaded the orders with their users: (Note: You should not load all the data if you don't need it, but for the sake of example, we will do so)
Controller
$orders = Order::query() ->with('user') ->get();
Then in our view, before the table of orders, we can display some statistics:
View
<div class="grid grid-cols-4 gap-4"> <div class=""> <h2 class="text-xl">Total Orders</h2> <span>$ {{ number_format($orders->sum('total'), 2) }}</span> </div> <div class=""> <h2 class="text-xl">Average Order Value</h2> <span>$ {{ number_format($orders->avg('total'), 2) }}</span> </div> <div class=""> <h2 class="text-xl">Minimum Order value</h2> <span>$ {{ number_format($orders->min('total'), 2) }}</span> </div> <div class=""> <h2 class="text-xl">Maximum Order value</h2> <span>$ {{ number_format($orders->max('total'), 2) }}</span> </div></div> {{-- Table code --}}
This will give us the following result:
With little code, we implemented quick ways to get different statistics from our data.
In the first example, on commentary line, have something wrong
// Note that the groupBy is AFTER the database query is executed, meaning that it's not a collection method
Oops, good catch!