skip to Main Content

I’ve gone through the possible related questions, but I haven’t found anything about it, please, if you see that I’m wrong and there’s something similar, let me know and I’ll delete this question if the rules say so to avoid duplication. thank you.

I’m stuck and I can’t get what I want in Laravel with eloquent, I hope you can help me or guide me to solve it, I didn’t really know how to ask the question, I will explain it in more detail and an example to make it as clear as possible.

I have divided the question into two parts, the first one is because I want to obtain the query grouped by dates and the second one, because I want to obtain the same one, but without grouping by date and specifying a specific date.

Thank you very much in advance and I hope you can help me, anything you need to help me solve this, do not hesitate to ask me, regards.

First part of the question

What I want is to get everything in the same eloquent query, without processing it afterwards.

I have the following table:

Schema::create('readings', function (Blueprint $table) {
    $table->id();
    $table->foreignId('counter_id')
        ->constrained()
        ->cascadeOnUpdate()
        ->restrictOnDelete();
    $table->date('date');
    $table->unsignedInteger('value');
});

The following is stored in the table (example):

id counter_id date value
1 1 2022-12-1 100000
2 2 2022-12-1 150000
3 1 2022-11-1 50000
4 2 2022-11-1 100000
5 1 2022-10-1 25000
6 2 2022-10-1 50000

And I want to get the following grouped (example):

Date Total readings difference
2022-12-1 2 100000
2022-11-1 2 75000
2022-10-1 2 0?

? This is zero because there are no previous readings with which to make the difference.

I’ve tried to do it with mysql and I think that’s what I want, but I can’t get it to speak to Laravel:

SELECT r_current.date,
       count(r_current.id) AS readings_in_group,
       SUM(r_current.value -
           (
                SELECT value
                FROM readings
                WHERE r_current.counter_id = counter_id
                  AND r_current.date > date
                ORDER BY date DESC
                LIMIT 1
           )
       ) AS difference
FROM `readings` AS r_current
GROUP by r_current.date
;

check icon First part of the question solved. Thank you.

The first part was solved with the first part of the partone() method of the example given by @Masunulla.

I passed the query I had in MySQL to Eloquent:

$subQuery = 'SELECT value FROM readings 
                        WHERE r_current.counter_id = counter_id 
                        AND r_current.date > date 
                        ORDER BY date DESC 
                        LIMIT 1';

$readings = Reading::from('readings AS r_current')
    ->select('r_current.date',
        DB::raw('COUNT(r_current.id) AS readings_in_group'),
        DB::raw('SUM(r_current.value - ('.$subQuery.')) AS total_difference')
    )
    ->groupBy('cr_current.date')
    ->get();

Second part of the question

What I want is to get everything in the same eloquent query, without processing it afterwards.

The second related part is that I want to obtain the same thing without grouping indicating a specific date, I have achieved this with a query in a looper, but I would like to optimize it, but the same thing happens to me as above, I don’t know how to do it in a query.

For example, for the date 2022-12-1 I want to get this:

id DateCurrentReading CurrentReading DatePreviousReading PreviousReading difference
1 2022-12-1 100000 2022-11-1 50000 50000
2 2022-12-1 150000 2022-11-1 100000 50000

For example, for the date 2022-11-1 I want to get this:

id DateCurrentReading CurrentReading DatePreviousReading PreviousReading difference
3 2022-11-1 50000 2022-10-1 25000 25000
4 2022-11-1 100000 2022-10-1 50000 50000

For example, for the date 2022-10-1 I want to get this:

id DateCurrentReading CurrentReading DatePreviousReading PreviousReading difference
5 2022-10-1 25000 null? null? 0?
6 2022-10-1 50000 null? null? 0?

? This is zero and null because there are no previous readings with which to make the difference.

I’ve managed to do this part using an additional query in foreach, but I’d like to have it all in one query, as it does a lot of queries and isn’t optimal.

This is what I do now:

IN CONTROLLER

 $query = Reading::query();
              ->with('counter');
              ->where('date', '=', $this->date) //Example 
              ->get();

IN MODEL

public function previous(): Model|null
{
    return $this->query()
        ->where('counter_id', '=', $this->counter_id)
        ->whereDate('date', '<', Carbon::parse($this->date)->format('Y-m-d'))
        ->orderBy('date', 'desc')
        ->first();
}

public function difference(): int
{
    $previous = $this->previous();
    
    if ($previous) {
        $difference = $this->value - $previous->value;
    } else {
        $difference = 0;
    }

    return $difference;
}

IN BLADE

@foreach($readings as $reading)
...
$reading->date
$reading->value
$reading->previous()->date
$reading->previous()->value
$reading->difference()
...
@endforeach

check icon Second part of the question solved. Thank you.

Based on what I did in the first part, I tried to do something similar and this is the result, it does exactly what I want and although I don’t know if it is the most optimal way, the response is quite fast. If anyone comes up with something better, you’re more than welcome to reply. Thank you so much.

$concatSubQuery = 'FROM readings 
                    WHERE r_current.counter_id = counter_id 
                    AND r_current.date > date 
                    ORDER BY date DESC 
                    LIMIT 1';

$query = Reading::from('readings AS r_current')
    ->select('r_current.id', 'r_current.counter_id', 'r_current.date', 'r_current.value',
        DB::raw('(SELECT date '.$concatSubQuery.')  AS previous_date'),
        DB::raw('(SELECT value '.$concatSubQuery.')  AS previous_value'),
        DB::raw('SUM(r_current.value - (SELECT value '.$concatSubQuery.')) AS difference')
    )
    ->with('counter');
    ->where('date', '=', $this->date);
    ->groupBy('r_current.id', 'r_current.counter_id', 'r_current.date', 'r_current.value');
    ->get();

2

Answers


  1. Chosen as BEST ANSWER

    check icon First part of the question solved. Thank you.

    The first part was solved with the first part of the partone() method of the example given by @Masunulla.

    I passed the query I had in MySQL to Eloquent:

    $subQuery = 'SELECT value FROM readings 
                            WHERE r_current.counter_id = counter_id 
                            AND r_current.date > date 
                            ORDER BY date DESC 
                            LIMIT 1';
    
    $readings = Reading::from('readings AS r_current')
        ->select('r_current.date',
            DB::raw('COUNT(r_current.id) AS readings_in_group'),
            DB::raw('SUM(r_current.value - ('.$subQuery.')) AS total_difference')
        )
        ->groupBy('cr_current.date')
        ->get();
    

    check icon Second part of the question solved. Thank you.

    Based on what I did in the first part, I tried to do something similar and this is the result, it does exactly what I want and although I don't know if it is the most optimal way, the response is quite fast. If anyone comes up with something better, you're more than welcome to reply. Thank you so much.

    $concatSubQuery = 'FROM readings 
                        WHERE r_current.counter_id = counter_id 
                        AND r_current.date > date 
                        ORDER BY date DESC 
                        LIMIT 1';
    
    $query = Reading::from('readings AS r_current')
        ->select('r_current.id', 'r_current.counter_id', 'r_current.date', 'r_current.value',
            DB::raw('(SELECT date '.$concatSubQuery.')  AS previous_date'),
            DB::raw('(SELECT value '.$concatSubQuery.')  AS previous_value'),
            DB::raw('SUM(r_current.value - (SELECT value '.$concatSubQuery.')) AS difference')
        )
        ->with('counter');
        ->where('date', '=', $this->date);
        ->groupBy('r_current.id', 'r_current.counter_id', 'r_current.date', 'r_current.value');
        ->get();
    

  2. I think this could be solve your problem

    
    use IlluminateSupportFacadesDB;
    use AppModelsReading;
    
    
    class ...Controller extends Controller
    {
    
        ##### First Part #####
        public function partone()
        {
            // get data from readings table
            $reading = DB::table('readings')
                    ->select('date', DB::raw('COUNT(date) as Total_readings'), DB::raw('SUM(value) as Total_value'))
                    ->orderBy('date', 'asc')
                    ->groupBy('date')
                    ->get();
    
            // prepare data as per require 
            $newReading = $this->preOne($reading);
    
            // code ... 
        }
    
        private function preOne($data)
        {
            /**
             * pre_Total variable store pervious dated SUM(`reading`.`value`);
             */
            $pre_Total = 0;
            $res = array();
            foreach ($data as $i => $row) {
                if($pre_Total === 0){
                    $res[$i]['Date'] = $row->date;
                    $res[$i]['Total_readings'] = $row->Total_readings;
                    $res[$i]['difference'] = 0;
                    $pre_Total = $row->Total_value;
                } else {
                    $res[$i]['Date'] = $row->date;
                    $res[$i]['Total_readings'] = $row->Total_readings;
                    $res[$i]['difference'] = $row->Total_value - $pre_Total;
                    $pre_Total = $row->Total_value;
                }
            }
            return array_reverse($res);
        }
    
        ##### End First Part #####
    
    
        ##### Second Part #####    
    
        public function index()
        {
            
            // get data from readings table
            $reading = Reading::orderBy('date')->orderByDesc('counter_id')->get();
            
            // Prepare data as per require 
            $newReading = $this->preTwo($reading);
    
            // code ...
        }
    
        private function preTwo($data)
        {
    
            /**
             * '$previous' variable store `previous_data` & `previous_value` on corespoding `reading`.`counter_id`
             * $previous = array (
             *      counter_id => [
             *              "pre_date" => '',
             *              "pre_value" => '',
             *          ]
             * )
             */
            $previous = array();
            $res = array();
            foreach ($data as $i => $row) {
                if(isset($previous[$row->counter_id]) ){
                    $difference =  $row->value - $previous[$row->counter_id]['pre_value'];
                    $res[$i]['pre_date'] = $previous[$row->counter_id]['pre_date'];
                    $res[$i]['pre_value'] = $previous[$row->counter_id]['pre_value'];
                    $previous[$row->counter_id]['pre_date'] = $res[$i]['date'] = $row->date;
                    $previous[$row->counter_id]['pre_value'] = $res[$i]['value'] = $row->value;
                    $res[$i]['difference'] = $difference;
                } else {
                    $res[$i]['date'] = $previous[$row->counter_id]['pre_date'] = $row->date;
                    $res[$i]['value'] = $previous[$row->counter_id]['pre_value'] =  $row->value;
                    $res[$i]['pre_date'] = null;
                    $res[$i]['pre_value'] = null;
                    $res[$i]['difference'] = 0;
                }
            }
            return array_reverse($res);
        }
    
        ##### End Second Part #####  
    
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search