其中具有与连接 ->应用全局作用域



我有一个查询,其中包含每个模型的一些连接和全局范围,例如:

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子句,但所有全局作用域(如orders表上的user_id(将自动应用:

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

问题是它很慢,最好有纯JOIN的东西而不是丑陋的嵌套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);

这不包括我对OrderSuborder模型的任何全局范围。我需要手动完成:

$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);

这很糟糕,因为每次编写这样的查询时,我都需要复制我的全局范围逻辑,而whereHas会自动应用它们。

有没有办法联接表,并自动应用联接模型中的所有全局范围?

我以前处理过类似的问题,并提出了一些方法。

首先,让我们在服务提供商中为 Illuminate\Database\Eloquent\Builder 定义一个宏:

use IlluminateDatabaseEloquentBuilder;
use IlluminateDatabaseQueryJoinClause;
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();

这是我能想到的最好的方法,可能有更好的方法。

实际上,你所描述的不可能是真的(将在下面提供证据(。EXISTS不会使查询变慢,在 99% 的情况下,它会使查询更快! 因此,laravel不提供从框为关系进行连接的能力。

我在 github 上看到了不同的解决方案和包,但我不会提供它的链接,因为当我查看逻辑时,发现很多字段选择问题和罕见情况。

  1. Laravel不会像您描述的那样使用EXISTS生成代码,它会按ID向每个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_idAND s.id = o.suborder_id

  1. 当您选择连接时,您应该从主表中精确地设置SELECT,以正确填充模型字段。

  2. 全局
  3. 范围是全局的。真正全球化的目的。如果你有超过 1-2 个地方没有它们,那么你应该找到另一个解决方案而不是全局范围。否则,您的应用程序将很难支持和编写新代码。开发人员不应该每次都记住,他应该关闭全局范围

最新更新