Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Laravel Fluent Query Builder Join with subquery

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 of a user (base on the timestamp). I can do this with the raw sql

SELECT  c.*, p.* FROM    users c INNER JOIN (   SELECT  user_id,           MAX(created_at) MaxDate   FROM    `catch-text`   GROUP BY user_id  ) MaxDates ON c.id = MaxDates.user_id INNER JOIN     `catch-text` p ON   MaxDates.user_id = p.user_id      AND MaxDates.MaxDate = p.created_at 

I got this query from another post here on stackoverflow.

I have tried everything to do this with the fluent query builder in Laravel however with no success.

I know the manual says you can do this:

DB::table('users')     ->join('contacts', function($join)     {         $join->on('users.id', '=', 'contacts.user_id')->orOn(...);     })     ->get(); 

But that is not helping much because I do not see how I could use a subquery there? Anyone who can light up my day?

like image 343
driechel Avatar asked Aug 06 '13 11:08

driechel


People also ask

How do I join a subquery in laravel?

Whenever you need to use subquery in your laravel project you can use following example and learn hot to use subquery. In bellow example you can see we add subquery using DB::raw(), DB::raw() throught we can select our subtable and after in second argument i added compare your field.

How do I use whereBetween in laravel?

The whereBetween() method is a query builder chained alongside other Laravel query builders used to fetch data from the database. The whereBetween() method queries the database table to fetch rows of records from the database within a range of values.

How write raw SQL query in laravel?

DB::raw() is used to make arbitrary SQL commands which aren't parsed any further by the query builder. They therefore can create a vector for attack via SQL injection. Since the query builder is using PDO in the background, we know there is a way to bind parameters to our query so it will sanitize the bound variables.

What is fluent query builder in laravel?

Laravel's database query builder provides a convenient, fluent interface to creating and running database queries. It can be used to perform most database operations in your application and works perfectly with all of Laravel's supported database systems.


2 Answers

Ok for all of you out there that arrived here in desperation searching for the same problem. I hope you will find this quicker then I did ;O.

This is how it is solved. JoostK told me at github that "the first argument to join is the table (or data) you're joining.". And he was right.

Here is the code. Different table and names but you will get the idea right? It t

DB::table('users')         ->select('first_name', 'TotalCatches.*')          ->join(DB::raw('(SELECT user_id, COUNT(user_id) TotalCatch,                DATEDIFF(NOW(), MIN(created_at)) Days,                COUNT(user_id)/DATEDIFF(NOW(), MIN(created_at))                CatchesPerDay FROM `catch-text` GROUP BY user_id)                TotalCatches'),          function($join)         {            $join->on('users.id', '=', 'TotalCatches.user_id');         })         ->orderBy('TotalCatches.CatchesPerDay', 'DESC')         ->get(); 
like image 176
driechel Avatar answered Sep 19 '22 09:09

driechel


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(); 
like image 25
ph4r05 Avatar answered Sep 20 '22 09:09

ph4r05