Je cherchais une solution à un problème assez proche : trouver les enregistrements les plus récents par groupe, ce qui est une spécialisation d'un problème typique. le plus grand-n par-groupe avec N = 1.
La solution implique le problème que vous traitez ici (c'est-à-dire, comment construire la requête dans Eloquent) donc je la poste car elle pourrait être utile à d'autres. Elle démontre une manière plus propre de construire des sous-requêtes en utilisant l'interface fluide puissante d'Eloquent avec des colonnes de jointure multiples et des colonnes d'entrée. where
condition dans la sous-sélection jointe.
Dans mon exemple, je veux récupérer les résultats les plus récents du scan DNS (table scan_dns
) par groupe identifié par watch_id
. Je construis la sous-requête séparément.
Le SQL que je veux qu'Eloquent génère :
SELECT * FROM `scan_dns` AS `s`
INNER JOIN (
SELECT x.watch_id, MAX(x.last_scan_at) as last_scan
FROM `scan_dns` AS `x`
WHERE `x`.`watch_id` IN (1,2,3,4,5,42)
GROUP BY `x`.`watch_id`) AS ss
ON `s`.`watch_id` = `ss`.`watch_id` AND `s`.`last_scan_at` = `ss`.`last_scan`
Je l'ai fait de la manière suivante :
// table name of the model
$dnsTable = (new DnsResult())->getTable();
// groups to select in sub-query
$ids = collect([1,2,3,4,5,42]);
// sub-select to be joined on
$subq = DnsResult::query()
->select('x.watch_id')
->selectRaw('MAX(x.last_scan_at) as last_scan')
->from($dnsTable . ' AS x')
->whereIn('x.watch_id', $ids)
->groupBy('x.watch_id');
$qqSql = $subq->toSql(); // compiles to SQL
// the main query
$q = DnsResult::query()
->from($dnsTable . ' AS s')
->join(
DB::raw('(' . $qqSql. ') AS ss'),
function(JoinClause $join) use ($subq) {
$join->on('s.watch_id', '=', 'ss.watch_id')
->on('s.last_scan_at', '=', 'ss.last_scan')
->addBinding($subq->getBindings());
// bindings for sub-query WHERE added
});
$results = $q->get();
UPDATE :
Desde Laravel 5.6.17 le site jointures de sous-requêtes ont été ajoutés afin qu'il y ait une façon native de construire la requête.
$latestPosts = DB::table('posts')
->select('user_id', DB::raw('MAX(created_at) as last_post_created_at'))
->where('is_published', true)
->groupBy('user_id');
$users = DB::table('users')
->joinSub($latestPosts, 'latest_posts', function ($join) {
$join->on('users.id', '=', 'latest_posts.user_id');
})->get();