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

motinska94's avatar

How can I speed up my controller?

I'm trying to get all the Sales records within past 2 years from the database and put them in comparison tables to compare this week/last week, this month/last month, this year/last year sales. Database seems pretty optimized (<400ms for 200k records) but my controller takes an absurd amount of time. Is there a way to optimize the controller or do I have to put a "Please wait, this may take a while" warning to the page?

Clockwork result :

clockwork

My Model :

<?php

namespace App\Http\Controllers;

use App\Models\Sales;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class StatsController extends Controller
{

    public function index()
    {
        $data = $this->getData();

        $this_month = $this->getMonthly($data);
        $last_month = $this->getMonthly($data, 1);

        $this_year = $this->getYearly($data);
        $last_year = $this->getYearly($data, 1);

        $this_week = $this->getWeekly($data);
        $last_week = $this->getWeekly($data, 1);

        return view('employee.stats.index', compact('this_week', 'last_week', 'this_month', 'last_month', 'this_year', 'last_year'));

    }

    public function getData()
    {
        $data = Sales::query()
            ->whereStoreId(Auth::user()->store_id)
            ->whereYear('created_at', '>=', date('Y') - 1)
            ->where('status', '>', '1')
            ->orderBy('created_at')
            ->get(["price_total", 'created_at']);

        return $data;
    }

    public function getMonthly($data, $minus = 0)
    {
        $data = $data->groupBy(fn($item) => intval($item->created_at->format('m')));

        if(isset($data[date('m') - $minus])){

            $get_this = $data[date('m') - $minus]->groupBy(fn($item) => $item->created_at->format('d'));

            $this_arr = [];
            foreach ($get_this as $day){
                $this_arr[intval($day->first()->created_at->format('d'))] = $day->sum('price_total');
            }

            return json_encode($this_arr);
        }else{
            return json_encode([]);
        }

    }

    public function getYearly($data, $minus = 0)
    {
        $data = $data->groupBy(fn($item) => $item->created_at->format('Y'));

        if ($data->count()){


            $this_arr = [];
            foreach ($data as $day){
                $this_arr[$day->first()->created_at->format('m')] = $day->sum('price_total');
            }

            return json_encode($this_arr);
        }
        else{
            return json_encode([]);
        }

    }


    public function getWeekly($data, $minus = 0)
    {
        $data = $data->groupBy(fn($item) => intval($item->created_at->format('W')));

        if(isset($data[date('W') - $minus]))
        {
            $get_this = $data[date('W') - $minus]->groupBy(fn($item) => $item->created_at->format('D'));

            $this_arr = [];
            foreach ($get_this as $day){
                $this_arr[$day->first()->created_at->format('D')] = $day->sum('price_total');
            }

            return json_encode($this_arr);
        }else{
            return json_encode([]);
        }
    }

}

0 likes
4 replies
Tray2's avatar
Tray2
Best Answer
Level 73

This is the problem

I'm trying to get all the Sales records within past 2 years from the database and put them in comparison tables > to compare this week/last week, this month/last month, this year/last year sales.

and this

400ms for 200k records

200k will always take time to render and then send to the browser. The only way to make it seem faster is to chunk it. Fetch a few thousand records, render and then another few thousand and render.

You are using over 200Mb memory, and that is really bad.

1 like
motinska94's avatar

@Tray2

I have an idea but you might not like it 😅 I'm kind of a beginner still so I thought I'd ask someone more experienced before directly going into it.

How about I make a new table called something like Sales_Comparison and have monthly, weekly, yearly data columns and save the result that I'm returning in these functions to the page as JSON string, all in the same row.

And when user requests to see the stats page, I'll load the data from Sales_Comparison table instead of querying it all from scratch. But somehow still run the command asynchronously when the page is requested, without affecting the user's load time, and update the Sales_Comparison row according to new query result. By doing that, user will see a close-enough representation of the comparisons and after async work is done (10-20secs later), they can refresh the page to see the updated data.

Does that make sense?

Tray2's avatar

@motinska94 Not really, but storing compiled historical results in a table is one approach you can take. I would however not store it as json, in case you want to query it in some other way than on the primary key.

If you are 100% sure that you will never query any of the values in the json field, then sure you can use a json field.

1 like
motinska94's avatar

@Tray2 I don't think I'll need to query that, since I'll only use the table to store selected store's sale records comparisons in selected time period. Thanks so much! I'll try to find out how can I run a function asynchronously now 😄

Please or to participate in this conversation.