Laravel whereIn not giving desired result - php

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.

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 OrderBy by related table column

I have a table (A) that has a One to Many relation with another table (B).
I want to query Table A and eager load Table B with the Table A results - but I also want to sort Table A by a value in Table B.
I have tried using OrderBy in the query and also trying SortBy on the resultant collection but cannot get the Table A data to be sorted by the value found in Table B.
Example of what I have tried:
$query = ModelA::with("ModelB"])->get()->sortByDesc('ModelB.sortValue');
Keep in mind, I am only interested in the LATEST record from Table B. So I need to query Table A and sort by a value in the LATEST records of Table B.
How can I achieve this?
EDIT:
The below (as suggested by #ljubadr) works pretty close, but the issue is that there are many record in Table B which means that it doesn't reliably sort as it doesn't seem to sortby the latest records in Table B. Can I have the join return ONLY the latest record for each ID?
$query = ModelA::select('TableA.*')
->join('TableB', 'TableA.id', '=', 'TableB.col_id')
->groupBy('TableA.id')->orderBy('TableB.sortCol', 'desc')
->with(['x'])
->get();
EDIT 2:
#Neku80 answer has gotten me closest but it seems to not sort the column with the greatest accuracy.. I'm sorting a Decimal column and for the most part it is in order but in some places the items are out of order..
$latestTableB = ModelB::select(['TableA_id', 'sortByColumnName'], DB::raw('MAX(created_at) as created_at'))
->groupBy('TableA_id');
$query = ModelA::select('TableA.*')
->joinSub($latestTableB, 'latest_TableB', function ($join) {
$join->on('TableA.id', '=', 'latest_TableB.TableA_id');
})
->orderBy('latest_TableB.sortByColumnName')
->get();
For example, the ordering is like:
0.0437
0.0389
0.0247 <-- -1
0.025 <-- +1
0.0127
When I delete all rows except for the 'latest' rows, then it orders correctly, so it still must be ordering with old data...
I have found a solution:
ModelA::select('TableA.*', 'TableB.sortByCol as sortByCol')
->leftJoin('TableB', function ($query) {
$query->on('TableB.TableA_id', '=', 'TableA.id')
->whereRaw('TableB.id IN (select MAX(a2.id) from TableB as a2 join TableA as u2 on u2.id = a2.TableA_id group by u2.id)');
})
->orderBy('TableB.sortByCol')
->get();
Another alternative to order is like this:
$users = User::orderBy(
Company::select('name')
->whereColumn('companies.user_id', 'users.id'),
'asc'
)->get();
Here we are ordering in asc order by company name field.
In this article it is explained in detail.
You can simply execute a left join query:
ModelA::query()->leftJoin('model_b_table', 'model_a_table.primary_key', '=', 'model_b_table.foreign_key')->orderBy('model_a_table.target_column')->get();
This should work if you only need TableB's ID and created_at columns:
$latestTableB = ModelB::select('TableA_id', DB::raw('MAX(created_at) as created_at'))
->groupBy('TableA_id');
$query = ModelA::select('TableA.*')
->joinSub($latestTableB, 'latest_TableB', function ($join) {
$join->on('TableA.id', '=', 'latest_TableB.TableA_id');
})
->orderBy('latest_TableB.created_at')
->get();

Using Laravel's WhereIn to search multiple tables

I have 3 SQL tables.
clients
events
client_events
Because a client can have multiple events, I made the third table to show those relationships. I am using the following code to retrieve all of the clients that have the have a record matching this event, but it is only returning 1 record when there are multiple.
$eventHosts = DB::table('clients')->whereIn('id', function($query) {
$query->select('client_id')->from('client_events')->where('event_id', '=', explode('/', $_SERVER['REQUEST_URI'])[2]);
})->get();
What am I overlooking?
You can fetch the ids first, then pass to the whereIn query.
$clientIds = DB::table('client_events')
->where('event_id', explode('/', $_SERVER['REQUEST_URI'])[2])
->pluck('client_id')
->toArray();
$eventHosts = DB::table('clients')->whereIn('id', $clientIds)->get();
To get the results in a single query and more efficeintly, try join
$eventHosts = DB::table('clients') //select your main table
->join('client_events','client_events.client_id','=','clients.id') //join it on related columns
->where('client_events.client_id',explode('/', $_SERVER['REQUEST_URI'])[2])) //apply your condition on client_events
->get();

How do I optimize this query with whereHas?

I was using this query to filter out stores with city and categories. It was working fine when I had around 1000 records in stores table. Now, when I have 5000 records it takes around 3-10 seconds to generate result.
A store belongs to multiple categories in my case.
How can I optimize this query using Eloquent orm or DB::raw()?
$stores = Store::where('city_id', $city_id)
->where('disabled', '0')
->whereHas('categories', function($q) use ($category_id){
$q->where('category_id', '=', $category_id);
})
->orderBy('rating','DESC')->paginate(10);
I solved my problem using whereRaw as DB::raw() or DB::select() can not paginate() the collection.
Problem:
Execution time: 11.449304103851s
city_id = 6 & $category_id = 1
$stores = Store::where('city_id', $city_id)
->where('disabled', '0')
->whereHas('categories', function($q) use ($category_id){
$q->where('category_id', '=', $category_id);
})
->orderBy('rating','DESC')->paginate(10);
Solution:
Execution time: 0.033660888671875s
city_id = 6 & $category_id = 1
$stores = Store::
where('city_id', $city_id)
->where('disabled', '0')
->whereRaw('stores.id in (select store_id from store_categories_pivot where category_id = ?)', [$category_id])
->orderBy('rating','DESC')
->paginate(10);
You could try running:
$stores = Store::where('city_id', $city_id)
->where('disabled', '0')
->leftJoin('categories','categories.store_id','=', 'stores.id')
->where('category_id', $category_id)
->orderBy('rating','DESC')->paginate(10);
and now verify your time execution. But you might need to add extra changes to such query because we don't know exact tables structure and how data is organized in them.
If it doesn't help you should get the query that is executed (exact query) and then run
EXPLAIN your_query
in Database Tool to show you what exactly is happening and whether do you really have indexes on everything that is needed.
Looking at your query you should probably have indexes for stores for columns:
city_id
disabled
rating
and for categories you should have indexes for columns:
category_id
store_id
or for some combinations of those columns.

Laravel 5.1 whereNotNull with join not working (returning all data)

I am trying to Select all non empty columns in 'user' table where column name is 'review'.
$applications = DB::table('users')
->join('applications', 'user.update_id', '=', 'applications.id')
->whereNotNull('users.review')
->select('user.id', 'user.rating', 'user.appid', 'user.review', 'applications.title', 'applications.price', 'applications.icon_uri')
->orderBy('user.id','asc')
->paginate(20);
$applications->setPath('');
return $applications;
But return data includes all information of both 'user.review' empty and not empty as well.
I feel there is no effect of whereNotNull() and i found no error in the statement.
I tried moving ->whereNotNull('user.review') this line top and bottom result is same.
I tried even by removing select and orderBy but returns same data.
$applications = DB::table('users')
->join('applications', 'user.update_id', '=', 'applications.id')
->whereNotNull('users.review')
->paginate(20);
$applications->setPath('');
return $applications;
Is there any way to make it work?
if your table is users you are missing an s in the table name, you should write
->whereNotNull('users.review')
same case in the join with the field update_id, otherwise you have to change the table name in the table method

Categories