简体   繁体   中英

Optimize Laravel Eloquent whereHas() query - very slow

I am using Laravel 5.4

I have 3 models: Order, OrderLine and Product.

Order hasMany() OrderLines OrderLine hasOne() Product via the product_id in the OrderLine model (I have properly indexed this, at least I think!)

My requirement is to retrieve all Orders and OrderLines where the Product is for a certain brand name.

Here is my eloquent query. I know the query works but it seems to infinitely run when put on a large dataset (circa 10,000 Orders, 12,000 OrderLines/Products)

$orders = Order::whereBetween('order_date', [$this->start_date,$this->end_date])
    ->whereHas('lines', function ($q1){
        $q1->whereHas('product', function ($q2){
            $q2->where('brand', 'Brands>SanDisk');
        });
    })->with('lines')->with('lines.product')->get()->toArray();

This produces the following SQL when debugging via toSql() method.

select
   *
from `orders`
where
   `order_date` between ? and ? 
and
  exists (select * from `order_lines` where `orders`.`id` =`order_lines`.`order_id` 
and
  exists (select * from `products` where `order_lines`.`product_id` = `products`.`id` and `brand` = ?))

My 3 migrations to create the tables are as follows (I have removed anything except keys for simplicity):

Schema::create('orders', function (Blueprint $table) {
    $table->increments('id');
});

Schema::create('order_lines', function (Blueprint $table) {
    $table->increments('id');
    $table->integer('product_id');
    $table->integer('order_id');
});

Schema::create('products', function (Blueprint $table) {
    $table->increments('id');
});

I then added the following index:

Schema::table('order_lines', function (Blueprint $table) {
    $table->integer('product_id')->unsigned()->change();
    $table->foreign('product_id')->references('id')->on('products');
});

Results of EXPLAIN syntax as follows:

1   PRIMARY orders  ALL                 91886   Using where 
2   DEPENDENT SUBQUERY  order_lines ALL                 93166   Using where 
3   DEPENDENT SUBQUERY  products    eq_ref  PRIMARY PRIMARY 4   mymemory_main.order_lines.product_id    1   Using where 

Try this:

$orders = Order::query()
    ->whereBetween('order_date', [$this->start_date, $this->end_date])
    ->hasByNonDependentSubquery('lines.product', null, function ($q) {
        $q->where('brand', 'Brands>SanDisk');
    })
    ->with('lines.product')
    ->get()
    ->toArray();

That's all. Happy Eloquent Life!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM