Search code examples
mysqllaraveleloquenteager-loadingrelationships

Querying Relationship Existence using multiple MySQL database connections in Laravel 5.2


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 = []


Solution

  • 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:

    1. create a method that returns a Relation object in the Model
    2. eager load this new relationship in the Controller
    3. filtered out the telephones of flag != 1 using a query scope in the Model

    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.