Laravel query builder join after where clause

Viewed 1051

I am using laravel 8. I have this mysql command which I want to convert into laravel query builder style:

select allocation.*, leav_leave_types.leave_type_code 
from (
    select * from leav_employee_annual_leave_allocations 
    where leave_year_id = $year_id and employee_id = $user_id
) as allocation
left join leav_leave_types on (leav_leave_types.id = allocation.leave_type_id)

Actually I want to apply a where clause first and then perform a left join for better performance.

How can I convert it into query builder style?

2 Answers

The only thing from your query that is not currently in the documentation is using a subquery as the main table.

This can be done by passing either a Closure or a Builder instance to the table() or from() method.

  • DB::table(closure, alias)
  • DB::table(builder, alias)
  • DB::query()->from(closure, alias)
  • DB::query()->from(builder, alias)

Using a Closure:

DB::table(function ($sub) use ($user_id, $year_id) {
        $sub->from('leav_employee_annual_leave_allocations')
            ->where('leave_year', $year_id)
            ->where('employee_id', $user_id);
    }, 'allocation')
    ->select('allocation.*', 'leav_leave_types.leave_type_code')
    ->leftJoin('leav_leave_types', 'leav_leave_types.id', 'allocation.leave_type_id')
    ->get();
DB::query()
    ->select('allocation.*', 'leav_leave_types.leave_type_code')
    ->from(function ($sub) use ($user_id, $year_id) {
        $sub->from('leav_employee_annual_leave_allocations')
            ->where('leave_year', $year_id)
            ->where('employee_id', $user_id);
    }, 'allocation')
    ->leftJoin('leav_leave_types', 'leav_leave_types.id', 'allocation.leave_type_id')
    ->get();

Using a Builder instance

$sub = DB::table('leav_employee_annual_leave_allocations') // or DB::query()->from('leav_employee_annual_leave_allocations')
    ->where('leave_year', $year_id)
    ->where('employee_id', $user_id);

DB::table($sub, 'allocation')
    ->select('allocation.*', 'leav_leave_types.leave_type_code')
    ->leftJoin('leav_leave_types', 'leav_leave_types.id', 'allocation.leave_type_id')
    ->get();
// personally my favorite way. I find it very readable.
$sub = DB::table('leav_employee_annual_leave_allocations') // or DB::query()->from('leav_employee_annual_leave_allocations')
    ->where('leave_year', $year_id)
    ->where('employee_id', $user_id);

DB::query()
    ->select('allocation.*', 'leav_leave_types.leave_type_code')
    ->from($sub, 'allocation')
    ->leftJoin('leav_leave_types', 'leav_leave_types.id', 'allocation.leave_type_id')
    ->get();

The generated SQL looks like this

select "allocation".*, "leav_leave_types"."leave_type_code" from (
    select * from "leav_employee_annual_leave_allocations"
    where "leave_year" = ? and "employee_id" = ?
) as "allocation"
left join "leav_leave_types" on "leav_leave_types"."id" = "allocation"."leave_type_id"

If you want a parenthesis around your join condition to be generated, you should use one of the following notations instead.

leftJoin('leav_leave_types', ['leav_leave_types.id' => 'allocation.leave_type_id'])
leftJoin('leav_leave_types', function ($join) {
    $join->on(['leav_leave_types.id' => 'allocation.leave_type_id']);
})
leftJoin('leav_leave_types', function ($join) {
    // will generate a parenthesis if there's more than one condition
    $join->on('leav_leave_types.id', 'allocation.leave_type_id')
         ->on(...) // and condition
         ->orOn(...); // or condition
})

Alternatively, you could turn the SQL around to

select *, 
       ( SELECT leave_type_code
             FROM leav_leave_types
             WHERE id = allocation.leave_type_id
       ) AS leave_type_code
    FROM leav_employee_annual_leave_allocations AS allocation
    where leave_year_id = $year_id and employee_id = $user_id

(This might be more efficient.)

In either case leav_employee_annual_leave_allocations would benefit from INDEX(employee_id, leave_year_id).

Related