Comment sélectionner une sous-requête à l'aide de Laravel Query Builder?

102

Je voudrais obtenir de la valeur par le SQL suivant en utilisant Eloquent ORM.

- SQL

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

Ensuite, j'ai considéré ce qui suit.

- Code

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

Je cherche une meilleure solution.

Veuillez me dire la solution la plus simple.

quenty658
la source

Réponses:

133

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

Actuellement, il n'y a pas de méthode pour créer une sous-requête dans la FROMclause, vous devez donc utiliser manuellement l'instruction brute, puis, si nécessaire, vous fusionnerez 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();

N'oubliez pas 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();
Jarek Tkaczyk
la source
3
Notez que si vous avez une requête complexe comme belongsToManysous-sélection, vous devez ajouter getQuery()deux fois =>$sub->getQuery()->getQuery()
Jordi Puigdellívol
1
@Skyzer Vous ne lisez pas ce que j'écris. Rien n'est échappé lorsque vous appelez toSql. Lisez à propos de PDO php.net/manual/en/book.pdo.php et voyez le résultat de votre$query->toSql()
Jarek Tkaczyk
5
En ce qui concerne -> mergeBindings ($ sub-> getQuery ()) il suffit de faire -> mergeBindings ($ sub)
Jimmy Ilenloa
1
@JimmyIlenloa Si la $subrequête est un Eloquent Builder , vous avez toujours besoin de la ->getQuery()partie, sinon vous obtenez une erreur, car cette méthode est associée à une Query\Builderclasse.
Jarek Tkaczyk
1
@Kannan non. c'est un candidat pour un PR je suppose, mais au final ce n'est pas un cas d'utilisation très courant. C'est probablement la raison pour laquelle il n'y en a pas jusqu'à ce jour.
Jarek Tkaczyk
79

Laravel v5.6.12 (2018-03-14) ajouté fromSub()et fromRaw()méthodes pour le générateur de requêtes (# 23476) .

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

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

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

select count(*) as aggregate from (select * from `abc` group by `col1`) as `a`
mpskovvang
la source
16

La solution de @JarekTkaczyk c'est exactement ce que je recherchais. La seule chose qui me manque, c'est comment le faire lorsque vous utilisez des DB::table()requêtes. Dans ce cas, voici comment je 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();

Une attention particulière comment faire le mergeBindingssans utiliser la getQuery()méthode

Thiago Mata
la source
L'utilisation a DB::raw()fait le travail pour moi
Nino Škopac
7

À partir de laravel 5.5, il existe une méthode dédiée pour les 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.*');
Sasa Blagojevic
la source
1
Il semble que subSelect ne puisse être utilisé que pour ajouter une sous-requête à SELECT, pas à FROM.
hagabaka
1
Call to undefined method subSelect()semble subSelectn'existe pas.
Maruf Alom
3
Merci d'avoir porté cela à ma connaissance, j'ai mal orthographié le nom, cela aurait dû l'être selectSub. J'ai mis à jour ma réponse maintenant.
Sasa Blagojevic
3

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.

Guy Mazuz
la source
Merci, cela a fonctionné pour moi, en passant, soyez prudent avec le contenu sélectionné car laravel a ajouté des guillemets et j'ai dû utiliser -> select (\ DB :: raw ('Your select')) pour m'en débarrasser.
Wak
2

Je n'ai pas pu faire votre code pour faire la requête souhaitée, l'AS est un alias uniquement pour la table abc, pas pour la table dérivée. Laravel Query Builder ne prend pas implicitement en charge les alias de table dérivés, DB :: raw est très 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 comme vous l'avez demandé:

$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;
peter.babic
la source
Merci pour votre réponse. Il y a un problème dans la méthode de "Abc :: from (???) et DB :: table (???)". $ sql = Abc :: where ('id', '=', $ id) -> groupBy ('col1') -> toSql (); $ count = DB :: table (DB :: raw ("($ sql) AS a")) -> count (); Une erreur SQL se produit dans le code ci-dessus. - où et attribuer les paramètres!
quenty658
2

La manière correcte décrite dans cette réponse: https://stackoverflow.com/a/52772444/2519714 La réponse la plus populaire à l'heure actuelle n'est pas totalement correcte.

De cette façon, https://stackoverflow.com/a/24838367/2519714 n'est pas correct dans certains cas comme: la sélection de sous a où les liaisons, puis la jonction de la table à la sous-sélection, puis d'autres endroits ajoutés à toutes les requêtes. Par exemple, requête: select * from (select * from t1 where col1 = ?) join t2 on col1 = col2 and col3 = ? where t2.col4 = ? Pour effectuer cette requête, vous allez écrire du code comme:

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

Pendant l'exécution de cette requête, sa méthode $query->getBindings()retournera les liaisons dans un ordre incorrect comme ['val3', 'val1', 'val4']dans ce cas plutôt correct ['val1', 'val3', 'val4']pour le SQL brut décrit ci-dessus.

Encore une façon correcte de le faire:

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

Les liaisons seront également automatiquement et correctement fusionnées avec la nouvelle requête.

dkop
la source
Merci beaucoup! Cela a beaucoup aidé!
Hasnat Babur