Laravel Fluent Query Builder Join with subquery

前端 未结 7 680
天涯浪人
天涯浪人 2020-11-27 13:03

Okay after hours of research and still using DB::select I have to ask this question. Because I am about to trough my computer away ;).

I want to get the last input o

7条回答
  •  一向
    一向 (楼主)
    2020-11-27 13:29

    I was looking for a solution to quite a related problem: finding the newest records per group which is a specialization of a typical greatest-n-per-group with N = 1.

    The solution involves the problem you are dealing with here (i.e., how to build the query in Eloquent) so I am posting it as it might be helpful for others. It demonstrates a cleaner way of sub-query construction using powerful Eloquent fluent interface with multiple join columns and where condition inside joined sub-select.

    In my example I want to fetch the newest DNS scan results (table scan_dns) per group identified by watch_id. I build the sub-query separately.

    The SQL I want Eloquent to generate:

    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`
    

    I did it in the following way:

    // 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:

    Since Laravel 5.6.17 the sub-query joins were added so there is a native way to build the query.

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

提交回复
热议问题