Limit on Group by in Mysql - mysql

In my web application, I would like to show a list of my users. Users can have different statuses (FB-like). I want to display the last 3 statuses below each user's name.
Dinosaur
I'm a dino!
I eat meat!
I like cows!
Fish
Blub!
I don't like dinosaurs!
Going for a swim!
I have the following SQL query:
SELECT s.status, u.voornaam, u.achternaam FROM status AS s INNER JOIN sportjefit_user AS u ON s.user = u.id where u.begeleider='53' group by id desc limit 3
However, this returns only the top 3 of the results, in this case it would only show the Dinosaur's statuses. I want to show the top 3 statuses for every users though. Can I do this with a group by and put a limit on this or do I need multiple queries here? And, if I do need multiple queries, how would I go about implementing this? The number of users will keep increasing as my application grows.
Can someone help me out?
Thanks.

I'd use multiple queries like this:
select all users
for each user
select last 3 statuses
using
SELECT * FROM sportjefit_user
and
SELECT status FROM status WHERE userid = ? ORDER BY id DESC LIMIT 3
How many users do you anticipate? If you're trying to show all users on a page, I'd paginate them. Hope this helps.

Related

SQL query for finding the two teachers who have access to most number of course rooms

I am a newbie with sql queries so I have no clue how to create an accurate SQL.
I tried my best but I literally cannot find any similar example online, please help me out here.
The Data Schema as follows:
User(userID, username password, email, , userType)
Course(courseID, courseTitle)
Enroll(userID, courseID)
course rooms that users can access; note that users include all sorts of users such as teachers and administrators
Material(materialID, materialText, teacherUserID, courseID)
Question:
Find the two teachers who have access to most number of course rooms. Should there be a tie break, choose the ones with smaller user IDs. List the user ID, email, and the number of course rooms that s/he can access for the two teachers.**
The problems are:
SELECT userid, email, MIN (userid)
How can I specifically find the 2 smaller user IDs and which table should I select for finding out the course rooms? Do I have to use COUNT in this case?
FROM user JOIN enroll ON (user.usertype=enroll.userid)
As the enroll_table cannot identify whether the userID is teacher or administrator, if I use JOIN, can I find the the result that I want?
WHERE....
I don't know how to specifically find two teachers AND make sure they have tie break
Do I have to use GROUP BY and ORDER BY as well?
Just saw your attempted query. Look up how to format the code, so it stands out from the text. But you started about right. While we don't have the full info, try the following:
select user.userid,user.username, count(*) as cnt
from enroll
join user on user.userid=enroll.userid
where user.usertype="teacher"
group by user.userid
order by cnt DESC;
So Mary teaches three courses and comes out ahead. Since you want only the top two you can add the line LIMIT 2 to just get the two most prolific teachers.
The part that is hardest to understand for beginners is the group by clause, which generates aggregation, and which requires something like a count(*) clause in the first line. Read up on this separately and make yourself an even smaller example so you understand this well.
kenken068 also asked for a "tie break" using the userid so maybe the "order by" should be
order by cnt DESC, userid ASC;
Problem 1?
Limit to 2 based on the order on total. And also userid as tie braker.
Problem 2?
That info should be in User.userType
But then you need to know which userType is used for the teachers.
However, teachers have Material?
Problem 3?
See problem 1.
Problem 4?
Not always. But to calculate a total, a count is often used together with a group by.
SELECT
u.userID,
u.email,
COUNT(DISTINCT e.courseID) as TotalCourses
FROM `User` AS u
LEFT JOIN `Enroll` AS e
ON u.userID = e.userID
WHERE u.userID IN (SELECT DISTINCT teacherUserID FROM `Material`)
GROUP BY u.userID, u.email
ORDER BY TotalCourses DESC, u.userID
LIMIT 2
Select teacheruserID, count(courseID) from material
group by teacheruserID;
This will give you the count of courses the teachers have access to.
Then Simply order it in descending with the help of order by desc clause.
and the select TOP 2 with TOP Keyword

MySQL query to find a slightly complex relationship

I am trying to devise a MySQL query to select a specific record based off 2 parameters. Lets say I have 3 tables. Users, Conversations, and UserConvo where UserConvo is a reference table that links the Many-to-Many relationship between a User and any Conversation that they are having. Think of it as a very basic web chat.
Now, if given an arbitrary number of UserIds (i.e. [1,2,3]), I want to find a Conversation that involves EXACTLY these Users. Therefore, for the case of UserIds 1, 2, and 3, I want to see if there exists a Conversation where only user 1, 2, and 3 are included.
Is there a purely MySQL way to do this? My first thoughts are to just query the Conversations in which UserId 1, 2 and 3 are present. Then, somehow check each record to see if all ConversationIds match, but I do not have a whole lot of MySQL experience and am not sure of its potential.
Thanks
Something like this should work. It uses count with case - the idea is to compare the overall count per conversation to those where the user id in (1,2,3):
select c.id
from conversations c
join userconversations uc on c.id = uc.conversationid
join users u on uc.userid = u.id
group by c.id
having count(u.id) = count(case when u.id in (1,2,3) then 1 end)
SQL Fiddle Demo

MySQL sorting by "relationships per day"

I need to create a mysql query for my project that's a bit too complicated for my scope...
So, I a table of images with id and timestamp columns, along with metadata columns
I also have a table of "loves", which has columns for id, imageid, userid, and timestamp
(userid not really important here)
Currently, I am using a LEFT JOIN to sort the images by their total number of likes
What I would like to do now is, instead, sort the images by their daily average of likes.
So, an image created today that has 5 likes associated with it should come before an image created 5 days ago with 20 likes associated.
Not even sure how to begin to approach this, any of you SQL gurus have any ideas? Cheers.
EDIT:
Using this query
SELECT images.*,
COUNT(loves.id) AS num_loves
FROM images
JOIN loves ON (images.id = loves.imageid)
GROUP BY images.id
ORDER BY num_loves/DATEDIFF(images.timestamp,CURDATE())
DESC LIMIT 0 , 24
getting this error
Reference 'num_loves' not supported (reference to group function)
Still getting a handle on MySQL syntax...
You can use any valid expression as your ORDER BY clause. This means we just need to recall a hint of algebra:
SELECT
images.url,
images.date_added
FROM IMAGES
JOIN image_likes ON image_likes.image_id = images.id
GROUP BY images.id
ORDER BY count(image_likes.id)/DATEDIFF(CURDATE(), images.date_added)

Complex MySQL Query - Find duplicates PER user_id?

I have a database of Facebook Likes from several people. There are duplicate "like_id" fields across many "user_id"s. I want a query that will find the amount of "like_id"s person A has in common with person B.
This query is fantastic for comparing likes when only 2 "user_id"s are in the database, but as soon as I add a 3rd, it messes it up. Basically, I want to see who has the most "likes" in common with with person A.
SELECT *,
COUNT(*)
FROM likes
GROUP BY like_id
HAVING COUNT(*) > 1
Anyone have a query that might work?
This SQL should work. You just need to put in the User A's user_id and it should compare with all other users and show the top matching one. You can change it to show the top 5 or do whatever else you need to do.
Basically what it is doing is that it is doing a self join on the table, but making sure that when it does a join, it is a different user_id but the "like" is the same. Then it does a group by each of the other user_id's and sums the same amount of likes for that user_id.
SELECT all_other_likes.user_id, count(all_other_likes.like_id) AS num_similar_likes
FROM likes original_user_likes
JOIN likes all_other_likes
ON all_other_likes.user_id != original_user_likes.user_id
AND original_user_likes.like_id = all_other_likes.like_id
WHERE original_user_likes = USER_ID_YOU_WANT_TO_COMPARE
GROUP BY all_other_likes.user_id
ORDER BY count(all_other_likes.like_id) DESC
LIMIT 1;
Not sure what database you are using. You might need to do a SELECT TOP 1 if it is MS-SQL, but this is valid PostgreSQL and MySQL syntax.
I think this will do it:
SELECT
likes_a.user_id,
likes_b.user_id
FROM
likes as likes_a JOIN likes as likes_b
ON
likes_a.like_id = likes_b.like_id
WHERE
likes_a.user_id <> likes_b.user_id
And then post-process the results to count up who has the most in common.

Using Joins, Group By and Sub Queries, Oh My!

I have a database with a table for details of ponies, another for details of contacts (owners and breeders), and then several other small tables for parameters (colours, counties, area codes, etc.). To give me a list of existing pony profiles, with their various details given, i use the following query:
SELECT *
FROM profiles
INNER JOIN prm_breedgender
ON profiles.ProfileGenderID = prm_breedgender.BreedGenderID
LEFT JOIN contacts
ON profiles.ProfileOwnerID = contacts.ContactID
INNER JOIN prm_breedcolour
ON profiles.ProfileAdultColourID = prm_breedcolour.BreedColourID
ORDER BY profiles.ProfileYearOfBirth ASC $limit
In the above sample, the 'profiles' table is my primary table (holding the Ponies info), 'contacts' is second in importance holding as it does the owner and breeder info. The lesser parameter tables can be identified by their prm_ prefix. The above query works fine, but i want to do more.
The first big issue is that I wish to GROUP the results by gender: Stallions, Mares, Geldings... I used << GROUP BY prm_breedgender.BreedGender >> or << GROUP BY ProfileBreedGenderID >> before my ORDER BY line, but than only returns two results from all my available profiles. I have read up on this, and apparantly need to reorganise my query to accomodate GROUP within my primary SELECT clause. How to do this however, gets me verrrrrrry confused. Step by step help here would be fantabulous.
As a further note on the above - You may have noticed the $limit var at the end of my query. This is for pagination, a feature I want to keep. I shouldn't think that's an issue however.
My secondary issue is more of an organisational one. You can see where I have pulled my Owner information from the contacts table here:
LEFT JOIN contacts
ON profiles.ProfileOwnerID = contacts.ContactID
I could add another stipulation:
AND profiles.ProfileBreederID = contacts.ContactID
with the intention of being able to list a pony's Owner and Breeder, where info on either is available. I'm not sure how to echo out this info though, as $row['ContactName'] could apply in either the capacity of owner OR breeder.
Is this a case of simply running two queries rather than one? Assigning a variable $foo to the first run of the query, then just run another separate query altogether and assign $bar to those results? Or is there a smarter way of doing it all in the one query (e.g. $row['ContactName']First-iteration, $row['ContactName']Second-iteration)? Advice here would be much appreciated.
And That's it! I've tried to be as clear as possible, and do really appreciate any help or advice at all you can give. Thanks in advance.
##########################################################################EDIT
My query currently stands as an amalgam of that provided by Cularis and Symcbean:
SELECT *
FROM (
profiles
INNER JOIN prm_breedgender
ON profiles.ProfileGenderID = prm_breedgender.BreedGenderID
LEFT JOIN contacts AS owners
ON profiles.ProfileOwnerID = owners.ContactID
INNER JOIN prm_breedcolour
ON profiles.ProfileAdultColourID = prm_breedcolour.BreedColourID
)
LEFT JOIN contacts AS breeders
ON profiles.ProfileBreederID = breeders.ContactID
ORDER BY prm_breedgender.BreedGender ASC, profiles.ProfileYearOfBirth ASC $limit
It works insofar as the results are being arranged as I had hoped: i.e. by age and gender. However, I cannot seem to get the alias' to work in relation to the contacts queries (breeder and owner). No error is displayed, and neither are any Owners or Breeders. Any further clarification on this would be hugely appreciated.
P.s. I dropped the alias given to the final LEFT JOIN by Symcbean's example, as I could not get the resulting ORDER BY statement to work for me - my own fault, I'm certain. Nonetheless, it works now although this may be what is causing the issue with the contacts query.
GROUP in SQL terms means using aggregate functions over a group of entries. I guess what you want is order by gender:
ORDER BY prm_breedgender.BreedGender ASC, profiles.ProfileYearOfBirth ASC $limit
This will output all Stallions, etc. next to each other.
To also get the breeders contact, you need to join with the contacts table again, using an alias:
LEFT JOIN contacts AS owners
ON profiles.ProfileOwnerID = owners.ContactID
LEFT JOIN contacts AS breeders
ON profiles.ProfileBreederID = breeders.ContactID
To further expand on what #cularis stated, group by is for aggregations down to the lowest level of "grouping" criteria. For example, and I'm not doing per your specific tables, but you'll see the impact. Say you want to show a page grouped by Breed. Then, a user picks a breed and they can see all entries of that breed.
PonyID ProfileGenderID Breeder
1 1 1
2 1 1
3 2 2
4 3 3
5 1 2
6 1 3
7 2 3
Assuming your Gender table is a lookup where ex:
BreedGenderID Description
1 Stallion
2 Mare
3 Geldings
SELECT *
FROM profiles
INNER JOIN prm_breedgender
ON profiles.ProfileGenderID = prm_breedgender.BreedGenderID
select
BG.Description,
count(*) as CountPerBreed
from
Profiles P
join prm_BreedGender BG
on p.ProfileGenderID = BG.BreedGenderID
group by
BG.Description
order by
BG.Description
would result in something like (counts are only coincidentally sequential)
Description CountPerBreed
Geldings 1
Mare 2
Stallion 4
change the "order by" clause to "order by CountsPerBreed Desc" (for descending) and you would get
Description CountPerBreed
Stallion 4
Mare 2
Geldings 1
To expand, if you wanted the aggregations to be broken down per breeder... It is a best practice to group by all things that are NOT AGGREGATES (such as MIN(), MAX(), AVG(), COUNT(), SUM(), etc)
select
BG.Description,
BR.BreaderName,
count(*) as CountPerBreed
from
Profiles P
join prm_BreedGender BG
on p.ProfileGenderID = BG.BreedGenderID
join Breeders BR
on p.Breeder = BR.BreaderID
group by
BG.Description,
BR.BreaderName
order by
BG.Description
would result in something like (counts are only coincidentally sequential)
Description BreaderName CountPerBreed
Geldings Bill 1
Mare John 1
Mare Sally 1
Stallion George 2
Stallion Tom 1
Stallion Wayne 1
As you can see, the more granularity you provide to the group by, the aggregation per that level is smaller.
Your join conditions otherwise are obviously understood from what you've provided. Hopefully this sample clearly provides what the querying process will do. Your group by does not have to be the same as the final order... its just common to see so someone looking at the results is not trying to guess how the data was organized.
In your sample, you had an order by the birth year. When doing an aggregation, you will never have the specific birth year of a single pony to so order by... UNLESS.... You included the YEAR( ProfileYearOfBirth ) as BirthYear as a column, and included that WITH your group by... Such as having 100 ponies 1 yr old and 37 at 2 yrs old of a given breed.
It would have been helpful if you'd provided details of the table structure and approximate numbers of rows. Also using '*' for a SELECT is a messy practice - and will cause you problems later (see below).
What version of MySQL is this?
apparantly need to reorganise my query to accomodate GROUP within my primary SELECT clause
Not necessarily since v4 (? IIRC), you could just wrap your query in a consolidating select (but move the limit into the outer select:
SELECT ProfileGenderID, COUNT(*)
FROM (
[your query without the LIMIT]
) ilv
GROUP BY ProfileGenderID
LIMIT $limit;
(note you can't ORDER BY ilv.ProfileYearOfBirth since it is not a selected column / group by expression)
How many records/columns do you have in prm_breedgender? Is it just Stallions, Mares, Geldings...? Do you think this list is likely to change? Do you have ponies with multiple genders? I suspect that this domain would be better represented by an enum in the profiles table.
with the intention of being able to list a pony's Owner and Breeder,
Using the code you suggest, you'll only get returned instances where the owner and breeder are the same! You need to add a second instance of the contacts table with a different alias to get them all, e.g.
SELECT *
FROM (
SELECT *
FROM profiles
INNER JOIN prm_breedgender
ON profiles.ProfileGenderID = prm_breedgender.BreedGenderID
LEFT JOIN contacts ownerContact
ON profiles.ProfileOwnerID = ownerContact.ContactID
INNER JOIN prm_breedcolour
ON profiles.ProfileAdultColourID = prm_breedcolour.BreedColourID
) ilv LEFT JOIN contacts breederContact
ON ilv.ProfileBreederID = breederContact.ContactID
ORDER BY ilv.ProfileYearOfBirth ASC $limit