web-dev-qa-db-fra.com

Comment sélectionner une sous-requête avec Laravel Query Builder?

J'aimerais que le code SQL suivant utilise Eloquent ORM.

- SQL

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

Alors j'ai considéré le suivant.

- Code

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

Je cherche une meilleure solution.

S'il vous plaît dites-moi la solution la plus simple.

76
quenty658

En plus de la réponse de @ delmadord et de vos commentaires:

Il n'existe actuellement aucune méthode pour créer une sous-requête dans la clause FROM. Vous devez donc utiliser manuellement une instruction brute. Si nécessaire, vous allez fusionner toutes les liaisons:

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

Notez que vous devez fusionner les liaisons dans le bon ordre. Si vous avez d'autres clauses liées, vous devez les mettre après 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();
110
Jarek Tkaczyk

Laravel v5.6.12 (2018-03-14) a ajouté les méthodes fromSub() et fromRaw() au constructeur de requêtes (# 23476) .

La réponse acceptée est correcte mais peut être simplifiée comme suit:

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

L'extrait ci-dessus produit le code SQL suivant:

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

La solution de @JarekTkaczyk est exactement ce que je cherchais. La seule chose qui me manque, c'est comment le faire lorsque vous utilisez les requêtes DB::table(). Dans ce cas, voici comment je le fais:

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

Attention particulière sur la manière de rendre la mergeBindings sans utiliser la méthode getQuery()

11
Thiago Mata

De laravel 5.5, il existe une méthode dédiée aux sous-requêtes et vous pouvez l'utiliser comme ceci:

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

ou

Abc::selectSub(Abc::select('*')->groupBy('col1'), 'a')->count('a.*');

3
Sasa Blagojevic

Je ne pouvais pas faire votre code pour faire la requête désirée, l'AS n'est un alias que pour la table abc, pas pour la table dérivée. Laravel Query Builder ne prend pas implicitement en charge les alias de tables dérivés, DB :: raw est probablement nécessaire pour cela.

La solution la plus simple que j'ai pu trouver est presque identique à la vôtre, mais produit la requête que vous avez demandée:

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

La requête produite est

select count(*) as aggregate from (select * from `abc` group by `col1`) AS a;
1
delmadord

J'aime faire quelque chose comme ça:

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

Ce n'est pas très élégant, mais c'est simple.

1
Guy Mazuz