Yad Hoshyar
Yad Hoshyar

Reputation: 43

How to optimize laravel eloquent query?

So I have orders model and table.

it contains integer fields that I want to calculate to find daily, monthly and yearly earnings. My current solution works fine but as shown in the image below it runs 3 queries to find all of them. is there any way better to calculate the earning without running the query 3 times?

I have also tried getting all the orders into one variable then using laravel's collection methods to calculate earnings, that also works but it's slower than my current solution.

    public function index()
{

    $dailyEarning = Order::whereDate('created_at', Carbon::today())->get()->sum(function ($order) {
        return (($order->cost - $order->product->original_cost) * $order->quantity);
    });

    $monthlyEarning = Order::whereBetween('created_at', [
        Carbon::today()->startOfMonth(),
        Carbon::today()->endOfMonth(),
    ])->get()->sum(function ($order) {
        return (($order->cost - $order->product->original_cost) * $order->quantity);
    });

    $yearlyEarning = Order::whereBetween('created_at', [
        Carbon::today()->startOfYear(),
        Carbon::today()->endOfYear(),
    ])->get()->sum(function ($order) {
        return (($order->cost - $order->product->original_cost) * $order->quantity);
    });

    return view('admin.home',[
        'dailyEarning' => $dailyEarning,
        'monthlyEarning' => $monthlyEarning,
        'yearlyEarning' => $yearlyEarning,
    ]);
}

Orders table schema:

   public function up()
{
    Schema::create('orders', function (Blueprint $table) {
        $table->id();

        $table->unsignedBigInteger('product_id');
        $table->foreign('product_id')->references('id')->on('products')->onDelete('restrict');

        $table->unsignedBigInteger('invoice_id');
        $table->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade');

        $table->double('cost');
        $table->double('quantity');

        $table->timestamps();
    });
}

Products table schema:

  public function up()
{
    Schema::create('products', function (Blueprint $table) {
        $table->id();

        $table->string('name');
        $table->string('code');
        $table->string('brand');

        $table->double('quantity')->default(0);
        $table->double('original_cost')->default(0);
        $table->double('cost')->default(0);

        $table->string('photo')->default('no-image');
        $table->timestamps();
    });
}

Upvotes: 0

Views: 410

Answers (2)

Matthew Daly
Matthew Daly

Reputation: 9476

Without seeing the structure of the tables I don't think it's possible to provide a specific working solution for this problem. However, in principle the way to resolve this is almost certainly to move as much of this work as possible to the database and use subqueries to calculate the different values.

Most SQL implementations have a SUM() implementation so it should be possible to use that to move aggregating the values into the database for each individual query. Then make each value a subquery of a larger query, and that should achieve what you want.

Upvotes: 1

Joe Ward
Joe Ward

Reputation: 181

Create an OrderSummary table. The idea should be that you run the monthly (to date) and yearly (to date) queries only once per day (automatically).

YTD Sales = Yesterday's YTD Sales + Today's Sales MTD Sales = Yesterday's MTD Sales + Today's Sales

(Both presume that yesterday is the same year, an the same month. If not, account or it)

Each time, you'll still run the query to get the current day's sales. But you will query an OrderSummary single row for the previous day with columns:

id | date | YTD Sales | Monthly Sales

If you're using caching, you'll cache those results as well.

If it returns zero records for the previous day, then you'll need to execute your queries and update OrderSummary. However, you can execute these in the background with a CRON so they are ready beforehand.

Note: Since you may need to account for order reversals, so the full YTD and MTD queries would have to be run daily (versus just adding the previous day's completed order total to the prior amounts to update them).

Upvotes: 1

Related Questions