有没有办法让表名自动添加到Eloquent查询方法中?

Gus*_*ube 18 php mysql laravel eloquent

我正在开发一个关于Laravel 5.5的应用程序,我面临着一个特定查询范围的问题.我有以下表结构(省略了一些字段):

orders
---------
id
parent_id
status
Run Code Online (Sandbox Code Playgroud)

parent_id列引用id同一个表中的.我有这个查询范围来筛选没有任何子代的记录:

public function scopeNoChildren(Builder $query): Builder
{
    return $query->select('orders.*')
        ->leftJoin('orders AS children', function ($join) {
            $join->on('orders.id', '=', 'children.parent_id')
                ->where('children.status', self::STATUS_COMPLETED);
        })
        ->where('children.id', null);
}
Run Code Online (Sandbox Code Playgroud)

单独使用时,此范围可正常工作.但是,如果我尝试将它与任何其他条件组合,它会抛出一个SQL异常:

Order::where('status', Order::STATUS_COMPLETED)
    ->noChildren()
    ->get();
Run Code Online (Sandbox Code Playgroud)

导致这个:

SQLSTATE [23000]:完整性约束违规:1052 where子句中的列'status'不明确

我发现了两种避免错误的方法:

解决方案#1:使用表名前缀所有其他条件

做这样的事情有效:

Order::where('orders.status', Order::STATUS_COMPLETED)
    ->noChildren()
    ->get();
Run Code Online (Sandbox Code Playgroud)

但我不认为这是一个好方法,因为不清楚表名是否需要以防其他开发人员甚至自己在将来尝试再次使用该范围.他们可能最终会搞清楚这一点,但这似乎不是一个好习惯.

解决方案#2:使用子查询

我可以在子查询中将模糊列分开.但是,在这种情况下,随着表的增长,性能将下降.

不过,这是我正在使用的策略.因为它不需要对其他范围和条件进行任何更改.至少不是我现在正在应用它的方式.

public function scopeNoChildren(Builder $query): Builder
{
    $subQueryChildren = self::select('id', 'parent_id')
        ->completed();
    $sqlChildren = DB::raw(sprintf(
        '(%s) AS children',
        $subQueryChildren->toSql()
    ));

    return $query->select('orders.*')
        ->leftJoin($sqlChildren, function ($join) use ($subQueryChildren) {
            $join->on('orders.id', '=', 'children.parent_id')
                ->addBinding($subQueryChildren->getBindings());
         })->where('children.id', null);
}
Run Code Online (Sandbox Code Playgroud)

完美的解决方案

我认为能够在不依赖子查询的情况下使用查询而不使用表名前缀将是完美的解决方案.

这就是我要问的原因:有没有办法让表名自动添加到Eloquent查询方法中?

Jon*_*eir 12

我会用一种关系:

public function children()
{
    return $this->hasMany(self::class, 'parent_id')
        ->where('status', self::STATUS_COMPLETED);
}

Order::where('status', Order::STATUS_COMPLETED)
    ->whereDoesntHave('children')
    ->get();
Run Code Online (Sandbox Code Playgroud)

这将执行以下查询:

select *
from `orders`
where `status` = ?
  and not exists
    (select *
     from `orders` as `laravel_reserved_0`
     where `orders`.`id` = `laravel_reserved_0`.`parent_id`
       and `status` = ?)
Run Code Online (Sandbox Code Playgroud)

它使用子查询,但它简短,不会引起任何歧义问题.

除非你有数百万行(我假设你没有),我认为性能不会是一个相关的问题.如果子查询性能将来会成为问题,您仍然可以返回JOIN解决方案.在那之前,我将专注于代码可读性和灵活性.

重用关系的方法(如OP所指出的):

public function children()
{
    return $this->hasMany(self::class, 'parent_id');
}

Order::where('status', Order::STATUS_COMPLETED)
    ->whereDoesntHave('children', function ($query) {
        $query->where('status', self::STATUS_COMPLETED);
    })->get();
Run Code Online (Sandbox Code Playgroud)

或者有两种关系的方式:

public function completedChildren()
{
    return $this->children()
        ->where('status', self::STATUS_COMPLETED);
}

Order::where('status', Order::STATUS_COMPLETED)
    ->whereDoesntHave('completedChildren')
    ->get();
Run Code Online (Sandbox Code Playgroud)

  • 它像魅力一样运作。不过,需要注意的是:我没有在关系方法中添加where子句,而是这样做了:Order :: whereDoesntHave('children',function($ query){$ query-> where('status',self :: STATUS_COMPLETED) });`。结果是相同的,只是使关系更灵活以用于其他情况。 (2认同)