Laravel search query optimization - laravel

hope you are doing good!
I have more than 4500 businesses in my database. Each business hasMany tags. So I have my main search input in navbar. When user submits any string via that search input I want to display all businesses that contain this string in any of their tags or/and in it's name field.
Example: When string = ab. It must display first 20 businesses that contains string ab in any of their tag name or/and name field.
Result:
Name: Nurabis Tags: Alcoholic Beverages
Name: Boirs Tags: Mobile Phones, Mobile Phone Accessories, Tablets
Name: Babilon Tags: Cable TV, Mobile Network Operators
......
This Eloquesnt/DB query was generated and executed
$businesses = Business::
->select('businesses.*')
->leftJoin('business_tag', 'businesses.id', '=', 'business_tag.business_id')
->leftJoin('tags', 'business_tag.tag_id', '=', 'tags.id')
->orWhere("tags.{$this->lang}_name", 'LIKE', "%$str%")
->orWhere('businesses.name', 'LIKE', "%$str%")
->where('businesses.status', true)
->groupBy('businesses.id')
->with(['tags'])
->withCount(['reviews as rating' => function($query){
$query->select(DB::raw('round(avg(rating), 1)'));
}])
->withCount('reviews')
->with(['reviews' => function($query){
$query->latest();
}])
->with(['images' => function ($query)
{
$query->where('avatar', true);
}])
->paginate(20);
The problem is that it took 21.05s to execute. This is what my debugger shows. These are two queries that took most of the time:
6.24s
select count(*) as aggregate from `businesses`
left join `business_tag` on `businesses`.`id` = `business_tag`.`business_id`
left join `tags` on `business_tag`.`tag_id` = `tags`.`id`
where (`tags`.`en_name` LIKE '%ab%' or `businesses`.`name` LIKE '%ab%')
and `businesses`.`status` = 1 group by `businesses`.`id`
and 14.78s
select `businesses`.*,
(select round(avg(rating), 1) from `reviews`
where `businesses`.`id` = `reviews`.`business_id` and `status` = 1) as `rating`,
(select count(*) from `reviews` where `businesses`.`id` = `reviews`.`business_id`
and `status` = 1) as `reviews_count` from `businesses`
left join `business_tag` on `businesses`.`id` = `business_tag`.`business_id`
left join `tags` on `business_tag`.`tag_id` = `tags`.`id`
where (`tags`.`en_name` LIKE '%ab%' or `businesses`.`name` LIKE '%ab%')
and `businesses`.`status` = 1 group by `businesses`.`id` limit 20 offset 0
But when I comment that tag part like so
$businesses = Business::
->select('businesses.*')
// ->leftJoin('business_tag', 'businesses.id', '=', 'business_tag.business_id')
// ->leftJoin('tags', 'business_tag.tag_id', '=', 'tags.id')
// ->orWhere("tags.{$this->lang}_name", 'LIKE', "%$str%")
->orWhere('businesses.name', 'LIKE', "%$str%")
I takes 40.47ms to execute.
And if I comment name part instead like so
$businesses = Business::
->select('businesses.*')
->leftJoin('business_tag', 'businesses.id', '=', 'business_tag.business_id')
->leftJoin('tags', 'business_tag.tag_id', '=', 'tags.id')
->orWhere("tags.{$this->lang}_name", 'LIKE', "%$str%")
// ->orWhere('businesses.name', 'LIKE', "%$str%")
It takes 90.84ms
Now the question is: Is there any way to optimize this query?
Thank you in advance! Have a nice day!

Try this below.See how much time it take time to execute.Aslo instead of lefjoin you can create belongsToMany relationship in Business Model for Tags
$businesses = Business::
select('businesses.*')
->leftJoin('business_tag', 'businesses.id', '=', 'business_tag.business_id')
->leftJoin('tags', 'business_tag.tag_id', '=', 'tags.id')
->where(function ($q)use($str){
$q->where('businesses.status', true);
$q->orWhere("tags.{$this->lang}_name", 'LIKE', "%$str%");
$q->orWhere('businesses.name', 'LIKE', "%$str%");
})
->groupBy('businesses.id')
->withCount(['reviews as rating' => function($query){
$query->select(DB::raw('round(avg(rating), 1)'));
}])
->withCount('reviews')
->with(['reviews' => function($query){
$query->latest();
}])
->with(['images' => function ($query)
{
$query->where('avatar', true);
}])
->paginate(20);

Related

Searching with multiple tables in laravel

I have this search query
$products = Product::query()
->where('name', 'LIKE', "%{$search}%")
->orWhere('slug', 'LIKE', "%{$search_any}%")
->where('status', 'Active')
->get();
i have user table which stores the user who listed the product and its relation is as follows
Product Model:
public function user()
{
return $this->belongsTo(User::class);
}
I have one field type in user table, i want if user enters search string matches type also then also all the products added by them should be returned i was trying this code but how can i combine both the queries
$products = Product::query()
->where('name', 'LIKE', "%{$search}%")
->orWhere('slug', 'LIKE', "%{$search_any}%")
->where('status', 'Active')
->get();
$productsAddedByUser = User::where('type', 'LIKE', "%{$search_any}%")->get();
// saved $productsAddedByUser result in this array $userIdarray
$productnew = Product::whereIn('user_id', $userIdarray)->get();
I want to combine both the results of $productnew and $products
At first save result in $userIdarray, then in products query:
$userIdarray = User::where('type', 'LIKE', "%{$search_any}%")->pluck('id');
$products = Product::query()
->where(
fn($query) => $query->where('name', 'LIKE', "%{$search}%")
->orWhere('slug', 'LIKE', "%{$search_any}%")
)
->where('status', 'Active')
->when(count($userIdarray), fn($query) => $query->whereIn('user_id', $userIdarray))
->get();

Laravel how to Order by relation in scope?

I have the following scope in User model
$query->with('country')
->when($filters['search'] ?? null, function ($query, $search) {
return $query->where('name', 'LIKE', '%' . $search . '%')
->orWhereHas('country', function ($query) use ($search) {
$query->where('name', 'LIKE', '%' . $search . '%');
});
});
$query->orderBy('name', 'asc');
}
return $query;
}
I am pretty new to Laravel - I currently the above query is sorting by user name but I would like to sort by country name. I can do this with country_id as there is a relation but not sure how to sort by country name.
Thanks
there are two approaches we can use to order these users by their company. The first is using a join:
$users = User::select('users.*')
->join('countries', 'countries.id', '=', 'users.country_id')
->orderBy('companies.name')
->get();
Here is the generated SQL for this query:
select users.*
from users
inner join countries on countries.id = users.country_id
order by countries.name asc
The second way is using a subquery:
$users = User::orderBy(Country::select('name')
->whereColumn('countries.id', 'users.country_id')
)->get();
And you can see the reference here: ordering database queries by relationship columns in laravel

Sort results where "seen" matches 2

I have a table for stories with fields id, user_id, file_name, seen (1-2)
Stories: id, user_id, file_name, seen
I would like to create a list that takes all the stories but with a primary order where seen corresponds to 2 (still to be seen then)
I have this simple query as a test, what should I do?
$users = App\Models\User::whereHas('stories', function ($query) {
})->get();
You can try next:
$users = App\Models\User::whereHas('stories')->with(['stories' => function($query) {
$query->orderBy(DB::raw('stories.seen=2'), 'desc')
}])->get();
Or
$users = App\Models\User::whereHas('stories')
->select('*')
->join('stories', 'stories.user_id', '=', 'users.id')
->orderBy(DB::raw('stories.seen=2'), 'desc')
->get();

Select unique records from child with condition on parent relationship

I have two tables, Shows and Episodes, each episode has show_id linking them one to many.
Now I need to get latest 6 episodes, one per show, where show.active is true
I've tried the following code:
$episodes = Episode::select(DB::raw('t.*'))
->from(DB::raw('(SELECT * FROM episodes ORDER BY id DESC) t'))
->whereHas('show', function($query) {
$query->where('active', '=', true);
})
->groupBy('t.show_id')
->take(6)
->get();
Unfortunately, I get the following:
Column not found: 1054 Unknown column 'episodes.show_id' in 'where clause' (SQL: select t.* from (SELECT * FROM episodes ORDER BY id DESC) t where exists (select * from shows where episodes.show_id = shows.id and active = 1) group by t.show_id limit 6)
I've also tried:
$episodes = Episode::where('active', true)
->orderBy('id', 'DESC')
->whereHas('show', function($query) {
$query->where('active', '=', true);
})
->groupBy('show_id')
->take(6)
->get();
It shows no error, but doesn't return latest of each show, groupBy gets the first record, I need the latest
This should work:
$episodes = Episode::where('active', true)
->whereHas('show', function($query) {
$query->where('active', '=', true);
})
->groupBy('show_id')
->orderBy('id', 'DESC')
->take(6)
->get();
You can try this
$episodes = Episode::selectRaw('max(id) as id, show_id')
->whereHas('show', function($query) {
$query->where('active', '=', true);
})
->orderBy('id', 'DESC')
->groupBy('show_id')
->take(6)
->get();
You can use a WHERE IN subquery:
$ids = Episode::selectRaw('max(id)')
->whereHas('show', function ($query) {
$query->where('active', '=', true);
})->groupBy('show_id');
$episodes = Episode::whereIn('id', $ids)
->take(6)
->get();

How to wrap Eloquent query with parenthesis when we use a conditional clauses in Laravel 5.7

I have a query that should load all the posts with just their english translation.
If the user enter a keyword it return just the english post with a title containing that keyword.
if ($searchKeywords||$searchCategory){
$posts = Post::
select('post_translations.post_id AS id', 'post_translations.title AS title', 'category_id', 'locale')
->join('post_translations', 'posts.id', '=', 'post_translations.post_id')
->where(‘post_translations.locale','=','en')
->when($searchKeywords, function ($query, $searchKeywords) {
return $query->where('post_translations.title', $searchKeywords)->orWhere('post_translations.title', 'like', '%' . $searchKeywords . '%');
})
->when($searchCategory, function ($query, $searchCategory) {
return $query->where('category_id', '=', $searchCategory);
->paginate(20);
}
else
$posts = Post::select('id', 'title', 'category_id')->orderBy('title')->paginate(20);
The generated query is this one:
SELECT `post_translations`.`post_id` AS `id`, `post_translations`.`title` AS `title`, `category_id`
FROM `posts` inner join `post_translations`
ON `posts`.`id` = `post_translations`.`post_id`
WHERE `post_translations`.`locale` = 'en'
AND `post_translations`.`title` = 'About'
OR `post_translations`.`title` like 'About’
LIMIT 20 OFFSET 0
That return me all the 3 posts translations of the post About.
This because of the orWhere.
How can I change the eloquent query in order to generate a query like this?
SELECT `post_translations`.`post_id` AS `id`, `post_translations`.`title` AS `title`, `category_id`
FROM `posts` inner join `post_translations`
ON `posts`.`id` = `post_translations`.`post_id`
WHERE `post_translations`.`locale` = 'en'
AND (`post_translations`.`title` = ‘About' OR `post_translations`.`title` like 'About’ )
LIMIT 20 OFFSET 0
The question is not a duplicate of this one because I have one more level of subquery.
How do you wrap Laravel Eloquent ORM query scopes in parentheses when chaining?
Add both condition inside a where query like this:
if ($searchKeywords) {
$posts = Post::select('post_translations.post_id AS id', 'post_translations.title AS title', 'category_id', 'locale')
->join('post_translations', 'posts.id', '=', 'post_translations.post_id')
->where(‘post_translations.locale','=','en')
->where(function ($query) use ($searchKeywords) {
$query->where('post_translations.title', $searchKeywords)
->orWhere('post_translations.title', 'like', '%' . $searchKeywords . '%');
})
->paginate(20);
}
I have solved with this code:
if ($searchKeywords||$searchCategory){
$posts = Post::
select('post_translations.post_id AS id', 'post_translations.title AS title', 'category_id', 'locale')
->join('post_translations', 'posts.id', '=', 'post_translations.post_id')
->when($searchKeywords, function ($query, $searchKeywords) {
return $query->where('post_translations.locale','=','en')
->where(function ($query) use ($searchKeywords) {
$query->where('post_translations.title', $searchKeywords)->orWhere('post_translations.title', 'like', '%' . $searchKeywords . '%');
});
})
->when($searchCategory, function ($query, $searchCategory) {
return $query->where('post_translations.locale','=','en')
->where(function ($query) use ($searchCategory) {
$query->where('category_id', '=', $searchCategory);
});
})
->paginate(20);
}
else
$posts = Post::select('id', 'title', 'category_id')->orderBy('title')->paginate(20);

Resources