Laravel: Eager loading duplication - laravel-4

I'm trying to set up eager loading to prevent N+1 queries, but I'm finding that I have to duplicate some eager loads in order to get laravel to use the 'eager loaded' data. Duplicating the eager loads of course results in duplicated queries.
This is my load call:
$provider->load('ads.gallery.images', 'ads.thumbnail', 'ads.user', 'ads.provider.values.field', 'values.field', 'galleries.images');
A provider is a polymorphic subtype of a user. This is for a user profile page, which displays the user values list, as well as a list of all their ads. The ad display is a sub-view that's reused across the site and has a tab that displays the user values list as well. Yes it's redundant on this page, but that's how the site is designed.
I would expect laravel to reuse the already-loaded values.field list when the sub-view accesses it, but it doesn't, so I had to add ads.provider.values.field to the list, which duplicated the queries:
select `provider_field_values`.*, `provider_values`.`user_id` as `pivot_user_id`, `provider_values`.`value_id` as `pivot_value_id` from `provider_field_values` inner join `provider_values` on `provider_field_values`.`id` = `provider_values`.`value_id` where `provider_values`.`user_id` in ('3')
select * from `provider_fields` where `provider_fields`.`id` in ('1', '2', '3')
select `provider_field_values`.*, `provider_values`.`user_id` as `pivot_user_id`, `provider_values`.`value_id` as `pivot_value_id` from `provider_field_values` inner join `provider_values` on `provider_field_values`.`id` = `provider_values`.`value_id` where `provider_values`.`user_id` in ('3')
select * from `provider_fields` where `provider_fields`.`id` in ('1', '2', '3')
Is there any way I can eliminate this duplication? I'm not sure if the issue is in how the views access the data or if it's in the controller. I can add snippets of the view code if needed.
Edit: Model relationships:
//Provider model
public function values()
{
return $this->belongsToMany('ProviderFieldValue', 'provider_values', 'user_id', 'value_id');
}
//ProviderField model
public function fieldValues()
{
return $this->hasMany('ProviderFieldValue', 'field_id');
}
//ProviderFieldvalue model
public function field()
{
return $this->belongsTo('ProviderField');
}
//ProviderValue model (this is a pivot table)
public function provider()
{
return $this->belongsTo('Provider');
}
public function fieldValue()
{
return $this->belongsTo('ProviderFieldValue');
}

Related

How to retrieve data with composite primary key in Laravel

I am working with four tables in Laravel and trying to display data to a view. I believe I am stuck because one of the tables has a composite primary key.
I have the following in my controller:
public function show($id)
{
//Get application for drug
$application = PharmaApplication::where('ApplNo', $id)->first();
//Return all products for application
$drugs = $application->products;
return view('profiles.drug', compact('drugs'));
}
I have the following in my PharmaApplication model:
public function products()
{
return $this->hasMany('App\PharmaProduct', 'ApplNo', 'ApplNo');
}
I have the following in my view (which I cannot complete)
#foreach ($drugs as $drug)
<li>{{$drug->Strength}}</li>
<li>{{$drug->Form}}</li>
<li>{{$drug->Form}}</li>
#endforeach
I am trying to accomplish the following:
Get the application for a drug - this part of my code works
Return an array of objects for the products (i.e. 7 products that belong to one application). I can do this but get stuck going to the next part.
Next, I have to use the array of objects and search a table with the following columns: MarketingStatusID, ApplNo, ProductNo. I know how to query this table and get one row using DB Query, but then how do I get the proper result to that query within the for loop in my view?
Finally, I use the MarketingStatusID to retrieve the MarketingStatusDescription which I will know how to do.

Laravel whereHas ManyToMany relation but then order by a value in the third table

I have 3 tables
entry (
id,
title,
other_stuff)
entry_award (
id,
award_id,
entry_id)
award (
id,
name,
type)
I am trying to create a laravel query which lets me get all the entries that have awards, and order them by award.type ASC
$Entries = Entry::with('award')
->whereHas('award', function ($query) {
$query->orderBy('award.award_id','ASC');
})->paginate(20);
But this doesn't work.
This is the sql version of it
SELECT DISTINCT entry.*
FROM entry, award, entry_award
WHERE entry.id = entry_award.entry_id
AND award.id = entry_award.award_id
ORDER BY award.type ASC;
Now I tried to just use the raw sql for it, but the problem seems to be that laravel does not then recognize the result as Entry models/objects. And i need to get other Entry relations later on in the html via blade.
So how can I either make a query-builder query that gets me all entries that have awards and orders them by award.type value
or use the raw sql but have Laravel see it as an array of Entry
objects instead of just an array of JSON values.
class Entry extends Model {
public function entry_award(){
return $this->belongsToMany('App\Award', 'entry_award');
}
}
class Award extends Model {
public function entries() {
return $this->belongsToMany('App\Entry', 'entry_award');
}
}

Laravel returns a Collection with duplicates of the first model

I'm developing a Laravel 5.7 (API) application with a PostgreSQL database behind it. The relevant Models are: User (customers and employees), Car, and Request.
An employee User creates a Request for a Car, that belongs to a customer User.
The relationships are:
Car (as customer) : User = n:m
Car : Request = 1:n
User : Request (as employee) = 1:n
(The data design is suboptimal, to put it mildly, but anyway, it's the given reality for now.)
Now to the actual issue. I want to display all Requests of a customer User:
Request::query()
->join('user_car', 'user_car.car_id', '=', 'request.car_id')
->join('user', 'user.id', '=', 'user_car.user_id')
->where('user.id', '=', $customer->id)
->select()
->get();
The customer with the given $customer->id has n Requests. And the length of the result Collection of the call above is correct. But all these n entries are duplicates of the first one. Means: I'm getting a list with n instances of Request#1.
Why does the first call return a list of references to the same Model object? Is it a (known) bug?
ADDITIONAL INFORMATION
Relationships:
class User extends \Illuminate\Foundation\Auth\User
{
// ...
public function cars()
{
return $this->belongsToMany('App\Car', 'user_car')->withTimestamps();
}
public function requests()
{
return $this->hasMany(Request::class, 'user_id');
}
}
class Car extends Model
{
// ...
public function users()
{
return $this->belongsToMany('App\User', 'user_car')->withTimestamps();
}
public function requests()
{
return $this->hasMany(Request::class);
}
}
class Request extends Model
{
// ...
public function car()
{
return $this->belongsTo(Car::class);
}
public function user()
{
return $this->belongsTo(User::class);
}
}
The query is correct.
I logged the database requests, got the generated statement
SELECT *
FROM "request"
INNER JOIN "user_car" ON "user_car"."car_id" = "request"."car_id"
INNER JOIN "user" ON "user"."id" = "user_car"."user_id"
WHERE "user"."id" = 1;
..., and executed it manually. The result table contains as expected n different entries.
NOT just references
The result Collection's entries instances references to the different objects:
$test1 = $resultCollection->first();
$test2 = $resultCollection->last();
$test3 = spl_object_hash($test1);
$test4 = spl_object_hash($test2);
Xdebug output:
$test3 = "0000000077505ccd000000007964e0a8" <-- ccd0
$test4 = "0000000077505c33000000007964e0a8" <-- c330
Workaround
I found a workaround. This call
Request::whereIn('car_id', $customer->cars()->pluck('id')->toArray())->get();
... retrieves the correct/expected set of model.
First, note that your object hashes are not actually identical, and you're likely dealing with two separate instances.
What you're likely experiencing is an issue with ambiguous column names. When you JOIN together multiple tables, any matching/duplicate column names will contain the value of the last matching column. Your SQL GUI/client usually separates these. Unfortunately Laravel doesn't have a prefixing mechanism, and just uses an associative array.
Assuming all of your tables have a primary key column of id, every Request object in your result set will likely have the same ID - the User's ID you pass in the WHERE condition.
You can fix this in your existing query by explicitly selecting the columns you need to prevent ambiguity. Use ->select(['request.*']) to limit the returned info to the Request object data.

Laravel - eager loading filtering related model on different database instance

i'm trying to implement filtering as defined here.
This works (to filter model A from controller A), however controller A/model A has a relation to model B, which is what I want to filter, as well as a 3rd relationship to model C from model B.
Model A is hosted on DB instance 1, Model B is hosted on DB instance 2 and are totally separated.
These relationships, without any filters, work fine.
Trying to mess around, I tried something like the below, which clearly does not work, however will hopefully illustrate what i'm trying to do. This filter gets applied to model A
protected function sn($sn)
{
$s= Bid::where('crm', function ($query) {
$query->where('sNumber', '=', 'SN512345');
})->get();
return $s;
}
SQLSTATE[42S22]: [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Invalid column name 'crm'. (SQL: select * from [bids] where [crm] = (select * where [sNumber] = SN512345))
Bid is model A/controller A, CRM is model B which is the one I want to filter.
I thought about having numerous different functions in the model to filter, however I don't know if this was the best solution and I thought it was better to get it all out into another class.
I tried the below, which does not work as it applies the query to DB1.
$s= Bid::with('crm')->whereHas('crm', function ($query) {
$query->where('sNumber', '=', 'SN512345');
})->get();
[SQL Server]Invalid object name 'Opportunity'. (SQL: select * from [bids] where exists (select * from [Opportunity] where [bids].[crm_ref] = [Opportunity].[sNumber] and [sNumber] = SN512345))
Is there a way to implement this in some coherent and reusable way? I was thinking something along the lines of load Bid::, load CRM:: with applied filters, then append CRM:: to Bid:: somehow in the normal way that Eloquent would do this.
Thanks.
EDIT:
I am using the following filter in BidFilter.php
protected function sn($sn)
{
$users = DB::connection('sqlsrv_crm')->table('OpportunityBase')->select('*')->where('new_SalesNumber', '=', $sn)->get();
return $users;
}
And this filters the result set, as I can see in the debug bar:
debug bar queries
However this is also loading the normal unfiltered eager loaded CRM relationship. How can I switch to the filtered CRM results instead of the default unfiltered?
BidController index method:
public function index(BidFilter $filters)
{
$bids = $this->getBids($filters);
return view('public.bids.index', compact('bids'));
}
BidFilter
public function index(BidFilter $filters)
{
$bids = $this->getBids($filters);
return view('public.bids.index', compact('bids'));
}

How to retrieve data through model?

I have Order model with another relation OrderPhoto:
public function OrderPhoto()
{
return $this->hasMany('App\OrderPhoto');
}
In turn OrderPhoto model has relation:
public function Photo()
{
return $this->belongsToMany('App\Photo');
}
So, how to get data from OrderModel with related data from third model Photo?
I guess this:
Order::with("OrderPhoto.Photo")->get();
to retrieve only data from Photo model for each Order
So, each Order has some OrderPhotos. Relationship is one to many.
But one item from OrderPhotos is related with primary key from table Photos. It is one to one relation.
My result query should be:
select `photos`.*, `ordersphoto`.`Orders_Id` from `photos` inner join `ordersphoto` on `ordersphoto`.`Photos_Id` = `photos`.`Id` where `ordersphoto`.`Orders_Id` in (1);
How to use hasManyThrough for this query?
Just having a quick look at your relationships it looks like you could create a hasManyThrough relationship on the order Model.
public function Photo {
return $this->hasManyThrough('App\OrderPhoto', 'App\Photo')
}
You may need to add the table keys to make it work
This will allow you to do:
Order::with("Photo")->get();
You can see more details here https://laravel.com/docs/5.5/eloquent-relationships#has-many-through
Update
Try this
public function Photo {
return $this->hasManyThrough('App\Photo', 'App\OrderPhoto', 'Order_id', 'Photos_id', 'id', 'id')
}
It is a little hard to get my head around your DB structure with this info but you should hopefully be able to work it out. This may also help
https://laravel.com/api/5.7/Illuminate/Database/Eloquent/Concerns/HasRelationships.html#method_hasManyThrough

Resources