Group by with join and subquery cannot see parent table alias - mysql

I am building a question / answer panel.
Tables:
users - typical with id, name, etc...,
Replies - user's replies,
Ratings - another user's ratings (1 or -1) related to a reply, every rating got a row
I need to get one row per user with related table having only 1 row for each user who rated his answer once. He got 20 points for each one. I also need when the rater sent 2 or more ratings for 60 points (not included in example) , but if I have solution I can extend it.
Problem with the following query: subquery cant see "user" alias, it says user.id is not found.
Thanks in advance!
SELECT user.*, (
SELECT COUNT(*) * 20
FROM (
SELECT SUM(rating_value) AS rv
FROM Ratings
LEFT JOIN Replies ON Replies.id = Ratings.replyId
WHERE rating_value > 0
AND Replies.userId = user.id
GROUP BY Ratings.userId
HAVING (rv = 1)
) ss
) AS points
FROM users user

Related

MySQL getting count of a related table

My SQL's more than a little rusty, and I'm having trouble getting this to work, so any assistance would be greatly appreciated.
I've got three tables:
sessions
---------------
id
session_visits
---------------
id | session_id
searches
---------------
id | session_visit_id
And I want to get a list of all sessions with the total visits and searches for each sessions, which is linked by the session_visits table. I can get the visits fine, but am having trouble getting the total of searches for each session too.
So far I've got
SELECT *,(SELECT Count(*)
FROM session_visits
WHERE session_id = sessions.id) AS num_visits,
(SELECT Count(*)
FROM searches
WHERE session_visit_id = (SELECT * FROM session_visits
WHERE session_id = sessions.id)) AS total_searches
FROM sessions
Which is failing on every count! Am I going about this the right way or am I fundamentally doing it wrong?
You can do this in one query, by joining the 3 tables together, and then use aggregates COUNT DISTINCT (to eliminate duplicated) and COUNT to get the total number of rows for the child and grandchild rows respectively, grouped by the Sessionid.
SELECT s.id AS SessionId, COUNT(DISTINCT sv.id) AS SessionVisits, COUNT(sr.ID) AS Searches
FROM sessions s
LEFT JOIN session_visits sv
ON s.id = sv.session_id
LEFT JOIN searches sr
ON sr.session_visit_id = sv.id
GROUP BY s.id;
SqlFiddle here
(Edit : Changed to left outer joins to handle scenarios where there are no visits for session, or no searches per visit)
query generates error because of column name is not mentioned in query
Operand should contain 1 column
and try with IN

MySQL - 3 tables, is this complex join even possible?

I have three tables: users, groups and relation.
Table users with fields: usrID, usrName, usrPass, usrPts
Table groups with fields: grpID, grpName, grpMinPts
Table relation with fields: uID, gID
User can be placed in group in two ways:
if collect group minimal number of points (users.usrPts > group.grpMinPts ORDER BY group.grpMinPts DSC LIMIT 1)
if his relation to the group is manually added in relation tables (user ID provided as uID, as well as group ID provided as gID in table named relation)
Can I create one single query, to determine for every user (or one specific), which group he belongs, but, manual relation (using relation table) should have higher priority than usrPts compared to grpMinPts? Also, I do not want to have one user shown twice (to show his real group by points, but related group also)...
Thanks in advance! :) I tried:
SELECT * FROM users LEFT JOIN (relation LEFT JOIN groups ON (relation.gID = groups.grpID) ON users.usrID = relation.uID
Using this I managed to extract specified relations (from relation table), but, I have no idea how to include user points, respecting above mentioned priority (specified first). I know how to do this in a few separated queries in php, that is simple, but I am curious, can it be done using one single query?
EDIT TO ADD:
Thanks to really educational technique using coalesce #GordonLinoff provided, I managed to make this query to work as I expected. So, here it goes:
SELECT o.usrID, o.usrName, o.usrPass, o.usrPts, t.grpID, t.grpName
FROM (
SELECT u.*, COALESCE(relationgroupid,groupid) AS thegroupid
FROM (
SELECT u.*, (
SELECT grpID
FROM groups g
WHERE u.usrPts > g.grpMinPts
ORDER BY g.grpMinPts DESC
LIMIT 1
) AS groupid, (
SELECT grpUID
FROM relation r
WHERE r.userUID = u.usrID
) AS relationgroupid
FROM users u
)u
)o
JOIN groups t ON t.grpID = o.thegroupid
Also, if you are wondering, like I did, is this approach faster or slower than doing three queries and processing in php, the answer is that this is slightly faster way. Average time of this query execution and showing results on a webpage is 14 ms. Three simple queries, processing in php and showing results on a webpage took 21 ms. Average is based on 10 cases, average execution time was, really, a constant time.
Here is an approach that uses correlated subqueries to get each of the values. It then chooses the appropriate one using the precedence rule that if the relations exist use that one, otherwise use the one from the groups table:
select u.*,
coalesce(relationgroupid, groupid) as thegroupid
from (select u.*,
(select grpid from groups g where u.usrPts > g.grpMinPts order by g.grpMinPts desc limit 1
) as groupid,
(select gid from relations r where r.userId = u.userId
) as relationgroupid
from users u
) u
Try something like this
select user.name, group.name
from group
join relation on relation.gid = group.gid
join user on user.uid = relation.uid
union
select user.name, g1.name
from group g1
join group g2 on g2.minpts > g1.minpts
join user on user.pts between g1.minpts and g2.minpts

Combining 2 tables- Subquery returns more than 1 row

When i join 2 tables i am getting an error
select * from picture where id=(SELECT id FROM user where age= 40 )
#1242 - Subquery returns more than 1 row
What is the best way to join these 2 large data tables? I want to have all the results
Replace = by IN
select * from picture where id in (select id from user where age = 40)
Note, this is only "semi-joining" the two tables, i.e. you will only get pictures as results, not a picture-user relation. If you're actually trying to do the latter, then you'll have to inner join the two tables:
select * from picture p
join user u on p.id = u.id -- are you sure about this join condition?
where u.age = 40
In the where clause, when you assign a value to id, SQL expects a single value. The problem is that the second query SELECT id FROM user where age= 40 returns more than one row.
What you can do is
select *
from picture
where id in
(select id from user where age = 40)
or
select *
from picture p, user u
where p.id = u.id
and u.age = 40
Try using 'IN' instead of '='
select * from picture where id IN (SELECT id FROM user where age= 40 )
From what Im guessing out of what Im seeing,
You have a "picture" table and a "user" table.
Table names should be plurals. Make it "pictures" and "users".
Define your relationships first. In your case, one user has_many pictures and one picture belongs_to one user.(One picture [on a peaceful world] wont belong to many users).
In your pictures table, have an "user_id" column and populate that column with the appropriate user_ids.
Now,
You want all the pictures of users whose ages are exactly 40.
You can do a,
SELECT *
FROM pictures
WHERE user_id IN (SELECT user_id
FROM users
WHERE user_age = 40)
Was that your requirement?. Do I make any sense?

MySQL nested query counting

A bit of background info; this is an application that allows users to created challenges and then vote on those challenges (bog standard userX-vs-userY type application).
The end goal here is to get a list of 5 users sorted by the number of challenges they have won, to create a type of leaderboard. A challenge is won by a user if it's status = expired and the user has > 50 votes for that challenge (challenges expire after 100 votes in total).
I'll simplify things a bit here, but essentially there are three tables:
users
id
username
...
challenges
id
issued_to
issued_by
status
challenges_votes
id
challenge_id
user_id
voted_for
So far I have an inner query which looks like:
SELECT `challenges`.`id`
FROM `challenges_votes`
LEFT JOIN `challenges` ON (`challenges`.`id` = `challenges_votes`.`challenge_id`)
WHERE `voted_for` = 1
WHERE `challenges`.`status` = 'expired'
GROUP BY `challenges`.`id`
HAVING COUNT(`challenges_votes`.`id`) > 50
Which in this example would return challenge IDs that have expired and where the user with ID 1 has > 50 votes for.
What I need to do is count the number of rows returned here, apply it to each user from the users table, order this by the number of rows returned and limit it to 5.
To this end I have the following query:
SELECT `users`.`id`, `users`.`username`, COUNT(*) AS challenges_won
FROM (
SELECT `challenges`.`id`
FROM `challenges_votes`
LEFT JOIN `challenges` ON (`challenges`.`id` = `challenges_votes`.`challenge_id`)
WHERE `voted_for` = 1
GROUP BY `challenges`.`id`
HAVING COUNT(`challenges_votes`.`id`) > 0
) AS challenges_won, `users`
GROUP BY `users`.`id`
ORDER BY challenges_won
LIMIT 5
Which is kinda getting there but of course the voted_for user ID here is always 1. Is this even the right way to go about this type of query? Can anyone shed any light on how I should be doing it?
Thanks!
I guess the following script will solve your problem:
-- get the number of chalenges won by each user and return top 5
SELECT usr.id, usr.username, COUNT(*) AS challenges_won
FROM users usr
JOIN (
SELECT vot.challenge_id, vot.voted_for
FROM challenges_votes vot
WHERE vot.challenge_id IN ( -- is this check really necessary?
SELECT cha.id -- if any user is voted 51 he wins, so
FROM challenges cha -- why wait another 49 votes that won't
WHERE cha.status = 'expired' -- change the result?
) --
GROUP BY vot.challenge_id
HAVING COUNT(*) > 50
) aux ON (aux.voted_for = usr.id)
GROUP BY usr.id, usr.username
ORDER BY achallenges_won DESC LIMIT 5;
Please allow me to propose a small consideration to the condition to close a challenge: if any user wins after 51 votes, why is it necessary to wait another 49 votes that will not change the result? If this constraint can be dropped, you won't have to check challenges table and this can improve the query performance -- but, it can worsen too, you can only tell after testing with your actual database.

MySQL summing points from various tables

I am trying to create an SQL statement that will essentially calculate points due to a variety of conditions. For example, I have tables: users, followers, pictures
followers and pictures have a user_id, and users has a column called "about"
I want to do some sort of select where I get the sum of:
1) If users.about is not empty, that is worth 100 points
2) If the user owns at least one pictures.type = "something", that is worth 50 points
3) For every follower that is associated with that user, give 20 points each
So this SQL statement should select the sum of those three calculations. After, I want to use this SQL to create a view that essentially lists the user_id and its associated score.
I have tried various permutations but nothing is working. How would I do this? Thanks!
Here is one way:
SELECT sum(points) FROM (
SELECT 100 points FROM users WHERE about <> '' AND user_id = ####
UNION
SELECT 50 points FROM users WHERE EXISTS (SELECT * FROM pictures WHERE user_id = ####)
UNION
SELECT 20 points FROM followers WHERE user_id = ####
)
Here is a second way:
SELECT IF(about <> '', 100, 0)
+ IF(EXISTS (SELECT * FROM pictures WHERE pictures.user_id = users.user_id), 50, 0)
+ (SELECT SUM(20) FROM followers WHERE followers.user_id = users.user_id) points
FROM users
WHERE user_id = ####