Laravel 5 orderBy relationship count and pagination - php

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

Related

Get all the items NOT included in a giving order using Eloquent

I have the following tables:
orders:
- id
- date
item_order:
- order_id
- item_id
items:
- id
- desc
- price
Using Eloquent, how can I get all the items NOT included in a giving order (say, the order with id = 6)?
I'm trying to do relationships & subqueries, but without luck.
Thanks in advance.
Your question is not clear enough, Using Eloquent, how can I get all the items NOT included in a giving order (say, the order with id = 6)?, this sentence is unclear.
In other hand based on your explanation, below example gonna return list of orders with their items, except something you don't want:
$id = 6;
// Get list of orders with itemOrder.
Order::with('itemOrder' => function ($query) use $($id) {
//make sure query don't return list of order items belong to order id = 6.
$query->where('order_id', '!=,' $id)
})->get();
You will get list of orders with item_order but you will not get item_order for order with id of 6.
If this is not what you've asked please clarify and update your question for better explanation.
Based on your comment : Your solution gives me the ORDERS, but I want all the ITEMS, except those of the order with id = 6., you can simply replace query with specified model and relations
$id = 6;
Item::with('itemOrder' => function ($query) use $($id) {
$query->where('order_id', '!=,' $id)
})->get();
// Or,
// This one only return items with itemOrders and of course returned itemsOrder
// will be affected by query inside callback function
Item::whereHas('itemOrder' => function ($query) use $($id) {
$query->where('order_id', '!=,' $id)
})->get();

Laravel OrderBy by related table column

I have a table (A) that has a One to Many relation with another table (B).
I want to query Table A and eager load Table B with the Table A results - but I also want to sort Table A by a value in Table B.
I have tried using OrderBy in the query and also trying SortBy on the resultant collection but cannot get the Table A data to be sorted by the value found in Table B.
Example of what I have tried:
$query = ModelA::with("ModelB"])->get()->sortByDesc('ModelB.sortValue');
Keep in mind, I am only interested in the LATEST record from Table B. So I need to query Table A and sort by a value in the LATEST records of Table B.
How can I achieve this?
EDIT:
The below (as suggested by #ljubadr) works pretty close, but the issue is that there are many record in Table B which means that it doesn't reliably sort as it doesn't seem to sortby the latest records in Table B. Can I have the join return ONLY the latest record for each ID?
$query = ModelA::select('TableA.*')
->join('TableB', 'TableA.id', '=', 'TableB.col_id')
->groupBy('TableA.id')->orderBy('TableB.sortCol', 'desc')
->with(['x'])
->get();
EDIT 2:
#Neku80 answer has gotten me closest but it seems to not sort the column with the greatest accuracy.. I'm sorting a Decimal column and for the most part it is in order but in some places the items are out of order..
$latestTableB = ModelB::select(['TableA_id', 'sortByColumnName'], DB::raw('MAX(created_at) as created_at'))
->groupBy('TableA_id');
$query = ModelA::select('TableA.*')
->joinSub($latestTableB, 'latest_TableB', function ($join) {
$join->on('TableA.id', '=', 'latest_TableB.TableA_id');
})
->orderBy('latest_TableB.sortByColumnName')
->get();
For example, the ordering is like:
0.0437
0.0389
0.0247 <-- -1
0.025 <-- +1
0.0127
When I delete all rows except for the 'latest' rows, then it orders correctly, so it still must be ordering with old data...
I have found a solution:
ModelA::select('TableA.*', 'TableB.sortByCol as sortByCol')
->leftJoin('TableB', function ($query) {
$query->on('TableB.TableA_id', '=', 'TableA.id')
->whereRaw('TableB.id IN (select MAX(a2.id) from TableB as a2 join TableA as u2 on u2.id = a2.TableA_id group by u2.id)');
})
->orderBy('TableB.sortByCol')
->get();
Another alternative to order is like this:
$users = User::orderBy(
Company::select('name')
->whereColumn('companies.user_id', 'users.id'),
'asc'
)->get();
Here we are ordering in asc order by company name field.
In this article it is explained in detail.
You can simply execute a left join query:
ModelA::query()->leftJoin('model_b_table', 'model_a_table.primary_key', '=', 'model_b_table.foreign_key')->orderBy('model_a_table.target_column')->get();
This should work if you only need TableB's ID and created_at columns:
$latestTableB = ModelB::select('TableA_id', DB::raw('MAX(created_at) as created_at'))
->groupBy('TableA_id');
$query = ModelA::select('TableA.*')
->joinSub($latestTableB, 'latest_TableB', function ($join) {
$join->on('TableA.id', '=', 'latest_TableB.TableA_id');
})
->orderBy('latest_TableB.created_at')
->get();

How to order by column in nested 2 level relationship in Laravel?

I have 3 tables: reports, fields and report_fields which is a pivot between the other 2. What i need to do is order report_field.field by the position column in the field table.
I tried ordering in the relation in the Models or when using with but I may be doing it wrong.
ex:
$query = Report::with([ 'reportFields.field' => function ($q) {
$q->orderBy('position', 'asc');
//$q->orderByRaw("fields.position DESC");
},
Can someone give a basic example of ordering a 2 level nested relationship?
Edit: I do not need to order by any column in the base table but the list of entries in the pivot table by an column in the second table.
Edit2:
To give an example how the output should be ordered:
Report
ReportField
Field.position = 1
ReportField
Field.position = 2
ReportField
Field.position = 3
You can add your needed ordering on the relation of the first table reports:
public function reportFields()
{
return $this->hasMany(ReportFields::class)
->select('report_fields.*')
->join('fields', 'report_fields.field_id', 'fields.id')
->orderBy('fields.position', 'asc');
}

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 4: Count relationship within whereHas

I have 2 tables as below:
Subscription table have a entrance_limit column, and it has a hasMany('Attendance') relationship. The entrance_limit column will actually limit the number of rows in Attendance. For example, if the entrance_limit value is 10, then we only can create 10 rows in Attendance.
Attendance table has a belongsTo('Subscription') relationship.
How do I get the list of Subscription that have total number of Attendance less than the entrance_limit value?
Subscription::whereHas('attendances', function($q) {
## something like this
$q->count() < subscription->entrance_limit
})
Model your query has below
Subscription::whereHas('attendances', function($q) {
$q->where('entrance_limit','>',$q->count());
})
Finally able to get it working with the following:
Subscription::whereRaw('
(select count(*) from `attendances` where `attendances`.`subscription_id` = `subscriptions`.`id`) < `subscriptions`.`entrance_limit`
');

Categories