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',
])
Related
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.
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.
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.
I need to populate my database column from seeding. I have 'interested_in' column in user_profiles table and I need to populate it with according id from 'value_id' column from user_interests table. Both user_profiles and user_interests table are connected to users table ('user_id' column is in both tables). So if the value of 'value_id' column is for example 1 it needs to be 1 in 'interested_in' column, I need help on how to populate that 'interested_in' column from seeders. 'value_id' column is already populated. Here are my tables and example data and my code so far but it doesn't work currently, it shows 'Array to string conversion' error.
user_interests table
user_id field_id value_id
1 1 1
user_profiles table
id user_id interested_in
1 1 and here needs to be 1, to associate value_id (1) from user_interests table
UserProfileTableSeeder.php
class UserProfileTableSeeder extends Seeder
{
use ChunkSeeder;
public function run()
{
$users = User::all()->pluck('id');
$user_interests = DB::table('user_interests')->select('value_id')->where('field_id', 1)->get();
$user_interests_values = $user_interests->pluck('value_id')->toArray();
$seed = [];
foreach ($users as $userId) {
$seed[] = factory(UserProfile::class)->make(
[
'user_id' => $userId,
'interested_in' => $user_interests_values, // Shows array to string error!!!
]
)->toArray();
}
$this->seedChunks($seed, [UserProfile::class, 'insert']);
}
}
That's why $user_interests_values is an array as you can see in this line (at the end):
$user_interests_values = $user_interests->pluck('value_id')->toArray();
Try in the factory to change $user_interests_values to current($user_interests_values) or probably $user_interests_values['id'] or whatever.
PS: I am not really sure what's inside $user_interests_values, you can see it with a dd() and running the migration then (don't worry, the migration won't be successful because of the dd() and you will be able to run it later again until it finishes properly.
By the way, I recommend you to do something more legible than you did in your rum() method. I mean, something like this:
$interest = factory(UserInterests::class)->create();
factory(UserProfiles::class, 20)->create([
'interest' => $interest->id,
]);
I am looking to return the full information on duplicate records from my table.
I am currently using the following:
DB::table($entity['table'])
->select('*')
->groupBy($entity['columns'])
->havingRaw('COUNT(*) > 1')
->get();
Which is great, it returns the duplicate records, however, this only returns one of the records I need to return all the duplicates so that I can greet the user with a choice on which one to delete or keep.
How can I modify the above query to accomplish that?
Joining against the same table will allow you to retrieve the duplicate records without just getting a single version of it (caused by your groupBy in your question)
$entity['columns'] = ['username', 'surname'];
$groupBy = implode(',', $entity['columns']);
$subQuery = DB::table('list')
->select('*')
->groupBy($groupBy)
->havingRaw('(count(id) > 1))');
$result = DB::table('list')
->select('*')
->join(
DB::raw("({$subQuery->toSql()}) dup"), function($join) use ($entity) {
foreach ($entity['columns'] as $column) {
$join->on('list.'.$column, '=', 'dup.'.$column);
}
})
->toSql();
// or ->get(); obviously
dd($result);
You need to join back to the table in $entity['table'] on the columns in the $entity['columns'] to get the duplicates.
Also, the select('*') is not really a good idea even if you are using mysql. This is against the sql standard and works in mysql only if it is configured in a certain way. If the configuration changes or you migrate your app to a mysql server with different configuration on sql modes, your query will fail.
I would use $entity['columns'] in the select list as well.
In sql the query should look like as follows:
select table.* from table inner join
(select field1, field2 from table t
group by field1, field2
having count(*)>1) t1
on table.field1=t1.field1 and table.field2=t1.field2
You can accomplish this by using whereIn and a function to query the same table that you are working with.
Let say you have a scenario where you want to look for duplicate records containing the same last name in the user table.
Database Table - User
--- user_id --- first_name --- last_name --- email ---
1 Dan Smith dan#test.com
2 Jim Jones jim#test.com
3 Amy Grant amy#test.com
4 Bob Brown bob#test.com
5 Sue Davis sue#test.com
6 Leo Grant leo#test.com
7 Ann Grant ann#test.com
Then you can use the following code;
$duplicates = DB::table('user')
->select('user_id', 'last_name')
->whereIn('user_id', function ($q){
$q->select('user_id')
->from('user')
->groupBy('last_name')
->havingRaw('COUNT(*) > 1');
})->get();
Which will return the following;
--- user_id --- last_name
3 Grant
6 Grant
7 Grant