Reputation: 39
I would like to convert this following Postgre SQL query in an Eloquent query:
SELECT ncts_info.nct_id, A.agency_model, ncts_info.indexation_id
FROM ncts_info
LEFT JOIN (
drug_centers LEFT JOIN LATERAL json_to_recordset(nct_numbers) AS tbl(nct_number text) ON true
) A ON A.nct_number = ncts_info.nct_id
How can I convert this into an Eloquent query?
Upvotes: 1
Views: 3030
Reputation: 3
I know this is an old question, but you can use this way.
<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; use Illuminate\Database\Query\Builder; use Illuminate\Database\Query\Expression; class AppServiceProvider extends ServiceProvider { /** * Register any application services. */ public function register(): void { // } /** * Bootstrap any application services. */ public function boot(): void { Builder::macro('joinLateral', function ($query, $as, $type = 'inner') { if (!in_array($type, ['inner', 'left', 'right', 'outer', 'cross'])) { throw new \InvalidArgumentException("Invalid type (". $type .") use: inner | left | right | outer | cross"); } if (!(($query instanceof \Closure) || ($query instanceof \Illuminate\Database\Query\Builder))) { throw new \InvalidArgumentException('$query need to be an instance of \Closure OR \Illuminate\Database\Query\Builder'); } [$subQuery, $bindings] = $this->createSub($query); $expression = "lateral ($subQuery) as {$this->grammar->wrapTable($as)} on true"; $join = $this->newJoinClause($this, $type, new Expression($expression)); $this->joins[] = $join; $this->addBinding($bindings, 'join'); return $this; }); Builder::macro('innerJoinLateral', function ($query, $as) { return $this->joinLateral($query, $as, 'inner'); }); Builder::macro('leftJoinLateral', function ($query, $as) { return $this->joinLateral($query, $as, 'left'); }); Builder::macro('rightJoinLateral', function ($query, $as) { return $this->joinLateral($query, $as, 'right'); }); .... } }
this way:
$db = DB::table($this->table, 'com'); $db->select($fields); $db->joinLateral($subAddr, 'addr', 'left');
or this way:
$db = DB::table($this->table, 'com'); $db->select($fields); $db->leftJoinLateral($subAddr, 'addr');
and others
Upvotes: 0
Reputation: 1329
This library adds a bunch of postgres-specific features to laravel including the ability to perform a lateral join:
https://github.com/tpetry/laravel-postgresql-enhanced#lateral-subquery-joins
$latestPostsBuilder = Post::select('id', 'created_at')
->whereColumn('user_id', '=', 'users.id') // This would not work without LATERAL join
->orderByRaw('created_at DESC NULLS LAST') // Put an index on this!
->limit(1);
User::lateralLeftJoin($latestPostsBuilder, 'latest_post')
->orderBy('latest_post.created_at') // Ordering users by their latest post!
->get([
'users.*',
'latest_post.*'
]);
Normally you would just eagerload the latest post for each user via a relationship. But in cases where you want to be able to order users by specific fields of the latest post in SQL this is a good way to do it.
A regular leftJoinSub
also works for most cases, but if your join subquery is a very large table its faster to use the above lateral join approach
Upvotes: 0
Reputation: 41
In your second response here, you mention that you need to reference the join as A
- you don't need to. You can change A
to drug_centers
in Laravel as it will be aliased as the table name.
FWIW, I solved the original question by creating a custom Macro for the Builder class that will let you do a join prefixed with the LATERAL keyword:
Builder::macro('joinSubLateral', function ($query, $as, $first, $operator = null, $second = null, $type = 'inner', $where = false) {
[$query, $bindings] = $this->createSub($query);
$expression = 'LATERAL ('.$query.') as '.$this->grammar->wrapTable($as);
$this->addBinding($bindings, 'join');
return $this->join(new Expression($expression), $first, $operator, $second, $type, $where);
});
Put this in the register
method of a Service Provider and you're all set!
Keep in mind it doesn't check for database compatibility.
Upvotes: 2
Reputation: 27
The best way I've found to do this is to define a subquery first. From the laravel docs:
Posts = 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')
->joinLeftSub($latestPosts, 'latest_posts', function ($join) {
$join->on('users.id', '=', 'latest_posts.user_id');
})->get();
Upvotes: 0
Reputation: 1
You can use DB like that in laravel
$data = DB::table('ncts_info')
->leftJoin('drug_centers', 'drug_centers.foreign_key', 'ncts_info.id')
->select('ncts_info.nct_id','A.agency_model','ncts_info.indexation_id')
->get();
This would produce the following query:
SELECT "ncts_info"."nct_id", "A"."agency_model", "ncts_info"."indexation_id"
FROM "ncts_info"
LEFT JOIN "drug_centers" ON "drug_centers"."foreign_key" = "ncts_info"."id";
Upvotes: -1