11

Problem: I want to get Customers only if they have already placed an order for the current year. Customers and Orders are on two separate Databases (this can't change). The relationships are all setup and working correctly but when I try the following I keep getting an SQL error as it is trying to search 'orders' on the 'customers' database. Is there anyway to force Laravel to use the correct database in this scenario?

$customers = $customers->whereHas('orders', function($query){
    $query->where('academic_year_id', '=', $this->current_academic_year->id);
});

$customers = $customers->orderBy('DFKEY','ASC')->get();

Order Model:

public function customer()
{
    return $this->belongsTo('Customer','dfkey');
}

Customer Model:

protected $connection = 'mysql2';

public function orders()
{
    return $this->hasMany('Order','dfkey','DFKEY');
}

Thanks in advance!

Pedro
  • 1,148
  • 4
  • 16
  • 35
  • sounds like there is a problem with the relationships, is orders a belongsToMany relationship with customers? You need an orders function in the Customer model, Also I think you need a get statement at the end to actually return the results – Mauricio Trajano Dec 10 '14 at 06:42
  • Thanks for the reply, I have updated my original question with more detail... – Pedro Dec 10 '14 at 06:47
  • http://fideloper.com/laravel-multiple-database-connections – Sachin Shukla Dec 10 '14 at 06:50
  • sorry I misread your question, instead of two separate databases I read two separate tables for some reason, I blame sleep – Mauricio Trajano Dec 10 '14 at 06:52
  • @Sachin Thanks but as I said I already have the relationships running fine across two databases. The problem is the database connection that is defined in the Model is being ignored when using the whereHas query. I have no issues getting the data and relationship otherwise. – Pedro Dec 10 '14 at 06:53
  • you can try to explicitly state the connection to use using the on method, so $customer->on('mysql') and $query->on('mysql2') – Mauricio Trajano Dec 10 '14 at 07:00
  • I get the error: Call to undefined method Illuminate\Database\Query\Builder::on() – Pedro Dec 10 '14 at 07:01
  • then it is not an eloquent object but a query builder object, I think you can use connection function, so instead use $query->connection, not sure if that will work but you can try – Mauricio Trajano Dec 10 '14 at 07:05
  • Yeah, I tried that also: Call to undefined method Illuminate\Database\Query\Builder::connection() – Pedro Dec 10 '14 at 07:08
  • 1
    In case you didn't find a solution to that problem yet it seems like prepending the database name to the table name in the customer model seems to do the trick: http://stackoverflow.com/questions/23457561/laravel-4-1-eloquent-use-a-custom-connection-with-wherehas – Mauricio Trajano Dec 10 '14 at 07:17
  • Thanks for that, I did come across that before asking this question but I still get an error (BTW Databases are on different servers, this shouldn't really matter though): SQLSTATE[42S02]: Base table or view not found: 1146 Table 'dc-pta.orders' doesn't exist (SQL: select * from `george`.`DF` where (select count(*) from `ST` where `ST`.`FAMILY` = `george`.`DF`.`DFKEY` and `STATUS` = FULL) >= 1 and (select count(*) from `dc-pta`.`orders` where `dc-pta`.`orders`.`dfkey` = `george`.`DF`.`DFKEY` and `academic_year_id` = 3) >= 1 order by `DFKEY` asc) – Pedro Dec 10 '14 at 07:45
  • You can't run single query between 2 databases on separate servers obviously, so yes, it matters. Mind that relations work in most cases using separate queries, so then it is not a problem. However `whereHas` runs single query with sub select. If it's MySQL, read this http://dev.mysql.com/doc/refman/5.0/en/federated-use.html – Jarek Tkaczyk Dec 10 '14 at 10:24
  • Ok, thanks for the info. Will read up on that page. – Pedro Dec 10 '14 at 23:46

8 Answers8

3

Late to the party but for anybody else who has a similar issue, the below should work (as long as both databases are on a single server).

Set BOTH the connection and table explicitly.

protected $connection = 'mysql2';
protected $table_name = 'mysql2.orders';

Or if you want - dynamically set the table like this:

protected $table = 'orders';
public function __construct() {
    $this->table = DB::connection($this->connection)->getDatabaseName() . '.' . $this->table_name;
}

Or even

public function __construct() {
    $this->table = DB::connection($this->connection)->getDatabaseName() . '.' . $this->getTable();
}
Blueberry
  • 2,211
  • 3
  • 19
  • 33
  • Thanks for this, but in the $table_name in your first block, is that the connection name or database name? – shane Dec 03 '21 at 01:02
1

Solved this by using a filter:

public function index()
{   
    $customers = new Customer;
    // Make sure customers are current parents of students
    $customers = $customers->whereHas('students', function($q) {
        $q->where('STATUS', '=', 'FULL');
    });

    //Filter results
    if(Input::get('academic_year') == 'ordered'){
        $customers = $customers->orderBy('DFKEY','ASC')->get();
        $filtered = $customers->filter(function($customer)
        {
            if ($customer->orders()->where('academic_year_id','=',$this->current_academic_year->id)->first()) {
                return true;
            }
        });
        $customers = $filtered;
        return View::make('admin.customers.index',compact('customers'));
    }

    $customers = $customers->orderBy('DFKEY','ASC')->get();

    return View::make('admin.customers.index',compact('customers'));
}
Pedro
  • 1,148
  • 4
  • 16
  • 35
1

This is actually a very good question! Simple queries directly on that model won't be a problem because Laravel uses the default connection from the model, but if you have a related model in another database and you execute some advanced queries, like: whereHas('relationName'), Laravel will use the connection of parent which will cause the SQL error about non existing column (because it looks up in wrong database).

This is the best solution for this problem:

In your related model make a constructor like this:

public function __construct(array $attributes = [])
{
    $this->table = 'db_name.'.$this->table;
    parent::__construct();
}
lewis4u
  • 14,256
  • 18
  • 107
  • 148
0

Unfurtanelly there is no direct way, but you can do it in 2 steps, which is kind of the same way that Laravel does it normally.

Its simple, clean, and efective, also you dont mess with core Laravel functions that may change on an update

    $orders = Order::where('academic_year_id', $this->current_academic_year->id)
                     ->select('id')
                     ->pluck('id');


    $customers = $customers->whereIn('order_id', $orders)
                           ->orderBy('DFKEY','ASC')
                           ->get();

Hope this works for you.

P.D.: you can skip the equal sign in where

Julio Popócatl
  • 712
  • 8
  • 16
0

Package hoyvoy/laravel-cross-database-subqueries allows you to do that for databases in the same server. You also need to specify all database connections and models related to them as specified by Agus Trombotto. Finally the model refering to another database must extend from Hoyvoy\CrossDatabase\Eloquent\Model instead of Illuminate\Database\Eloquent\Model.

0

It's safer to just modify the original getTable function without overwriting the constructor:

public function getTable()
{
    return join('.', [
        $this->getConnection()->getDatabaseName(),
        Str::snake(Str::pluralStudly(class_basename($this))) // original function
    ]);
}

That way functionality of dynamic database switching would work, like Model::on($connection).

Max Flex
  • 1,116
  • 10
  • 16
-1

Try to write query like this and put your database and tablename as it is->

$customers = Schema::connection('your_database_name')::table('respective_table_name')
->where('academic_year_id', '=', $this->current_academic_year->id)
->orderBy('DFKEY','ASC')
->get();
Sachin Shukla
  • 1,249
  • 14
  • 17
  • and please let me know what is the table structure of order and customer table, i measn how r u saving data. so i can clarify more on it. First what u hsould have to get from which table and then change database and run second query for cursotmer info like name, email, phone number. – Sachin Shukla Dec 10 '14 at 08:12
-1

I 've solved this by adding 'connection' and 'table' variables models specificing the database in wich the model is saved.

For example, i have a model called 'User' in the database called 'core_database' in the table users. In the other side, i have a model called 'UserEvents' in the database called 'logs_database' in the table 'user_events'

So i will have a two conections on config/database.php file:

'core_db_connection' => [
        'driver' => 'mysql',
        'host' => host_ip,
        'port' => port,
        'database' => 'core_database',
        'username' => username,
        'password' => password,
        ....
    ],

'logs_db_connection' => [
        'driver' => 'mysql',
        'host' => host_ip,
        'port' => port,
        'database' => 'logs_database',
        'username' => username,
        'password' => password,
        ....
    ],

And the models will be like:

class User extends Authenticatable {
  protected $table = 'core_database.users';
  protected $connection = 'core_db_connection';
  ...
}

class UserEvents extends Model {
  protected $table = 'logs_database.user_events';
  protected $connection = 'logs_db_connection';
  ...
}

This was tested in databases in the same database server. Database connections have the same host ip. I have not tried with a different way

Using this configuration, i can make any query across two or more separeted database in the same database server, in my case, RDS.

I hope this help you!

Agus Trombotto
  • 117
  • 2
  • 7
  • 1
    This will not allow whereHas to function correctly, you will get an SQL error as whereHas does not support cross database relations – Wesley Smith Aug 18 '19 at 18:33