Count comments and get average rating from mysql - mysql

I just can't figure out how to get average rating and count comments from my mysql database.
I have 3 tables (activity, rating, comments) activity contains the main data the "activities", rating holds the ratings and comments - of course, the ratings.
activity_table
id | title |short_desc | long_desc | address | lat | long |last_updated
rating_table
id | activityid | userid | rating
comment_table
id | activityid | userid | rating
I'm now trying to the data from activity plus the comment_counts and average_rating in one query.
SELECT activity.*, AVG(rating.rating) as average_rating, count(comments.activityid) as total_comments
FROM activity LEFT JOIN
rating
ON activity.aid = rating.activityid LEFT JOIN
comments
ON activity.aid = comments.activityid
GROUP BY activity.aid
...doesn't do the job. It gives me the right average_rating, but the wrong amount of comments.
Any ideas?
Thanks a lot!

You are aggregating along two different dimensions. The Cartesian product generated by the joins affects the aggregation.
So, you should aggregate before the joins:
SELECT a.*, r.average_rating, COALESCE(c.total_comments, 0) as total_comments
FROM activity a LEFT JOIN
(SELECT r.activityid, AVG(r.rating) as average_rating
FROM rating r
GROUP BY r.activityid
) r
ON a.aid = r.activityid LEFT JOIN
(SELECT c.activityid, COUNT(*) as total_comments
FROM comments c
GROUP BY c.activityid
) c
ON a.aid = c.activityid;
Notice that the outer GROUP BY is no longer needed.

Related

Selecting a count of rows having a max value

Working example: http://sqlfiddle.com/#!9/80995/20
I have three tables, a user table, a user_group table, and a link table.
The link table contains the dates that users were added to user groups. I need a query that returns the count of users currently in each group. The most recent date determines the group that the user is currently in.
SELECT
user_groups.name,
COUNT(l.name) AS ct,
GROUP_CONCAT(l.`name` separator ", ") AS members
FROM user_groups
LEFT JOIN
(SELECT MAX(added), group_id, name FROM link LEFT JOIN users ON users.id = link.user_id GROUP BY user_id) l
ON l.group_id = user_groups.id
GROUP BY user_groups.id
My question is if the query I have written could be optimized, or written better.
Thanks!
Ben
You actual query is not giving you the answer you want; at least, as far as I understand your question. John actually joined group 2 on 2017-01-05, yet it appears on group 1 (that he joined on 2017-01-01) on your results. Note also you're missing one Group 4.
Using standard SQL, I think the next query is what you're looking for. The comments in the query should clarify what each part is doing:
SELECT
user_groups.name AS group_name,
COUNT(u.name) AS member_count,
group_concat(u.name separator ', ') AS members
FROM
user_groups
LEFT JOIN
(
SELECT * FROM
(-- For each user, find most recent date s/he got into a group
SELECT
user_id AS the_user_id, MAX(added) AS last_added
FROM
link
GROUP BY
the_user_id
) AS u_a
-- Join back to the link table, so that the `group_id` can be retrieved
JOIN link l2 ON l2.user_id = u_a.the_user_id AND l2.added = u_a.last_added
) AS most_recent_group ON most_recent_group.group_id = user_groups.id
-- And get the users...
LEFT JOIN users u ON u.id = most_recent_group.the_user_id
GROUP BY
user_groups.id, user_groups.name
ORDER BY
user_groups.name ;
This can be written in a more compact way in MySQL (abusing the fact that, in older versions of MySQL, it doesn't follow the SQL standard for the GROUP BY restrictions).
That's what you'll get:
group_name | member_count | members
:--------- | -----------: | :-------------
Group 1 | 2 | Mikie, Dominic
Group 2 | 2 | John, Paddy
Group 3 | 0 | null
Group 4 | 1 | Nellie
dbfiddle here
Note that this query can be simplified if you use a database with window functions (such as MariaDB 10.2). Then, you can use:
SELECT
user_groups.name AS group_name,
COUNT(u.name) AS member_count,
group_concat(u.name separator ', ') AS members
FROM
user_groups
LEFT JOIN
(
SELECT
user_id AS the_user_id,
last_value(group_id) OVER (PARTITION BY user_id ORDER BY added) AS group_id
FROM
link
GROUP BY
user_id
) AS most_recent_group ON most_recent_group.group_id = user_groups.id
-- And get the users...
LEFT JOIN users u ON u.id = most_recent_group.the_user_id
GROUP BY
user_groups.id, user_groups.name
ORDER BY
user_groups.name ;
dbfiddle here

SQL COUNT Return Wrong Number

so I have a problem with my query. I have 2 tables:
courses:
The user_id in this table is the instructor of the course.
-----------------------------------------------------------------------
| course_id | user_id | course_name | other information |
-----------------------------------------------------------------------
| 6 | 1 | My Course 1 | ... |
-----------------------------------------------------------------------
my_courses:
The user_id in this table is a student of the course.
--------------------------------------------------
| user_id | course_id | created_at |
--------------------------------------------------
| 5 | 6 | [UNIX_TIMESTAMP] |
--------------------------------------------------
The my_courses contains the number of people that have joined that course. I want to get all the course info as well as the number of people that have joined a course. Everything returns as expected except the number of people that joined a course. This is the query I'm using:
SELECT
courses.*,
users.name, //This is the name of the instructor
users.last_name, //This is the last name of the instructor
COUNT(my_courses.user_id) as count_students
FROM courses
LEFT JOIN users
ON courses.user_id = courses.user_id
LEFT JOIN my_courses
ON courses.course_id = my_courses.course_id
WHERE courses.course_id = '6'
Like I said, this query returns the course info like normal but returns 3 as count_students when it should only return 1. Does anyone know why this is happening? Any help is greatly appreciated.
It's probably because of the JOIN condition for users. It should be:
LEFT JOIN users
ON courses.user_id = users.user_id
You should also add a GROUP BY clause in your query:
SELECT
c.*,
u.name,
u.last_name,
COUNT(mc.user_id) AS count_students
FROM courses c
LEFT JOIN users u
ON c.user_id = u.user_id
LEFT JOIN my_courses mc
ON c.course_id = mc.course_id
WHERE c.course_id = '6'
GROUP BY
<columns not in the aggregate function>
Additionally, alias your tables to improve readability.
JOIN operations cause combinatorial multiplication of rows. You need to summarize the student count from its own table like so.
SELECT course_id, COUNT(*) students
FROM my_courses
GROUP BY course_id
That gives you a result set with either one or zero rows per course_id. You can then join it to the rest of your query.
SELECT courses.*,
users.name, //This is the name of the instructor
users.last_name, //This is the last name of the instructor
aggr.count_students
FROM courses
LEFT JOIN users ON courses.user_id = courses.user_id
LEFT JOIN (
SELECT course_id, COUNT(*) students
FROM my_courses
GROUP BY course_id
) aggr ON courses.course_id = aggr.course_id
WHERE courses.course_id = '6'
That way you'll avoid multiple-counting your students for courses with, perhaps, more than one instructor.

Sql conditional count with join

I cannot find the answer to my problem here on stackoverflow. I have a query that spans 3 tables:
newsitem
+------+----------+----------+----------+--------+----------+
| Guid | Supplier | LastEdit | ShowDate | Title | Contents |
+------+----------+----------+----------+--------+----------+
newsrating
+----+----------+--------+--------+
| Id | NewsGuid | UserId | Rating |
+----+----------+--------+--------+
usernews
+----+----------+--------+----------+
| Id | NewsGuid | UserId | ReadDate |
+----+----------+--------+----------+
Newsitem obviously contains newsitems, newsrating contains ratings that users give to newsitems, and usernews contains the date when a user has read a newsitem.
In my query I want to get every newsitem, including the number of ratings for that newsitem and the average rating, and how many times that newsitem has been read by the current user.
What I have so far is:
select newsitem.guid, supplier, count(newsrating.id) as numberofratings,
avg(newsrating.rating) as rating,
count(case usernews.UserId when 3 then 1 else null end) as numberofreads from newsitem
left join newsrating on newsitem.guid = newsrating.newsguid
left join usernews on newsitem.guid = usernews.newsguid
group by newsitem.guid
I have created an sql fiddle here: http://sqlfiddle.com/#!9/c8add/8
Both count() calls don't return the numbers I want. numberofratings should return the total number of ratings for that newsitem (by all users). numberofreads should return the number of reads for the current user for that newsitem.
So, newsitem with guid d104c330-c319-40e8-8be3-a7c4f549d35c should have 2 ratings and 3 reads for the current user with userid = 3.
I have tried conditional counts and sums, but no success yet. How can this be accomplished?
The main problem that I see is that you're joining in both tables together, which means that you're going to effectively be multiplying out by both numbers, which is why your counts aren't going to be correct. For example, if the Newsitem has been read 3 times by the user and rated by 8 users then you're going to end up getting 24 rows, so it will look like it has been rated 24 times. You can add a DISTINCT to your COUNT of the ratings IDs and that should correct that issue. Average should be unaffected because the average of 1 and 2 is the same as the average of 1, 1, 2, & 2 (for example).
You can then handle the reads by adding the userid to the JOIN condition (since it's an OUTER JOIN it shouldn't cause any loss of results) instead of in a CASE statement for your COUNT, then you can do a COUNT on distinct id values from Usernews. The resulting query would be:
SELECT
I.guid,
I.supplier,
COUNT(DISTINCT R.id) AS number_of_ratings,
AVG(R.rating) AS avg_rating,
COUNT(DISTINCT UN.id) AS number_of_reads
FROM
NewsItem I
LEFT OUTER JOIN NewsRating R ON R.newsguid = I.guid
LEFT OUTER JOIN UserNews UN ON
UN.newsguid = I.guid AND
UN.userid = #userid
GROUP BY
I.guid,
I.supplier
While that should work, you might get better results from a subquery, as the above needs to explode out the results and then aggregate them, perhaps unnecessarily. Also, some people might find the below to be a little clearer.
SELECT
I.guid,
I.supplier,
R.number_of_ratings,
R.avg_rating,
COUNT(*) AS number_of_reads
FROM
NewsItem I
LEFT OUTER JOIN
(
SELECT
newsguid,
COUNT(*) AS number_of_ratings,
AVG(rating) AS avg_rating
FROM
NewsRating
GROUP BY
newsguid
) R ON R.newsguid = I.guid
LEFT OUTER JOIN UserNews UN ON UN.newsguid = I.guid AND UN.userid = #userid
GROUP BY
I.guid,
I.supplier,
R.number_of_ratings,
R.avg_rating
I'm with Tom you should use a subquery to calculate the user count.
SQL Fiddle Demo
SELECT NI.guid,
NI.supplier,
COUNT(NR.ID) as numberofratings,
AVG(NR.rating) as rating,
user_read as numberofreads
FROM newsitem NI
LEFT JOIN newsrating NR
ON NI.guid = NR.newsguid
LEFT JOIN (SELECT NewsGuid, COUNT(*) user_read
FROM usernews
WHERE UserId = 3 -- use a variable #user_id here
GROUP BY NewsGuid) UR
ON NI.guid = UR.NewsGuid
GROUP BY NI.guid,
NI.supplier,
numberofreads;

How to count the number of instances of each foreign-key ID in a table?

Here's my simple SQL question...
I have two tables:
Books
-------------------------------------------------------
| book_id | author | genre | price | publication_date |
-------------------------------------------------------
Orders
------------------------------------
| order_id | customer_id | book_id |
------------------------------------
I'd like to create a query that returns:
--------------------------------------------------------------------------
| book_id | author | genre | price | publication_date | number_of_orders |
--------------------------------------------------------------------------
In other words, return every column for ALL rows in the Books table, along with a calculated column named 'number_of_orders' that counts the number of times each book appears in the Orders table. (If a book does not occur in the orders table, the book should be listed in the result set, but "number_of_orders" should be zero.
So far, I've come up with this:
SELECT
books.book_id,
books.author,
books.genre,
books.price,
books.publication_date,
count(*) as number_of_orders
from books
left join orders
on (books.book_id = orders.book_id)
group by
books.book_id,
books.author,
books.genre,
books.price,
books.publication_date
That's almost right, but not quite, because "number_of_orders" will be 1 even if a book is never listed in the Orders table. Moreover, given my lack of knowledge of SQL, I'm sure this query is very inefficient.
What's the right way to write this query? (For what it's worth, this needs to work on MySQL, so I can't use any other vendor-specific features).
Your query is almost right and it's the right way to do that (and the most efficient)
SELECT books.*, count(orders.book_id) as number_of_orders
from books
left join orders
on (books.book_id = orders.book_id)
group by
books.book_id
COUNT(*) could include NULL values in the count because it counts all the rows, while COUNT(orders.book_id) does not because it ignores NULL values in the given field.
SELECT b.book_id,
b.author,
b.genre,
b.price,
b.publication_date,
coalesce(oc.Count, 0) as number_of_orders
from books b
left join (
select book_id, count(*) as Count
from Order
group by book_id
) oc on (b.book_id = oc.book_id)
Change count(*) to count(orders.book_id)
You're counting the wrong thing. You want to count the non-null book_id's.
SELECT
books.book_id,
books.author,
books.genre,
books.price,
books.publication_date,
count(orders.book_id) as number_of_orders
from books
left join orders
on (books.book_id = orders.book_id)
group by
books.book_id,
books.author,
books.genre,
books.price,
books.publication_date
select author.aname,count(book.author_id) as "number_of_books"
from author
left join book
on(author.author_id=book.author_id)
GROUP BY author.aname;

mysql multi count() in one query

I'm trying to count several joined tables but without any luck, what I get is the same numbers for every column (tUsers,tLists,tItems). My query is:
select COUNT(users.*) as tUsers,
COUNT(lists.*) as tLists,
COUNT(items.*) as tItems,
companyName
from users as c
join lists as l
on c.userID = l.userID
join items as i
on c.userID = i.userID
group by companyID
The result I want to get is
---------------------------------------------
# | CompanyName | tUsers | tlists | tItems
1 | RealCoName | 5 | 2 | 15
---------------------------------------------
what modifications do i have to do to my query to get those results?
Cheers
Try this
SELECT u.userID, companyName,
Count(DISTINCT l.listid) as tLists, Count(DISTINCT i.items) as tItems
FROM users u
LEFT JOIN lists l ON u.userID=l.userID
LEFT JOIN items i ON u.userID=i.userID
GROUP BY u.companyID
You can do it by using sub query
select (select count(*) from users where userID=YourUserID) tUsers,
(select count(*) from lists where userID=YourUserID) as tLists,
(select count(*) from items where userID=YourUserID) as tItems,
companyName
from company group by companyID