How do I optimize this query with whereHas? - php

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.

Related

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

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.

Use query 2 in query 1 to get all data inside one query

I have 3 tables :
*hinteractions (id, name...)
*effects (id, name...)
*hinteractions_has_effects (hinteractons_id, effects_id)
I might have one to many effects in a hinteractions et one hinteraction might have one to many effects, that's why I have this join table.
I have this Eloquent query :
Query 1 :
$informations_plante = DB::table('herbs')
->select('herbs.name as hname', 'herbs.sciname', 'herbs.id as herbid','hinteractions.id as hinteractionid','hinteractions.note as hinteractionnote','hinteractions.force_id','targets.name as targetname', 'forces.name as force_name')
->leftJoin('hinteractions', 'herbs.id', '=', 'herb_id')
->leftJoin('forces', 'forces.id', '=', 'force_id')
->leftJoin('targets', 'targets.id', '=', 'hinteractions.target_id')->where('herbs.id', $id)
//I would like to use the query 2 here to select effects.name with hinteraction.id used in this query to get effects' name in this query (I might have one to many).
->get();
Query 2 :
$hinteractions_has_effects = DB::table('hinteraction_has_effects')
->select(DB::raw('effect_id, hinteraction_id','effects.name'))
->where('hinteraction_has_effects', '=', hinteraction.id (from the query 1))
->get();
With query 1, I retrieve some informations like hinteractions_id.
I would like to use those hinteractions_id and use them in the query 2.
The best way will be to merge both queries (1 and 2) to get only one Eloquent query.
Do you have any idea ?
Try below query and let me know if this is what you're looking for -
$informations_plante = DB::table('herbs')
->select('herbs.name as hname', 'herbs.sciname', 'herbs.id as herbid','hinteractions.id as hinteractionid','hinteractions.note as hinteractionnote','hinteractions.force_id','targets.name as targetname', 'forces.name as force_name', 'effects.id as effects_id', 'effects.name as effects_name')
->leftJoin('hinteractions', 'herbs.id', '=', 'herb_id')
->leftJoin('forces', 'forces.id', '=', 'force_id')
->leftJoin('targets', 'targets.id', '=', 'hinteractions.target_id')->where('herbs.id', $id)
//added below lines
->leftJoin('hinteraction_has_effects', 'hinteraction_has_effects.hinteractons_id', '=', 'hinteractions.id')
->leftJoin('effects', 'hinteraction_has_effects.effects_id', '=', 'effects.id')
->get();
HTH!

Codeigniter : Join 2 tables based on where condition

I have to select data from two tables for APIs.
There are two tables garage_services, and garages. I receive garage_sevices id.
Garage_services table is like this
id | garage_id | service _id
Now from this table I select row based on service_id from here I have to select garage_id and get details about garage from garage table
$garages = $this->db
->select('*')
->from('garage_services')
->join('garage', 'garage_services.id=garage.id')
->where($where)
->get();
Above is the query I came up with, I don't know if its correct. as I am not sure about $where as well.
Please help me with this
Here I have written the query for your solution.
$garages = $this->db->from('garage_services as gs, garages as g')
->where('g.id', $garage_service_id, FALSE)
->get();
Here $garage_service_id is the variable that passed in function argument as you have written you in your question.
Try this
->select('*')
->from('garage_services as t1')
->join('garage as t2', 't1.garage_id = t2.id')
->where('t1.id', $service _id)
->get();
As per the detail is given in the question your query look correct and for the where condition:
$this->db->select('*');
$this->db->from('table1');
$this->db->join('table2', 'table1.col1 = table2.col1');
$this->db->where('table1.col1', 2);
$query = $this->db->get();

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.

Categories