Laravel group by and order by, how to solve? - php

I have a database called maintenance that has a field called cost. I now want to get the sum the costs by month and year and sort them in descending order by month and year. Could someone please help me with that?
Here what I have tried
$MaintenanceStats = Maintenance::oldest()
->get()
->groupBy(function($val) {
return Carbon::parse($val->from)->format('F');
})
->take(7);
I got this collection object bellow, which works fine. But how can I group them by both month and year and then order them in descending order rather than just grouping by month? Plus, I only need the monthly total cost; I don't need all maintenance records.
Illuminate\Database\Eloquent\Collection {#1517 ▼ // app\Http\Controllers\GeneralController.php:40
#items: array:5 [▼
"September" => Illuminate\Database\Eloquent\Collection {#1452 ▶}
"July" => Illuminate\Database\Eloquent\Collection {#1530 ▶}
"January" => Illuminate\Database\Eloquent\Collection {#1519 ▶}
"November" => Illuminate\Database\Eloquent\Collection {#1520 ▶}
"December" => Illuminate\Database\Eloquent\Collection {#1521 ▶}
]
#escapeWhenCastingToString: false
}

The best way is to do it with the DB queries, but you'll need a raw query as well:
$maintenanceStats = Maintenance::selectRaw("year(`from`) AS year, month(`from`) AS month, sum(cost) AS cost_total")
->groupByRaw("year(`from`)")
->groupByRaw("month(`from`)")
->orderBy('year')
->orderBy('month')
->get();

Related

how to sum a collection after groupBy laravel

i have a collection named detailed as below :
Collection {#1421 ▼
#items: array:2 [▼
3943 => Collection {#1419 ▼
#items: array:2 [▼
0 => RoomPricingHistory {#923 ▶}
1 => RoomPricingHistory {#1042 ▶}
]
}
3944 => Collection {#1420 ▼
#items: array:2 [▼
0 => RoomPricingHistory {#1153 ▶}
1 => RoomPricingHistory {#1264 ▶}
]
}
]
}
now i want to get the sum of RoomPricingHistory for 3943 item and 3944 ofc it can be more of 2 item so i want to get the sum of each collection how can i achieve that ??
The Collection sum method can take a callback so you can define what is going to be calculated. In this case you can call sum on the main collection and in the callback which will give you access to the internal collections, also call sum.
$detailed->sum(function ($group) {
return $group->sum('sales_price');
});
Laravel 6.x Docs - Collections - Available Methods - sum
Since that isn't what you are looking for, you can use something like mapWithKeys to go through the collection and call sum for the groups to get the sum for each group:
$sums = $detailed->mapWithKeys(function ($group, $key) {
return [$key => $group->sum('sales_price')];
});
Laravel 6.x Docs - Collections - Available Methods - mapWithKeys

showing array data in single row

I want to show my array list data with same id into a single row of data like this:
I get my array like this:
Collection {#308 ▼
#items: array:2 [▼
0 => {#303 ▼
+"bill_no": "12334"
+"company_name": "sacacas"
+"contact": "0770230006"
+"payment": 250.0
+"installment_method": 1
+"date": "2019-03-20 00:00:00"
}
1 => {#301 ▼
+"bill_no": "12334"
+"company_name": "sacacas"
+"contact": "0770230006"
+"payment": 250.0
+"installment_method": 2
+"date": "2019-03-27 00:00:00"
}
]
}
I want to show same bill no data like this table with installement method type and payment. I want to get if same bill no has then show all array data into a single row like above picture I noted. Can anyone help me?
This is the query I'm using:
$sales = DB::table('sales')
->join('installments', 'sales.bill_no', '=', 'installments.bill_no')
->select('sales.bill_no', 'sales.company_name', 'sales.contact', 'installments.payment', 'installments.installment_method', 'installments.date')
->where('sales.status', '=', 'pending')
->where('installments.status', '=', 'paid')
->orderBy('sales.bill_no')
->get();

Sum function with join is giving sum multiplies number of times of entries in laravel ORM with mysql

There are 2 tables bills and payments, and total sales are inserted into an order table and revenue is inserted into payment table. I wanted to show total sales and total cash collection of last 7 days but my query giving me result multiplies by the number of times a data of the same date in laravel.
table bills:
table payments:
I want to fetch the total amount of each day of the last seven days where bills.created_date=payments.created_date. my query works good but it gives me total sum multiply by entries of the particular day.
My query (Laravel):
public function salesAndRevenue(){
$date = new Carbon\Carbon;
$salesAndRevenue =
DB::table('bills')
->Join(
'payments',
DB::raw('DATE(payments.created_at)'),
'=',
DB::raw('DATE(bills.created_at)'))
->select(
DB::raw('DATE(payments.created_at) as date'),
DB::raw('sum(payments.amount) as total_revenue'),
DB::raw('sum(bills.total_amount) as total_sales'))
->groupBy(DB::raw('DATE(payments.created_at)'))
->where(
DB::raw('DATE(bills.created_at)'),
'>',
$date->subDays(7)->toDateTimeString())
->get()->toArray();
return $salesAndRevenue;
}
My expected result:
array:2 [▼
0 => {#254 ▼
+"date": "2019-03-11"
+"total_revenue": 5.0
+"total_sales": 5.0
}
1 => {#255 ▼
+"date": "2019-03-12"
+"total_revenue": 1500.0
+"total_sales": 1500.0
}
]
my output:
array:2 [▼
0 => {#254 ▼
+"date": "2019-03-11"
+"total_revenue": 5.0
+"total_sales": 5.0
}
1 => {#255 ▼
+"date": "2019-03-12"
+"total_revenue": 4500.0
+"total_sales": 4500.0
}
]
But this code works perfectly without using join to fetch data from a single table.
like:
$sales =
DB::table('bills')
->select(
DB::raw('DATE(created_at) as date'),
DB::raw('sum(total_amount) as total_sales'))
->groupBy('date')
->where(
DB::raw('DATE(created_at)'),
'>',
$date->subDays(7)->toDateTimeString())
->get()->toArray();
output:
array:2 [▼
0 => {#259 ▼
+"date": "2019-03-11"
+"total_sales": 5.0
}
1 => {#260 ▼
+"date": "2019-03-12"
+"total_sales": 1500.0
}
]
The problem comes from you JOIN condition:
DB::table('bills')
->Join(
'payments',
DB::raw('DATE(payments.created_at)'),
'=',
DB::raw('DATE(bills.created_at)')
)
You are removing the time part of the dates, then joining: hence you end up with several matching payments for each bill.
From your sample data, it looks to me like column payments.bill_id is there for the purpose of joining:
DB::table('bills')
->Join(
'payments',
DB::raw('payments.bill_id'),
'=',
DB::raw('bills.id')
)

Using Laravel to average values, group by a category and a date range, and show the output on a single page

I have a table that looks something like this:
I want to be able to query this table and display results based on grouping by subject and by date. The dates would satisfy a range (between x and y).
I am looking to output something like this:
Term 1 - Art - (average of all asssessed_level for x range)
Term 2 - Art - (average of all asssessed_level for y range)
Term 3 - Art - (average of all asssessed_level for z range)
And this:
Term 1 - Math - (average of all asssessed_level for x range)
Term 2 - Math - (average of all asssessed_level for y range)
Term 3 - Math - (average of all asssessed_level for z range)
(etc.)
If there is no assessed_level for a date range, then I still need it to return a subject with perhaps an 'N/A' for that spot.
Ultimately, I want a table of data that will look something like this:
('E' would be where the average assessed_level goes and the YEAR column would be a result that is averaged and ignores the date range)
This is the code that I have written, but it doesn't provide the results that I need:
for($b=0; $b < $assessed_subjects->count(); $b++) {
$assessment_strengths[] = DB::table('assessment_data')
->join('subjects', 'assessment_data.subject_id', '=', 'subjects.id')
->join('units', 'assessment_data.unit_id', '=', 'units.id')
->join('outcomes', 'assessment_data.outcome_id', '=', 'outcomes.id')
->join('assessments', 'assessment_data.assessment_id', '=', 'assessments.id')
->select('subjects.short_name as subject_name', 'units.name as unit_name', 'outcomes.name as outcome_name', 'assessments.assessment_name as assessment_name', 'assessment_data.*')
->where('assessment_data.user_id', 28)
->where('assessment_data.student_id', $student_id)
->where('assessment_data.subject_id', $assessed_subjects[$b]->id)
->whereBetween('assessment_data.created_at', [$current_term->term_start, $current_term->term_end])
->orderBy('assessment_data.assessed_level', 'desc')
->get();
}
This is the output that I get when I dd() the query:
array:8 [▼
0 => Collection {#371 ▼
#items: []
}
1 => Collection {#386 ▼
#items: []
}
2 => Collection {#392 ▼
#items: array:1 [▼
0 => {#390 ▼
+"subject_name": "Math"
+"unit_name": "Rocks and Minerals"
+"outcome_name": "Analyze how positive health habits can be supported by a variety of approaches to health practices and treatments"
+"assessment_name": "The First Assessment"
+"id": 36
+"user_id": 28
+"room_id": 1
+"assessment_id": 1
+"student_id": 6
+"subject_id": 19
+"unit_id": 188
+"outcome_id": 476
+"assessed_level": 4.0
+"created_at": "2018-03-29 10:04:42"
+"updated_at": "2018-03-29 10:04:42"
}
]
}
3 => Collection {#399 ▼
#items: array:1 [▼
0 => {#397 ▼
+"subject_name": "Social"
+"unit_name": "Animals"
+"outcome_name": "Demonstrate and explain the effect of adding zero to, or subtracting zero from, any number."
+"assessment_name": "The First Assessment"
+"id": 48
+"user_id": 28
+"room_id": 1
+"assessment_id": 1
+"student_id": 6
+"subject_id": 25
+"unit_id": 122
+"outcome_id": 27
+"assessed_level": 4.0
+"created_at": "2018-01-01 10:04:42"
+"updated_at": "2018-03-29 10:04:42"
}
]
}
4 => Collection {#406 ▼
#items: []
}
5 => Collection {#412 ▼
#items: []
}
6 => Collection {#418 ▼
#items: array:1 [▼
0 => {#416 ▼
+"subject_name": "ELA"
+"unit_name": "Explore - Clarify and Extend"
+"outcome_name": "State, orally in their own words, that in French the indefinite article is not used when identifying one’s profession (e.g., m. brown est dentiste.)"
+"assessment_name": "The First Assessment"
+"id": 18
+"user_id": 28
+"room_id": 1
+"assessment_id": 1
+"student_id": 6
+"subject_id": 6
+"unit_id": 25
+"outcome_id": 3000
+"assessed_level": 4.0
+"created_at": "2018-03-29 10:04:42"
+"updated_at": "2018-03-29 10:04:42"
}
]
}
7 => Collection {#425 ▼
#items: []
}
]
As you can see, there are empty values (0, 1, 4, 5, 7) that I would like to push some data to. I took a look at IFNULL and COALESCE as a way of providing a fall-back, but that wasn't helpful in this case. Is there a way to append (or something like that) values into query results, even if they are otherwise empty?
My thought process is that if I can define each of the records that are being output as belonging to a specific term, I will be closer to having what I need.
I would suggest an approach like this:
$result = [];
foreach($assessed_subjects as $subject) {
$query = DB::table('assessment_data')
->select(DB::raw('avg(`assessed_level`) as `avg`'))
->where('user_id', 28)
->where('student_id', $student_id)
->where('subject_id', $subject->subject_id);
foreach($user_terms as $term) {
$result[$subject->subject_id][$term->id] = (clone $query)
->whereBetween('created_at', [$term->term_start, $term->term_end])
->first()->avg;
}
$result[$subject->subject_id]['year'] = (clone $query)
->whereBetween('created_at', [$user_terms->first()->term_start, $user_terms->last()->term_end])
->first()->avg;
}
This gives you the average level grouped by subject and term. If there is no value, it's NULL.
Or an alternative solution with fewer queries:
$result = [];
$query = DB::table('assessment_data')
->select('subject_id', DB::raw('avg(`assessed_level`) as `avg`'))
->where('user_id', 28)
->where('student_id', $student_id)
->whereIn('subject_id', $assessed_subjects->pluck('subject_id'))
->groupBy('subject_id');
foreach($user_terms as $term) {
$rows = (clone $query)
->whereBetween('created_at', [$term->term_start, $term->term_end])
->get();
foreach($rows as $row) {
$result[$row->subject_id][$term->id] = $row->avg;
}
}
$rows = (clone $query)
->whereBetween('created_at', [$user_terms->first()->term_start, $user_terms->last()->term_end])
->get();
foreach($rows as $row) {
$result[$row->subject_id]['year'] = $row->avg;
}
The only disadvantage is that it doesn't add NULL values for missing averages to the result.

Symfony2 queryBuilder why array repeats every field

I have queryBuilder as shown below and want to retrieve data by given date. It works, but not as I want. It returns array with 11 items(number of fields in table) instead of only one.
php
$query = $queryBuilder->select(array('menu'))
->from('InfoMenuBundle:Menu', 'menu')
->where("menu.date = :date")
->setParameter('date', $date)
->getQuery();
$entity = $query->getResult();
Output
array:11 [▼
0 => Menu {#533 ▼
-id: 1
-date: "2015-04-01"
-food1: "Ezogelin Çorbası"
-calories1: "217"
-food2: "Etli Nohut"
-calories2: "395"
-food3: "Şehriyeli Pirinç Pilavı"
-calories3: "342"
-food4: "Mevsim Salatası"
-calories4: "180"
-other: "Ekmek (1 Dilim)"
-calories5: "80"
-totalCalories: "1134"
}
1 => Menu {#538 ▼
-id: 31
-date: "2015-04-01"
-food1: "Ezogelin Çorbası"
-calories1: "217"
-food2: "Etli Nohut"
-calories2: "395"
-food3: "Şehriyeli Pirinç Pilavı"
-calories3: "342"
-food4: "Mevsim Salatası"
-calories4: "180"
-other: "Ekmek (1 Dilim)"
-calories5: "80"
-totalCalories: "1134"
}
2 => Menu {#536 ▶}
3 => Menu {#537 ▶}
4 => Menu {#534 ▶}
5 => Menu {#531 ▶}
6 => Menu {#530 ▶}
7 => Menu {#529 ▶}
8 => Menu {#495 ▶}
9 => Menu {#543 ▶}
10 => Menu {#544 ▶}
]
As you see it repeats the same row.
What is wrong with the code?
If you only want one result you can use ->getSingleResult() instead of ->getResult() and a function called setMaxResults
You should then however order the rows first, for which you can use ->orderBy([field], [direction]). You need to come up with a criteria which row is going to be most relevant to you.
Full example:
$query = $queryBuilder
->select(array('menu'))
->from('InfoMenuBundle:Menu', 'menu')
->where("menu.date = :date")
->setParameter('date', $date)
->orderBy('menu.date', 'asc')
->setMaxResults(1)
->getQuery()
;
$entity = $query->getSingleResult(); // getSingleResult hydrates to object instead of an ArrayCollection.
If all went well $entity is now going to be an instance of InfoMenuBundle:Menu.

Categories