I've been leaning Laravel and trying to implement something and I did, it worked but I heard that it might be possible to do the same in an easier way using Eager Loading.
Imagine that we have 4 tables: garages, cars, securities, places.
garages is where you can find the cars, securities is the security that is keeping that car safe inside of a garage, and places is where you can find garages similar to that one.
What I want is to list garages and join the three tables that is cars, securities and places like this:
Garage 1 has 2 cars with 3 securities and has 3 more garages similar
Garage 2 has 1 cars with 1 securities and has 2 more garages similar
And here is the query:
select
g.garage_name,
count(distinct c.car_id) as count_cars,
count(distinct s.security_id) as count_securities,
count(distinct p.place_id) as count_places
from garages g
left join cars c on c.garage_id = g.garage_id
left join securities s on s.car_id = c.car_id
left join places p on p.garage_id = g.garage_id
group by g.garage_name
order by g.garage_name;
It's working, as you can see here.
I converted it to:
$garages = Garages::select('garage_name',
DB::raw('count(distinct cars.car_id) as count_cars'),
DB::raw('count(distinct securities.security_id) as count_securities'),
DB::raw('count(distinct places.place_id) as count_places'))
->leftJoin('cars', 'cars.garage_id', '=', 'garages.garage_id')
->leftJoin('securities', 'securities.car_id', '=', 'cars.car_id')
->leftJoin('places', 'places.garage_id', '=', 'garages.garage.id')
->groupBy('garages.garage_name')
->orderBy('garages.garage_name')
->get();
As I said above, it's working but I'm wondering if there's an easier way for doing this using Eager Loading and how to convert?
When I say easier I mean more readable, separated, the right way instead of that big query.
Lets say you do have the 4 Eloquent Model classes for the entities.
If that's the case, you can try eager loading through the Eloquent ORM.
$Garages = Garage::with(['cars','places', 'securities'])->get();
Then you may prepare the necessary output as you like using the returned result set which is a collection (Illuminate\Support\Collection). You can transform the items in a collection as any way you like. Here is an example.
In your case, I am assuming this would be the way to do it.
$Garages->transform(function($garage) {
return array(
'garage_name' => $garage->garage_name,
'count_cars' => $garage->cars->count(),
'count_securities' => $garage->securities->count(),
'count_places' => $garage->places->count(),
// add as many custom details you want here
);
});
return $Garages->toArray(); // see the output
Now I hope you got the idea.
Keep in mind that your model class files should have the relationships. It should be like the following.
class Garage extends Eloquent {
public function cars() {
return $this->hasMany('Car');
}
public function securities()
{
return $this->hasMany('Security');
}
public function places()
{
return $this->hasMany('Place');
}
}
class Car extends Eloquent {
// relationships here
}
class Security extends Eloquent {
// relationships here
}
class Place extends Eloquent {
// relationships here
}
This is just a simple example to help you understand.
Enjoy ! :D
Related
i have 3 models that i want to start from a and load the relation with b and i want to load the relation betwen b and c there in a too is that possible ?? here is what i want to do in code :
AccommodationRoomModel which is the B model:
public function accommodation(){
return $this->belongsTo(Accommodation::class);
}
public function roomPricingHistory(){
return $this->hasMany(RoomPricingHistory::class);
}
and in The accomodation model :
public function accommodationRoom()
{
return $this->Hasmany(AccommodationRoom::class);
}
and finaly in the room pricingHistory :
public function accommodationRoom(){
return $this->belongsTo(AccommodationRoom::class);
}
now in my accomodation controller i want to get All the accomodation with the room and from room i want to get the price so here is it
A = Accomodation
B = Room
C = price
and i want to call somehow like this
From A get B and The relation Of it with C and show all in A
You can use Laravels nested eager loading for this:
From the docs:
To eager load nested relationships, you may use "dot" syntax. For
example, let's eager load all of the book's authors and all of the
author's personal contacts in one Eloquent statement:
$books = App\Book::with('author.contacts')->get();
In your case:
$accomodations = Accomodation::with('accommodationRoom.roomPricingHistory')->get();
nested-eager-loading
A::with('B.C');
So, I have three tables: 'cars', 'hotlaptimes' and 'tyrestemperature'. Lets say I'm sensing the tyres temperature in real time. I want to show in a view the best hot lap time, and the avg tyres temperature of that hot lap. I still can't find a solution to get the collection of tyre temperatures. Table 'cars' is the "father", with a 'cars_id' field on 'hotlaptimes' and 'tyrestemperature'. The models:
class Car extends Model
{
protected $table = "cars";
public function hotlaptimes(){
return $this->hasMany('App\Hotlap');
}
public function tyrestemperature(){
return $this->hasMany('App\Tyretemp');
}
}
class Hotlap extends Model
{
protected $table = "hotlaptimes";
public function car(){
return $this->belongsTo('App\Car');
}
}
class Tyretemp extends Model
{
protected $table = "tyrestemperature";
public function car(){
return $this->belongsTo('App\Car');
}
}
This is my closest approach on the controller:
$car = Car::with(['hotlaptimes' => function ($query){
$query->min('time');
}])->get(['id']);
$temperature = Tyretemp::where('cars_id',$car->id);
It gets me an error saying that Property [id] does not exist on this collection instance.
I'm sure it's not impossible, I must be missing something. Any help will be appreciated.
I want to show in a view the best hot lap time, and the avg tyres temperature of that hot lap.
If you are trying to retrieve the car with the best hot lap time this is how you approach it:
$best_hot_lap = Hotlap::with("car")
->orderBy("time", "ASC")
->groupBy("cars_id")
->first();
Then you can either simply use the cars_id attribute of best_hot_lap to retrieve Tyretemp
$temp = Tyretemp::where("cars_id", $best_hot_lap->cars_id)->get();
Or use the Car relationship in your Hotlap model
$temp = $best_hot_lap->car->tyrestemperature;
Did you try just using eager loading? There is a section in the laravel documentation under "Relationships".
I can't test right now, had only small break, but maybe try something like
$bestlap = Hotlap::with('car.tyrestemperature')->sortBy('time')->first();
dump($bestlap->car->tyrestemperature->avg('temperature'));
It should load the best lap and eagerload the corresponding car and tyrestemperatures
Edit
I just realised you only want the temperature, not the lap...
Temperature::where('car_id', function($query) {
return $query->select('car_id')->from(with(new Hotlap())->getTable())->sortBy('time')->first();
})->get();
This will get a collection of temperatures with the car_id that we will get by getting the car_id (the select part) of the first entry of the hotlap table sorted by time.
I guess you can work the average in there by yourself.
I am currently running on Laravel 5.1.19 and am observing the following issue:
Assume the following models (Students and Teachers as example):
class Teacher extends \Illuminate\Database\Eloquent\Model {
public function rel_students() {
return $this->hasMany(Student::class);
}
}
class Student extends \Illuminate\Database\Eloquent\Model {
public function rel_teacher() {
return $this->belongsTo(Teacher::class);
}
}
If you then query an instance of Teacher and (lazy) eager load its Students, the ->rel_teacher magic member of all students triggers a fresh query against that teacher:
$teacher = Teacher::with('rel_students')->find(1);
// or this, the effect is the same
$teacher = Teacher::find(1); $teacher->load('rel_students');
foreach ($teacher->rel_students as $student)
echo $student->teacher->name . "<br>";
Query log for the above code:
SELECT * FROM teachers WHERE id = 1
SELECT * FROM students WHERE teacher_id IN (1)
SELECT * FROM teachers WHERE id = 1
SELECT * FROM teachers WHERE id = 1 # what is going on here?!
SELECT * FROM teachers WHERE id = 1
... and so forth for every student ...
The issue: Eloquent has an instance of Teacher #1 when find(1) finishes. I expect eloquent to pass a reference to this very PHP-Objet to the eager loaded Students so that $student->teacher returns that reference instead of triggering another query.
another, negative sideffect: even if the queries were no performance issue (which they are!) i'd have hundrets of instances for Teacher #1 fyling around and that is very contrary to the unit-of-work pattern.
Question: What can i do to make eloquent behave as i expect it to? if not possible: which of the PHP-ORMs are "intelligent" enough and do this simple trick?
UPDATE 1: I did try to remove the underscore from the relation name - same result.
UPDATE 2: As mentioned in an answer, with/load with both relations does a almost-perfect job: $teacher->load('rel_students.rel_teacher').
This reduces the queries for the teacher to 2, but that still leaves the issue that $student->rel_teacher !== $teacher. This is fine until someone modifies either object and then it starts getting hairy.
If you want to reference the teacher from students you need to lazy load them as well for each student so with('rel_students.rel_teacher') and then echo $student->rel_teacher->name OR use the teacher model you are looping on echo $teacher->name
EDIT
$teacher = Teacher::with('rel_students.rel_teacher')->find(1);
foreach($teacher->rel_students as $student)
{
if($teacher->id != $student->rel_teacher->id)
{
dd('error');
}
echo $student->rel_teacher->name . "<br>";
}
I have a many to many relationship for orders and products.
<?php
class Order extends Eloquent {
public function user()
{
return $this->belongsTo('User');
}
public function products()
{
return $this->belongsToMany('Product');
}
}
?>
<?php
class Product extends Eloquent {
public function orders()
{
return $this->belongsToMany('Order');
}
}
?>
Need to fetch the number of times each product is ordered.In mysql,this task can be achieved by using the following query
SELECT products.id, products.description, count( products.id )
FROM products
INNER JOIN order_product ON products.id = order_product.product_id
INNER JOIN orders ON orders.id = order_product.order_id
GROUP BY product_id
LIMIT 0 , 30
Result of the above query is as follows:-
id description count(products.id)
1 Shoes 3
2 Bag 2
3 Sun glasses 2
4 Shirt 2
How this task can be achieved using laravel eloquent (without using query builder)????How can i fetch the number of times each product is ordered using laravel eloquent??
For future viewers, as of Laravel 5.2, there is native functionality for counting relationships without loading them, without involving your resource model or accessors -
In the context of the example in the approved answer, you would place in your controller:
$products = Product::withCount('orders')->get();
Now, when you iterate through $products on your view, there is a orders_count (or, generically, just a {resource}_count) column on each retrieved product record, which you can simply display as you would any other column value:
#foreach($products as $product)
{{ $product->orders_count }}
#endforeach
This method produces 2 fewer database queries than the approved method for the same result, and the only model involvement is ensuring your relationships are set up correctly. If you're using L5.2+ at this point, I would use this solution instead.
Mind that Eloquent uses Query\Builder under the hood, so there is no such thing in Laravel, like 'query eloquent without using query builder'.
And this is what you need:
// additional helper relation for the count
public function ordersCount()
{
return $this->belongsToMany('Order')
->selectRaw('count(orders.id) as aggregate')
->groupBy('pivot_product_id');
}
// accessor for easier fetching the count
public function getOrdersCountAttribute()
{
if ( ! array_key_exists('ordersCount', $this->relations)) $this->load('ordersCount');
$related = $this->getRelation('ordersCount')->first();
return ($related) ? $related->aggregate : 0;
}
This will let you take advantage of eager loading:
$products = Product::with('ordersCount')->get();
// then for each product you can call it like this
$products->first()->ordersCount; // thanks to the accessor
Read more about Eloquent accessors & mutators,
and about dynamic properties, of which behaviour the above accessor mimics.
Of course you could use simple joins to get exactly the same query like in you example.
If you already have the $products object, you can do the following:
$rolecount = $products->roles()->count();
Or if you are using eager loading:
$rolecount = $products->roles->count();
Cheers.
I am using Laravel 5.1 and i am able to accomplish that by doing this
$photo->posts->count()
And the posts method in Photo model looks like this
public function posts(){
return $this->belongsToMany('App\Models\Posts\Post', 'post_photos');
}
I have 4 mysql tables, as the following:
Makes:
id - make_name
Models:
id - model_name - make_id
Trims:
id - trim_name - model_id
Forsale_Cars:
id - trim_id - year - price
in the Makes table I have ~700 records, so my question is, how can get a list of Makes which only have a child trim in the forsale table?
I have 20 records in the forsale table, I want to get the list of Makes for these cars.
I am using Laravel, so if anybody has achieved that previously using eloquent it will be great
Eloquent way:
// Makes that have forsale nested relation
Make::whereHas('models', function ($q) {
$q->whereHas('trims', function ($q) {
$q->has('forsales');
});
})->get(); // returns Eloquent Collection
Models with correct relations (hasMany can be replaced with hasOne if that's actual relation somewhere):
// Make model
public function models()
{
return $this->hasMany('CarModel');
}
// CarModel (as you can't use Model name)
public function trims()
{
return $this->hasMany('Trim');
}
public function make()
{
return $this->belongsTo('Make');
}
// Trim model
public function forsales()
{
return $this->hasMany('Forsale');
}
public function carModel()
{
return $this->belongsTo('CarModel');
}
// Forsale model
public function trim()
{
return $this->belongsTo('Trim');
}
You could use this query for the first question you asked. Not sure if the this completely answers your question.
SELECT * FROM Makes m left join Foresale_Cars fc on (m.id = fc.id) left join Trims t on (fc.trim_id = t.id) where t.trim_name = "child trim"
I assume you have all your models set up, e.g.:
class Makes extends Eloquent {
protected $table = 'makes';
public function models() {
return $this->hasMany('Model', 'makes_id');
}
}
and so on.. (you have to do this with all your models, of course)
Now, if you want to get all the cars for sale you'd simply chain some foreach loops:
foreach( $makes->models as $model ) {
foreach( $model->trims as $trim ) {
{{ $trim->forsale_cars }}
...
}
}
Edit: Yes you can use raw queries, of course, but using models and the power of eloquent is much more elegant and useful...
For more information on this topic: http://laravel.com/docs/eloquent#relationships