I have a user table (User) and 3 tutorial tables (Text, Video and Other).
Each tutorial has the columns rating_positive and rating_negative
and is linked to a user (id).
I want to select the 10 users with the most tutorials and the sum
of positive/negative ratings of their tutorials.
I tried the following query but it does not work. It returns way too many results for tutorials_count/pos/neg. How can I do it correctly?
SELECT
u.id AS user_id,
(COUNT(t.id) + COUNT(v.id) + COUNT(o.id)) AS tutorials_count,
(SUM(t.rating_positive) + SUM(v.rating_positive) + SUM(o.rating_positive)) AS pos,
(SUM(t.rating_negative) + SUM(v.rating_negative) + SUM(o.rating_negative)) AS neg
FROM
user u LEFT JOIN trick t ON u.id = t.submitter_id
LEFT JOIN video v ON u.id = v.submitter_id
LEFT JOIN other o ON u.id = o.submitter_id
GROUP BY u.id
ORDER BY tutorials_count DESC
LIMIT 10
Try making a subquery with a UNION ALL of the three tables you are interested in and then join with that:
SELECT
u.id AS user_id,
COUNT(submitter_id) AS tutorials_count,
IFNULL(SUM(rating_positive), 0) AS pos,
IFNULL(SUM(rating_negative), 0) AS neg
FROM user u
LEFT JOIN (
SELECT submitter_id, rating_positive, rating_negative FROM trick
UNION ALL
SELECT submitter_id, rating_positive, rating_negative FROM video
UNION ALL
SELECT submitter_id, rating_positive, rating_negative FROM other
) T1
ON u.id = T1.submitter_id
GROUP BY u.id
ORDER BY tutorials_count DESC
LIMIT 10
The LEFT JOINs are fine, and will perform better than unioning all three tables before performing aggregation.
The issue is that SUMmation on a LEFT JOIN means the result could be NULL, which you can't add in conjunction with the sum from the other columns. IE:
... SUM(t.rating_positive) + 1
...will return NULL if SUM(t.rating_positive) there are no supporting records, because NULL + 1 equals NULL.
You need to use COALESCE to convert these to zero for the math to work - IFNULL is an acceptable alternative, but it's MySQL specific so not likely to be portable:
SELECT u.id AS user_id,
COALESCE(COUNT(t.id), 0) + COALESCE(COUNT(v.id), 0) + COALESCE(COUNT(o.id), 0) AS tutorials_count,
COALESCE(SUM(t.rating_positive), 0) + COALESCE(SUM(v.rating_positive), 0) + COALESCE(SUM(o.rating_positive), 0) AS pos,
COALESCE(SUM(t.rating_negative), 0) + COALESCE(SUM(v.rating_negative), 0) + COALESCE(SUM(o.rating_negative), 0) AS neg
FROM USER u
LEFT JOIN trick t ON u.id = t.submitter_id
LEFT JOIN video v ON u.id = v.submitter_id
LEFT JOIN other o ON u.id = o.submitter_id
GROUP BY u.id
ORDER BY tutorials_count DESC
LIMIT 10
Related
I have a big query that I have been struggling with and tweaking for awhile.
SELECT
tastingNotes.userID, tastingNotes.beerID, tastingNotes.noteID,
tastingNotes.note, user.userName,
COALESCE(sum(tasteNoteRate.score),0) as `score`
FROM
tastingNotes
INNER JOIN `user` on tastingNotes.userID = `user`.userID
LEFT JOIN tasteNoteRate on tastingNotes.noteID = tasteNoteRate.noteID
WHERE tastingNotes.beerID = 'C5RJc0'
GROUP BY tastingNotes.noteID
ORDER BY score DESC
LIMIT 0,50;
I am using the COALESCE(sum(tasteNoteRate.score),0) to give results returned a value of zero if they do not have a score yet.
The odd behavior was that when I should have had two results it only returned one note with a score of zero.
When I then gave one a score they then both showed up, one with its score and then the second with zero.
Try
SELECT q.noteID, q.userID, q.beerID, q.note, q.score, u.userName
FROM (
SELECT n.noteID, n.userID, n.beerID, n.note, COALESCE(SUM(r.score), 0) score
FROM tastingNotes n LEFT JOIN tasteNoteRate r
ON n.noteID = r.noteID
WHERE n.beerID = 'C5RJc0'
GROUP BY n.noteID, n.userID, n.beerID, n.note
) q JOIN `user` u ON q.userID = u.userID
ORDER BY score DESC
LIMIT 50
SQLFiddle
I am trying to SELECT from one table and count from two other tables based on the rows from the first table. I tried the following code below but the rows keep coming empty.
SELECT list.id, list.title, list.body, list.poster, list.created_at, count(comments.id) as comcount, count(supports.topic_id) as supcount
FROM (
SELECT *
FROM topics
ORDER BY created_at DESC
LIMIT 5
) AS list, comments, supports
WHERE
list.id = comments.id OR
list.id = supports.topic_id
Through in this scenario table topics has only two rows and tables comments and supports have no rows in them, but yet still I should be able to get two rows with their aliases supcount and comcount each having a value 0 as an output.
I got the solution to the above but am trying something else with the solution provided which I explained in the comment area of the solution provided.
SELECT
t.id,
t.title,
t.body,
t.poster,
t.created_at,
s.supporter,
IFNULL((SELECT COUNT(*) FROM comments c WHERE c.id = t.id), 0) AS comcount,
IFNULL((SELECT COUNT(*) FROM supports s WHERE s.topic_id = t.id), 0) AS supcount,
CASE WHEN (s.supporter = "Davies Alex") THEN '1' ELSE '0' END sup,
CASE WHEN (c.commenter = "Davies Alex") THEN '1' ELSE '0' END com
FROM topics t, comments c, supports s
ORDER BY created_at DESC
This gonna be working, give a try (using subquery for just counting entries in another table is more suitable):
SELECT
id,
title,
body,
poster,
created_at,
IFNULL((SELECT COUNT(*) FROM comments c WHERE c.id = t.id), 0) AS comcount,
IFNULL((SELECT COUNT(*) FROM supports s WHERE s.topic_id = t.id), 0) AS supcount
FROM topics t
ORDER BY created_at DESC
LIMIT 5
Update for new requirement:
SELECT
t.id,
t.title,
t.body,
t.poster,
t.created_at,
s.supporter,
IFNULL(COUNT(c.id), 0) AS comcount,
IFNULL(COUNT(s.id), 0) AS supcount,
SUM(IF(s.supporter IS NOT NULL AND s.supporter = "Davies Alex", 1, 0)) > 0 AS sup,
SUM(IF(c.commenter IS NOT NULL AND c.commenter = "Davies Alex", 1, 0)) > 0 AS com
FROM topics t
LEFT JOIN comments c ON c.id = t.id
LEFT JOIN supports s ON s.topic_id = t.id
GROUP BY t.id
ORDER BY created_at DESC
In your query, you require list.id to either match comments.id or supports.topic_id. If you use an outer join, you'll be able to retrieve data from the initial table even though the joined tables don't match or contain any data.
SELECT
topics.id, topics.title, topics.body, topics.poster, list.created_at,
count(comments.id) as comcount,
count(supports.topic_id) as supcount
FROM lists
LEFT JOIN comments ON comments.id = topics.id
LEFT JOIN supports ON supports.topic_id = topics.id
ORDER BY created_at DESC
LIMIT 5
SELECT jargons.jargon as jargon,
jargons.description as description,
jargons.example as example,
IF(jargons.rootJargon != 0, (SELECT jargon FROM jargons WHERE id = jargons.rootJargon), NULL) as rootJargonName
FROM jargons
LEFT JOIN users ON users.id = jargons.addedBy
ORDER BY jargons.id DESC
I have two rows inside jargons table:
"id" "jargon" "description" "example" "rootJargonName" "rootJargon"
"2" "Child" "jd" "1" NULL 1
"1" Root" "sad" "1" NULL 0
Why does the rootJargonName returns null instead of Root on Child row?
What I was trying to do is that if rootJargon column is not equals to zero then select the row with the id that specified in the column which is not equals to zero.
It's likely a table alias issue you're seeing, but you can rewrite this using a LEFT JOIN:
SELECT J.jargon as jargon,
J.description as description,
J.example as example,
R.jargon As rootJargonName
FROM jargons as J
LEFT JOIN users as U ON U.id = J.addedBy
LEFT JOIN jargons as R ON J.rootJargon = R.ID
ORDER BY J.id DESC
If you would like to keep the IF() function, you can use the following, however I recommend using the LEFT JOIN.
SELECT J.jargon as jargon,
J.description as description,
J.example as example,
IF(J.rootJargon != 0, (SELECT jargon FROM jargons as R WHERE R.id = J.rootJargon), NULL) as rootJargonName
FROM jargons as J
LEFT JOIN users as U ON U.id = J.addedBy
ORDER BY J.id DESC
why dont you try a case
like:
SELECT jargons.jargon as jargon,
jargons.description as description,
jargons.example as example,
CASE
WHEN (jargons.rootJargon == 0) THEN NULL
WHEN (jargons.rootJargon != 0) THEN
(SELECT jargon FROM jargons WHERE id = jargons.rootJargon)
END) AS 'rootJargonName'
FROM jargons
LEFT JOIN users ON users.id = jargons.addedBy
ORDER BY jargons.id DESC
I'm probably wrong somewhere in the syntax but i think thats the idea....
How can I adjust this JOIN clause so that rows with a NULL value for the CountLocId or CountNatId columns are returned in the result?
In other words, if there is no match in the local_ads table, I still want the user's result from the nat_ads table to be returned -- and vice-versa.
SELECT u.franchise, CountLocId, TotalPrice, CountNatId, TotalNMoney, (
TotalPrice + TotalNMoney
)TotalRev
FROM users u
LEFT JOIN local_rev lr ON u.user_id = lr.user_id
LEFT JOIN (
SELECT lrr_id, COUNT( lad_id ) CountLocId, SUM( price ) TotalPrice
FROM local_ads
GROUP BY lrr_id
)la ON lr.lrr_id = la.lrr_id
LEFT JOIN nat_rev nr ON u.user_id = nr.user_id
INNER JOIN (
SELECT nrr_id, COUNT( nad_id ) CountNatId, SUM( tmoney ) TotalNMoney
FROM nat_ads
WHERE MONTH = 'April'
GROUP BY nrr_id
)na ON nr.nrr_id = na.nrr_id
WHERE lr.month = 'April'
AND franchise != 'Corporate'
ORDER BY franchise
Thanks in advance for your help!
try the following in where clause while making a left join. This will take all rows from right table with matched condition
eg.
LEFT JOIN local_rev lr ON (u.user_id = lr.user_id) or (u.user_id IS NULL)
Use this template, as it ensures that :
you have only one record per user_id (notice all subquerys have a GROUP BY user_id) so for one record on user table you have one (or none) record on subquery
independent joins (and calculated data) are not messed togeder
-
SELECT u.franchise, one.CountLocId, one.TotalPrice, two.CountNatId, two.TotalNMoney, (COALESCE(one.TotalPrice,0) + COALESCE(two.TotalNMoney,0)) TotalRev
FROM users u
LEFT JOIN (
SELECT x.user_id, sum(xORy.whatever) as TotalPrice, count(xORy.whatever) as CountLocId
FROM x -- where x is local_rev or local_ads I dont know
LEFT JOIN y on x.... = y.... -- where y is local_rev or local_ads I dont know
GROUP BY x.user_id
) as one on u.user_id = one.user_id
LEFT JOIN (
SELECT x.user_id, sum(xORy.whatever) as TotalNMoney, count(xORy.whatever) as CountNatId
FROM x -- where x is nat_rev or nat_ads I dont know
LEFT JOIN y on x.... = y.... -- where y is nat_rev or nat_ads I dont know
GROUP BY x.user_id
) as two on u.user_id = two.user_id
I have a problem im my sql query:
SELECT table_2.id, SUM(table_2.time + table_4.time + table_6.time + table_8.time + table_10.time + table_12.time) AS total_time, SUM(table_2.connects + table_4.connects + table_6.connects + table_8.connects + table_10.connects + table_12.connects) AS total_connects FROM table_2
INNER JOIN table_4 ON table_2.id = table_4.id
INNER JOIN table_6 ON table_2.id = table_6.id
INNER JOIN table_8 ON table_2.id = table_8.id
INNER JOIN table_10 ON table_2.id = table_10.id
INNER JOIN table_12 ON table_2.id = table_12.id
GROUP BY table_2.authid ORDER BY total_time DESC
Ok, I have a script that grabs the user IDS and the time they spent and then puts it in mysql tables depending on which forum they have entered. The query above seems to be working fine but only for the users that have entered all forums, because the others that haven't entered in all forums don't return any result.
The ID is always the same, it is the users ID and connects is the number of times the user enters the specific forum.
If you understand my problem and know the answear please let me know, otherwise I'll try to explain better
Thanks
I think a good way to do this may be:
SELECT id, SUM(time), SUM(connects)
FROM (
SELECT id, time, connects FROM table_2
UNION ALL
SELECT id, time, connects FROM table_4
UNION ALL
SELECT id, time, connects FROM table_6
UNION ALL
SELECT id, time, connects FROM table_8
UNION ALL
SELECT id, time, connects FROM table_10
UNION ALL
SELECT id, time, connects FROM table_12
) uniontable
GROUP BY id
Since they all have the same fields.
EDIT: And it sounds like it may not be such a strange idea to merge them into one table, especially if you want to count the total time like this ;)
EDIT 2: I'm sorry, that wasn't valid SQL at all. This should be better.
Replace the INNER JOIN by a LEFT OUTER JOIN this will get you results over all the tables. You might need to re-define the SUM probably you'll get NULL on users who haven't visited all forums...
Replace the JOIN with the LEFT JOIN:
SELECT people.id, SUM(COALESCE(table_2.time, 0) + COALESCE(table_4.time, 0) + table_6.time, 0) + COALESCE(table_8.time, 0) + COALESCE(table_10.time, 0) + COALESCE(table_12.time, 0)) AS total_time,
SUM(COALESCE(table_2.connects, 0) + COALESCE(table_4.connects, 0) + COALESCE(table_6.connects, 0) + COALESCE(table_8.connects, 0) + COALESCE(table_10.connects, 0) + COALESCE(table_12.connects, 0)) AS total_connects
FROM people p
LEFT JOIN
table_2
ON table_2.id = people.id
LEFT JOIN
table_4
ON people.id = table_4.id
LEFT JOIN
table_6
ON people.id = table_6.id
LEFT JOIN
table_8
ON people.id = table_8.id
LEFT JOIN
table_10
ON people.id = table_10.id
LEFT JOIN
table_12
ON people.id = table_12.id
GROUP BY
people.authid
ORDER BY
total_time DESC
#afonso: I think you should populate all those time tables whenever a new user is created by inserting the user with a time of 0 (just so that they're in the table). Would this be an acceptable compromise for your application just to have those users in the tables so your initial query will work?
You will need to use outer joins and you will need to use IFNULL or something similar to map the "user wasn't in forum" nulls to zeroes.
SELECT t2.id,
SUM(IFNULL(t2.time, 0) + IFNULL(t4.time, 0) +
IFNULL(t6.time, 0) + IFNULL(t8.time, 0) +
IFNULL(t10.time, 0) + IFNULL(t12.time, 0)) AS total_time,
SUM(IFNULL(t2.connects, 0) + IFNULL(t4.connects, 0) +
IFNULL(t6.connects, 0) + IFNULL(t8.connects, 0) +
IFNULL(t10.connects, 0) + IFNULL(t12.connects, 0)) AS total_connects
FROM table_2 AS t2
LEFT JOIN table_4 AS t4 ON t2.id = t4.id
LEFT JOIN table_6 AS 76 ON t2.id = t6.id
LEFT JOIN table_8 AS t8 ON t2.id = t8.id
LEFT JOIN table_10 AS t10 ON t2.id = t10.id
LEFT JOIN table_12 AS t12 ON t2.id = t12.id
GROUP BY t2.id ORDER BY total_time DESC
This almost works; it does work for all users that visit the forum for which the information is stored in table_2. To make it work for people who don't visit the 'table_2 forum', the query needs to use the table that defines users and do the outer joins with that table:
SELECT u.id,
SUM(IFNULL(t2.time, 0) + IFNULL(t4.time, 0) +
IFNULL(t6.time, 0) + IFNULL(t8.time, 0) +
IFNULL(t10.time, 0) + IFNULL(t12.time, 0)) AS total_time,
SUM(IFNULL(t2.connects, 0) + IFNULL(t4.connects, 0) +
IFNULL(t6.connects, 0) + IFNULL(t8.connects, 0) +
IFNULL(t10.connects, 0) + IFNULL(t12.connects, 0)) AS total_connects
FROM Users AS u
LEFT JOIN table_2 AS t2 ON u.id = t2.id
LEFT JOIN table_4 AS t4 ON u.id = t4.id
LEFT JOIN table_6 AS 76 ON u.id = t6.id
LEFT JOIN table_8 AS t8 ON u.id = t8.id
LEFT JOIN table_10 AS t10 ON u.id = t10.id
LEFT JOIN table_12 AS t12 ON u.id = t12.id
GROUP BY u.id ORDER BY total_time DESC
However, given the symmetry of the table_N tables, I think you should do as Spiny Norman
suggests and redesign your schema so that there is one table that stores the timing and connection values:
CREATE TABLE ForumUsage
(
ForumID INTEGER NOT NULL REFERENCES Forums,
UserID INTEGER NOT NULL REFERENCES Users,
Time INTEGER NOT NULL,
Connects INTEGER NOT NULL
);
Then you sum over the entries in that table.