How to to use laravel WhereIn with two tables? - php

I have two tables (cards and composed_card).
The first table is the cards table, which contains multiple cards with IDs(1, 2, 3, 4, 5, 6, ....).
The second table is card_compositions, which contains three columns (composed_card and used_card).
In the second table both composed_card and used_card are foreign keys to cards table.
I'm trying to write a query in Laravel to get all cards that has at least one composed_card in card_compositions table.
I'm trying to create this query in Laravel Eloquent.
select * from `cards` where `id` in (select `composed_card` from `card_compositions` where `composed_card` = cards.id) and `cards`.`deleted_at` is null
The above SQL query works fine and returns the required results.
When I try to execute the same query in Laravel, it's not returning any results:
return Card::whereIn('id', function ($query) {
$query->select('composed_card')
->from('card_compositions')
->where('composed_card', '=', 'cards.id');
});
I wrote the above code to simulate the SQL query, but the above code is not working.
What I'm possibly doing wrong in the php code?

You have to use a WhereRaw. Explanation and code:
Your code is currently trying to find a match for the string 'cards.id', not join on the table cards.id. If you do
DB::enableQueryLog(); // Enable query log
\\Run your query with ->get()
dd(DB::getQueryLog());
Then you will see:
array:1 [
0 => array:3 [
"query" => "select * from `cards` where `id` in (select `composed_card` from `card_compositions` where `composed_card` = ?)"
"bindings" => array:1 [
0 => "cards.id"
]
"time" => 4.48
]
]
Note your query now has a binding, your raw query takes no parameters. Your id column is integer, and its trying to find the string "cards.id" inside it, which it never will, which explains why you get no results. So replace with:
return Card::whereIn('id', function ($query) {
$query->select('composed_card')
->from('card_compositions')
->whereRaw('composed_card = cards.id'); \\new WhereRaw clause
});
If you dump that, you will get the raw query you're expecting, and running it will get the results back.

Related

Laravel filtering - get the last created entry from the DB which has a specific ID

For this data (from a table called status_student):
I want to do some filtering. This table is a pivot table and I want to return a record from a base table only if the latest created object has a specific status_id.
For example, if I filter for status_id = 1, I shouldn't get any object for those two rows.
Here's my query for filtering:
$query = Student::whereHas('statusuri', function($q) use ($status) {
$q->orderBy('status_student.data_sfarsit', 'desc')->where('status_id', '=', $status);
});
statusuri From Student:
public function statusuri()
{
return $this->belongsToMany(Status::class, 'status_student')
->withPivot('id', 'data_inceput', 'data_sfarsit', 'document', 'status_id', 'stare_stagiu_id')
->withTimestamps();
}
My query works, but it returns an object if I filter for a status_id of 1 and of 6 too. I only want to return an object for filtering with status_id = 6 (because that is the latest status).
I tried modifying my query like this:
$query = Student::whereHas('statusuri', function($q) use ($status) {
$q->orderBy('status_student.data_sfarsit', 'desc')->first()->where('status_id', '=', $status);
});
but then it didn't work, and I think I know the reason.
first() returns an instance of another type (after an inner join, basically), which doesn't have a status_id. So I shouldn't use it this way. Also, it returns an object, not a Query Builder instance anymore.
The error:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'students.id' in 'where clause' (SQL: select * from `statusuri` inner join `status_student` on `statusuri`.`id` = `status_student`.`status_id` where `students`.`id` = `status_student`.`student_id` order by `status_student`.`data_sfarsit` desc limit 1)
So, how can I make that query filter my latest created DB row with a specific status_id?
Thanks.
//Example:
For student_id = 1 as in the picture:
if status_id = 1:
query returns nothing
if status_id = 6:
query returns the student
Explanation:
$status_id is provided through a HTML form, and I want to return that specific student if the last status known is equal to this $status_id.
My approach was filtering with latest(id) so I get the latest entry on the first row, then getting the first entry, then doing a where clause on the status_id. But it doesn't work because first() returns an object, not a QueryBuilder anymore.

Laravel whereIn not giving desired result

I have the following two tables:
unotes unote_unit
====== ==========
id unote_id
notes unit_id
I am trying to retrieve unotes row whose related unit_id columns exactly match an input array.
So, I run following query:
$unote = UNote::whereHas('units', function ($query) use ($request) {
$query->whereIn('units.id', $request->unit_lists);
})
->withCount('units')
->having('units_count', '=', $u_list_count)
->get()
->pluck("id");
But, the problem with the above query is that even if it has just a single matching unit_id, it retrieves the data. For example I have following datasets:
unotes //with related unit_id
========
id = 3 //49865, 49866, 49867
id = 4 //49865, 49866
With above mentioned code, if I pass [49866,55555], it should return nothing but it returns ids 3 and 4, which contain one match but not all.
I have found similar question on Laracasts as well but running the query returns Cardinality violation: 1241 Operand should contain 2 column(s):
$unote = UNote::with('units')
->whereHas('units', function ($query) use ($request) {
$query->selectRaw("count(distinct id)")->whereIn('id', $request->unit_lists);
}, '=', $u_list_count)
->get()
->pluck("id");
I also found a similar question here, but seems it is too expensive.
Here is the dummy SQL to get started: http://sqlfiddle.com/#!9/c3d1f7/1
Because whereHas just adds a WHERE EXISTS clause to the query with your specified filters, a whereIn will indeed return true for any matches. One thing you could try is running a raw subquery to get a list of device IDs and compare it.
$search_ids = [49866, 49865];
sort($search_ids);
$search_ids = implode(",", $search_ids);
Unote::select("id")
->whereRaw(
"(SELECT GROUP_CONCAT(unit_id ORDER BY unit_id) FROM unote_unit WHERE unote_id = unotes.id) = ?",
[$search_ids]
)
->get()
->pluck("id");
Note, if you have soft deletes enabled you will also want to filter out soft deleted items in the subquery.

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

subquery in andFilterWhere in yii2

I have to execute this query
select count(id) as numberofcompanies, sum(offered_value) as total_offered_value from campaign_comapany
where ( campaign_id in (select id from campaing where user_id = current_user ) or campaing_company.sp_user_id = current_user)
and campaign_company.status = '3'
And I wrote the query like this in yii2-
$query->select(['count(id) as numberofcompanies','sum(offered_value) as total_offered_value'])
->from('campaign_company')
->andFilterWhere(['or',['in','campaign_id',(new yii\db\Query())->select(['id'])->from('campaign')->where(['=','user_id',Yii::$app->user->id])],['=','campaign_company.sp_user_id',Yii::$app->user->id]])
->andWhere(['=','status','3'])
->one();
But it gives me error with unknow column campaign_company.user_id
but working if I just use where like following-
$query->select(['count(id) as numberofcompanies','sum(offered_value) as total_offered_value'])
->from('campaign_company')
->where(['in','campaign_id',(new yii\db\Query())->select(['id'])->from('campaign')->where(['=','user_id',Yii::$app->user->id])])
->andWhere(['=','status','3'])
->one();
What should I do to get the result mentioned sql query, I don't want to hit database server two time, thanks in advance
Use join:
$query->select(['count(id) as numberofcompanies','sum(offered_value) as total_offered_value'])
->from('campaign_company')
->innerJoin('campaign','`campaign`.`id` = `campaign_company`.`campaign_id`')
->where([
['=','campaign.user_id',Yii::$app->user->id])],
['=','campaign.campaign_company.sp_user_id',Yii::$app->user->id],
['=','campaign_company.status','3']
]
->one();
As is it is difficult to follow your logic. To simplify your code and your query, add the first condition as plain sql. Also since the second condition is required, you can swap the positions as follows:
$query
->select(['count(id) as numberofcompanies','sum(offered_value) as total_offered_value'])
->from('campaign_company')
->where(['status' => '3'])
->andWhere(['
campaign_id in (select id from campaign where user_id = :current_user )
or campaign_company.sp_user_id = :current_user1',
[':current_user' => Yii::$app->user->id, ':current_user1' => Yii::$app->user->id]])
->one();

Laravel 4 Eloquent returns wrong ID

I have 3 tables in my database:
Campaigns
Users
Companies
One company may have some users. One user may have some campaigns. A user (with admin rights) can do some actions with any campaign that belongs to his company. So, I want to check whether
he's doing these actions with his campaign or not (in the last case I return something like "access denied").
My condition
Campaign::join('users', 'users.id', '=', 'campaigns.user_id')
->where('users.company_id', '=', Auth::user()->company->id)
->where('campaigns.id', '=', Input::get('id'))
->first();
So if I got unique campaign - it's ok, if I got null - something's wrong and I send "access denied" to user as he's dealing with other company campaign.
This code produces next query:
array(3) {
["query"]=>
string(148) "select * from `campaigns` inner join `users` on `users`.`id` = `campaigns`.`user_id` where `users`.`company_id` = ? and `campaigns`.`id` = ? limit 1"
["bindings"]=>
array(2) {
[0]=>
string(1) "2"
[1]=>
string(2) "13"
}
["time"]=>
float(0.42)
}
Using phpmyadmin I tried the same query and got a campaign with ID = 13.
But when I was debugging my application I found out that
dd($campaign->id);
returns 8 instead. 8 also equals campaigns.user_id (The record has both campaigns.id and campaigns.user_id = 8).
I can't figure out why it's happening. Even if something wrong with my SQL query (what I doubt as phpmyadmin returned right results), I got where condition campaigns.id = Input::get('id'), where Input::get('id') = 13. Why id is being changed?
Of course I can do this security check in two steps, like first get the campaign, then check
$campaign->user->company->id = Auth::user()->company->id but just wondering ...
If you run this query in phpMyAdmin you should probably be able to see that the result contains multiple columns by the name "id". When PHP parses the query result to an associative array or object, keys must be unique! If keys are colliding, the last column will be used!
Example:
SQL result:
id user_id name id name company_id
1 2 Camp1 2 Pelle 1
PHP result:
array (size=1)
0 =>
object(stdClass)[131]
public 'id' => string '2' (length=1)
public 'user_id' => string '2' (length=1)
public 'name' => string 'Pelle' (length=5)
public 'company_id' => string '1' (length=1)
To solve this you could add a select clause to only select the campaign columns:
Campaign::select('campaigns.*')
->join('users', 'users.id', '=', 'campaigns.user_id')
->where('users.company_id', '=', Auth::user()->company->id)
->where('campaigns.id', '=', Input::get('id'))
->first();
This seems like a limitation in the Eloquent library. Instead of using the last "id", it should be more proactive in searching for the "id" of the main table of the query. Or use "as" in the SQL statements.
Another solution would be to name each table's id field uniquely. e.g. user.user_id, campaigns.campaign_id. This would collide with the foreign keys in other tables, so name foreign keys as campaigns.user_fid.
If you want control over the actual table who's duplicate column is used, just add the tables to an array in a specific order. The last one will be the ->id property.
->select([
'table1.*',
'table2.*',
'tags.*',
'companies.*',
'users.*',
])
This way you preserve all unique columns that are selected with the join.
Should you want specific columns from a specific table you can use an AS:
->select([
'table1.*',
'table2.*',
'tags.*',
'companies.*',
'users.*',
'table1.id AS table1_id',
'tags.name AS tag_name',
'companies.name AS company_name',
])

Categories