Laravel query builder join after where clause - php

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?

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

How to convert raw SQL query to Laravel Query Builder

I need a following code to convert to Laravel query can any one help me with these.
SELECT id, `leave_name`, `total_leave_days`, leave_id, leave_taken_days FROM `leaves` AS t1 INNER JOIN ( SELECT leave_id, SUM(`leave_taken_days`) AS leave_taken_days FROM `leave_applications` WHERE user_id = 2 AND statuses_id = 2 GROUP BY leave_id ) AS t2 ON t1.id = t2.leave_id
I even tried but the output is not showing atall.
$user_leaves = DB::table('leaves')
->select('id', 'leave_name', 'total_leave_days', 'leave_id', 'leave_taken_days')
->join('leave_application', 'leave_application.leave_id', '=', 'leave.id')
->select('leave_application.leave_id', DB::raw("SUM(leave_taken_days) as leave_application.leave_taken_days"))
->where('user_id','=', 2)
->where('statuses_id','=', 2)
->get();
How can I solve this issue?
UPDATE
Relations between two models.
Leave Model
public function leave_application()
{
return $this->belongsTo(LeaveApplication::class, 'id' , 'leave_id');
}
Leave Application Model
public function leave()
{
return $this->belongsTo(Leave::class, 'leave_id', 'id');
}
Try this :
$user_leaves = Leave::select('leaves.id', 'leaves.leave_name', 'leaves.total_leave_days', 'leave_applications.leave_id', DB::raw('SUM(leave_applications.leave_taken_days) as leave_taken_days'))
->with('leave_application')
->whereHas('leave_application', function($q) {
$q->where('user_id', 2)
->where('statuses_id', 2);
})
->groupBy('leaves.id')
->get();
On this topic I would like to give my recommendations for some tools to help you out in the future.
SQL Statement to Laravel Eloquent to convert SQL to Laravel query builder. This does a decent job at low level queries. It also saves time when converting old code.
The other tool I use to view the query that is being run is Clock Work
I keep this open in a tab and monitor slow nasty queries or, also gives me perspective on how the query builder is writing SQL. If you have not use this extension I highly recommend getting and using it.
Actually I found my answer,
$user_leaves = DB::table('leaves as t1')
->select('t1.id', 't1.leave_name', 't1.total_leave_days', 't2.leave_id', 't2.leave_taken_days')
->join(DB::raw('(SELECT leave_id, SUM(leave_taken_days) AS leave_taken_days FROM leave_applications WHERE user_id = ' . $user_id . ' AND statuses_id = 2 GROUP BY leave_id) AS t2'), function ($join) {
$join->on('t1.id', '=', 't2.leave_id');
})
->get();
You can use DB:select("your query", params) and put your query and params (as an array (optional)
As below sample:
$result = DB:select("
SELECT id, `leave_name`, `total_leave_days`, leave_id, leave_taken_days
FROM `leaves` AS t1
INNER JOIN (
SELECT leave_id, SUM(`leave_taken_days`) AS leave_taken_days
FROM `leave_applications`
WHERE user_id = 2
AND statuses_id = 2
GROUP BY leave_id
) AS t2 ON t1.id = t2.leave_id" , $params
);
return response()->json($result);

laravel elequent builder join with paginate

i have this query to return data from two tables based on DISTINCT destination_tbls.destination
like the following:
SELECT DISTINCT
destination_tbls.destination,
MIN(sms_details.id),
MIN(sms_details.msg_timestamp) AS TIMESTAMP,
MIN(destination_tbls.count)
FROM
sms_details
JOIN
(
SELECT
*
FROM
destination_tbls
)
destination_tbls
ON destination_tbls.id = sms_details.destination_tbls_id
GROUP BY
destination_tbls.destination;
Now how to use the paginate with them,
I tried something like this but don't work:
DB::select('
SELECT DISTINCT destination_tbls.destination,MIN(sms_details.id),MIN(sms_details.msg_timestamp) AS TIMESTAMP,MIN(destination_tbls.count)
FROM sms_details
JOIN(SELECT * FROM destination_tbls) destination_tbls ON destination_tbls.id=sms_details.destination_tbls_id
GROUP BY destination_tbls.destination
')->simplePaginate(100);
Any help would be appreciated!
If you want to use laravel's pagination, you need to write the query using the query builder methods.
$results = DB::table('sms_details')
->select('destination_tbls.destination')
->selectRaw('min(sms_details.id)')
->selectRaw('min(sms_details.msg_timestamp) as timestamp')
->selectRaw('min(destination_tbls.count)')
->distinct()
->joinSub(
function ($sub) {
$sub->from('destination_tbls');
},
'destination_tbls',
function ($join) {
$join->on('destination_tbls.id', '=', 'sms_details.destination_tbls_id');
}
)
->groupBy('destination_tbls.destination')
->simplePaginate(100);
Since you're not really doing anything in the subquery join, you could join the table instead.
$query = DB::table('sms_details')
->select('destination_tbls.destination')
->selectRaw('min(sms_details.id)')
->selectRaw('min(sms_details.msg_timestamp) as timestamp')
->selectRaw('min(destination_tbls.count)')
->distinct()
->join('destination_tbls', 'destination_tbls.id', '=', 'sms_details.destination_tbls_id')
->groupBy('destination_tbls.destination')
->simplePaginate(100);

laravel access outer query column inside subquery

I am trying to convert raw sql queries into laravel queries.
Here's the raw query:
select
tsk.id,
tsk.request_id,
tsk.sys_index,
tsk.category_group,
tsk.category,
tsk.is_assigned,
tsk.hash_id
from
user_tasks as usr
inner join
unassigned_tasks as tsk
on usr.task_id = tsk.id
where
usr.assigned_to = 12
AND
tsk.product_id NOT IN ( SELECT product_id FROM product_progresses WHERE request_id = tsk.request_id )
AND
BINARY hash_id NOT IN ( SELECT hash_id FROM product_match_unmatches WHERE request_id = tsk.request_id AND auto_unmatched_by IS NOT NULL )
The laravel query is:
public function getTasks($assigned_to) {
/** fetch products assigned to a specific user token,
* ignore already matched skus, and links that are auto-unmatched
**/
$tasks = DB::table('user_tasks as usr')
->join('unassigned_tasks as tsk', 'usr.task_id', '=', 'tsk.id')
->select('tsk.id', 'tsk.request_id', 'tsk.sys_index', 'tsk.category_group', 'tsk.category', 'tsk.is_assigned', 'tsk.hash_id')
->where('usr.assigned_to', '=', $assigned_to);
$tasks->whereNotIn('tsk.product_id', function($qs) {
$qs->from('product_progresses')
->select(['product_id'])
->where('request_id', '=', 'tsk.request_id')
->get();
});
$tasks->whereNotIn(DB::raw('BINARY `hash_id`'), function($qs) {
$qs->from('product_match_unmatches')
->select('hash_id')
->where('request_id', '=', 'tsk.request_id')
->whereNotNull('auto_unmatched_by')
->get();
});
return $tasks->toSql();
The below query should take tsk.request_id value from outer query, but I think the column value is not passed to it.
Here's the output of toSql():
SELECT `tsk`.`id`,
`tsk`.`request_id`,
`tsk`.`sys_index`,
`tsk`.`category_group`,
`tsk`.`category`,
`tsk`.`is_assigned`,
`tsk`.`hash_id`
FROM `user_tasks` AS `usr`
INNER JOIN `unassigned_tasks` AS `tsk`
ON `usr`.`task_id` = `tsk`.`id`
WHERE `usr`.`assigned_to` = ?
AND `tsk`.`product_id` NOT IN (SELECT `product_id`
FROM `product_progresses`
WHERE `request_id` = ?)
AND BINARY `hash_id` NOT IN (SELECT `hash_id`
FROM `product_match_unmatches`
WHERE `request_id` = ?
AND `auto_unmatched_by` IS NOT NULL)
Note the ? inside where clauses.
The resultset is different from the raw and laravel query.
I even tried see the bindings value:
//dd($tasks->getBindings());
$sql = str_replace_array('?', $tasks->getBindings(), $tasks->toSql());
dd($sql);
And on running this raw query, it is outputting the correct result-set.
UPDATE:
On checking the bindings, here's what I found:
array:3 [▼
0 => 12
1 => "tsk.request_id"
2 => "tsk.request_id"
]
Here outer query column is wrapped inside quotes and hence treated as a string.
So maybe where clause is trying to compare request_id with a string rather than the outer column.
If it is so, then how do I make them treat as columns rather than string?
use DB::raw() where you trying to add value of request_id
Example
AND `tsk`.`product_id` NOT IN (SELECT `product_id`
FROM `product_progresses`
WHERE `request_id` = DB::raw('tsk.request_id'))
whereRaw('pgr.request_id = tsk.request_id');
Solved the string issue.
You should try to remove select() method, in the subquery replace where() method with whereColumn() method and remove get() method:
$tasks = DB::table('user_tasks', 'urs')
->join('unassigned_tasks as tsk', 'usr.task_id', '=', 'tsk.id')
->where('usr.assigned_to', '=', $assigned_to);
Note: i put the alias 'urs' as second argument (view docs)
$tasks->whereNotIn('tsk.product_id', function($qs) {
$qs->from('product_progresses')
->select(['product_id'])
->whereColumn('request_id', 'tsk.request_id');
});
If you want get specific fields, you must specify the fields in get() method:
return $tasks->get(array('tsk.id', 'tsk.request_id', 'tsk.sys_index', 'tsk.category_group', 'tsk.category', 'tsk.is_assigned', 'tsk.hash_id'));

Using subqueries in Eloquent/Laravel

Here's the query in raw SQL:
SELECT *
FROM (
SELECT `characters`.`id`,`characters`.`refreshToken`,
`characters`.`name`,max(`balances`.`created_at`) as `refreshDate`
FROM `characters`
INNER JOIN `balances` ON `characters`.`id` = `balances`.`character`
WHERE `characters`.`refreshToken` IS NOT NULL
GROUP BY `characters`.`id`
) AS `t1`
WHERE `refreshDate` < '2017-03-29';
I've tested this in phpMyAdmin and it returns the expected results. However I'm using the Eloquent and Laravel libraries in my PHP app and I'm not sure how to approach this. How exactly do subqueries work in this case?
You can do a subquery as a table but need to create the subquery first and then merge the bindings into the parent query:
$sub = Character::select('id', 'refreshToken', 'name')
->selectSub('MAX(`balances`.`created_at`)', 'refreshDate')
->join('balances', 'characters.id', '=', 'balances.character')
->whereNotNull('characters.refreshToken')
->groupBy('characters.id');
DB::table(DB::raw("($sub->toSql()) as t1"))
->mergeBindings($sub)
->where('refreshDate', '<', '2017-03-29')
->get();
If that is your entire query you can do it without the subquery and use having() instead like:
Character::select('id', 'refreshToken', 'name')
->selectSub('MAX(`balances`.`created_at`)', 'refreshDate')
->join('balances', 'characters.id', '=', 'balances.character')
->whereNotNull('characters.refreshToken')
->groupBy('characters.id')
->having('refreshDate', '<', '2017-03-29');
You can use subqueries in Eloquent by specifying them as a closure to the where method. For example:
$characters = Character::where(function ($query) {
// subqueries goes here
$query->where(...
...
->groupBy('id');
})
->where('refreshDate', '<', '2017-03-29')
->get();
You have to chain your methods to the $query variable that is passed to the closure in the above example.
If you want to pass any variable to the subquery you need the use keyword as:
$characterName = 'Gandalf';
$characters = Character::where(function ($query) use ($characterName) {
// subqueries goes here
$query->where('name', $characterName)
...
->groupBy('id');
})
->where('refreshDate', '<', '2017-03-29')
->get();

Eloquent ORM check IFNULL() in GET()

I am using LARAVEL 4 with MySQL back-end. I am novice to it.
I have a statement that returns records from 3 different tables as below :
$templates = Template::with('children')
->leftJoin('template_masters', function($join) {
$join->on('templates.template_master_id', '=', 'template_masters.id');
})
->leftJoin('surveyes', function($join) {
$join->on('templates.survey_id', '=', 'surveyes.id');
})
->get([
'templates.id',
'templates.survey_id',
'surveyes.title', // Here I want the IFNULL() condition e.g. IFNULL('surveyes.title','templates.title')
'templates.type',
'templates.created_at',
'template_masters.is_default'
]);
Basically this creates a query something like :
select `templates`.`id`,
`templates`.`survey_id`,
`surveyes`.`title`,
`templates`.`type`,
`templates`.`created_at`,
`template_masters`.`is_default`
from `templates`
left join `surveyes` on `templates`.`survey_id` = `surveyes`.`id`
left join `template_masters` on `templates`.`template_master_id` = `template_masters`.`id`
But I want this query like :
select `templates`.`id`,
`templates`.`survey_id`,
IFNULL(`surveyes`.`title`, `templates`.`title`),
`templates`.`type`,
`templates`.`created_at`,
`template_masters`.`is_default`
from `templates`
left join `surveyes` on `templates`.`survey_id` = `surveyes`.`id`
left join `template_masters` on `templates`.`template_master_id` = `template_masters`.`id`
In short, instead of surveyes.title, I want IFNULL(surveyes.title,templates.title).
How can I achieve this in ->GET([]) statement of given Eloquent ORM?
Thanks.
You need to use raw statement:
...
->get([
'templates.id',
'templates.survey_id',
DB::raw('IFNULL(surveyes.title,template_masters.title) as title'),
// or if you use namespace:
\DB::raw('IFNULL(surveyes.title,template_masters.title) as title'),
'templates.type',
'templates.created_at',
'template_masters.is_default'
]);

Categories