Eloquent relations from multiple joined tables - laravel

Let's say we have a table users and we are left joining multiple tables to it.
$users = User::query()
->select('users.id', 'bananas.id as banana_id', 'dogs.id as dog_id')
->leftJoin('bananas', 'banana.user_id', '=', 'users.id')
->unionAll($usersWithDogs) // a similar query with a left join on `dogs`
->orderByDesc('users.created_at')
->paginate(...);
We end up with a collection of User models with attributes id, dog_id, banana_id.
Now imagine we want to eager load these, but the eloquent relations are based on the one to many relationships, $user->dogs, $user->bananas.
Trying to find a solution that will do all the following:
a) not break pagination
b) allow ordering on the user table
c) allow eager loading
d) use clean code
e) end up with a collection of users
Brainstorming so far has led to the following options:
A union of bananas and dogs, eager load the user relation, then invert the collection (messy code)
Dynamic relationships created on User, possibly with a macro on \Illuminate\Database\Eloquent\Builder. Maybe by leveraging Model::resolveRelationUsing()
Manual eager loading with a union select with a left join to each table in each arm of the union, then a whereIn() to get the related records
Restructure the relations so that there is a polymorphic many to many relationship between users and other entities
e.g.
user_id | model_type | model_id
1 | App\Models\Banana | 2
1 | App\Models\Dog | 5
Maybe I'm missing something obvious...?

Related

Laravel. Join three tables from three way pivot where one column (jsonb) contains an array of id's

I have a table called user_business_survey which has a user_uuid, business_uuid and a third containing one or more survey_uuid's IN A JSONB column. I need to query the DB to bring back the following two scenarios: USER1 belongs to BUSINESS1 AND HAS SURVEY A, SURVEY B, SURVEY C then also USER1 belongs to BUSINESS3 AND HAS SURVEY B, SURVEY D, SURVEY E
This is the query I am trying but to no avail. It return empty.
DB::table('user_business_survey')
->select(['*'])
->leftJoin('businesses', 'businesses.uuid', '=', 'user_business_survey.business_uuid')
->leftJoin('surveys', 'surveys.uuid', '=', 'user_business_survey.survey_uuid')
->whereJsonContains('survey_uuid', 'surveys.uuid')
->where('user_business_survey.user_uuid', '=', $id)
->get();
DB table is as follows:
user_uuid
business_uuid
surveys_uuids is an column with one or more survey id's as jsonb
I need to itrate through all id's and join them from the jsonb column
I thank you in advance for any help to build this query

Laravel where clause of current and related table

how to compare current table column to related table column
example: A.quantity < B.criticalQuantity
similarly like this AModel::where('quantity', "<", "b.criticalQuantity")->get()
the relations is
B HasMany A
A BelongsTo B
you can use whereColumn
it is specialist in comparing columns not a column with value.
anyway you can't directly compare two columns from two table, you have to join them first by anyway of join types
something like:
$values = ModelA::join('model_b_table_name', 'model_b_table_name.id', 'model_a_table_name.model_b_id')
->whereColumn('model_b_table_name.column.quantity', 'model_b_table_name.quantity')
->get();
you must be specific in joining the table, you should join by the columns that consist the relation between the two tables.
->whereRaw('table_1.name = table_2.name')

Is it possible to join eloquent relationship query?

I have a users, departments, and positions table.
I want to select one department with all the users in that department with their position name.
Currently, I have this.
$department = DepartmentView::with('users')
->findOrFail($departmentId);
It returns me a department with users, but I want to join the users with positions table so I can also get the position name. (user table only has position id)
Assuming you have your relationships setup properly in your User model and the relationship is called position, it should be like this:
$department = DepartmentView::with('users.position')
->findOrFail($departmentId);
Look at eager loading -> nested eager loading.
You can do
$department = DepartmentView::with('users.position')->findOrFail($departmentId)
position is referred to the relationship set on the User model to get the user position.

Laravel Eloquent select function cause empty relation

Following is my query
$user = User::select(['uuid','name','about'])->with(['education','work'])->first();
this returns empty data for relationship education and work,
but if I remove select function from query I am getting data in relationship and it also returns all columns of user table which I don't want.
how can solve this problem
The problem is that relationships (with(...)) execute an additional query to get the related results. Let's say you have one to many relationship where users have many works. User::with('work')->find(1) will then execute these 2 queries:
select user where id = 1 and select works where user_id = 1.
So basically in order to be able to execute the second query (fetch relationship data) you need to include id (or whichever column you're referencing) in you select statement.
Fix:
$user = User::select(['uuid','name','about', 'id'])->with(['education','work'])->first();
Same principle in different forms applies to all relationships. For example in the inverse of hasMany which is belongsTo you would need to select the foreign key (for example user_id).

Eloquent makes a lot of queries

I just started playing with Laravel 4 and Eloquent. I have a blog table and lots of other related tables to it:
blog <- main info about the blog record
blog_lang <- translations for each blog record
blog_categories <- name speaks for itself
blog_categories_lang <- translations for blog categories titles
blog_to_categories <- pivot table between blog and blog_categories
blog hasMany blog_lang.
blog_categories hasMany blog_categories_lang
blog belongsToMany blog_categories
I want to show the following info in one grid : blog_id, blog_title, username, and all categories:
$data['blogs'] = Blog::with(array(
'translations' => function ($q) {
$q->where('lang_id', '=', 1);
},
'user',
'categories',
'categories.translations' => function ($q) {
$q->where('lang_id', '=', 1);
}
))->get();
This executes 5 queries... aren't they a little too many? Will it be better to just use Fluent and join all these tables with 1 bigger query?
Eloquent making a lot of small, indexed queries is a much better thing than doing one big query for a wide variety of reasons:
MySQL will not have to load multiple tables in temporary memory for each query, and may re-use them between queries
The SQL optimizer will run more swiftly through each query
It allows you to cache your results without having to throw out the JOINs (and other similar clauses) from your data, which makes caching easy
You're not actually noticing it, but the path taken by the SQL optimizer is the same between the following queries:
SELECT a.*, b.* FROM a INNER JOIN b ON (a.id=b.id) WHERE a.id = 1
SELECT a.*, b.* FROM a, b WHERE a.id = b.id AND a.id = 1
Both of them will cause the SQL optimizer to perform these queries under-the-hood:
SELECT a.* WHERE a.id = 1
SELECT b.* WHERE b.id = 1
And from there, depending on your indices, the SQL optimizer will perform matching based on either the indices or the full table data. What is Eloquent doing? Exactly those two queries. You're not gaining anything by one big query - in fact, you are losing on data reusability. In everything, prefer small, optimized, re-usable, cacheable queries to bulky statements.

Resources