Wrong counting after grouping - laravel

Have tables with wallets and services
Service
id and name
Wallets
id service_id balance
$statistic = Wallets::leftJoin('service', 'service.id', '=', 'wallets.service_id')->select('name as label', 'balance as value')->where('balance', '>', 0)->whereYear('wallets.updated_at', $now->year)->whereMonth('wallets.updated_at', $now->month)->get();
And get result
[{"label":"Service1","value":0.0711679},
{"label":"Service1","value":0.015},
{"label":"Service2","value":0.065572},
{"label":"Service2","value":0.02},
{"label":"Service3","value":0.0206064},
{"label":"Service2","value":0.04399}]
but after ->groupBy('label'):
[{"label":"Service1","value":0.0711679},
{"label":"Service2","value":0.065572}
{"label":"Service3","value":0.0206064}]
get only first results

<?php
// If you are referring count as total balance then you can do this
$statistic = Wallets::leftJoin('service', 'service.id', '=', 'wallets.service_id')
->where('balance', '>', 0)
->whereYear('wallets.updated_at', $now->year)
->whereMonth('wallets.updated_at', $now->month)
->select(
\DB::raw('name as label'),
\DB::raw('SUM(balance) as value')
)
->groupBy('name')
->get();
// If you are referring count as total entries for each label then you can do this
$statistic = Wallets::leftJoin('service', 'service.id', '=', 'wallets.service_id')
->where('balance', '>', 0)
->whereYear('wallets.updated_at', $now->year)
->whereMonth('wallets.updated_at', $now->month)
->select(
\DB::raw('name as label'),
\DB::raw('balance as value'),
\DB::raw('count(*) as aggregate')
)
->groupBy('name')
->get();

Related

Laravel 8 Carbon MM/DD/YYYY to Age compared inside join condition

Table: Search Preferences
Column: search_min_age (two digit number)
Table: Users
Column: birthdate (mm/dd/yyyy)
Both of these are joined together, but here is my error.
Could not parse 'users.birthdate': DateTime::__construct(): Failed to parse time string (users.birthdate) at position 0 (u): The timezone could not be found in the database
Here is snippet from my code:
$users = DB::table('users')
->join('search_preferences', function($q) {
$q->on('search_preferences.search_type', '=', 'users.account_type')
->on('search_preferences.search_identity', '=', 'users.account_identity')
->on('search_preferences.search_gender', '=', 'users.gender')
->on('search_preferences.search_tribe', '=', 'users.tribe')
->on('search_preferences.search_position', '=', 'users.position')
->on('search_preferences.search_min_age', '<=', Carbon::parse('users.birthdate')->age)
->on('search_preferences.search_max_age', '>=', Carbon::parse('users.birthdate')->age);
})
->where('users.verified_id', '=', 'yes')
->where('search_preferences.user_id', '=', auth()->user()->id)
->orderBy('users.id', 'ASC')
->select('users.*')
->paginate(9);

Laravel search query optimization

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

select from table (laravel) - where and orWhere

So, I'm using laravel and I want to make this query to work in laravel 'formula'
SELECT * FROM `complaints` WHERE (`fID` = 0) AND (`hide_topic` = 0 OR (`hide_topic` <= 0 OR (`hide_topic` > 3 AND byID = 52))) ORDER BY `status` ASC, `id` DESC
And I made something like this in laravel but not sure that selects correctly like the query above does.
DB::table('complaints')
->where('fID', '=', 0)
->where('hide_topic', '=', 0)
->orWhere('hide_topic', '<', 0)
->orWhere('hide_topic', '>', 3)
->where('byID', '=', 52)
->orderBy('status', 'asc')
->orderBy('id', 'desc')
->get();
Any help?
If you carefully examine your query. You can definitely remove some extra filters. If you see inner where if hide_topic <= 0 it also covers hide_topic =0 so you can reduce your query to.
SELECT *
FROM `complaints`
WHERE `fID` = 0
AND (`hide_topic` <= 0 OR (`hide_topic` > 3 AND byID = 52))
ORDER BY `status` ASC, `id` DESC
Best thing from laravel is that you can actually see the query that will be executed using query builders or eloquent model using toSql method. You can use toSql method at place of get, all or first functions.
$result = DB::table('complaints')
->where('fID', '=', 0)
->where(function($query) {
$query->where('hide_topic', '<=', 0)
->orWhere(function($query) {
$query->where('hide_topic', '>', 3)
->where('byID', '=', 52);
});
})
->orderBy('status', 'asc')
->orderBy('id', 'desc')
->get();
// to check the query just replace `->get();` to `->toSql();` in above query.
Now if you don't want any modifications in your existing query. You can reference to #OMR answer. Still I'll put it here.
$result = DB::table('complaints')
->where('fID', '=', 0)
->where(function($query) {
$query->where('hide_topic', '=', 0)
->orWhere(function($query) {
$query->where('hide_topic', '<=', 0)
->orWhere(function($query) {
$query->where('hide_topic', '>', 3)
->where('byID', '=', 52);
});
});
})
->orderBy('status', 'asc')
->orderBy('id', 'desc')
->get(); -- use toSql() to actually see the query before bindings.
you simply use where with clouser for every compound condition:
$admin_level=3;
$playerid=52;
DB::table('complaints')->where('fID', '=', 0)
->where(function ($query)use($admin_level,$playerid){
$query->where('hide_topic', '=', 0)->orWhere(function ($query)use($admin_level,$playerid){
$query->Where('hide_topic', '<', 0)->orWhere(function ($query)use($admin_level,$playerid){
$query ->Where('hide_topic', '>', $admin_level)
->where('byID', '=', $playerid);
});
});
})
->orderBy('status', 'asc')
->orderBy('id', '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();

Laravel Query Builder - group by and sum

I want to get a list of user activities.
But it should give me back only the 5 users, which have the most activities and a col with the sum of the activities ordered by sum of activities DESC.
$now = Carbon::now();
$lastWeekMonday = Carbon::now()->subWeek(1)->startOfWeek()->toDateString();
$mondayThisWeek = Carbon::now()->startOfWeek()->toDateString();
$userActivitiesLastWeek = DB::table('useractions')
->select('id', 'comment', 'created_user', 'created_at')
->where('created_at', '>=', $lastWeekMonday )
->where('created_at', '<', $mondayThisWeek )
->get();
Thanks for help!
You can use withCount Query Builder function to get the count of particular relationship.
And then you can order by that count and use the take Collection function to take desired number of items from a collection. In your case, 5.
$now = Carbon::now();
$lastWeekMonday = Carbon::now()->subWeek(1)->startOfWeek()->toDateString();
$mondayThisWeek = Carbon::now()->startOfWeek()->toDateString();
$uses = User::with('activities')
->withCount('activities')
->where('created_at', '>=', $lastWeekMonday )
->where('created_at', '<', $mondayThisWeek )
->get()
->sortByDesc('activities_count')
->take(5);

Resources