php - relaciones - tinker laravel




¿Hay alguna forma de agregar automáticamente el nombre de la tabla a los métodos de consulta Eloquent? (2)

Estoy desarrollando una aplicación en Laravel 5.5 y estoy enfrentando un problema con un alcance de consulta específico. Tengo la siguiente estructura de tabla (algunos campos omitidos):

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

La columna parent_id referencia al id de la misma tabla. Tengo este ámbito de consulta para filtrar registros que no tienen hijos:

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

Este alcance funciona bien cuando se usa solo. Sin embargo, si trato de combinarlo con cualquier otra condición, se produce una excepción de SQL:

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

Conduce a esto:

SQLSTATE [23000]: Violación de la restricción de integridad: 1052 'estado' de la columna en donde la cláusula es ambigua

Encontré dos formas de evitar ese error:

Solución # 1: Prefija todas las demás condiciones con el nombre de la tabla

Hacer algo como esto funciona:

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

Pero no creo que este sea un buen enfoque, ya que no está claro si se requiere el nombre de la tabla en caso de que otros desarrolladores o yo mismo intentemos usar ese alcance nuevamente en el futuro. Probablemente terminarán descubriendo eso, pero no parece una buena práctica.

Solución # 2: usar una subconsulta

Puedo mantener separadas las columnas ambiguas en una subconsulta. Aún así, en este caso y a medida que la tabla crece, el rendimiento se degradará.

Sin embargo, esta es la estrategia que estoy usando. Porque no requiere ningún cambio a otros ámbitos y condiciones. Al menos no en la forma en que lo estoy aplicando ahora mismo.

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

La solucion perfecta

Creo que tener la capacidad de usar consultas sin prefijo con nombre de tabla sin depender de subconsultas sería la solución perfecta.

Por eso pregunto: ¿hay alguna forma de que el nombre de la tabla se agregue automáticamente a los métodos de consulta Eloquent?


Debe crear un SomeDatabaseBuilder extendiendo el Illuminate\Database\Query\Builder , y un SomeEloquentBuilder extendiendo el Illuminate\Database\Eloquent\Builder y, finalmente, un BaseModel extendiendo Illuminate\Database\Eloquent\Model y sobreescriba estos métodos:

/**
 * @return SomeDatabaseBuilder
 */
protected function newBaseQueryBuilder()
{
    $connection = $this->getConnection();

    return new SomeDatabaseBuilder(
        $connection, $connection->getQueryGrammar(), $connection->getPostProcessor()
    );
}

/**
 * @param \Illuminate\Database\Query\Builder $query
 * @return SameEloquentBulder
 */
public function newEloquentBuilder($query)
{
    return new SameEloquentBulder($query);
}

Luego, en SomeDatabaseBuilder y SameEloquentBulder , cambie los métodos para calificar las columnas de manera predeterminada (o SameEloquentBulder opcional).


En MySQL hay dos buenas maneras de encontrar los nodos de hoja (filas) en una lista de adyacencia . Uno es el método LEFT-JOIN-WHERE-NULL ( antijoin ), que es lo que hiciste. La otra es una subconsulta NO EXISTA. Ambos métodos deberían tener un rendimiento comparable (en teoría hacen exactamente lo mismo). Sin embargo, la solución de subconsulta no introducirá nuevas columnas al resultado.

return $query->select('orders.*')
    ->whereRaw("not exists (
        select *
        from orders as children
        where children.parent_id = orders.id
          and children.status = ?
    )", [self::STATUS_COMPLETED]);




eloquent