Ager loading and custom queries

Asked

Viewed 44 times

0

This question is a follow up of this and of this. But I’ll try to make them independent.

I have the following tables, weathers:

+--------+-------+---------+
| id     | temp  | id_freg |
+--------+-------+---------+
|    337 | 12.36 |       1 |
|   3556 | 11.46 |       2 |
|   6775 |  9.30 |       3 |
|  10210 |  8.55 |       1 |
|  13429 |  9.69 |       2 |

freguesias:

+----+-----------+
| id | name      |
+----+-----------+
|  1 | Cabeção   |
|  2 | Pavia     |
|  3 | Brotas    |
|  4 | Mora      |

Each parish belongs to a county:

County Model (county):

...
public function freguesias() {
    return $this->hasMany(\App\Freguesia::class, 'id_county');
}
...

Freguesia Model:

...
public function county() {
    return $this->belongsTo(\App\County::class, 'id_county');
}

public function current_weather() {
    return $this->hasOne(\App\Current_Weather::class, 'id_freg')->orderBy('id', 'DESC');
}
...

I’m doing an Eager loading this way:

...
$county = County::with(['freguesias' => function($q) {
    $q->with(['current_weather']);
}])->select(['id'])->findOrfail(request()->id);
...

What I want is the last weathers for each parish belonging to the County, that is, in other words the Current time, more recent of each parish, for this we can follow us by the weathers.id.

I I already have SQL that I would like to use to get the last lines inserted for a set of parishes:

select w.* 
from weathers w
where w.id_freg in (X, Y, ...) and
     w.id = (select max(w2.id) 
     from weathers w2 
     where w2.id_freg = w.id_freg
     );

Note: This is a table with lots of data (more than 4 million lines, and more every hour that passes)

The solution hasOne(\App\Weather::class, ...) no Model Parish is not effective (very poor performance) because it does not limit in the query itself, ... LIMIT 1, in my opinion should do but does not.

  • The relationship would not be belongsTo with Hasmany? 1 Weathers has 1 parish and 1 parish may be in several Weathers! there are things wrong with relationships and their way of interpreting, or thing if you need to use that SQL can use directly DB::select see documentation

  • But I would like to make that query a @Virgilionovic relationship, so I can make Ager loading the relationship in it. Meanwhile I solved with an answer in SO EN, below the solution.

1 answer

0

I managed to solve thanks to a colleague in the SO EN:

In my Model Freguesia:

...
public function current_weather() {
    $f_key = 'id_freg';
    return $this->hasOne(\App\Weather::class, $f_key)
        ->where('id', function($query) use ($f_key) {
            $query->selectRaw('max(id)')
                ->from('weathers as w2')
                ->where('w2.' .$f_key, DB::raw('weathers.' .$f_key));
        });
}
....

So I can still:

...
$county = County::with(['freguesias' => function($q) {
    $q->with(['current_weather']);
}])->select(['id'])->findOrfail(request()->id);
...

My problem was to come across the fact that the hasOne relation does not limit the result in the query itself, extracts all the lines and then returns the first, that’s horrible for the performance when you have a few million rows in a table (Weathers in this case). REF: https://softonsofa.com/tweaking-eloquent-relations-how-to-get-n-related-models-per-parent/

  • "Commentary": My problem was I came across the fact that the relationship hasOne not limit the result in the query, extracts all lines and then returns the first one, this is horrible for performance when you have a few million rows in a table(Weathers in this case). -> this is true it does not limit the amount (for example put limit 1 ), because, he immediately believes that the relationship and 1 for 1 and not 1 for N, because you do a search on max(id) of your table where the primary key other table repeats on it then it is not hasOne! it is hasMany! cont

  • continuing, that is, it has a configuration problem in its relations with the way the ORM framework Eloquent works. For you to have a general idea doesn’t need that lot of code was just write: return $this->hasOne(\App\Weather::class, $f_key)->limit(1)->orderByRaw('id desc'); I believe that this alone would solve, even though it’s a different relationship than it should be, so it solved your problem, but I’d like to say that your perception is a little misguided.

  • continuing. my intention is to help understand, and do not be worried, I will not negativate, because somehow arrived at some solution that brings the result you need, but, not always what works is correct.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.