Best practice to update or get ranks of players in Laravel - php

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

Related

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

Need opinion with relations count

Very long time I searching for solution for this problem:
Lets say we have 2 tables one table is Clients table and second table is ClientAssignment table:
the ClientAssignment table is related to Clients table:
public function assignment()
{
return $this->hasOne(ClientAssignment::class, 'client_id');
}
now when I want to count how many ClientAssignment has Clients and i do it like that:
$users =[1,2,3,4 .....]
$userAssignments = array();
foreach ($users as $user) {
$user_assignments = Client::whereHas('assignment', function ($query) use ($user) {
$query->where('assigned_id', $user);
});
$ua['user_id'] = $user;
$ua['count'] = $user_assignments->count();
array_push($userAssignments, $ua);
}
The code works well but it hits the performance and query execution time ~20 + seconds on a relatively small table with 80k Clients,
My question if can be another way to do the same thing but with minimum performance and query execution time hit ?
According to your post, I think
Client --- hasOne ----> ClientAssignment <----- hasMany ---- User
[client_id, assigned_id]
So User can hasMany Client through ClientAssignment,
However, your client_id and assigned_id are all in client_assignment table. So you can not use hasManyThrough, this just like a pivot table;
Fortunately, you can directly count the client_id and get the assigned_id as user_id just use this pivot table.
The query is like this (Use distinct(client_id) for preventing the dirty records):
ClientAssignment::whereIn('assigned_id', $users)
->groupBy('assigned_id')
->select('assigned_id AS user_id',
DB::raw('COUNT(DISTINCT(client_id)) AS count'))
->get()->toArray();
And add assigned_id index to improve the performance.
$table->index('assigned_id');
Use the count()
Client::first()->assignments->count();
// Or
Client::find(ID)->assignments()->count();
// or ...

Laravel Group Count

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.

Laravel 5 orderBy relationship count and pagination

Im working on a link sharing website and need to order the links by their votes.
I have a 'links' table and a link_votes table.
The link_votes table has a 'link_id' reference field to the links table 'id' field.
I have a hasMany relationship in my Link model:
public function votes()
{
return $this->hasMany('\App\LinkVote');
}
This returns all rows (each row is a single vote) int he link_votes table.
My standard query to get all my Links and order them by date created is:
$links = Link::withCount('votes')->orderBy('votes_count', 'desc')->paginate(10);
What I wish to do now is order my links by the amount of votes they have.
What I have currently is:
$links = Link::orderBy('created_at', 'desc')->with('votes')->paginate(20);
This works, but almost too well. I have 2 types of vote in my link_votes table by having a vote_type field. If the vote is an upvote its a type of 1 but if its a downvote its a type of 2.
The issue is here by ordering by vote count its taking all votes as a count and putting votes with a negative vote above those links that don't have any votes.
I need to find a way of ordering by the sum of the upvotes (vote_type = 1) minus the downvotes (vote_type = 2).
In my view I show the vote count by doing that maths.
A change of logic allowed me to do this easier than first thought.
All I needed to do was count the positive (upvotes) as the count to order by as they are the only ones that matter.
To do this I have changed the withCount to:
$links = Link::withCount([
'votes' => function ($query) {
$query->where('vote_type', 1);
}
])
->orderBy('votes_count', 'desc')
->paginate(10);

Laravel Many to many without using id column

I have 3 tables. Players, player_skills and Skills.
Players
- bb1_player_id
Skills
- bb1_skill_id
Player_skills
- bb1_player_id
- bb1_skill_id
I have created the Player Model with this method.
public function skills()
{
return $this->belongsToMany('App\Skills', 'player_skills', 'bb1_player_id', 'bb1_skill_id');
}
That outputs this Query:
"select `skills`.*, `player_skills`.`bb1_player_id` as `pivot_bb1_player_id`, `player_skills`.`bb1_skill_id` as `pivot_bb1_skill_id` from `skills` inner join `player_skills` on `skills`.`id` = `player_skills`.`bb1_skill_id` where `player_skills`.`bb1_player_id` = ?"
The query works, but I am hoping to get a query that does not every use the id field of the tables and only uses the fields I am specifying. It uses both the id of the skill and player.id at the '?' of the query. Is there a way around this? Or do I need to rework my tables to conform?
I have also tried adding more options like below to no avail.
public function skills()
{
return $this->belongsToMany('App\Skills', 'player_skills','bb1_player_id','bb1_skill_id','bb1_player_id','bb1_skill_id');
}

Categories