Laravel - getting the count from two pivot tables as one number - php

I have a table with articles and two pivot tables views and votes for articles that have columns article_id and user_id. For articles I have a column publish where I check if the article is published. I need to make a query by popularity, where I get all the published articles with their count from both pivot tables, where each vote from the table votes counts as 3, and each count from the table views counts as 1. Not sure how to do this, I previously had a query where I was getting published articles with their count of only votes, but I was only getting the articles that had some votes, and not the ones that were published but had no votes. And in the new query I need to add views as well, and change the logic for counting as well as including the articles that are published and not views and order them by their count from these two pivot tables.
So, the wanted output would something like this:
[{"id":117,"popularityCount":17},{"id":118,"popularityCount":15},{"id":121,"popularityCount":13}]
This was the query, but that is not what I need anymore:
$articles = DB::table('articles')
->select([DB::raw('articles.id'), DB::raw('COUNT(*) as "votes"')])
->join('votes', 'article_id', '=', 'articles.id')
->where('articles.publish', 1)
->groupBy('votes.article_id')
->orderBy('votes', 'desc')
->get();
return $articles;

This is what worked for me in the end if anyone will need it in the future!
$result = Article::select('articles.*', 'v.vote_count', 'vw.view_count')
->where('publish', 1)
->leftJoin(DB::raw('(SELECT votes.article_id, COUNT(*) AS vote_count FROM votes GROUP BY votes.article_id) v'), 'v.article_id', '=', 'articles.id')
->leftJoin(DB::raw('(SELECT views.article_id, COUNT(*) AS view_count FROM views GROUP BY views.article_id) vw'), 'vw.article_id', '=', 'articles.id')
->orderByRaw('(v.vote_count * 3) + vw.view_count DESC')
->where('created_at', '>', $date->format('Y-m-d') . ' 00:00:00')
->get();

Related

How to select from a table based on another table's values Eloquent?

I am trying to get the data on some students that are still active. Even tho I have data from inactive students in the same table.
This is the StudentAttendance
This is the StudentClass
This is the Eloquent query that I came up with:
StudentAttendance::
select('student_classes.active', 'student_attendances.student_id', 'student_attendances.classroom_id', 'classrooms.classroom', 'attendance_rules.option')
->join('classrooms', 'classrooms.id', '=', 'student_attendances.classroom_id')
->join('attendance_rules','attendance_rules.id', '=', 'student_attendances.attendance_id')
->join('student_classes', 'student_attendances.student_id', '=', 'student_classes.student_id')
->where('attendance_date', date("Y-m-d"))
->orderBy('classrooms.classroom', 'ASC')
->get();
SQL:
select `student_classes`.`active`, `student_attendances`.`student_id`, `student_attendances`.`classroom_id`, `classrooms`.`classroom`, `attendance_rules`.`option`
from `student_attendances`
inner join `classrooms` on `classrooms`.`id` = `student_attendances`.`classroom_id`
inner join `attendance_rules` on `attendance_rules`.`id` = `student_attendances`.`attendance_id`
inner join `student_classes` on `student_attendances`.`student_id` = `student_classes`.`student_id`
where `attendance_date` = '2020-02-11'
order by `classrooms`.`classroom` asc
Now my Eloquent query results into this:
As you can see the student_id 22 with the classroom_id of 2 is inactive but it appears to be inactive once and the rest active. If I remove the student_classes join I won't get all the repeated results.
The goal is to display all the attendances of today where the student is active (active=1) in the StudentClass even if I query in the StudentAttendance.
You will want to scope your join to student_classes to only look at active records.
You can do this by using a callback in your join method:
->join('student_classes', function ($join) {
$join->on('student_attendances.student_id', '=', 'student_classes.student_id')
->on('student_classes.classroom_id', '=', 'student_attendances.classroom_id')
->where('student_classes.active', 1);
})
This is covered under the 'Advanced Join Clauses' in the Query Builder docs - https://laravel.com/docs/5.8/queries#joins

Duplicate rows with multiple join

I'm using this code to join two table on contents table then group by user
when I join only analytics table the result is correct also the same wehn joining only reactions table
but both table is causing wrong result in sum and count
->where('users.type_id', 2)
->leftJoin('contents', 'users.id', '=', 'contents.instructor_id')
->leftJoin('analytics', 'contents.id', '=', 'analytics.content_id')
->leftJoin('reactions', 'contents.id', '=', 'reactions.content_id')
->selectRaw('
users.id as id,
concat(users.name, "-", users.last_name) as name,
sum(analytics.total_watched) as sum_watched,
count(analytics.id) as times_watched,
sum(analytics.completed=1) as completed_video,
sum(reactions.reaction_type="love") as love,
sum(reactions.reaction_type="happy") as happy,
sum(reactions.reaction_type="thinking") as thinking,
sum(reactions.reaction_type="sad") as sad,
sum(reactions.reaction_type="angry") as angry
')
->groupBy('users.id')
->limit(10)
->get();

Laravel - Getting data from few tables (joining and ordering)

I'm creating a ranking of players badges and I'm stuck with db query.
Tables:
user(id), club(id), club_user(user_id, club_id), badges(user_id)
I would like to get list of all users from specified club (for example club.id = 1) with amount of badges they have. Results should be ordered by number of badges.
How to create that kind of db query? Is it possible with Eloquent?
Should it be made with db::table and join?
Table user
id|name
1|John
2|Robert
3|Kate
Table club
id|name
1|Sunshine Club
2|Example Club
Table club_user
user_id|club_id
1|1
2|1
3|2
Table bagdes
id|name|user_id|club_id
1|Champion|1|1
2|Some badge|1|1
3|example|2|1
4|Gold Badge|3|2
so if I would like to get ranking of users from club 1, ordered by badge count.
I should get:
name|number of badges
John|2 (badges)
Robert|1 (badge)
Kate is not it this club.
Try this
select user.name ,user.id as userid , (select count(bagdes.id) from
bagdes where user_id= userid)
as total_badges from user inner join club_user on
user.id = club_user.user_id where club_user.club_id = 1
You will get your output.
Finally I made it with this DB::table query:
$users = DB::table('users')
->select('users.name', DB::raw('count(*) as badges'))
->join('badges', 'badges.user_id', '=', 'users.id')
->where('badges.club_id', 1)
->groupby('users.id')
->orderBy('badges', 'DESC')
->get();

Convert a Laravel Builder response to a Collection

I've got 3 tables - challenge, challenge_users, and climbs with the following definitions:
table challenge
id, name, start_date, end_date
table challenge_users
id, challenge_id, user_id
table climbs
id, user_id, date
I want to pull a list of climbs that fall within the dates of the challenge for any user in the challenge. The following SQL query does what I want:
select
*
from
climbs c
join
challenge_user cu on c.user_id=cu.user_id
where
date >= '2015-03-01'
and date <= '2015-03-06'
and cu.challenge_id=1;
I can build this with a query builder like
$climbs = DB::table('climbs')
->join('challenge_user', 'climbs.user_id', '=', 'challenge_user.user_id')
->where('climbs.date', '>=', $this->start_datetime->tz($tz)->startOfDay()->toDateString())
->where('climbs.date', '<', $this->end_datetime->tz($tz)->endOfDay()->toDateString())
->where('climbs.date', '<=', Carbon::now()->tz($tz)->endOfDay()->toDateString())
->get();
The problem is, this return an array of arrays. What I really want is to get a Collection of Climb models. I know I can do something like Climb::all()->filter(...), but then I'm doing a pretty large select (the SQL query ends up just being "select * from climbs").
Is there a way to use the DB to limit my results but still get my Collection of Climb models?
You can use Eloquent with a join, but you need to manually specify the select statement so that you're only getting the fields from the parent table and not the joined table:
$climbs = Climb::select('climbs.*')
->join('challenge_user', 'climbs.user_id', '=', 'challenge_user.user_id')
->where('climbs.date', '>=', $this->start_datetime->tz($tz)->startOfDay()->toDateString())
->where('climbs.date', '<', $this->end_datetime->tz($tz)->endOfDay()->toDateString())
->where('climbs.date', '<=', Carbon::now()->tz($tz)->endOfDay()->toDateString())
->get();

How to order grouped results by last id using Eloquent and MySQL?

I tried this query but it only order id column, rest is not.
$chapters = Test::select(DB::raw('*, max(id) as id'))
->groupBy('id_equip')
->orderBy('id', 'asc')
->get();
In MySQL when using group by you can't rely on order by clause (it won't work as you expect, ie. it will not order the results in groups, but rather return random row from the group).
So in order to achieve what you want, you need a subquery or join:
// assuming tests table, group by id_equip, order by id
SELECT * FROM tests WHERE id = (
SELECT MAX(id) FROM tests as t WHERE t.id_equip = tests.id_equip
) ORDER BY id
SELECT * FROM tests
JOIN (SELECT MAX(id) as id FROM tests ORDER BY id DESC) as sub
ON sub.id = tests.id
This will get the highest id for each id_equip and return whole row for each of them.
Now, in eloquent I suggest first approach, if you want it to look more intuitive:
Test::where('id', function ($sub) {
// subquery
$sub->selectRaw('max(id)')
->from('tests as t')
->where('t.id_equip', DB::raw('tests.id_equip'));
// order by
})->orderBy('id', 'desc')->get();
but 2nd appreach is probably the way if you have big table to scan (in terms of performance):
Test::join( DB::raw(
'(select max(id) as id from tests group by id_equip order by id desc) sub'
), 'sub.id', '=', 'posts.id')
->get(['tests.*']);
Here you need to set order by clause inside the raw join statement.
You could also build that join subquery with the builder if you like:
$sub = Test::selectRaw('max(id)')
->groupBy('id_equip')
->orderBy('id', 'desc')
->toSql();
Test::join( DB::raw(
"({$sub}) as sub"
), 'sub.id', '=', 'posts.id')
->get(['tests.*']);

Categories