Eloquent Relationship Between Two Unions - laravel

I have four models. TableFoo and TableFooArchive share a common schema (items for TableFoo are periodically moved to TableFooArchive). I also have TableBar and TableBarArchive. TableFoo may or may not have one related row in TableBar or TableBarArchive. TableFooArchive may or may not have one related row in TableBarArchive.
I'd like to be able, using Eloquent, to union TableFoo and TableFooArchive then eager load the union of TableBar and TableBarArchive. If I only had TableFoo and TableBar, I could just set up a hasOne on the model and be done. How can I achieve the same with all four tables?
My start is to have a repository with the following:
public function getData($id)
{
$a = TableFoo::selectRaw('
columnA,
columnB as column_b_abc,
columnB as colum_b_def,
created_at
')
->whereRaw("
id = $id,
and so forth...
");
$b = TableFooArchive::selectRaw('
columnA,
columnB as column_b_abc,
columnB as colum_b_def,
created_at
')
->whereRaw("
id = $id,
and so forth...
")
->unionAll($a)
->orderBy('created_at', 'DESC')
->get();
return $b;
}
My guess is that it's possible to define a method on each model that returns TableBarTableBarArchive. I'm just not sure how I would do that nor how I would pull it through on the above using ->with('unionedResults'). FWIW, I can't change the table structure to use two instead of four.
I feel like there's probably an obvious and simple solution that just isn't occurring to me at the moment. Any help or insight would be appreciated!
UPDATE:
What I was looking for was to lazy load the relationship. The example above achieves this provided you have the correct methods defined on the related models.
It would still be interesting to see an example of eager loading the whole thing.

Related

Laravel - Get records between two dates from second table

I have something like this:
Table 1: Training Name, created_at, user_id (Plan_Treninga)
Table 2: user_id, created_at, expire_at (InvoiceUser)
I want to pull all from Table 1 where created_at is between Table 2 created_at and expire_at.
This is something what i am trying to..
$plan = Plan_Treninga::whereBetween(function($q) use ($id){
$inv = InvoiceUser::where([
["user_id",$id],
["status","paid"],
])->latest("id")->first();
})
I haven't finished it yet, but my brain stopped working so I have to ask here.
If I understand what you want clearly is. you want to query all from table 1 which created exist between table 2 created and expire_at right? if so you can use where exist query to achieve this.
// assume your table name is plan_treningas & invoice_users
Plan_Treninga::whereExists(function ($query) {
$query->select(DB::raw(1))
->from('invoice_users')
->whereRaw('plan_treningas.created_at BETWEEN invoice_users.created_at AND invoice_users.expire_at'); // add more query depend your logic
})->get();
for more you can take a look at docs
or if you want to use raw query
SELECT
*
FROM plan_treningas
WHERE EXISTS (
SELECT 1 FROM invoice_users WHERE plan_treningas.created_at BETWEEN invoice_users.created_at AND invoice_users.expire_at
)
Take a look at joins https://laravel.com/docs/7.x/queries#joins
I am not saying this is the exact solution but I have something similar that I have changed to point you in the right direction.
With joins you can do lots of things.
$results = DB::table('table1')
->join('table2', function ($join) {
$join->on('table1.user_id', '=', 'table2.user_id')
->where('table2.status', '=', 'paid')
->where('table2.created_at', '>', 'table1.created_at');
})
->get();
Also look at relationships. There is some good answers for setting up many to many relationships.
https://laravel.com/docs/7.x/eloquent-relationships#many-to-many

Laravel - How to paginate united Many To Many(Polymorphic) collection?

Trying to figure out how to fetch two related models(obviously united) of my Many To Many(Polymorphic) relationship.
What we have:
3 models: Bucket, Template and DesignPack.
Bucket has Many-To-Many(Polymorphic) relationship with Template and DesignPack(It means we have pivot table bucketables).In essence Bucket can have(be related with) both: Template and DesignPack.
Laravel 6.*
What I want to get:
I want to get a Bucket templates and design packs united in one collection and paginated!
Please check one of the solutions I've tried:
$templates = Bucket::find($bucket_id)->templates()->select(['id', 'file_name as name', 'size', 'preview']);
$design_packs = Bucket::find($bucket_id)->dps()->select(['id', 'name', 'size', 'preview']);
$all = $templates ->union($design_packs )->paginate(10);
Unfortunately that solution throws me the error(thought I checked what each request returns and it returns the same fields, not different):
"SQLSTATE[21000]: Cardinality violation: 1222 The used SELECT statements have a different number of columns (SQL: (select `id`, `size`, `preview`, `bucketables`.`bucket_id` as `pivot_bucket_id`, `bucketables`.`bucketable_id` as `pivot_bucketable_id`, `bucketables`.`bucketable_type` as `pivot_bucketable_type` from `design_packs` inner join `bucketables` on `design_packs`.`id` = `bucketables`.`bucketable_id` where `bucketables`.`bucket_id` = 3 and `bucketables`.`bucketable_type` = App\DesignPack and `design_packs`.`deleted_at` is null) union (select `id`, `size`, `preview` from `templates` inner join `bucketables` on `templates`.`id` = `bucketables`.`bucketable_id` where `bucketables`.`bucket_id` = 3 and `bucketables`.`bucketable_type` = App\Template and `templates`.`deleted_at` is null))"
Are there any different way to get what I want?
May be examples, documentation links or any helpful ideas?
Will be so grateful guys for any help!
Thank you!
You can pass closure to queries:
$templates = Bucket::whereHas('templates', function($query) use $bucket_id {
$query->where('bucket_id', $bucket_id);
})->get();
$designPacks = Bucket::whereHas('dps', function($query) use $bucket_id {
$query->where('bucket_id', $bucket_id);
})->get();
then merge 2 eloquent collections:
$mergedCollections = $templates->merge($designPacks);
now you have a collection of both results, you can select specific fields, limit the results or etc. you may want take a look at Laravel collection helpers.
also if you insist to use the union, you may want to take a look at this treat:
The used SELECT statements have a different number of columns (REDUX!!)

How can i convert this sql query in a eloquent laravel command?

in sql query this commando do exactly i wanted:
SELECT
v.id,
(
SELECT sv.status_id
FROM status_viagem sv
WHERE sv.viagem_id = v.id
ORDER BY sv.created_at DESC LIMIT 1 ) AS status_id
FROM viagens v
Here is the sql results:
But i have no idea how can i do this using Laravel eloquent
Basically, a viagem entry can has a lot of status, but i need to get each viagem and their last status entry from status_viagem table (the pivot table)
by the way viagem/viagens means travel.
My class mapping:
class Viagem extends Model
{
...
public function status()
{
return $this->belongsToMany('App\Status')->withTimestamps();
}
...
}
class Status extends Model
{
public function viagens()
{
return $this->belongsToMany('App\Viagem')->withTimestamps();
}
}
The belongsToMany at both classes gets me a many-to-many:
can someone help me? thanks
---------- Temporary Solution -------
Thanks for all help guys. In fact i can't find a nice solution using only eloquent.
Step 1/3 - To bypass this situation i first execute the above sql to grab only the viagens under the desired status_id (last status_viagem entry):
$viagens_ids = DB::select(
"SELECT viagem_id FROM (
SELECT
v.id AS viagem_id,
(
SELECT sv.status_id
FROM status_viagem sv
WHERE sv.viagem_id = v.id
ORDER BY sv.id DESC LIMIT 1 ) AS status_id
FROM viagens v
) AS tt
WHERE tt.status_id = {$status->id}"
);
Step 2/3 - then i used the array_map to organize my viagens ids
$a = array_map(
function($obj) { return $obj->viagem_id; },
$viagens_ids
);
Step 3/3 - And at last i used elequent whereIn to fetch my viagens:
Viagem::with( 'status')->whereIn('id', $a)->get();
In fact i have solved the problem by a-old-sashion-way but i not happy with it because i wish i learn how to do it using eloquent. what bad to me.
There are many ways to query in laravel. I have created a test project for you to try. The gist are:
1. Eloquent ORM
Eloquent ORM is Laravel's magic which have some limitations in eager loading - which i just come across while contemplating your question for hours. It wont play nicely with first(), last(), and some more functions in the constrained eager loading closure.
In your case, our almost there can be fixed:
App\Models\Viagem::with(['status' => function($query){
return $query->orderBy('pivot_created_at', 'desc');
}])
->get()
It will return entire field for Viagem and Status including its pivot table (the status_viagem).
However, if you wanted to retrieve only viagem.id and status_viagem.status_id, you can map() it as such:
App\Models\Viagem::with(['status' => function($query){
return $query->orderBy('pivot_created_at', 'desc');
}])
->get()
->map(function($data){
$o = new stdClass();
$o->id = $data->id;
$o->status_id = $data->status->first()->id;
return $o;
});
Please take note that the statement above require sql query to be ran twice. Eager loading basically works by querying all the Viagem first then queries the Status and map them in memory based on the foreign keys. You can observe that replacing get with toSql will only give you the first query. Please enable Query Logging to see the second query.
2. Query Builder
Embarking from Ryan Adhitama Putra answer, you could do something like:
App\Models\Viagem::join('status_viagem', 'viagens.id', '=', 'status_viagem.viagem_id')
->orderBy('status_viagem.created_at', 'desc')
->groupBy(['status_viagem.status_id', 'viagens.id'])
->select(['viagens.id', 'status_viagem.status_id'])
->get();
This query builder approach guaranteed to be ran only once, you can replace the get() with toSql() to see the resulting query.
3. Raw Queries
Throwing DB::raw() can help sometime, but i really did not want to mention it.
I am not sure what viagens and viagem represent, but I think one of the relationships has to be belongsToMany() and the other hasMany().
then after you set relationships correctly, you can use Eloquent like this :
$status_id = Viagem::with('status')->orderBy('created_at', 'desc')->first()->pluck('status_id');
Try this.
$status_id = Viagem::join('status','viagem.id','status_viagem.viagem_id')
->select('viagem.id','status_viagem.status_id')
->get();

Laravel, Many-to-many relationships, find() vs where()

I have a Laravel 4.2 site with a pretty simple database layout. The important part is a People model and a Subject model that have a many-to-many relationship. This works, so that, for instance:
$id = 5;
$ppl = Subject::find($id)->people()->orderBy('lastname')->get();
Returns all People for a given Subject. What I'm trying to do is instead of finding all the People for a single subject, to find all the people for multiple subjects. My guess was something like this:
$subjects = array(5, 6, 7);
$ppl = Subject::whereIn('id', $subjects)->people()->orderBy('lastname')->get();
That doesn't work (undefined method people()). Neither does the following (undefined property people):
$ppl = Subject::whereIn('id', $subjects)->people->orderBy('lastname')->get();
I'm currently just using raw SQL t get around this. How can I use eloquent relationships with where() or whereIn() calls on a model? Or, is there just a better eloquent way of approaching this problem.
Edit: Here's the raw SQL I used to get a list of the people.id's for a given array of subjects:
SELECT
DISTINCT(people.id)
FROM people
LEFT JOIN person_subject ON person_subject.person_id=people.id
WHERE
person_subject.subject_id IN (%s) AND
deleted_at IS NULL
Your eloquent relationships should allow you to eagar load the people related to a subject, you can do something like this:
Subject::whereIn('id', $subjects)->with('people')->orderBy('lastname')->get();
This will return your people related to the subjects.
Let me know if this doesn't work.
What you need is to join the tables to order by the lastname:
$ppl = Subject::whereIn('id', $subjects)
->select('subjects.*')
->distinct()
->join('people', 'people.id', '=', 'subjects.people_id')
->orderBy('lastname')
->get();
Check all your tables names because I don't know them and above are just an example one.

Eloquent query if value in relationship isn't same as value in other relationship

I have four models: Item, Car, ItemLocation and Branch.
An Item has a Car and a ItemLocation via a person_id and a item_location_id field in the DB.
A Car has a branch_id which links to Branch, and an ItemLocation also has a branch_id in the same way.
What I want to do is to select all Items where their Cars's branch is not equal to their ItemLocation's branch.
I tried this statement, though I knew that it wouldn't work:
Item::with('car','item_location')
->where('car.branch_id', '<>', 'item_location.branch_id')
->get();
I'm aware of querying on relationships, but don't understand how to do that across relationships like this.
Any ideas?
Querying relationships won't help you, since you want to compare values from separate tables. You need joins:
$items = Item::select('items.*')
->join('item_locations as il', 'il.id', '=', 'items.item_location_id')
->join('cars', function ($j) {
$j->on('cars.person_id', '=', 'items.id')
->on('cars.branch_id', '<>', 'il.branch_id');
})
->get()
This will fetch all the items having both cars and item_location and matching your criteria. If you want to include also ones that don't have either of the relations, then use leftJoins instead and whereNull('cars.id')
ps. It's hard to read your question. Instead of describing these relationships, better simply show the tables with relevant fields.

Resources