I am dealing with the following situation: I have two models, an Employee
with id
and name
fields and a Telephone
with id
, employee_id
and flag
fields. There is also an one-to-many relationship between these two models, that is an employee may have many telephones and a telephone may belong to a single employee.
class Employee extends Model
{
public function telephones()
{
return $this->hasMany(Telephone::class);
}
}
class Telephone extends Model
{
public function employee()
{
return $this->belongsTo(Employee::class);
}
}
The Employee
model references a table employees
that exists in database schema named mydb1
, while the Telephone
model is related to a telephones
table that exists in a different database schema named mydb2
.
What I want is to fetch only the employees with at least one telephone of a specific flag eager loaded, using Eloquent and (if possible) not the query builder
What I tried so far without success is:
1) use the whereHas method in the Controller
$employees = Employee::whereHas('telephones', function ($query) {
$query->where('flag', 1); //Fetch only the employees with telephones of flag=1
})->with([
'telephones' => function ($query) { //Eager load only the telephones of flag=1
$query->where('flag', 1);
}
])->get();
What I try to do here is first to retrieve only the employees that have telephones with flag=1 and second to eager load only these telephones, but I get the following query exception because of the different db connections used:
Base table or view not found: Table mydb1.telephones doesn't exist (this is true, telephones exists in mydb2)
2) Eager load with constrains in the Controller
$employees = Employee::with([
'telephones' => function ($query) {
$query->where('flag', 1);
},
])->get();
This method eager loads the telephones with flag=1, but it returns all the employee instances, which is not what I really want. I would like to have a collection of only the employee models that have telephones with flag
= 1, excluding the models with telephones = []
Laravel has inbuilt support for multiple database systems, you need to provide connection details in config/database.php file Once you have this you can create two base model class for each connection and define the connection name in those models You can extend these models to create more models for tables in each DB.
We could run join from posts table to type table because their database is still hosted in same engine. They are basically has same connection but only different in database name. Laravel has ability to run in multiple database connection even in across different database engine. We can create migration based on connection we made before.
Note Be careful about attempting to build relationships with tables across databases! It is possible to do, but it can come with some caveats and depends on what database and/or database settings you have. When using multiple connections, you may access each connection via the connection method on the DB facade.
Laravel has ability to run in multiple database connection even in across different database engine. We can create migration based on connection we made before. Eloquent ORM has ability to run relationship with different connection but we need to override connection attribute to define which connection is used by model.
Taking into account this post, this post and @Giedrius Kiršys answer below, I finally came up with a solution that fits my needs, using the following steps:
In Employee model
/**
* This is the new relationship
*
*/
public function flaggedTelephones()
{
return $this->telephones()
->where('flag', 1); //this will return a relation object
}
/**
* This is the query scope that filters the flagged telephones
*
* This is the raw query performed:
* select * from mydb1.employees where exists (
* select * from mydb2.telephones
* where telephones.employee_id = employee.id
* and flag = 1);
*
*/
public function scopeHasFlaggedTelephones($query, $id)
{
return $query->whereExists(function ($query) use ($id) {
$query->select(DB::raw('*'))
->from('mydb2.telephones')
->where('telephones.flag', $flag)
->whereRaw('telephones.employee_id = employees.id');
});
}
In the Controller
Now I may use this elegant syntax a’la Eloquent
$employees = Employee::with('flaggedTelephones')->hasFlaggedTelephones()->get();
which reads like "Fetch all the employees with flagged telephones eager loaded, and then take only the employees that have at least one flagged telephone"
EDIT:
After dealing with the Laravel framework for a while (current version used 5.2.39), I figured, that in fact, whereHas()
clauses do work in case of the relationship model exists in a different database using the from()
method, as it is depicted below:
$employees = Employee::whereHas('telephones', function($query){
$query->from('mydb2.telephones')->where('flag', 1);
})->get();
@Rob Contreras credits for stating the use of the from()
method, however it looks like the method requires to take both the database and the table as an argument.
Not sure if this will work but you can use the from method to specify your database connection within the closure:
$employees = Employee::whereHas('telephones', function($query){
$query->from('mydb2')->where('flag', 1);
})->get();
Hope this helps
Dirty solution:
Use whereExists
and scope for better readability.
In Your Employee
model put:
public function scopeFlags($query, $flag)
{
$query->whereExists(function ($q) use ($flag) {
$q->select(\DB::raw(1))
->from('mydb2.telephones')
->where('telephones.flag', $flag)
->whereRaw('telephones.employee_id = employees.id');
});
}
Then modify your query like so:
$employees = Employee::flags(1)->get();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With