Laravel aggregate function within single query - laravel

I have a User model with a table as users(id, email, password, gender, status)
I want to list the users based on condition along with the gender counts, here is my code:
$users = App\User::where('status', '1');
$users = $users->orderBy('email');
$data['all_users'] = $users; // all users
$data['total_males'] = $users->where('gender', 'M')->count(); // male count
$data['total_females'] = $users->where('gender', 'F')->count(); // female count
return view('users.list', $data);
The above code does not returns all the users on the view and filters gender, even when I have already passed the result object $users to the $data array.
Off course, I can set a male_count and female_count variables on the view and increment it based on the conditions like if (gender == 'M') total_males++ and display out of the loop.
What I am doing wrong here?

The proper way to do your eloquent queries on your controller and pass the data to the view would be:
$users = App\User::where('status', '1')->orderBy('email')->get();
$data['all_users'] = $users; // all users
$data['total_males'] = App\User::where('gender', 'M')->count(); // male count
$data['total_females'] = App\User::where('gender', 'F')->count(); // female count
return view('users.list', $data);

Just a simple thing, that I forgot! I have a habit of putting get() function on the view: :p
$data['all_users'] = $users->get(); // all users
$data['total_males'] = $users->where('gender', 'M')->count(); // male count
$data['total_females'] = $users->where('gender', 'F')->count(); // female count

You didn't called get method. Please try
$data['all_users'] = $users->get(); // all users

The Eloquent didn't execute, because nothing get or count method in there
$data['all_users'] = $users->count(); // all users

Related

Laravel Eloquent Only Get One Row

So I want to show id from order table where user id is the same as currently login user id, then later used to show orders have been made by the user
$orderId = Order::select('id')->firstWhere('user_id', auth()->id())->id;
$orders = SubOrder::where('order_id', $orderId)->orderBy('created_at', 'desc')->get();
it works but it only shows the first record, after some digging later I found out that the problem is on the $orderId, it only shows the first record. but I want it to be all the records. if I change the id to get(), it shows nothing since it give the result like "id = 1" instead of the number only. also have tried to change the firstWhere into where and got error like "Property [id] does not exist on the Eloquent builder instance."
please help, thanks
If you are going to use the other Orders associated with the User soon after you get the first Order, return all the relevant Orders and then just grab the first one when you need it.
$orders = Order::where('user_id', auth()->user()->id)->get();
$firstOrder = $orders->first();
$subOrders = SubOrder::whereIn('order_id', $orders->pluck('id'))->get();
Alternatively, you could use a subOrders relationship defined on your Order model.
class Order extends Model
{
public function subOrders()
{
return $this->hasMany(SubOrder::class);
}
}
$orders = Order::where('user_id', auth()->user()->id)->get();
$firstOrder = $orders->first();
$firstOrderSubOrders = $firstOrder->subOrders;
If you're confident you're going to be working with SubOrder records, you can use eager loading on your Order to improve performance.
$orders = Order::where('user_id', auth()->user()->id)
->with('subOrders')
->get();
$firstOrder = $orders->first();
$firstOrderSubOrders = $firstOrder->subOrders;
first() will return the first id queried and stop execution.
$firstOrder = $orders->first();

Eloquent: How to return the matching rows of two datasets

I have an accessor function for my User model which returns all the conversations of which a User is a participant.
public function getConversationsAttribute()
{
$results = DB::select('SELECT * FROM conversation_user WHERE user_id = ?', [$this->id]);
$conversations = array();
foreach($results as $result){
$conversation = Conversation::find($result->conversation_id);
array_push($conversations, $conversation);
}
return $conversations;
}
Now suppose I have two users $userA and $userB, how can I return the conversations of which both users are participants?
i.e., the common results between $userA->conversations and $userB->conversations
I imagine a UNION operator for duplicates is what is required.
What is the:
MySQL solution
Eloquent solution
Using intersect method of Laravel Collection, you can write
collect($userA->conversations)->intersect($userB->conversations);

Laravel Pivot Table, Get Room belonging to users

The structure of my pivot table is
room_id - user_id
I have 2 users that exist in the same room.
How can I get the rooms they both have in common?
It would be nice to create a static class to have something like this.
Room::commonToUsers([1, 5]);
Potentially I could check more users so the logic must not restrict to a certain number of users.
Room::commonToUsers([1, 5, 6, 33, ...]);
I created a Laravel project and make users, 'rooms', 'room_users' tables and their models
and defined a static function in RoomUser Model as below :
public static function commonToUsers($ids)
{
$sql = 'SELECT room_id FROM room_users WHERE user_id IN (' . implode(',', $ids) . ') GROUP BY room_id HAVING COUNT(*) = ' . count($ids);
$roomsIds = DB::select($sql);
$roomsIds = array_map(function ($item){
return $item->room_id;
}, $roomsIds);
return Room::whereIn('id', $roomsIds)->get();
}
in this method, I use self join that the table is joined with itself, A and B are different table aliases for the same table, then I applied the where condition between these two tables (A and B) and work for me.
I hope be useful.
I don't know the names of your relations, but I guess you can do like this :
$usersIds = [1, 5];
$rooms = Room::whereHas('users', function($query) use ($usersIds) {
foreach ($usersIds as $userId) {
$query->where('users.id', $userId);
}
})->get();
It should work. whereHas allows you to query your relation. If you need to have a static method, you can add a method in your model.
There might be a more efficient way but laravel collection does have an intersect method. You could create a static function that retrieves and loop through each object and only retain all intersecting rooms. something like this
public static function commonToUsers($userArr){
$users = User::whereIn('id',$userArr)->get();
$rooms = null;
foreach($users as $user){
if($rooms === null){
$rooms = $user->rooms;
}else{
$rooms = $rooms->intersect($user->rooms);
}
}
return $rooms;
}
This code is untested but it should work.
Room has many users, user has many rooms, so you can find the room which have those two users.
If your pivot table's name is room_users, then you can easily get the common room like this:
public static function commonToUsers($user_ids) {
$room = new Room();
foreach($user_ids as $user_id) {
$room->whereHas('users', function($query) use ($user_id) {
$query->where('room_users.user_id', $user_id);
});
}
return $room->get();
}
This code will convert to raw sql:
select *
from `rooms`
where exists (
select * from `rooms` inner join `room_users` on `rooms`.`id` = `room_users`.`room_id` where `rooms`.`id` = `room_users`.`room_id` and `room_users`.`user_id` = 1
)
and exists
(
select * from `rooms` inner join `room_users` on `rooms`.`id` = `room_users`.`room_id` where `rooms`.`id` = `room_users`.`room_id` and `room_users`.`user_id` = 5
)

Any short way for handling multiple queries in laravel?

I need to get multiple results from different queries on one table.
For example I need to get Count, Sum, Average of one table. Should I do like this or is there a shorter way?
public function index()
{
$count = Patient::all()->count();
$dateCount = Patient::where('date', date("Y-m-d"))->count();
$loanAmount = DB::table('patients')->sum('loan_amount');
$payAmount = DB::table('patients')->sum('pay_amount');
return view('index', compact('count','dateCount','loanAmount' ,'payAmount'));
}
If you see All queries are for one table to get specific results, So basically is there a short way to get these results not by single queries for each?
You can do this by DB query as below :
$data=\DB::table('patients')
->selectRaw('count(id) as count,sum(loan_amount) as loanAmount,sum(pay_amount) as payAmount,sum(case when date = "'.date("Y-m-d").'" then 1 else 0 end) AS dateCount')
->first();
You can also do this using eloquent but it will return you collection.
$data=Patient::selectRaw('count(id) as count,sum(loan_amount) as loanAmount,sum(pay_amount) as payAmount,sum(case when date = "'.date("Y-m-d").'" then 1 else 0 end) AS dateCount')
->first();
You can do something like this.
public function index()
{
$patient = Patient::all();
$count = $patient->count();
$dateCount = Patient::today()->count();
$loanAmount = $patient->sum('loan_amount');
$payAmount = $patient->sum('pay_amount');
return view('index', compact('count','dateCount','loanAmount' ,'payAmount'));
}
Also you can create scope in your patient model:
public function scopeToday($query) {
return $query->where('date', date("Y-m-d"));
}

Group collection by month and type user

I have a collection of invoices and to make a chart from it I want to group them in months and in those months a subdivision by the type of the user.
If I group by month or group by user type separately they work but if I want to group them by month and in that month by the user type it's not working.
Vue
const invoices = await this.$http.get('/invoices/stats', {
params: {
with: 'user,customer,extension',
start: this.startDate,
end: this.endDate,
}
})
Laravel
public function stats(Request $request)
{
$result = Invoice::with(explode(',', $request->with))
->scopes(['period'])
->get()
->groupBy(function ($q) {
return Carbon::parse($q->date)->format('m');
})
->groupBy('user.type');
return $result;
}
From what i know, i do not think you can do multiple groupby on a collection. Just did a quick test and it does not work on my side.
I think you have 2 options:
Do a query for each month, group each result collection by user type then you will merge the collections (basically). but you will do more queries.
If you just need counts to display charts you can do all of that with multiple group by on an SQL query, outside Eloquent.
It would look like :
Select count(*), YEAR(i.date), MONTH(i.date), u.type
from invoice i left join user u on i.user_id = u.id
group by MONTH(i.date), YEAR(i.date), u.type
Keep in mind the kind of data you need is a bit out of scope regarding what Eloquent is meant to be used for.
This is my solution:
// group by user types
$user_types = Invoice::with(explode(',', $request->with))
->scopes(['byMonth'])
->orderBy('date')
->get()
->groupBy('user.type');
// group the user types by month
// (double groupBy is not possible in 1 query)
foreach ($user_types as $key => $value) {
$user_types_by_month[$key] = $value->groupBy(function ($q) {
return Carbon::parse($q->date)->format('m');
});
}
// calculate the total by month by user types and replace the array with the total
foreach ($user_types_by_month as $i => $types) {
foreach ($types as $j => $months) {
$total = 0;
foreach ($months as $m) {
$total += $m->total;
}
$user_types_by_month[$i][$j] = $total;
}
}
return $user_types_by_month;

Resources