laravel access outer query column inside subquery - php

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'));

Related

Laravel query builder join after where clause

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).

How can I make select in select on laravel eloquent?

I use laravel 5.3
My sql query is like this :
SELECT *
FROM (
SELECT *
FROM products
WHERE `status` = 1 AND `stock` > 0 AND category_id = 5
ORDER BY updated_at DESC
LIMIT 4
) AS product
GROUP BY store_id
I want to change it to be laravel eloquent
But I'm still confused
How can I do it?
In cases when your query is to complex you can laravel RAW query syntax like:
$data = DB::select(DB::raw('your query here'));
It will fire your raw query on the specified table and returns the result set, if any.
Reference
If you have Product model, you can run
$products = Product::where('status', 1)
->where('stock', '>', 0)
->where('category_id', '=', 5)
->groupBy('store_id')
->orderBy('updated_at', 'desc')
->take(4)
->get();
I think this should give you the same result since you pull everything from your derived table.

Use number in where condition in Laravel Query

The below query works well in mysql but how to represent the same thing using Laravel.
select * from user_subscription where vendor_id = 'user_100'
and 0 = (select count(*) from user_restricted_dates where vendor_id = 'user_100')
I tried with code but gives error as unknown column '0' in where clause
$list = UserSubscription::where('vendor_id', '=', $vendor_obj->vendor_id)
->where(0, '=', "(select count(*) from user_restricted_dates where vendor_id = 'user_100'")
->get();
Well the error indicates what it is but how to represent it
The where method of the Query Builder maps the first value you pass to a field in the model. You'd have to use the whereRaw method instead.
$list = UserSubscription::where('vendor_id', '=', $vendor_obj->vendor_id)
->whereRaw("0 = (select count(*) from user_restricted_dates where vendor_id = 'user_100')")
->get();

SQL exists in Laravel 5 query builder

Good morning,
I've been trying for quite a lot of time to translate this query(which returns an array of stdClass) into query builder so I could get objects back as Eloquent models.
This is how the query looks like untranslated:
$anketa = DB::select( DB::raw("SELECT *
FROM v_anketa a
WHERE not exists (select 1 from user_poeni where anketa_id=a.id and user_id = :lv_id_user)
Order by redni_broj limit 1"
), array( 'lv_id_user' => $id_user,
));
I have tried this, but it gives a syntax error near the inner from in the subquery:
$anketa = V_anketa::selectRaw("WHERE not exists (select 1 from user_poeni where anketa_id=a.id and user_id = :lv_id_user)", array('lv_id_user' => $id_user,)
)->orderBy('redni_broj')->take(1)->first();
The problem is this exists and a subquery in it. I couldn't find anything regarding this special case.
Assume each table has an appropriate Eloquent model.
V_anketa is a view. The db is postgresql.
As far as the query goes I believe this should work:
$anketa = V_anketa::whereNotExists(function ($query) use ($id_user) {
$query->select(DB::raw(1))
->from('user_poeni')
->where('anketa.id', '=', 'a.id')
->where('user_id', '=', $id_user);
})
->orderBy('redni_broj')
->first();
but I'm not clear on what do you mean by "assuming every table has an Eloquent model" and "V_anketa" is a view...
Assuming the SQL query is correct, this should work:
$anketa = DB::select(sprintf('SELECT * FROM v_anketa a WHERE NOT EXISTS (SELECT 1 FROM user_poeni WHERE anketa_id = a.id AND user_id = %s) ORDER BY redni_broj LIMIT 1', $id_user));
If you want to get back an Builder instance you need to specify the table:
$anketa = DB::table('')->select('');
If you however, want to get an Eloquent Model instance, for example to use relations, you need to use Eloquent.

laravel 5.1 eloquent query

I got a query that works fine in PMA and I'm trying to convert it to Eloquent, but I must be missing something.
Here the SQL query:
SELECT t1.* FROM ppr__child_absence t1
LEFT JOIN ppr__children t3 ON t3.idcode=t1.child_idcode
WHERE t1.id = (SELECT t2.id FROM ppr__child_absence t2
WHERE t2.child_idcode = t1.child_idcode
ORDER BY t2.id DESC LIMIT 1)
AND t3.deleted_at IS NULL
AND $input BETWEEN start AND stop;
and hes is my eloquent query:
$input = Request::all();
$pprs =
DB::table('ppr__child_absence')
->join('ppr__children', function($join) {
$join->on('ppr__children.idcode', '=', 'ppr_children_absence.child_idcode')
->where('ppr__child_absence.child_idcode', '=', DB::raw('SELECT t2.id FROM ppr__child_absence t2
WHERE t2.child_idcode = ppr__child_absence.child_idcode
ORDER BY t2.id DESC LIMIT 1'));})
->whereNull('ppr__children.deleted_at')
->whereBetween($input, array('ppr_children_absence.start','ppr_children_absence.stop'))->get();
I'm keep getting error:
ErrorException in Grammar.php line 58:
strtolower() expects parameter 1 to be string, array given.
Can anyone here lead me to right direction?
Basically user has 1 date input with datepicker and submit button to get result according to the date chosen.
working query here with out errors but no results
$input = Request::all();
$pprs = DB::table('ppr__child_absence')
->select('ppr__child_absence.*')
->join('ppr__children', function($join) {
$join->on('ppr__children.idcode', '=', 'ppr__child_absence.child_idcode')
->where('ppr__child_absence.id', '=', DB::raw('SELECT t2.id FROM ppr__child_absence t2
WHERE t2.child_idcode = ppr__child_absence.child_idcode
ORDER BY t2.id DESC LIMIT 1'));})
->whereNull('ppr__children.deleted_at')
->where('ppr__child_absence.start', '<', $input['ppr_day'])
->where('ppr__child_absence.stop', '>', $input['ppr_day'])->get();
$input = Request::all(); stores all the input fields in an array.
You are using ->whereBetween($input,...) and the first parameter of whereBetween() is expected to be a string (the name of the table column) but you are passing an array.
You didn't give details about the input. Assuming the input contains a field called 'field', the you should use ->whereBetween($input['field'],...) instead
What you seem to want is not what the whereBetween method offers. When using whereBetween the logic is that you want a given column value to be between two input values. The example in the Laravel Query Builder Documentation is chosen well:
// the column 'votes' value should be between 1 and 100
$users = DB::table('users')->whereBetween('votes', [1, 100]);
What you seem to want is to make sure an input value is between two different column values. You can do that by having two separate conditions:
->where('ppr_children_absence.start', '<', $input['value'])
->where('ppr_children_absence.stop', '>', $input['value'])
This will make sure that $input['value']) is between the start and stop column values.

Categories