¿Cómo seleccionar de una subconsulta usando Laravel Query Builder?

102

Me gustaría obtener valor mediante el siguiente SQL usando Eloquent ORM.

- SQL

 SELECT COUNT(*) FROM 
 (SELECT * FROM abc GROUP BY col1) AS a;

Luego consideré lo siguiente.

- código

 $sql = Abc::from('abc AS a')->groupBy('col1')->toSql();
 $num = Abc::from(\DB::raw($sql))->count();
 print $num;

Busco una mejor solución.

Por favor dime la solución más simple.

quenty658
fuente

Respuestas:

131

Además de la respuesta de @ delmadord y sus comentarios:

Actualmente no hay un método para crear subconsultas en la FROMcláusula, por lo que debe usar manualmente la declaración sin procesar, luego, si es necesario, fusionará todos los enlaces:

$sub = Abc::where(..)->groupBy(..); // Eloquent Builder instance

$count = DB::table( DB::raw("({$sub->toSql()}) as sub") )
    ->mergeBindings($sub->getQuery()) // you need to get underlying Query Builder
    ->count();

Tenga en cuenta que necesita fusionar enlaces en el orden correcto . Si tiene otras cláusulas vinculadas, debe ponerlas después de mergeBindings:

$count = DB::table( DB::raw("({$sub->toSql()}) as sub") )

    // ->where(..) wrong

    ->mergeBindings($sub->getQuery()) // you need to get underlying Query Builder

    // ->where(..) correct

    ->count();
Jarek Tkaczyk
fuente
3
Tenga en cuenta que si tiene una consulta compleja como una belongsToManysubselección, debe agregar getQuery()dos veces =>$sub->getQuery()->getQuery()
Jordi Puigdellívol
1
@Skyzer No estás leyendo lo que escribo. Nada se escapa cuando llamas toSql. Lea acerca de PDO php.net/manual/en/book.pdo.php y vea el resultado de su$query->toSql()
Jarek Tkaczyk
5
Con respecto a -> mergeBindings ($ sub-> getQuery ()) simplemente hazlo -> mergeBindings ($ sub)
Jimmy Ilenloa
1
@JimmyIlenloa Si la $subconsulta es un Eloquent Builder , entonces todavía necesita la ->getQuery()parte, de lo contrario obtendrá un error, ya que este método está escrito en contra de la Query\Builderclase.
Jarek Tkaczyk
1
@ Kannan no. es un candidato para un PR, supongo, pero al final ese no es un caso de uso muy común. Probablemente esta sea la razón por la que no lo tenga allí hasta el día de hoy ..
Jarek Tkaczyk
76

Se agregó Laravel v5.6.12 (2018-03-14) fromSub()y fromRaw()métodos para el generador de consultas (# 23476) .

La respuesta aceptada es correcta pero se puede simplificar en:

DB::query()->fromSub(function ($query) {
    $query->from('abc')->groupBy('col1');
}, 'a')->count();

El fragmento anterior produce el siguiente SQL:

select count(*) as aggregate from (select * from `abc` group by `col1`) as `a`
mpskovvang
fuente
15

La solución de @JarekTkaczyk es exactamente lo que estaba buscando. Lo único que echo de menos es cómo hacerlo cuando se utilizan DB::table()consultas. En este caso, así es como lo hago:

$other = DB::table( DB::raw("({$sub->toSql()}) as sub") )->select(
    'something', 
    DB::raw('sum( qty ) as qty'), 
    'foo', 
    'bar'
);
$other->mergeBindings( $sub );
$other->groupBy('something');
$other->groupBy('foo');
$other->groupBy('bar');
print $other->toSql();
$other->get();

Atención especial a cómo hacer el mergeBindingssin usar el getQuery()método.

Thiago Mata
fuente
Usar DB::raw()hizo el trabajo por mí
Nino Škopac
7

Desde laravel 5.5 hay un método dedicado para subconsultas y puede usarlo así:

Abc::selectSub(function($q) {
    $q->select('*')->groupBy('col1');
}, 'a')->count('a.*');

o

Abc::selectSub(Abc::select('*')->groupBy('col1'), 'a')->count('a.*');
Sasa Blagojevic
fuente
1
Parece que subSelect solo se puede usar para agregar una subconsulta a SELECT, no FROM.
hagabaka
1
Call to undefined method subSelect()parece subSelectque no existe.
Maruf Alom
3
Gracias por informarme de esto, escribí mal el nombre, debería haber sido selectSub. Actualicé mi respuesta ahora.
Sasa Blagojevic
3

Me gusta hacer algo como esto:

Message::select('*')
->from(DB::raw("( SELECT * FROM `messages`
                  WHERE `to_id` = ".Auth::id()." AND `isseen` = 0
                  GROUP BY `from_id` asc) as `sub`"))
->count();

No es muy elegante, pero es simple.

Guy Mazuz
fuente
Gracias, esto funcionó para mí, como nota al margen, tenga cuidado con el contenido seleccionado porque laravel agregó algunas comillas y tuve que usar -> select (\ DB :: raw ('Your select')) para deshacerme de ellos.
Wak
2

No pude crear su código para hacer la consulta deseada, el AS es un alias solo para la tabla abc, no para la tabla derivada. Laravel Query Builder no admite implícitamente los alias de tablas derivadas, lo más probable es que DB :: raw sea necesario para esto.

La solución más directa que se me ocurrió es casi idéntica a la suya, sin embargo, genera la consulta que solicitó:

$sql = Abc::groupBy('col1')->toSql();
$count = DB::table(DB::raw("($sql) AS a"))->count();

La consulta producida es

select count(*) as aggregate from (select * from `abc` group by `col1`) AS a;
peter.babic
fuente
Gracias por su respuesta. Hay un problema en el método de "Abc :: from (???) y DB :: table (???)". $ sql = Abc :: donde ('id', '=', $ id) -> groupBy ('col1') -> toSql (); $ count = DB :: table (DB :: raw ("($ sql) AS a")) -> count (); Se produce un error de SQL en el código anterior. - dónde y asignación de parámetros!
quenty658
2

Forma correcta descrita en esta respuesta: https://stackoverflow.com/a/52772444/2519714 La respuesta más popular en el momento actual no es totalmente correcta.

De esta manera, https://stackoverflow.com/a/24838367/2519714 no es correcto en algunos casos, como: la selección secundaria tiene enlaces donde, luego se une la tabla a la selección secundaria, luego se agregan otros sitios a todas las consultas. Por ejemplo consulta: select * from (select * from t1 where col1 = ?) join t2 on col1 = col2 and col3 = ? where t2.col4 = ? Para realizar esta consulta, escribirás un código como:

$subQuery = DB::query()->from('t1')->where('t1.col1', 'val1');
$query = DB::query()->from(DB::raw('('. $subQuery->toSql() . ') AS subquery'))
    ->mergeBindings($subQuery->getBindings());
$query->join('t2', function(JoinClause $join) {
    $join->on('subquery.col1', 't2.col2');
    $join->where('t2.col3', 'val3');
})->where('t2.col4', 'val4');

Durante la ejecución de esta consulta, su método $query->getBindings()devolverá enlaces en orden incorrecto, como ['val3', 'val1', 'val4']en este caso, correctos ['val1', 'val3', 'val4']para el sql sin formato descrito anteriormente.

Una forma más correcta de hacer esto:

$subQuery = DB::query()->from('t1')->where('t1.col1', 'val1');
$query = DB::query()->fromSub($subQuery, 'subquery');
$query->join('t2', function(JoinClause $join) {
    $join->on('subquery.col1', 't2.col2');
    $join->where('t2.col3', 'val3');
})->where('t2.col4', 'val4');

Además, los enlaces se fusionarán automática y correctamente con la nueva consulta.

dkop
fuente
¡Muchas gracias! ¡Ayudó mucho!
Hasnat Babur