Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

fcno's avatar
Level 6

DB query report by month with the month being returned, including if the result is 0

DB::table('prints')
            ->selectRaw('SUM(pages* copies) AS total, DATE_FORMAT(print_date, "%c-%Y") AS month')
            ->whereBetween('print_date', [Carbon::now()->subMonths($qtd_months - 1)->startOfDay(), Carbon::now()->endOfDay()])
            ->groupBy('month')
            ->get();

// Current output
// ['month' => '4-2023', 'total'=> 100] ,
// ['month' => '2-2023', 'total'=> 200] ,

// Desired output
// ['month' => '4-2023', 'total'=> 100] ,
// ['month' => '3-2023', 'total'=> 0] ,
// ['month' => '2-2023', 'total'=> 200] ,

Hello Masters;

The problem with the above query is that in the months in which there is no printing, there is no result. I would like to know if it would be possible to have this behavior directly from the query instead of dealing with it in PHP?

Using PHP, my code seemed to smell.

0 likes
3 replies
LaryAI's avatar
Level 58

To achieve the desired output directly from the query, you would need to generate a series of months and then left join your prints table to this series. Unfortunately, SQL doesn't have a built-in way to generate a series of dates or months, so you'll need to create it manually or use a stored procedure if your database supports it.

However, since you're using Laravel, you can create an array of months in PHP and then loop through this array to fetch the data for each month. This way, you can ensure that you get a result for each month, even if it's zero.

Here's how you can do it:

use Carbon\Carbon;

// Assuming $qtd_months is defined and is the number of months you want to go back
$end = Carbon::now()->endOfDay();
$start = Carbon::now()->subMonths($qtd_months - 1)->startOfDay();

// Generate an array of months
$months = collect();
for ($month = $start->copy(); $month->lte($end); $month->addMonth()) {
    $months->push($month->format('n-Y'));
}

// Query the database for each month and build the result set
$results = $months->mapWithKeys(function ($month) use ($start, $end) {
    $monthStart = Carbon::createFromFormat('n-Y', $month)->startOfMonth();
    $monthEnd = Carbon::createFromFormat('n-Y', $month)->endOfMonth();

    // Adjust the start and end dates if they exceed the range we're looking at
    if ($monthStart->lt($start)) {
        $monthStart = $start;
    }
    if ($monthEnd->gt($end)) {
        $monthEnd = $end;
    }

    $total = DB::table('prints')
        ->whereBetween('print_date', [$monthStart, $monthEnd])
        ->sum(DB::raw('pages * copies'));

    return [$month => $total];
});

// Format the results as desired
$finalResults = $results->map(function ($total, $month) {
    return ['month' => $month, 'total' => $total];
})->values();

// Now $finalResults contains the desired output

This code snippet will give you a collection of results with a total for each month, including months with zero prints. It's not as efficient as doing it all in a single query, but it's a straightforward solution that works within the constraints of Laravel and MySQL.

fcno's avatar
Level 6

@LaryAI Since we have the start (starting month) and end (end month) of the report, I think it would be more efficient to just do one database query instead of one for each month. Then, with the result already in hand, we created the report with this information.

Could you suggest an approach like this?

jlrdw's avatar

You can use count also, and if 0 you can with an if statement display 0 if needed.

1 like

Please or to participate in this conversation.