I have an application with tutors and courses and subscribers and ratings. These are the tables I am using:
tbl_tutors:
id
name
tbl_subscribers:
id
user_id
course_id
tbl_courses:
id
name
tutor_id
tbl_ratings:
id
user_id
course_id
rating
I need to get 1 tutor with the number of courses he has, the number of total subscribers for those courses and the average course rating for all his courses. This is a lot of data; can it be done in 1 sql query or do I need to code foreach statements in php to get the average ratings and the total subscribers for those courses?
Well do you need totals per tutor-course combination or a total (and average) at the tutor level?
And what is the rating table adding over the subscriber table? Aren't they both unique user-course combinations?
If one user attends multiple courses by the same tutor, how many subscribers do they count as?
The SQL provided by #alfasin is easily extended to all tutors. The syntax below is for SQL server, you may need to change for MySQL
Select t.name, count(distinct c.id) courseCount, count(s.id) subscribers, avg(r.rating) subRating
From tbl_tutors t
Inner join tbl_courses c on c.tutorid = t.id
Inner join tbl_subscribers s on s.courseid = c.id
Inner join tbl_ratings r on r.userid = s.userid and r.courseid = c.id
Group by t.name
Note that when trying to build queries like this it's usually best to do them without grouping so you can inspect which rows are contributing to the counts and ensure you're including everything you expect and that you're not duplicating results
select t.name "Tutor", count(c.id) "# courses", count(s.id) "# subscribers"
from tbl_tutors t, tbl_subscribers s, tbl_courses c, tbl_ratings r
where t.id = XXX
and c.tutor_id = t.id
and s.course_id = c.id
and r.user_id = s.user_id
group by t.name
this sql will get you all you need besides the courses average (substitute the XXX with the tutor-id you want to find). for courses average you can run a separate select.
Related
So i am making this webpage (for fun to practice web dev) where users can rate or comment on a movie. One page I have is where you click on the movie for full details and it lists all the ratings and comments (together if the user has commented by review and rated through a page called "reviewMovie"...which if they went this way the rating is mandatory, otherwise they can comment on this page "listMovieReviews").
The problem I am having is incorrect details when doing my queries
the discussion table stores: the discussion ID (primary key) the timestamp of the
comment, the comment, the user who made the comment, and the movie they commented
about.
the discussion table stores: the discussion ID (primary key) the timestamp of the
comment, the comment, the user who made the comment, and the movie they commented
about.
the rating table stores: the rating ID (primary key), the movie being rated, the
user who did the rating, and the rating score (out of 10)
So some examples of the combined data are:
User1 (user1) has rated "American Psycho" a 4/10 and has made a comment "comment1" on
it
User2 (admin..for testing purposes) has rated "American Psycho" a 8/10 and has made a
comment "comment2" on it
So on the page that lists the details of "American Psycho" and the ratings/comments I should have this list of ratings and comments:
<TIMESTAMP FOR COMMENT1> User1 Rating 4/10 "comment1"
<TIMESTAMP FOR COMMENT2> admin Rating 8/10 "comment2"
Using the following queries:
SELECT *
FROM discussion
INNER JOIN users ON discussion.userID = users.userID
WHERE discussion.movieID = <American Psycho's Movie ID>;
AND
SELECT *
FROM ratings
INNER JOIN movies ON ratings.movieID = movies.movieID
WHERE ratings.movieID = <American Psycho's Movie ID>;
I get this:
<TIMESTAMP FOR COMMENT2> admin Rating 4/10 "comment2"
<TIMESTAMP FOR COMMENT2> admin Rating 8/10 "comment2"
I have tried several other INNER JOINS with joining the table that stores user information and table that stores movies information but I keep getting mixed data
Also tried DISTINCT and UNION but still to no avail
Where am I going wrong??
Also first post so sorry If I have not been too clear, bad formatting, or not shown enough work but I am really really stuck
I assume:
A movie could have from 0 to n comments.
A movie could have from 0 to n ratings.
A user could rate a movie only once or none.
A user could comment a movie from 0 to n times.
Your queries are fine, maybe your problem is in your php code.
You have to account that a user maybe comment a movie several time and never rated it.
In second query you should JOIN with user (instead of with movie, because you do not get movie information) to get the user name.
Maybe you should display the info in two table: one for ratings and other for comments.
(You have to replace quotation marks by movie ID)
SELECT u.userName, r.score
FROM ratings AS r
INNER JOIN users AS u ON r.userID = u.userID
WHERE r.movieID = ?;
SELECT u.userName, d.commentTime, d.comment
FROM discussion AS d
INNER JOIN users AS u ON d.userID = u.userID
WHERE d.movieID = ?;
You could group all comments per user in one row this way (but I think this is not what you are looking for):
SELECT u.userName, GROUP_CONCAT(CONCAT(d.commentTime, ': ', d.comment) SEPARATOR '; ') AS comments
FROM discussion AS d
INNER JOIN users AS u ON d.userID = u.userID
WHERE d.movieID = ?
GROUP BY u.userName
I think do not have sense to make one query in this case, but if you want get all data in one query you could try something like this:
You will have a comment per row, so for example if a user make two comment you will have two rows for the same user with the same score.
First I get all user that comment or rating the selected movie and make a CROSS JOIN between movie and these users. Then I make a LEFT JOIN with discussion ON movieID and userID and another LEFT JOIN with ratings ON movieID and userID.
You need to make LEFT JOIN instead of INNER JOIN because if a movie do not have ratings or comments your result will be empty.
In SELECT clause you should list only the columns that you need.
SELECT *
FROM movies m
CROSS JOIN (SELECT d.userID
FROM discussion d
WHERE d.movieID = ?
UNION
SELECT r.userID
FROM ratings r
WHERE r.movieID = ?) AS u
LEFT JOIN discussion AS d ON m.movieID = d.movieID AND u.userID = d.userID
LEFT JOIN ratings AS r ON m.movieID = r.movieID AND u.userID = r.userID
LEFT JOIN users ON u.userID = users.userID
WHERE m.movieID = ?;
You need to join all three tables.
SELECT *
FROM movies AS m
JOIN ratings AS r ON r.movieID = m.movieID
JOIN discussion AS d ON d.userID = r.userID AND d.movieID = m.movieID
WHERE m.movieID = <American Psycho's Movie ID>
ORDER BY r.userID, d.timestamp
This will repeat the movie and rating information for each comment. You can remove those duplicates in the application code that displays the results. See How can i list has same id data with while loop in PHP? for an example of how to do this.
SELECT * FROM movies AS MOVIE
JOIN ratings AS RATING ON `RATING.movieID` = `MOVIE.movieID`
JOIN discussion AS DISCUS ON `DISCUS.userID` = `RATING.userID`
WHERE `MOVIE.movieID` = <SOME Movie ID>
ORDER BY `RATING.userID`, `DISCUS.timestamp`
I have 3 tables, users, news, news_viewed. I'm trying to join these 3 tables and find a list of news each user has not viewed.
TABLE users
userid
username
status
TABLE news
newsid
title
post_time
TABLE news_viewed
nvid
username
newsid
Looking to find a list from users that have not read news (found in news_viewed)
I've tried many different joins, including left joins and inners and outers but cannot get the results I need.
$_30daysago = strtotime('-30 days');
SELECT * FROM
(
SELECT users.username, news_id
FROM users inner join news_viewed ON
users.username = news_viewed.username and users.status='active'
UNION
SELECT news_viewed.username, post_time
FROM news_viewed inner join news ON
news_viewed.newsid = news.newsid and news.post_time>'$_30daysago'
) as JoinedTable
I need the required results to include the users.username, news.newsid and news.title.
Any help would be appreciated, thank you!
This is a good spot to use the LEFT JOIN antipattern:
SELECT u.username, n.newsid, n.title
FROM users u
INNER JOIN news n ON n.post_time > ?
LEFT JOIN news_viewed nv
ON n.newsid = nv.newsid
AND nv.username = u.username
WHERE
u.status = 'active'
AND nv.nvid IS NULL
This query generates a cartesian product of users and recent news (ie having a post time greater than the parameter indicated by ?), and returns the users/news tuples for which the left join on news_viewed did not succeed (hence the antipattern).
Note: it is unclear what column to use in the join; column name news_viewed (username) tend to indicate that it relates to users(username), whereas the primary key of users seems to be userid. Fix your column names or fix your relationship.
Ellaborating on #GMB's answer
Your query:
$_30daysago = strtotime('-30 days');
SELECT * FROM
(
SELECT users.username, news_id
FROM users inner join news_viewed ON
users.username = news_viewed.username and users.status='active'
UNION
SELECT news_viewed.username, post_time
FROM news_viewed inner join news ON
news_viewed.newsid = news.newsid and news.post_time>'$_30daysago'
) as JoinedTable
is saying:
get all active users with the news they have read (inner join)
SELECT users.username, news_id
FROM users inner join news_viewed ON
users.username = news_viewed.username and users.status='active'
and add all the news with the users that have read them in the last 30 days (inner join again)
SELECT news_viewed.username, post_time
FROM news_viewed inner join news ON
news_viewed.newsid = news.newsid and news.post_time>'$_30daysago'
That is actually bringing up all the tuples from news_viewed minus the ones where the user is not active AND the new is over 30 days old.
however, given the usage of inner join, you're bringing a lot of duplicate records
1.- The results from the first query where the new is less than 30 days old
2.- The results from the second query where the user is active
since you're using UNION and not UNION ALL, you are implicitly asking for a SELECT DISTINCT, but the fields are different (it makes no sense to display newsid and then post_time in the same field)
plus, you have a typo in the field name, which is not news_id
You have to look at it from the other way around. The potential combinations amount for a scenario where every user has read every new. So you get that universe as a basis (number of users times number of news) and then
1- remove inactive users
2- remove news older than 30 days
3- remove tuples that are unrelated in the news_viewed table
SELECT users.username, news.newsid
FROM users
JOIN news
ON users.status='active' -- removes inactive users
AND news.post_time>'$_30daysago' -- removes older news
LEFT JOIN
news_viewed nv USING (username, newsid)
WHERE nv.nvid IS NULL -- removes unrelated entries
I don't think this will be too complicated to explain, but certainly complicated to get it working.
First of all, I have a couple of tables regarding users comments, one table for each section (forum, articles etc), as shown below:
site_users (id, username, ...) [Table that holds user's info]
site_articles_comments (id, user_id, comment, ...) [Where user_id = site_users.id]
site_forum_comments (id, user_id, comment, ...) [Same for site_articles_comments]
The thing is that every new row is a new comment and users can comment multiple times, which means that more rows are being added, thus making the need of sorting the number of rows to get the amount of comments in some sort of ranking system.
I was able to make a simple forum rank by doing this simple query:
SELECT u.id, u.username, COUNT(r.id) AS rank FROM site_users AS u LEFT
JOIN site_forum_comments AS r ON u.id = r.user_id GROUP BY u.username,
u.id ORDER BY rank DESC LIMIT :l
This query sorts all users from the database, where the user who has commented the most is always on top.
What I need, in the other hand, is to have a global ranking system, which sums the amount of comments in each section (articles, forum etc) and displays the users accordingly.
I was playing around with the sql to do that and the last thing I came up with was this huge query:
SELECT u.id, u.username, (COUNT(a.id) + COUNT(f.id)) AS rank FROM
site_users u LEFT JOIN site_articles_comments a ON a.user_id = u.id
LEFT JOIN site_forum_comments f ON f.user_id = u.id GROUP BY
u.username, u.id ORDER BY rank DESC LIMIT :l
This, however, returns null. What could I possibly do to achieve the result I want?
Thanks in advance,
Mateus
EDIT1: Sorry for the lack of information, this is regarding MySQL.
The problem is math with nulls, and ordering with nulls (check into the "NULLS LAST" option for overriding the default ordering which returns the nulls first for a descending order).
In your case, with the outer joins, if the user has a ton of article comments but no forum comments, well, 100 + null = null in Oracle math. So to get the math to work you need to make null=0. That's where NVL() comes in (and also has the nice side-effect of eliminating pesky nulls from your result set)!
SELECT u.id, u.username, (NVL(COUNT(a.id),0) + NVL(COUNT(f.id),0)) AS rank
FROM site_users u
LEFT JOIN site_articles_comments a ON a.user_id = u.id
LEFT JOIN site_forum_comments f ON f.user_id = u.id
GROUP BY u.username, u.id ORDER BY rank DESC LIMIT :l
I see you have both MySQL and Oracle in your tags - the above is for Oracle. If for MYSQL use COALESCE(COUNT(),0) instead.
try SELECT u.id, MIN(u.username) AS username, (COALESCE(COUNT(DISTINCT(a.id)),0) + COALESCE(COUNT(DISTINCT(f.id)),0)) AS rank
FROM site_users AS u
LEFT JOIN site_articles_comments AS a ON (a.user_id = u.id)
LEFT JOIN site_forum_comments AS f ON (f.user_id = u.id)
GROUP BY u.id
ORDER BY rank DESC
LIMIT :l
I am querying four tables (activities, notes, categories, user_entries) looking for matches that are EITHER activities OR notes. In my development database, I have 8 activities and 2 notes that match: instead I get 16 results. Each activity is duplicated: one result including the first note, the other including the second.
This is the query as it stands:
SELECT a.`aid`, a.`activityname`, a.`date`, u.`points`, u.`enid`, c.`catname`, n.`nid`, n.`notename`, n.`dates`
FROM activities a
INNER JOIN user_entries u ON a.`aid` = u.`aid_FK`
INNER JOIN categories c ON a.`category` = c.`cat`
INNER JOIN notes n ON u.`mem_no_FK` = n.`mem_no_FK`
WHERE u.`mem_no_FK` = 1995
GROUP BY a.`aid`, a.`activityname`, a.`date`, u.`points`, u.`enid`, c.`catname`, n.`nid`, n.`notename`, n.`dates`
ORDER BY `date`
I have looked at quite a few similar questions here, (especially question 7696248): but nothing I've tried has worked. Despite trying many suggestions (with/without DISTINCT, with/without GROUP BY, many join types, etc.) I get the same result each time: there is something basically wrong with the query. My knowledge of SQL is not good and I don't know how to fix it.
Can anyone help?
EDIT: FOR CLARITY
The database is a record of continuing professional development activities. Notes (confusingly named, but not by me!) are not linked to activities: they are there for users to record significant events- "No CPD done for 3 months due to road accident", and so on.
I need to list the notes and the activities on the same page: if it's possible, I'd prefer to run one DB query; if it isn't, I'll have to run two queries and amalgamate the two results arrays.
Please try DISTINCT for aid
SELECT DISTINCT(a.`aid`), a.`activityname`, a.`date`, u.`points`, u.`enid`, c.`catname`, n.`nid`, n.`notename`, n.`dates`
FROM activities a
INNER JOIN user_entries u ON a.`aid` = u.`aid_FK`
INNER JOIN categories c ON a.`category` = c.`cat`
INNER JOIN notes n ON u.`mem_no_FK` = n.`mem_no_FK`
WHERE u.`mem_no_FK` = 1995
GROUP BY a.`aid`, a.`activityname`, a.`date`, u.`points`, u.`enid`, c.`catname`, n.`nid`, n.`notename`, n.`dates`
ORDER BY `date`
If you want to select all notes in one column you can use GROUP_CONCAT or other aggregate functions
SELECT a.`aid`, a.`activityname`, a.`date`, u.`points`, u.`enid`, c.`catname`, GROUP_CONCAT(n.`notename` SEPARATOR ' ') as notes
FROM activities a
JOIN user_entries u ON a.`aid` = u.`aid_FK`
JOIN categories c ON a.`category` = c.`cat`
JOIN notes n ON u.`mem_no_FK` = n.`mem_no_FK`
WHERE u.`mem_no_FK` = 1995
GROUP BY a.`aid`
ORDER BY `date`
I need a little help setting up my query. I'm simply trying to access the amount of people who are in the same 'clan' by joining these two tables together, clan, users. Each users has a column 'clan' which is the same as the table clan's column 'roomOwner' and then I'm trying to get the table clan's information along with the amount of members so it would be like: room, roomOwner, members
So basically all I have is this:
SELECT c.*, count(u.clan) AS members FROM clans c inner join users u WHERE c.roomOwner = u.clan ORDER BY members;
It only shows one clan though. Any help please?
Your query has no GROUP BY clause. and I think it's only returning single record right? LEFT JOIN is needed here since there are possibilities that a clan has no member.
SELECT b.roomOwner, COUNT(a.clan) memberCount
FROM clan b
LEFT JOIN users a
ON a.clan = b.roomOwner
GROUP BY b.roomOwner
ORDER BY memberCount
You forgot GROUP BY. Do you have some "id" column in "clans" table? Group by that "id"
SELECT c.*, count(u.clan) AS members
FROM clans c
inner join users u ON c.roomOwner = u.clan
GROUP BY clans.id
And you need LEFT JOIN there instead of INNER JOIN if you want to see info about all clans, even having 0 users.
Perhaps this will help:
select c.*, count(links.id) as members
from clans c
left join users u on c.roomOwner = u,clan
group by u.clan
order by members