@connecteev I would write out a sql statement that does what you want then use that to make a laravel equivalent and in code. It’s hard to make that without more about the algorithm and how the weighting is determined.
Need to order posts based on a composite score
I am building a content platform and have a Laravel API for my news feed (infinite-scrolling feed like the Linkedin feed, Facebook feed, etc). I need to order the posts in my news feed based on the algorithm below (each parameter is going to have a weight, and I want to use these to calculate and order the results / Posts by the composite score).
Parameters for algorithm:
Num Comments
Num Likes
Date Created (Recency of Post)
Date Updated (Recency of Post)
Date last comment (Recency of Comments)
Date last like / reaction (Recency of Likes / Reactions)
Length of Post?
Fast Rising: Number of likes / comments in past X <hours / days>
The DB structure looks like this: Post hasMany Comments Post HasMany Reactions ('likes')
The schema is here: https://gist.github.com/connecteev/0e0d6751a7056af00ead267189a5864e
How can you do an OrderBy in Laravel based on
- a composite score or combined metric from multiple columns
- values from related tables (comments, reactions) (like what I am trying to do above)?
@jlrdw @willvincent @whoisthisstud There's no way I can mark all of your answers "Best Answer", but I'd like to say I'm appreciative of the help all of ya'll have been. I'm going to mark mine the "best" because it's closest to what works and I hope it will help someone else in the future. The thoughts shared here have been eye-opening, though.
Turns out that my prior SQL query was incomplete (I was missing the where condition in my Raw SQL - and this one below actually works:)
$returnedPosts = Post::where([
['status', '=', 'published'],
['is_removed_by_admin', '=', '0'],
]);
$returnedPosts = $returnedPosts->orderByRaw("SUM( (IFNULL(num_comments, 0)*0.5) + (IFNULL(num_likes, 0)*0.4) + (CHAR_LENGTH(body)*0.1) ) / (UNIX_TIMESTAMP(now()) - UNIX_TIMESTAMP(created_at)) DESC")->groupBy('id');
Unlike what I mentioned in my last comment above, it matches with the SQL query below:
select `id`, `title`, `slug`, `author_id`, `featured_image_caption`, `reading_time_minutes`, `num_comments`, `num_likes`, `created_at`, `updated_at`
from `posts`
where (`status` = 'published' and `is_removed_by_admin` = '0')
and (`created_at` >= "2000-01-01T00:00:00.000000Z")
and `posts`.`deleted_at` is null
group by `id`
order by SUM( (IFNULL(num_comments, 0)*0.5) + (IFNULL(num_likes, 0)*0.4) + (CHAR_LENGTH(body)*0.1) ) / (UNIX_TIMESTAMP(now()) - UNIX_TIMESTAMP(created_at)) DESC
limit 15 offset 0;
;
I was missing the where (`status` = 'published' and `is_removed_by_admin` = '0') conditions and so the results were inconsistent previously.
@jlrdw I did read most of your comments and examples, though I have to admit some of it was a bit complicated.
@willvincent I like the cron approach as well, I might give that a go in the future if this API becomes slow. I agree with you here: There would be some lag time between sort ordering getting updated, but the drastic ease of querying may be a worthwhile tradeoff to some of the realtimeyness
Another approach that others could consider (which won't work in this specific case, unfortunately): a DB trigger to calculate the hotness_score every time a row is inserted or updated. This would have worked except for the fact that my algorithm relies on NOW() and calculates the time difference, and that would quickly become stale if we relied on updating the hotness_score on create/update only.
Please or to participate in this conversation.