I'm developing an app on Laravel 5.5 and I'm facing an issue with a specific query scope. I have the following table structure (some fields omitted):

orders
---------
id
parent_id
status

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

This scope works fine when used alone. However, if I try to combine it with any another condition, it throws an SQL exception:

Order::where('status', Order::STATUS_COMPLETED)
    ->noChildren()
    ->get();

Leads to this:

SQLSTATE[23000]:违反完整性约束:WHERE子句中的列‘STATUS’不明确

我找到了两种方法来避免这个错误:

Solution #1: Prefix all other conditions with the table name

Doing something like this works:

Order::where('orders.status', Order::STATUS_COMPLETED)
    ->noChildren()
    ->get();

But I don't think this is a good approach since it's not clear the table name is required in case other dev or even myself try to use that scope again in the future. They'll probably end up figuring that out, but it doesn't seem a good practice.

解决方案2:使用子查询

我可以在子查询中将不明确的列分开.不过,在这种情况下,随着表的增长,性能将会降低.

This is the strategy I'm using, though. Because it doesn't require any change to other scopes and conditions. At least not in the way I'm applying it right now.

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

完美的解决方案

I think that having the ability to use queries without prefixing with table name without relying on subqueries would be the perfect solution.

That's why I'm asking: Is there a way to have table name automatically added to Eloquent query methods?

推荐答案

我会用一种关系:

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

Order::where('status', Order::STATUS_COMPLETED)
    ->whereDoesntHave('children')
    ->get();

这将执行以下查询:

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` = ?)

它使用子查询,但它很短、简单,并且不会导致任何歧义问题.

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

A way to reuse the relationship (as pointed out by the 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();

Or a way with two relationships:

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

Order::where('status', Order::STATUS_COMPLETED)
    ->whereDoesntHave('completedChildren')
    ->get();

Laravel相关问答推荐

Inertia React中的错误文件上传更新

发送邮箱后,Laravel重定向偶尔会导致错误500

AJAX响应中未定义的全名

如何在 Laravel 中使用 return 停止 Trait php 的执行

laravel vue 惯性分页器删除上一个和下一个链接

Laravel init 查询多种用途

在部署到 AWS 时保持我的环境参数安全

Laravel 5 如何配置 Queue 数据库驱动程序以连接到非默认数据库?

Laravel 5.4 - 如何为同一个自定义验证规则使用多个错误消息

Laravel belongsTo 关系 - 试图获取非对象的属性

Laravel 5 Eloquent 范围连接并 Select 特定列

Laravel 4:如何将 WHERE 条件应用于 Eloquent 类的所有查询?

带有 Laravel Passport 的 SSO

Laravel 5 - 没有重叠的任务计划不起作用

未定义的类常量App\Providers\RouteServiceProvider::HOME

如何更改 ember-cli 中的 dist 文件夹路径?

在 laravel 6.0 中未定义命令ui

控制器外部的 Laravel 访问请求对象

Laravel 不活动时间设置

Laravel 5如何获取路由动作名称?