Laravel Eloquent, multiple schema query - php

I have the below query (simplified):
$q = ModelOne::with('relation_one', 'relation_two')
->whereHas('relation_three', function ($q) {
$q->where('object', 'Obj1');
})
->whereHas('relation_four', function ($q) {
$q->where('object', 'Obj2');
})
->get();`
It loads the relation_one and relation_two relationships fine, I also need to load another relationship per row, either relation_three or relation_four depending on the value of ModelOne->object.
The issue I am having is that ModelOne is from schema1 and the tables used in relation_three & relation_four are from schema2.
Both models are set up correct with their individual protected $connection and protected $table variables.
The error I am recieving is that the tables for relationship_three or relationship_four does not exist as the sub-query is checking the wrong schema.
Can anyone suggest how to fix this? Have had a look through the docs but couldn't find a solution.

Maybe not the most elegant solution but got this working by calling relationships and joining as follows:
$q = ModelOne::with('relation_one', 'relation_two')
->with(['relation_three' => function ($q) {
$q->leftJoin(
'schema1.model_one',
'table_three.id',
'=',
'model_one.object_id'
)
->where('object', 'Obj1');
}])
->with(['relation_four' => function ($q) {
$q->leftJoin(
'schema1.model_one',
'table_four.id',
'=',
'model_one.object_id'
)
->where('object', 'Obj2');
}])
->get();`
If anyone can suggest some improvements or a more efficient way to do this please let me know

I would suggest separating the different databases relations to different fields, at least. This way you can then load both (as suggested in comments) and differentiate the logic within controller/model code.
Also, I guess you'll need to define the connection name on the Model level, if not done yet:
class Model_Two_Relation {
protected $connection = 'your-database-name-from-config';
}
You also might want to specify the connection within the relation join condition:
$q = ModelOne::with('relation_one', 'relation_two')
->whereHas('relation_three', function ($q) {
$q->from('resources.one')->where('object', 'Obj1');
})
->whereHas('relation_four', function ($q) {
$q->from('resources.two')->where('object', 'Obj2');
})
->get();
Links: http://fideloper.com/laravel-multiple-database-connections

Related

Using Model Relationship in a Single Query

Consider the following:
$posts = $this->model->newQuery()
->whereIn('user_id', $user->following) // specifically this line
->orWhere('user_id', $user->id)
->get();
The problem with the above is that there are two queries:
Get following: $user->following
Get posts: Above
This would be much more efficient with the use of a subquery, however, I cannot actually remember the correct way to do it...
I have tried all of the following:
// This was a long-shot...
...->whereIn('user_id', function ($query) use ($user) {
$query->raw($user->following()->toSql());
});
// This works but pretty sure it can be done better with eloquent...
...->whereIn('user_id', function ($query) use ($user) {
$query->select('follow_id')
->from('user_follows')
->where('user_id', $user->id);
});
Is there a way that this can be achieved by using the previously defined relationship $user->following() instead of manually defining the relationship query like the last example above?
Reference
The following relationship is defined as follows:
/**
* Get the users that the user follows.
*/
public function following()
{
return $this->belongsToMany('SomeApp\User\Models\User', 'user_follows', 'user_id', 'follow_id')
->withTimestamps();
}
Use this:
->whereIn('user_id', $user->following()->getQuery()->select('id'))

Return belonging name with ID form Laravel, check for the type?

sorry for the title of this question but I am not sure how to ask it...
I am working on a project where I have two Models Trains and Cars, to this model I have a belonging Route.
I want to make a query and check if the routeable_type is App\Car than with the selected routeable_id to get the data from the Car. And if the routeable_type is Train then with the ID to get the data from the Tran.
So my models go like this:
Train:
class Train extends Model
{
public function routes()
{
return $this->morphMany('App\Route', 'routeable');
}
}
Car:
class Car extends Model
{
public function routes()
{
return $this->morphMany('App\Route', 'routeable');
}
}
Route:
class Route extends Model
{
public function routeable()
{
return $this->morphTo();
}
}
And the query I have at the moment is:
$data = Route::leftjoin('cars', 'cars.id', '=', 'routes.routeable_id')
->leftjoin('trains', 'trains.id', '=', 'routes.routeable_id')
->select('routes.id', 'cars.model AS carmodel', 'trains.model AS trainmodel', 'routeable_type', 'routes.created_at');
With this query if I have the same ID in cars and trains I get the data from both and all messes up. How do I check if routeable_type is Car ... do this, if routeable_type is Train .. do that?
Will something like this be possible in a 1 single query:
$data = Route::select('routes.id', 'routeable_type', 'routes.created_at');
if(routeable_type == 'Car'){
$data = $data->leftjoin('cars', 'cars.id', '=', 'routes.routeable_id')->select('routes.id', 'cars.model AS carmodel', 'routeable_type', 'routes.created_at');
}else{
$data = $data->leftjoin('trains', 'trains.id', '=', 'routes.routeable_id')->select('routes.id', 'trains.model AS trainmodel', 'routeable_type', 'routes.created_at');
}
Maybe this is what you are looking for?
DB::table('routes')
->leftJoin('cars', function ($join) {
$join->on('cars.id', '=', 'routes.routeable_id')
->where('routes.routeable_type', 'App\Car');
})
->leftJoin('trains', function ($join) {
$join->on('trains.id', '=', 'routes.routeable_id')
->where('routes.routeable_type', 'App\Train');
})
->select('routes.id', 'cars.model AS car_model', 'trains.model AS train_model', 'routes.routeable_type', 'routes.created_at');
->get();
I think you may want to follow the morphedByMany design.
https://laravel.com/docs/5.7/eloquent-relationships#many-to-many-polymorphic-relations
This was also a neat visual for the different relation types.
https://hackernoon.com/eloquent-relationships-cheat-sheet-5155498c209
I was faced with a similar issue though I failed to follow the correct design initially and was forced to query the many possible relations then wrote custom logic after to collect the relation types and ids then do another query and assign them back through iteration. It was ugly but worked... very similar to how Eloquent does things normally.
i don't have enough repo, so i can't comment. that's why i am putting as an answer.
You should use 2 different queries, for each model.
This will be better, code wise as well as performance wise. also if both models have similar fields you should merge them to 1 table and add a 'type' column.
and put non-similar fields in a 'meta' column.
( in my opinion )

Easiest way to query data where relationship is in a pivot table?

I've two tables which are in relation by a standard pivot table.
Now, i only want to those articles which belongs to a specific category_id
I found solutions like:
Article::whereHas('categories', function($q) use ($cat_id) { $q->where('category_id', $cat_id); })->get();
Is this the best and easiest way in Laravel 5 with Eloquent?
In general - yes, this is the way, but you could create also scope for this for cleaner usage especially in case you use it multiple times.
You can add scope into Article model like so:
public function scopeHavingCategory($query, $cat_id)
{
return $query->whereHas('categories', function($q) use ($cat_id) {
$q->where('category_id', $cat_id);
});
}
and now you can use it like so:
Article::havingCategory($cat_id)->get();

Laravel - is there a way to combine whereHas and with

I'm currently facing a small problem. I want to return a model only if a relation with certain conditions exists. That's working fine with the whereHas()-method.
$m = Model
::whereHas(
'programs',
function($q) {
$q->active();
}
);
However, calling the relation as a property like this will give me all (not filtered results).
$m->programs;
So basically what I'm doing now is this:
$m = Model
::whereHas(
'programs',
function($q) {
$q->active();
}
)
->with(array('programs' => function($q) {
$q->active();
}))
;
That's working fine but I feel very bad about doing the same thing again. That can't be the right way. How can I achieve this without kind of duplicating the code?
If a concept of an "active program" is important in your application, consider creating a separate relation just for active programs (in this case I'm presuming you have a HasMany relation):
class Model
{
public function activePrograms()
{
return $this->hasMany(Program::class)->active();
}
}
Then you can simplify your query to:
Model::with('activePrograms')->has('activePrograms')->get();
In Laravel 9.17 withWhereHas
Example:
$a= Model::withWhereHas('programs', function ($query) {
$query->active();
})->get();
Check out the documentation for more information.

Merge 'with' and 'whereHas' in Laravel 5

I have this code in Laravel 5, using Eloquent, which is working perfectly:
$filterTask = function($query) use ($id) {
$query->where('taskid', $id);
};
User::whereHas('submissions', $filterTask)->with(['submissions' => $filterTask])->get();
Basically the goal is to get only those users with their filtered submissions, which has any of them.
However, it seems wasting to run both whereHas and with methods with the same callback function. Is there a way to simplify it?
Thanks.
In terms of performance you can't really optimize anything here (except if you were to move from eloquent relations to joins). With or without whereHas, two queries will be run. One to select all users another one to load the related models. When you add the whereHas condition a subquery is added, but it's still two queries.
However, syntactically you could optimize this a bit by adding a query scope to your model (or even a base model if you want to use this more often):
public function scopeWithAndWhereHas($query, $relation, $constraint){
return $query->whereHas($relation, $constraint)
->with([$relation => $constraint]);
}
Usage:
User::withAndWhereHas('submissions', function($query) use ($id){
$query->where('taskid', $id);
})->get();
The 'macroable' way (Laravel 5.4+)
Add this inside a service provider's boot() method.
\Illuminate\Database\Eloquent\Builder\Eloquent::macro('withAndWhereHas', function($relation, $constraint){
return $this->whereHas($relation, $constraint)->with([$relation => $constraint]);
});
I want to extend the answer from #lukasgeiter using static functions.
public static function withAndWhereHas($relation, $constraint){
return (new static)->whereHas($relation, $constraint)
->with([$relation => $constraint]);
}
Usage is the same
User::withAndWhereHas('submissions', function($query) use ($id){
$query->where('taskid', $id);
})->get();
You can now achieve that In Laravel 9.17
Example:
use App\Models\User;
$users = User::withWhereHas('posts', function ($query) {
$query->where('featured', true);
})->get();
Check out the documentation for more information

Categories