whereHas vs join -> применение глобальных областей - PullRequest
2 голосов
/ 13 января 2020

У меня есть запрос с несколькими объединениями и глобальными областями действия для каждой модели, например:

SELECT *
FROM products p
WHERE EXISTS(
  SELECT *
  FROM orders o
  WHERE o.user_id = 4
  AND o.status_id = 1
  AND o.user_id = 3
  AND EXISTS(
    SELECT *
    FROM suborders s
    WHERE s.status_id = 2
  )
);

Это означает, что я могу просто написать несколько whereHas операторов, и мой запрос будет иметь несколько вложенных EXIST, но все глобальные области (например, user_id в таблице orders) будут применены автоматически:

$this->builder->whereHas('orders', function ($q) {
  $q->where('status_id', '=', 1)
    ->whereHas('suborder', function ($q) {
      $q->where('status_id', '=', 2);
    });
});

Проблема в том, что это медленно, было бы гораздо лучше иметь что-то с простыми JOIN s вместо уродливых вложенных EXIST предложений:

SELECT *
FROM products p
INNER JOIN orders o ON p.order_id = o.id
INNER JOIN suborders s ON o.id = s.order_id
WHERE o.status_id = 1
AND u.user_id = 3
AND s.status_id = 2;

Проблема в том, что мне нужно использовать построитель запросов, чтобы присоединиться к ним:

$this->builder->join('orders', 'products.order_id', '=', 'orders.id')
              ->join('suborders', 'orders.id', '=', 'suborders.order_id')
              ->where('orders.status_id', 1)
              ->where('suborders.id', 2);

И это не будет включать ни одну из моих глобальных областей применения на моделях Order и Suborder. Мне нужно сделать это вручную:

$this->builder->join('orders', 'products.order_id', '=', 'orders.id')
              ->join('suborders', 'orders.id', '=', 'suborders.order_id')
              ->where('orders.status_id', 1)
              ->where('suborders.id', 2)
              ->where('orders.user_id', 3);

Это плохо, потому что мне нужно реплицировать мои глобальные логи областей c каждый раз, когда я пишу такой запрос, в то время как whereHas применяет их автоматически.

Есть ли способ присоединиться к таблице и автоматически применить все глобальные области действия из объединенной модели?

Ответы [ 2 ]

0 голосов
/ 13 января 2020

На самом деле, то, что вы описываете, не может быть правдой (предоставим доказательства ниже). EXISTS не делает запрос медленнее и в 99% случаев он делает быстрее! Так что laravel не предоставляет возможность создавать соединения для отношений из коробки.

Я видел различные решения для этого и пакета на github, но я не буду предоставлять ссылку на него, потому что когда я просматривал logi c, обнаружил много проблем с выбором полей и редкие случаи.

  1. Laravel не генерировать код с EXISTS, как вы описали, он добавляет поиск по идентификатору для каждого EXISTS подзапрос типа
SELECT *
FROM products
WHERE EXISTS(
  SELECT *
  FROM orders
  WHERE o.user_id = 4
  AND o.status_id = 1
  AND o.id = p.order_id
  AND EXISTS(
    SELECT *
    FROM suborders s
    WHERE s.status_id = 2
    AND s.id = o.suborder_id
  )
);

внимание на AND o.id = p.order_id и AND s.id = o.suborder_id

Когда вы выбираете с помощью объединений, вы должны точно установить SELECT из основной таблицы, чтобы заполнить поля модели справа.

Глобальные области являются глобальными. Цель быть действительно глобальной. Если у вас есть более 1-2 мест без них, то вы должны найти другое решение вместо глобальных областей. В противном случае ваше приложение будет очень сложно поддерживать и писать новый код. Разработчик не должен помнить каждый раз, что могут быть глобальные области видимости, которые он должен отключить

0 голосов
/ 13 января 2020

Ранее я уже сталкивался с подобной проблемой и предложил некоторый подход.

Сначала давайте определим макрос для Illuminate \ Database \ Eloquent \ Builder в поставщике услуг:

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\JoinClause;

class AppServiceProvider
{
    public function boot()
    {
        Builder::macro('hasJoinedWith', function ($table) {
            return collect(
                $this->getQuery()->joins
            )
            ->contains(function (JoinClause $joinClause) use ($table) {
                return $joinClause->table === $table;
            })
        });
    }
}

Затем давайте определим области:

class Product extends Model
{
    public function scopeOrderStatus($query, $orderStatusId)
    {
        if (! $query->hasJoinedWith('orders')) {
            $query->join('orders', 'products.order_id', '=', 'orders.id');
        }

        return $query->where('orders.status_id', $orderStatusId)
    }

    public function scopeOrderUser($query, $userId)
    {
        if (! $query->hasJoinedWith('orders')) {
            $query->join('orders', 'products.order_id', '=', 'orders.id');
        }

        return $query->where('orders.user_id', $userId)
    }

    public function scopeSubOrder($query, $subOrderId)
    {
        if (! $query->hasJoinedWith('orders')) {
            $query->join('orders', 'products.order_id', '=', 'orders.id');
        }

        if (! $query->hasJoinedWith('suborders')) {
            $query->join('suborders', 'orders.id', '=', 'suborders.order_id');
        }

        return $query->where('suborders.id', $subOrderId)
    }
}

Наконец, вы используете области вместе:

Product::orderStatus(1)
    ->subOrder(2)
    ->orderUser(3)
    // This is optional. There are possibly duplicate products.
    ->distinct()
    ->get();

Это лучший подход, который я могу придумать, может быть лучшие.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...