I will try below query, but not sure is prevent sql injection?
$status = [1, 2, 3];
$param = implode(', ', $status);
$rows = (new \yii\db\Query())
->select('*')
->from('user')
->leftJoin('post', "post.user_id = user.id AND post.some_column = $value AND post.status IN ($param)");
->all();
return expected results but may be occur sql injection. My IN condition look like is IN (1, 2, 3)
$rows = (new \yii\db\Query())
->select('*')
->from('user')
->leftJoin('post', "post.user_id = user.id AND post.some_column = :sid AND post.status IN (:param)", [':param' => $param, ':sid' => $value]);
->all();
only compare first element in array because is look like this IN ('1, 2, 3') its consist single string not check second element in array only work on first element.
I refer below link but no idea for how to implement this condition.
Can I bind an array to an IN() condition?
Please give the solution for how to use IN() Condition in On part of join(PDO/Yii2/mysql).
Based on this issue:
$rows = (new \yii\db\Query())
->select('*')
->from('user')
->leftJoin('post', ['post.user_id' => new \yii\db\Expression('user.id'), 'post.some_column' => $sid, 'post.status' => $statuesArray]);
->all();
Yii2 can create a parametrized IN condition by passing the condition as an array i.e:
['post.status' => $status]
However, converting your join condition to the array format will not work as explained in the Yii guide:
Note that the array format of where() is designed to match columns to values instead of columns to columns, so the following would not work as expected: ['post.author_id' => 'user.id'], it would match the post.author_id column value against the string 'user.id'. It is recommended to use the string syntax here which is more suited for a join:
'post.author_id = user.id'
Since you are using an INNER JOIN the result of putting the join condition in WHERE instead of in ON will be syntactically equal as explained in INNER JOIN condition in WHERE clause or ON clause?. For readability and ease of maintenance, you can leave the comparison for the tables columns in the join condition:
$rows = (new \yii\db\Query())
->select('*')
->from('user')
->innerJoin('post', 'post.user_id = user.id')
->where(['post.some_column' => $value, 'post.status' => $status])
->all();
Related
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).
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'));
i have two table, tutorial:id,title and tutorial_tags:id,tutorial_id,title
the relation in tutorial model is defined like this :
function TutorialTag(){
return $this->hasMany('App\TutorialTag');
}
i want to left join tutorials with tutorial_tags , like (please ignore syntax errors):
select tutorials.* , tutorial_tags.* from `tutorials` left Join
`tuotrial_tags` ON tutorials.id = tutorial_tags.tutorial_id
but i want to be able to use Conditions on tutorial_tags in case user want to search a particular tags:
select tutorials.* , tutorial_tags.* from `tutorials` left Join
`tuotrial_tags` ON tutorials.id = tutorial_tags.tutorial_id
where tutorial_tags.title = 'ABC'
if i use whereHas like this :
$tutorials = Tutorial::whereHas('TutorialTag',function ($query){
if(isset($_GET['tag']))
$query->where('title',$_GET['tag']);
})->get();
i dont get tutorials that are without any tag, basically it works like inner Join.
and if i use with :
$tutorials = Tutorial::with(['TutorialTag'=>function($query){
if(isset($_GET['tag']))
$query->where('title',$_GET['tag']);
}])->get();
then ill get two separate queries with no effect on eachother, basically the where condition on tutorial_tags has no effect on tutorials and i get all the tutorials even the ones without sreached tag, here is the query log :
Array
(
[0] => Array
(
[query] => select * from `tutorials`
[bindings] => Array
(
)
[time] => 0
)
[1] => Array
(
[query] => select * from `tutorial_tags` where `tutorial_tags`.`tutorial_id` in (?, ?, ?) and `title` = ?
[bindings] => Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => ABC
)
[time] => 2
)
)
how can i get left Join like query with optional condition on the right table
This is not an efficient sql query, but you can solve the issue with laravel collections and an extra query.
Grab all the intersections:
$tutorials1 = Tutorial::whereHas('TutorialTag',function ($query){
if(isset($_GET['tag']))
$query->where('title',$_GET['tag']);
})->get();
Grab the rest:
$tutorials2 = Tutorial::doesntHave('TutorialTag')->get();
Merge both collections:
$tutorials = $tutorials1->merge($tutorials2);
To get the left join query do the following
$tutorials = Tutorial::leftJoin('tutorial_tags', 'tutorials.id', '=', 'tutorial_tags.tutorial_id')
->where(function($query){
if(isset($_GET['tag']) {
$query->where('tutorial_tags.title' , $_GET['tag'];
}
});
This will give you the following outputs:
Case 1: If tag parameter was provided
SELECT * FROM tutorials LEFT JOIN tutorial_tags ON tutorials.id = tutorial_tags.tutorial_id WHERE tutorial_tags.title = 'ABC';
Case 2: When no tag parameter has been provided
SELECT * FROM tutorials LEFT JOIN tutorial_tags ON tutorials.id = tutorial_tags.tutorial_id;
The whereHas() method returns only those models where any relationship matches the condition in the closure. The with() method only returns relationships that match the condition in the closure. What you need to do is use them both. You can also use the when() method to filter on the boolean condition of the input value being filled.
$tag = $request->input('tag');
$tutorials = Tutorial->when($tag, function ($query) use ($tag) {
$query
->whereHas('tutorialTags', fn ($q) => $q->where('title', $tag)
->with(['tutorialTags' => fn ($q) => $q->where('title', $tag]);
})
->get();
You also should be following proper Laravel conventions that will make your life easier, especially when sharing code with others.
You should never see $_GET used in your code anywhere. I've removed it in favour of getting it from the request object, but it should properly be set as a route parameter. Relationship methods that return more than one model (such as a HasMany) should have plural names. Relationships names should be in camel case.
After spending a while I'm able to fix this with Lazy Eager Loading
$tutorials = Tutorial::all();
$tutorials->load(['TutorialTag' => function ($query) {
$query->where('title', $_GET['tag']);
}]);
How can I pass an array variable in where clause with codeigniter?
For example with a SQL query like:
Select(" item,price Where(userid=123 and proid=3 or userid=124 and proid=3... [upto n user id]userid=nth_id and proid=3");
If userid is stored in an array variable then how can I create such a condition in codeigniter?
I you are looking for this, it should look for a userid that is in the array and also has a proid of 3. I have not tested it but let us know how you get on.
$array = array( '123', '124' );
$this->db
->select( 'item, price' )
->from( 'table')
->where( 'proid', '3' )
->where_in( 'userid', $array )
->get();
$result = $query->result();
Hi I've done a simple query for a ranking mechanism with the query builder.
$result = $qb
->select('u')
->where('u.status = 1')
->from('PGMainBundle:User', 'u')
->groupBy('u.id')
->addSelect('COUNT(c.id) as HIDDEN nChallenges')
->leftJoin('u.challenges', 'c', 'WITH', 'c.closed = 1' )
->add('orderBy','u.points DESC, nChallenges DESC')
->orderBy('u.points', 'DESC')
->addOrderBy('nChallenges', 'DESC')
->setFirstResult($offset*50)
->setMaxResults(50)
->getQuery()
->getResult();
Now while my ranking mechanism works fine, I'd like to check what loop.index a user with an $id has.
Said this, I don't want to use a foreach loop on the result to do so.
Is there a more optimal way just to return the "position" in the ranking ?
Possibly using the query builder ?
The result should be an array collection so you can get the index of a given element like this :
$result->indexOf($yourelement)
Else if the keys are not in order, but are the id of the entities :
$keys = $result->getKeys();
$id = $yourElement->getId();
$position = array_search($id, $keys);