Laravel Group Count - php

I want to ask how to implement Laravel Query with Multiple Group Count Result.
Basically it display the result of each voters in a barangay or town. However, aside from it I want to display the counts of voters if they are supporter or not labaled as 0 or 1 in their status.
public function brgy()
{
$brgy = Voter::groupBy('brgy')
->selectRaw('count(*) as total, brgy')
->get();
return view('brgy', compact('brgy'));
}
Please help.

Related

Grouping a pivot table and getting the sum of pivot values

I'm trying to generate a monthly report in Laravel Livewire
I have a many to many relationship between books and orders with a pivot value for quantity of books in the order.
This is the database designer for the 3 tables :
These are the eloquent relationships in my App\Models\ ... .php
// In Order model
public function books(){
return $this->belongsToMany(Book::class)->withPivot('quantity');
}
// In Book model
public function orders(){
return $this->belongsToMany(Order::class)->withPivot('quantity')->as('orders');
}
Code generating the monthly report data:
public function generate($month)
{
// Gets count of orders and the sum of their totals from month by their status
$this->orderReport = Order::select(DB::raw('COUNT(*) as count,SUM(total_price) as total, status'))
->whereMonth('created_at', $month)
->groupBy('status')
->get()->keyBy('status');
// Gets number of all orders from the month
$this->totalOrders = $this->orderReport->sum('count');
// Gets number of orders and total spending of 10 users with most STATUS_SUCCESSFULL orders for the month
$this->orderUserReport = Order::select(DB::raw('COUNT(*) as count,SUM(total_price) as total, user_id'))
->whereMonth('created_at', $month)
->status(Order::STATUS_SUCCESSFULL)
->groupBy('user_id')
->orderBy('count', 'DESC')
->take(10)->get()->keyBy('user_id');
// THIS IS WHERE IM STUCK
$test = Order::whereMonth('created_at', $month)->status(Order::STATUS_SUCCESSFULL)->with('books')->get();
}
I would like to get a similar result for my products as i do for orders / users. Problem is the quantity of sold products is in the pivot table connecting Books with Orders.
So precisely what I need is the pivot table grouped by book_order.book_id with sums of book_order.quantity only where book_order.order_id is in orders with STATUS_SUCCESSFULL and whereMonth($month).
How would I go about attaining that data?
I had some trouble formulating this question in my mind so if anything is unclear please feel free to comment i'll clarify.
EDIT
$orders = Order::whereMonth('created_at', $month)->status(Order::STATUS_SUCCESSFULL)->with('books')->get();
foreach($orders as $order)
{
foreach($order->books as $book){
if(empty($this->productReport[$book->id])){
$this->productReport[$book->id] = $book->pivot->quantity;
break;
}
$this->productReport[$book->id] += $book->pivot->quantity;
}
}
ksort($this->productReport);
}
This code gives me the result I need but is ugly and inefficient, any way to reproduce this result with Eloquent or Query builder?
The above image is the result from the last snippet [book_id => quantity].

Laravel query builder add complex query result

I have three models with the following hierarchy :
User
id
....some other properties
Journey
id
user_id
budget
....some other properties
Confirmation
id
journey_id
user_id
....some other properties
I have a HasMany from User to Journey, a HasMany from Journey to Confirmation.
I want to get the sum for a column of the journeys table by going through the confirmations table but I cannot create an intermediate HasManyThrough relation between User and Journey by using Confirmation.
I have tried to do
public function journeysMade(): HasManyThrough
{
return $this->hasManyThrough(Journey::class, Confirmation::class);
}
// And after,
User::with(...)->withSum('journeysMade','budget')
But it was not possible because the relations are not adapted.
With hindsight, the sql query I want to translate would look like
select coalesce(sum(journeys.budget), 0) as income
from journeys
inner join confirmations c on journeys.id = c.journey_id
where c.user_id = ? and c.status = 'finalized';
How can I implement this query considering how I will use my query builder :
$driversQueryBuilder = User::with(['profile', 'addresses']); // Here
$pageSize = $request->input('pageSize', self::DEFAULT_PAGE_SIZE);
$pageNumber = $request->input('pageNumber', self::DEFAULT_PAGE_NUMBER);
$driversPaginator = (new UserFilterService($driversQueryBuilder))
->withStatus(Profile::STATUS_DRIVER)
->withCountry($request->input('country'))
->withSex($request->input('sex'))
->withActive($request->has('active') ? $request->boolean('active') : null)
->get()
->paginate(perPage: $pageSize, page: $pageNumber);
return response()->json(['data' => $driversPaginator]);
The reason why I want to get a builder is because UserFilterService expects a Illuminate\Database\Eloquent\Builder.
Do you have any idea about how I can solve this problem ?
Not 100% sure what exactly you want to sum, but I think you need the following query
$user->whereHas('journeys', function($query) {
$query->whereHas('confirmations', function($subQuery) {
$subQuery->sum('budget);
}
});
If you the above query isn't summing the budget you need, you just add another layer of abstraction with whereHas methods to get exactly what you need. Hope this helps!
EDIT:
$user->whereHas('confirmations', function($q) {
$q->withSum('journeys', 'budget')->journeys_sum_budget;
}

How to sort query results by distance in Laravel QueryBuilder / MySQL Spatial package?

First I want to show you the current database structure. There are three tables:
dishes (id, name)
locations (id, name, coordinates (POINT))
dish_location(location_id, dish_id)
Now I want to implement an API which gets the position (latitude, longitude) of the user and returns a list of dishes sorted by the distance in km. I already have a method which takes two latitudes and two longitudes and gives me the distance. But I am sure you can show me a way, which is a more performant way to do this directly in the MySQL query.
Additional: I want to do a "load more"-function in the API. So I pass the count of already received items or how would I solve this in this case?
I am using this for the MySQL Spatial package
First, let's take a look at how to do this with the basic query builder. Then, we'll discuss how to execute this query with Eloquent models:
function paginateDishesFromPoint(Point $point, $pageSize)
{
$distanceField = "ST_Distance_Sphere(locations.coordinates, "
. "ST_GeomFromText('{$point->toWKT()}') AS distance";
return DB::table('dishes')
->select('dishes.*', DB::raw($distanceField))
->join('dish_locations', 'dish_locations.dish_id', '=', 'dishes.id')
->join('locations', 'locations.id', '=', 'dish_locations.location_id')
->orderBy('distance')
->paginate($pageSize);
}
The ST_Distance_Sphere() function calculates a distance that we can sort results by. Laravel's paginate() method performs automatic pagination for us using the page parameter passed through the request URL. Read the pagination docs for more information. With the function above, we can fetch a paginated result set as follows:
$point = new Point($latitude, $longitude);
$sortedDishes = paginateDishesFromPoint($point, 15);
...where Point is the Grimzy\LaravelMysqlSpatial\Types\Point class from the package we're using, and 15 is the number of results per page.
Now, let's try to do this with Eloquent models. We'll use a local query scope to encapsulate the logic needed to create the portion of the query that performs the ordering:
class Dish extends Model
{
...
public function locations()
{
return $this->belongsToMany(App\Location::class);
}
public function scopeOrderByDistanceFrom($query, Point $point)
{
$relation = $this->locations();
$locationsTable = $relation->getRelated()->getTable();
$distanceField = "ST_Distance_Sphere($locationsTable.coordinates, "
. "ST_GeomFromText('{$point->toWKT()}') AS distance";
return $query
->select($this->getTable() . '.*', DB::raw($distanceField))
->join(
$relation->getTable(),
$relation->getQualifiedForeignKeyName(),
'=',
$relation->getQualifiedParentKeyName()
)
->join(
$locationsTable,
$relation->getRelated()->getQualifiedKeyName(),
'=',
$relation->getQualifiedRelatedKeyName()
)
->orderBy('distance');
}
}
This implementation uses metadata on the models to add the table and field names to the query so we don't need to update this method if they change. Now we can fetch the ordered set using the model:
$point = new Point($latitude, $longitude);
$sortedDishes = Dish::orderByDistanceFrom($point)->paginate($pageSize);
$sortedDishes is an instance of Laravel's LengthAwarePaginator which wraps a Collection of the models. If we pass the results to a view, here's how to display them in a Blade template:
<ul>
#foreach($sortedDishes as $dish)
<li>{{ $dish->name }} is {{ $dish->distance }} meters away.</li>
#endforeach
</ul>
Load more...
As shown above, the paginator provides convenience methods that we can use to easily move between paged results.
Alternatively, we could use AJAX requests to load the results. Just be sure to pass the current page + 1 in the page parameter of the request data.
SQL
SELECT d.name AS `Dish`,
l.name AS `Location`,
ST_Distance(l.coordinates, POINT(<<<longitude>>>, <<<latitude>>>)) AS `Distance`
FROM dish_locations dl
JOIN dishes d
ON d.id = dl.dish_id
JOIN locations l
ON l.id = dl.location_id
ORDER BY `Distance`
LIMIT <<<n-1>>>, <<<page size>>>;
...inserting <<<longitude>>> and <<<latitude>>> from the user's position, <<<n-1>>> from the number of rows that have already been retrieved minus one and <<<page size>>> as the desired next number of rows to retrieve.
Demo
http://rextester.com/YAEBF16430
Explanation
ST_Distance_Sphere is used to calculate the distance in meters.
LIMIT <<offset>>, <<num>> is used to implement the paging (where offset is zero-based).
Postgis has two operators (<-> and <#>) to work distance and calculate KNN(Nearest Neighbors). You can use them instead of st_distance. Operators
Query would be something like:
WITH index_query AS (
SELECT ST_Distance(geom, 'SRID=3005;POINT(1011102 450541)'::geometry) as d,edabbr, vaabbr
FROM va2005
ORDER BY geom <-> 'SRID=3005;POINT(1011102 450541)'::geometry LIMIT 100)
SELECT *
FROM index_query
ORDER BY d limit 10;
Using distance as d you can order.

Best practice to update or get ranks of players in Laravel

I've been starting a project where multiple players are in multiple leagues. They get points and in the end there is a ranking-table which displays the player with the most points.
So far so good, but I've got a problem getting the ranking of the players correctly.
The competitors I getting like that because competitors can be teams or players (of course teams OR players per league, not both in one league):
return $this->belongsToMany('App\User', 'competitors', 'league_id', 'competitors_id')
->where('competitors.competitors_type', 'App\User')
->withPivot('id', 'points', 'wins', 'lose', 'score', 'enemy_score')->withTimestamps();
I tried adding following method to the pivot-table-model Competitor:
public function getRankAttribute()
{
return $this->league->competitors()->where('points', '>=', $this->points)->count();
}
But the problem with this logic is, that I want to add more logic to the ranking like: Player A has same amount of points like Player B. But Player B is better than Player A because he has more wins.
Next I tried to give a rank in the query after multiple orderBy:
// $ranking is a relation or Eloquent Builder instance
// which has already got multiple orderBy() statements.
$query = null;
$baseQuery = null;
if($ranking instanceof Relation) {
$query = $ranking->getQuery();
$baseQuery = $ranking->getBaseQuery();
} else {
$query = $ranking;
$baseQuery = $ranking->getQuery();
}
// Set the rank offset
$offset = (int) $baseQuery->offset;
DB::statement(DB::raw("set #rank={$offset}"));
// Adjust SELECT clause to contain the rank
if ( ! count($baseQuery->columns)) $query->select($columns);
$query->addSelect([DB::raw('#rank:=#rank+1 as rank')]);
// Return the object again
return $ranking;
This doesn't work as well, because the sorting is done AFTER the rank was given to the entry. So I get the increasing number of the row but not the rank. In my example the last player which joins the league gets the highest "rank".
Now I'm thinking of a scheduled task which will update the ranks of the players every 5 minutes or so. But is this really best practice? What do you think? How should I do this?
I'm using a MYSQL database and Laravel 5.2

Laravel eager loading with limit

I have two tables, say "users" and "users_actions", where "users_actions" has an hasMany relation with users:
users
id | name | surname | email...
actions
id | id_action | id_user | log | created_at
Model Users.php
class Users {
public function action()
{
return $this->hasMany('Action', 'user_id')->orderBy('created_at', 'desc');
}
}
Now, I want to retrieve a list of all users with their LAST action.
I saw that doing Users::with('action')->get();
can easily give me the last action by simply fetching only the first result of the relation:
foreach ($users as $user) {
echo $user->action[0]->description;
}
but I wanted to avoid this of course, and just pick ONLY THE LAST action for EACH user.
I tried using a constraint, like
Users::with(['action' => function ($query) {
$query->orderBy('created_at', 'desc')
->limit(1);
}])
->get();
but that gives me an incorrect result since Laravel executes this query:
SELECT * FROM users_actions WHERE user_id IN (1,2,3,4,5)
ORDER BY created_at
LIMIT 1
which is of course wrong. Is there any possibility to get this without executing a query for each record using Eloquent?
Am I making some obvious mistake I'm not seeing? I'm quite new to using Eloquent and sometimes relationship troubles me.
Edit:
A part from the representational purpose, I also need this feature for searching inside a relation, say for example I want to search users where LAST ACTION = 'something'
I tried using
$actions->whereHas('action', function($query) {
$query->where('id_action', 1);
});
but this gives me ALL the users which had had an action = 1, and since it's a log everyone passed that step.
Edit 2:
Thanks to #berkayk looks like I solved the first part of my problem, but still I can't search within the relation.
Actions::whereHas('latestAction', function($query) {
$query->where('id_action', 1);
});
still doesn't perform the right query, it generates something like:
select * from `users` where
(select count(*)
from `users_action`
where `users_action`.`user_id` = `users`.`id`
and `id_action` in ('1')
) >= 1
order by `created_at` desc
I need to get the record where the latest action is 1
I think the solution you are asking for is explained here http://softonsofa.com/tweaking-eloquent-relations-how-to-get-latest-related-model/
Define this relation in User model,
public function latestAction()
{
return $this->hasOne('Action')->latest();
}
And get the results with
User::with('latestAction')->get();
I created a package for this: https://github.com/staudenmeir/eloquent-eager-limit
Use the HasEagerLimit trait in both the parent and the related model.
class User extends Model {
use \Staudenmeir\EloquentEagerLimit\HasEagerLimit;
}
class Action extends Model {
use \Staudenmeir\EloquentEagerLimit\HasEagerLimit;
}
Then simply chain ->limit(1) call in your eager-load query (which seems you already do), and you will get the latest action per user.
My solution linked by #berbayk is cool if you want to easily get latest hasMany related model.
However, it couldn't solve the other part of what you're asking for, since querying this relation with where clause would result in pretty much the same what you already experienced - all rows would be returned, only latest wouldn't be latest in fact (but latest matching the where constraint).
So here you go:
the easy way - get all and filter collection:
User::has('actions')->with('latestAction')->get()->filter(function ($user) {
return $user->latestAction->id_action == 1;
});
or the hard way - do it in sql (assuming MySQL):
User::whereHas('actions', function ($q) {
// where id = (..subquery..)
$q->where('id', function ($q) {
$q->from('actions as sub')
->selectRaw('max(id)')
->whereRaw('actions.user_id = sub.user_id');
})->where('id_action', 1);
})->with('latestAction')->get();
Choose one of these solutions by comparing performance - the first will return all rows and filter possibly big collection.
The latter will run subquery (whereHas) with nested subquery (where('id', function () {..}), so both ways might be potentially slow on big table.
Let change a bit the #berkayk's code.
Define this relation in Users model,
public function latestAction()
{
return $this->hasOne('Action')->latest();
}
And
Users::with(['latestAction' => function ($query) {
$query->where('id_action', 1);
}])->get();
To load latest related data for each user you could get it using self join approach on actions table something like
select u.*, a.*
from users u
join actions a on u.id = a.user_id
left join actions a1 on a.user_id = a1.user_id
and a.created_at < a1.created_at
where a1.user_id is null
a.id_action = 1 // id_action filter on related latest record
To do it via query builder way you can write it as
DB::table('users as u')
->select('u.*', 'a.*')
->join('actions as a', 'u.id', '=', 'a.user_id')
->leftJoin('actions as a1', function ($join) {
$join->on('a.user_id', '=', 'a1.user_id')
->whereRaw(DB::raw('a.created_at < a1.created_at'));
})
->whereNull('a1.user_id')
->where('aid_action', 1) // id_action filter on related latest record
->get();
To eager to the latest relation for a user you can define it as a hasOne relation on your model like
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
class User extends Model
{
public function latest_action()
{
return $this->hasOne(\App\Models\Action::class, 'user_id')
->leftJoin('actions as a1', function ($join) {
$join->on('actions.user_id', '=', 'a1.user_id')
->whereRaw(DB::raw('actions.created_at < a1.created_at'));
})->whereNull('a1.user_id')
->select('actions.*');
}
}
There is no need for dependent sub query just apply regular filter inside whereHas
User::with('latest_action')
->whereHas('latest_action', function ($query) {
$query->where('id_action', 1);
})
->get();
Migrating Raw SQL to Eloquent
Laravel Eloquent select all rows with max created_at
Laravel - Get the last entry of each UID type
Laravel Eloquent group by most recent record
Laravel Uses take() function not Limit
Try the below Code i hope it's working fine for u
Users::with(['action' => function ($query) {
$query->orderBy('created_at', 'desc')->take(1);
}])->get();
or simply add a take method to your relationship like below
return $this->hasMany('Action', 'user_id')->orderBy('created_at', 'desc')->take(1);

Categories