Get last 5 rows from Table for each user [duplicate] - mysql

This question already has answers here:
Select first row in each GROUP BY group?
(20 answers)
Closed 4 years ago.
I need you help with this problem.
Scenario:
Users can vote any number of songs, but I only want to select the last 5 votes per user. I do not want to select the last 5 votes.
Table structure:
Example:
3 users
Karl, voted 10 songs
Robert, voted 6 songs
Joanne, voted 3 songs
I want to get Karls last 5 votes, Roberts last 5 votes and Joannes 3 votes.
Current SQL query:
SELECT songs.genre, songs.title, songs.band, COUNT(*) AS anzahlVotes, users.name
FROM T_Songs AS songs
RIGHT JOIN T_Votes AS votes ON songs.P_Song_id = votes.F_Song_id
LEFT JOIN T_Users AS users ON votes.F_User_id = users.P_User_id
WHERE votes.P_Vote_id IN (
SELECT P_Vote_id
FROM T_Votes
GROUP BY F_User_id
HAVING P_Vote_id > MAX(P_Vote_id)-5
);
But this query doesn't return the right vote count.
Solution (Thanks to Gordon Linoff and Paul Spiegel):
SELECT songs.genre, songs.title, songs.band, COUNT(*) AS anzahlVotes, users.name
FROM T_Songs AS songs
RIGHT JOIN T_Votes AS votes ON songs.P_Song_id = votes.F_Song_id
LEFT JOIN T_Users AS users ON votes.F_User_id = users.P_User_id
WHERE users.nobility_house IS NOT NULL
AND votes.P_Vote_id >= coalesce(
(select votes2.P_Vote_id
from T_Votes votes2
where votes2.F_User_id = votes.F_User_id
order by votes2.P_Vote_id desc
limit 1 offset 4
), 0)
GROUP BY votes.F_User_id

Assuming "last 5" means the five with the highest ids, you can do:
select v.*
from t_votes v
where v.p_vote_id >= (select v2.p_vote_id
from t_votes v2
where v2.f_user_id = v.f_user_id
order by v2.p_vote_id desc
limit 1 offset 4
);
I'll let you figure out how to bring in the columns from the other tables.
EDIT:
If there are fewer than 5 rows:
select v.*
from t_votes v
where v.p_vote_id >= coalesce( (select v2.p_vote_id
from t_votes v2
where v2.f_user_id = v.f_user_id
order by v2.p_vote_id desc
limit 1 offset 4
), p_vote_id
);

You can filter with a join on the same table :
SELECT songs.genre, songs.title, songs.band, COUNT(*) AS anzahlVotes, users.name
FROM T_Songs AS songs
RIGHT JOIN T_Votes AS votes ON songs.P_Song_id = votes.F_Song_id
LEFT JOIN T_Users AS users ON votes.F_User_id = users.P_User_id
LEFT JOIN (
SELECT F_User_id, Max(P_Vote_id)-5 as MaxVoteId
FROM T_Votes
GROUP BY F_User_id
) DF ON ((DF.F_User_id = votes.F_User_id) AND (P_Vote_id >= MaxVoteId));

Related

SQL Query with drop or null on statement

On a Userpage we show Awardpictures recived from 2 sql tables.
In zg15_auszeichnungen_vergeben which contains all user awards in form from award type id`s, award dates ... we read over the users pilotID the award datas.
Over a LEFT JOIN from zg15_auszeichnungen_main we get the Awardpicture and the Award Name which shows on the page.
SELECT v.pilotID, v.auszBereichID, v.auszTypID, v.gameID, v.auszDatum, m.mainGrafik, m.mainName
FROM zg15_auszeichnungen_vergeben
LEFT JOIN zg15_auszeichnungen_main m ON v.auszMainID = m.mainID
WHERE v.pilotID = '$pilotID'
AND v.auszBereichID = 6
AND v.auszTypID = 100
AND v.gameID = 1
ORDER BY v.auszDatum;
Because there are to many year awards(1 year,2years,3years...12 years) together wit some other awards, i want just show the oldest award of the years (just 12 years instead of all). The year awards have the value 11 from m.mainKatID and the oldest has the highest value from m.mainPunkte.
Other awards like (most Postpoints) have other values (10) in mainKatID.
I'm not familiar with mysql and don't get the result i want.
Change your ORDER BY to ORDER BY m.mainKatID DESC LIMIT 3 and set limit to return 3 records.
SELECT
v.pilotID, v.auszBereichID, v.auszTypID, v.gameID, v.auszDatum,
m.mainGrafik, m.mainName FROM zg15_auszeichnungen_vergeben v
LEFT JOIN zg15_auszeichnungen_main m ON v.auszMainID = m.mainID
WHERE v.pilotID = '$pilotID'
AND v.auszBereichID = 6
AND v.auszTypID = 100
AND v.gameID = 1
ORDER BY m.mainKatID DESC LIMIT 3
Use a subquery that gets the oldest year award for each mainID. Combine that with the non-year awards using UNION.
SELECT v.pilotID, v.auszBereichID, v.auszTypID, v.gameID, v.auszDatum, m.mainGrafik, m.mainName
FROM zg15_auszeichnungen_vergeben AS v
LEFT JOIN (
SELECT * FROM zg15_auszeichnungen_main
WHERE mainKatID != 11
UNION
SELECT m1.*
FROM zg15_auszeichnungen_main AS m1
JOIN (SELECT mainID, MAX(mainPunkte) AS maxPunkte
FROM zg15_auszeichnungen_main
WHERE mainKatID = 11
GROUP BY mainID) AS m2
ON m1.mainID = m2.mainID AND m1.mainPunkte = m2.maxPunkte
WHERE m1.mainKatID = 11) AS m
ON v.auszMainID = m.mainID
WHERE v.pilotID = '$pilotID'
AND v.auszBereichID = 6
AND v.auszTypID = 100
AND v.gameID = 1
ORDER BY v.auszDatum;

MySql throws error Subquery returns more than 1 row

I want to retrieve values from 3 table where i am getting error "Sub query returns more than 1 row " .
My concept is to retrieve all the post where i have to calculate the sum of votes from ttpostvotes table with respect to each post and if provided userid is voted for the that post then it will shows the post count like 1 or -1.
My query is as below:
SELECT r.PostId, r.`Post`,r.PostTime, coalesce(x.Votes, 0) as Votes ,
(Select Votes From `ttpostvotes` where UserId=30 and x.PostId=r.PostId ) as IsUservoted,
(Select Count(*) From ttreply where PostId=r.PostId ) AS ReplyCount FROM `ttpost` r
left join ( SELECT PostId, sum(Votes) as Votes FROM `ttpostvotes` GROUP BY PostId ) x ON
x.PostId = r.PostId WHERE r.OffensiveCount<3 and r.SpamCount<5 and r.OtherCount<7 and r.`PeekId`=101 ORDER BY `r`.`PostTime` DESC
The 3 tables are like as below:
ttpost
ttpostvotes
ttreply
This is your select:
SELECT r.PostId, r.`Post`,r.PostTime, coalesce(x.Votes, 0) as Votes,
(Select Votes From `ttpostvotes` where UserId = 30 and x.PostId = r.PostId
) as IsUservoted,
(Select Count(*) From ttreply where PostId=r.PostId ) AS ReplyCount
The first subquery has no aggregation, so I suppose a user could vote more than once for a post. This will fix the syntax error:
SELECT r.PostId, r.`Post`,r.PostTime, coalesce(x.Votes, 0) as Votes,
(Select SUM(Votes) From `ttpostvotes` where UserId = 30 and x.PostId = r.PostId
) as IsUservoted,
(Select Count(*) From ttreply where PostId = r.PostId ) AS ReplyCount
Whether it does what you want is a different question.
Note: if you want your original query to work, you should define a unique constraint/index on ttpostvotes:
create unique index unq_ttpostvotes_userid_postid on ttpostvotes(userid, postid);

Limit query for each group

I have this query:
select pl.photo_id, pl.user_id, pl.liker_id, p1.filename user_filename, p2.filename liker_filename
FROM photo_likes pl
left join photos p1 on (pl.photo_id = p1.photo_id)
left join photos p2 on (pl.liker_id = p2.user_id and p2.avatar = 1)
where pl.user_id = $id order by pl.liker_id, pl.date_liked desc
It gets the correct data, but I would like to modify it to limit the data. So, in a nut shell, this query will get all the likes from all the people that liked a photo of theirs, it works great, this can grab lots of photos for each person. But I want to limit it to get only 5 from each person:
So, say user A likes 10 of my photos, user B likes 8 of my photos, and user C likes 2 of my photos, I only want the last 5 from user A, the last 5 from user B and the last 2 from user C. If that makes sense, how can this be done?
The query you have is good, but I'm wrapping that and using MySQL variables to check each return variable and increase the sequence per each "liker". When the liker changes, set the sequence back to 1.... Then, apply HAVING < 6 for the sequence. You can't do it in the WHERE clause because you want EVERY record to be QUALIFIED which keeps updating the #likeSeq and #lastLiker. Only AFTER that is done, the HAVING says... AFTER that, if the seq is greater than you 5 cap, it throws it out.
per alternate rows being included per your print-screens...
select
AllRanks.*
from
( select
PreQualified.*,
#likeSeq := if( PreQualified.Liker_ID = #lastLiker, #likeSeq +1, 1 ) as Seq,
#lastLiker := PreQualified.Liker_ID
from
( select
pl.photo_id,
pl.user_id,
pl.liker_id,
p1.filename user_filename,
p2.filename liker_filename
FROM
photo_likes pl
left join photos p1
on pl.photo_id = p1.photo_id
left join photos p2
on pl.liker_id = p2.user_id
and p2.avatar = 1
where
pl.user_id = $id
order by
pl.liker_id,
pl.date_liked desc ) PreQualified,
( select #lastLiker := 0,
#likeSeq := 0 ) sqlvars
) AllRanks
where
AllRanks.Seq < 6
Could you wrap your joined tables in a sub query?
select ...
from photo_likes
left join photos p1 ...
left join (select p2.filename liker_filename from photos where p1.liker_id = p2.user_id and avatar = 1 LIMIT 5) p2
where ...

MySQL GROUP BY on a calculated value gives an error - using other values produces unexpected results

This query returns all 23 of these music.id's:
39,64,1327,1608,1644,1657,1666,1676,1681,1686,1691,1711,1726,1730,1811,1851,2346,2440,2967,2968,2996,2998,3110
But... I want to group on a calculated value, then group by category (since 'name' is unique to each record and category isn't)
SELECT
music.id,
music.name AS name,
music.filename,
music.url_name,
music.file_path_high,
music.filesize,
music.categories AS category,
music.duration,
music.folder AS folder,
SUM(active0.weight) + SUM(active1.weight) AS total_weight
FROM (music)
INNER JOIN linked_tags AS active0
ON active0.track_id = music.id AND active0.tag_id = 1
INNER JOIN linked_tags AS active1
ON active1.track_id = music.id AND active1.tag_id = 11
GROUP BY name
ORDER BY total_weight DESC
But... when I try to GROUP BY total_weight I get an error (it says I "can't group on total_weight"). Can I not GROUP BY a calculated value? Also, if I try to GROUP BY any other value, the results that are returned are unique for that GROUP BY parameter, so depending on the parameter, I may get 0 results. GROUP BY 'music.categories' for instance only returns 7 items, 7 items all with different categories. Each item's 'name' value is unique, so it really can't group anything anyway, so I figured that by grouping by 'categories' would at least group them by their like integers of a category they belong to, but that's not what I'm seeing.
My table structure is like so:
//music table
ID name categories
1 Hopeful 02 1
2 Organic 01b 3
3 Organic 01c 3
4 Instrumental 01 8
// linked_tags table
track_id tag_id weight
1 1 3
2 2 4
2 3 5
2 1 2
You can't group by an aggregate function. It has to calculate the sum using the group and it gets the group using the sum, it's a catch 22 situation.
You may have meant to group on active0.weight + active1.weight
I think you have misunderstood what group by does in SQL - it ensures that only one row is returned for each combination of groups.
In order to retrieve multiple rows in a specific order, you need to use order by - like so:
SELECT
music.id,
music.name AS name,
music.filename,
music.url_name,
music.file_path_high,
music.filesize,
music.categories AS category,
music.duration,
music.folder AS folder,
SUM(active.weight) AS total_weight
FROM (music)
INNER JOIN linked_tags AS active
ON active.track_id = music.id AND active.tag_id in (1, 11)
GROUP BY music.id
HAVING COUNT(DISTINCT active.tag_id)=2
ORDER BY category, total_weight DESC
I think this is what you want:
SELECT
music.id,
music.name AS name,
music.filename,
music.url_name,
music.file_path_high,
music.filesize,
music.categories AS category,
music.duration,
music.folder AS folder,
total_weights.weight as total_weight
FROM music
JOIN (SELECT
music.categories as category,
SUM(active0.weight + active1.weight) as weight
FROM (music)
INNER JOIN linked_tags AS active0
ON active0.track_id = music.id AND active0.tag_id = 1
INNER JOIN linked_tags AS active1
ON active1.track_id = music.id AND active1.tag_id = 11
GROUP BY music.categories
) total_weights
ON music.categories = total_weights.category
INNER JOIN linked_tags AS active0
ON active0.track_id = music.id AND active0.tag_id = 1
INNER JOIN linked_tags AS active1
ON active1.track_id = music.id AND active1.tag_id = 11
ORDER BY total_weight DESC

MySQL SELECT DISTINCT ORDER BY problem

To begin with I have 4 tables I am dealing with.
I have a classes table that is a 1->N relationship with a sections table which also has a 1->N relationship with a lessons table.
So to put it in perpective:
Classes
Sections
Lessons
The last table is an activityLog, when the student accesses a lesson this is recorded using the following:
ActivityLog Row -> actorID (user ID), classID, sectionID, lessonID
I want to pull out the last 5 unique lessons the student has visited. I tried using both DISTINCT and GROUP BY without success.
The same records are being returned each time, not the latest classes that they have visited.
Using GROUP BY
SELECT activityLog.actorID, activityLog.activityDate,
strClasses.classID, strClasses.className,
strSections.sectionID, strSections.sectionName,
strLessons.lessonID, strLessons.lessonName
FROM activityLog
LEFT JOIN strClasses ON strClasses.classID = activityLog.classID
LEFT JOIN strSections ON strSections.sectionID = activityLog.sectionID
LEFT JOIN strLessons ON strLessons.lessonID = activityLog.lessonID
WHERE activityLog.activityTypeID = 6 AND activityLog.actorID = 3
GROUP BY activityLog.lessonID
ORDER BY activityLog.activityDate DESC
LIMIT 5
Using DISTINCT
SELECT DISTINCT activityLog.actorID,
strClasses.classID, strClasses.className,
strSections.sectionID, strSections.sectionName,
strLessons.lessonID, strLessons.lessonName
FROM activityLog
LEFT JOIN strClasses ON strClasses.classID = activityLog.classID
LEFT JOIN strSections ON strSections.sectionID = activityLog.sectionID
LEFT JOIN strLessons ON strLessons.lessonID = activityLog.lessonID
WHERE activityLog.activityTypeID = 6 AND activityLog.actorID = 3
ORDER BY activityLog.activityDate DESC
LIMIT 5
I cannot figure out why the latest records are not being displayed.
Based on your change, how does this suit you?
SELECT activityLog.actorID, activityLog.activityDate,
strClasses.classID, strClasses.className,
strSections.sectionID, strSections.sectionName,
strLessons.lessonID, strLessons.lessonName
FROM activityLog
LEFT JOIN strClasses ON strClasses.classID = activityLog.classID
LEFT JOIN strSections ON strSections.sectionID = activityLog.sectionID
LEFT JOIN strLessons ON strLessons.lessonID = activityLog.lessonID
WHERE activityLog.activityTypeID = 6 AND activityLog.actorID = 3
AND activityLog.activityDate = (SELECT MAX(activityDate) FROM activityLog AS lookup WHERE lessonID = activityLog.lessonID)
ORDER BY activityLog.activityDate DESC
LIMIT 5
Based on your description, I'm not sure why you're using LEFT JOIN, but I've left it in just in case.
Try group by like below
GROUP BY activityLog.classID,activityLog.sectionID,activityLog.lessonID
I think it will work, or just sent me create scripts for these I will create that query
Well, there's got to be a datetime in the ActivityLog I hope... so Try this:
Select s.Name, c.ClassName
From Students s
left Join On Classes c
On c.ClassId In
(Select Distinct ClassId From Classes
Where (Select Count(Distinct ClassId) From Classes ic
Join ActivityLog l On l.UserId = s.UserId
And l.ClassId = c.ClassId
Where classId = c.ClassId
And activityDateTime > l.activityDateTime)
< 5)