mySQL reference correlation in a subquery with join - mysql

I'm working on a small social network service.
Very classically, I have 3 tables :
- table "circles_users" : all users that belong to the same "circle"
- table "friends" : friendship relation between users
- table "checkin" : is a user "checkin" somewhere or not
Here the structure of the database : http://sqlfiddle.com/#!2/27888/1
I would like to ask my database to give me :
all users from a specific "circle" with for each of them :
- the number of friends this user has in common with user id = 3
- if this user is checkin or not
Here what I'm trying to do :
SELECT a.uid,
(
SELECT COUNT(*)
FROM (SELECT IF (uid1 = 3, uid2, uid1) AS cf FROM friends WHERE (friends.uid1 = 3 OR friends.uid2 = 3 )) as b
JOIN friends ON ((friends.uid1 = b.cf AND friends.uid2 = a.uid) OR (friends.uid1 = a.uid AND friends.uid2 = b.cf))
) as common_friends,
checkin.status as checkin_status
FROM
(SELECT circles_users.uid FROM circles_users WHERE circles_users.circlename = 'circle_A') as a
LEFT JOIN checkin ON checkin.uid = a.uid
I get this error message : Unknown column 'a.uid' in 'on clause'
It has been now 2 days that I'm trying to fix this unsuccessfully.
It seems that it is not possible to reference correlation name in my subquery.
For instance, if I replace a.uid in the subquery by a specific uid (for instance let's say '4'), I don't get any error. But of course, the result is false...
Is there someone who could help me ?
That would be very nice :)
OTHER OPTION TO FOLLOW ?
Another option would be to pass the "common_friends" subquery as a join.
I tried to do something like this :
SELECT a.uid,
c.cnt as common_friends,
checkin.status as checkin_status
FROM
(SELECT circles_users.uid as uid FROM circles_users WHERE circles_users.circlename = 'circle_A') as a
LEFT JOIN
(
SELECT DISTINCT COUNT(*) as cnt
FROM (SELECT IF (uid1 = 3, uid2, uid1) AS cf FROM friends WHERE (friends.uid1 = 3 OR friends.uid2 = 3 )) as b
JOIN friends ON ((friends.uid1 = b.cf AND friends.uid2 = a.uid) OR (friends.uid1 = a.uid AND friends.uid2 = b.cf))
) as c ON 1=1
LEFT JOIN checkin ON checkin.uid = a.uid
But again : I get this error message : Unknown column 'a.uid' in 'on clause'Anyway, do you think this version would be easier to handle and would open new possibilities to resolve my problem ?
If you want to play with my queries : (thanks to #zundarz)
http://sqlfiddle.com/#!2/27888/1

Rewrote a query based on the info you give and what you are looking for.
SELECT cu.uid, COUNT(f.uid1) common_friends, c.status
FROM circles_users cu
LEFT JOIN friends f
ON (f.uid1 = cu.uid OR f.uid2 = cu.uid)
AND f.status = "on"
AND IF (f.uid1 = cu.uid, f.uid2, f.uid1) IN (
SELECT IF (uid1 = 3, uid2, uid1)
FROM friends
WHERE status = "on"
AND (friends.uid1 = 3 OR friends.uid2 = 3)
)
LEFT JOIN checkin c
ON c.uid = cu.uid AND c.status = "on"
WHERE cu.circlename = "circle_A"
GROUP BY cu.uid
Example sqlFiddle

Related

How to select multiple values in a subquery but have each value assigned to matching parent select value?

Sorry for the wordy mess of a question. I have a query that draws data from the User table and I want to pull the completionstate from the course_modules_completion table for each activity in a course per user and course. I'm having trouble linking multiple values from various tables to a user per row.
I was able to obtain the info I wanted from the User table just fine. I then join multiple tables in order to be able to pull the completionstate field from the course_modules_completion table. What I need to do is pull the value of completionstate corresponding to each activity in a course for every user.
SELECT u.username AS 'ID'
,u.firstname AS 'Names'
,u.lastname AS 'Lastnames'
,(SELECT completionstate FROM prefix_course_modules_completion cmc
INNER JOIN prefix_user u ON cmc.userid = u.id
INNER JOIN prefix_course_modules cm ON cmc.coursemoduleid = cm.id
INNER JOIN prefix_course c ON cm.course = c.id
INNER JOIN prefix_resource r ON c.id = r.course
WHERE (r.id = 6) AND (c.id = 5)
) AS 'Activity 1'
FROM prefix_user u LEFT JOIN prefix_grade_grades g ON u.id = g.userid
INNER JOIN prefix_course_modules_completion cmc ON cmc.userid = u.id
INNER JOIN prefix_course_modules cm ON cmc.coursemoduleid = cm.id
INNER JOIN prefix_course c ON cm.course = c.id
INNER JOIN prefix_resource r ON c.id = r.course
WHERE (u.id > 7) AND (u.firstaccess > 0) AND (c.id = 5)
GROUP BY u.username
ORDER BY u.firstname ASC
What I expected was something like this:
ID - Name - Last name - Email - Activity 1 - Activity 2 - Test
382 - John - Johnson - email#e.com - Seen - Not seen - Failed
17 - Mark - Markson - email2#e.com - Seen - Seen - Passed
Instead what I'm getting are multiple rows of the same user with the values wanted or the same value wanted for all users as if it wasn't making any distinction about the user. As you can see in the code above, I tried with a subquery just for the completionstate value but it's returning multiple rows and thus, an error.
Furthermore, I want to show the values from the completionstate column as text. There are four values in that column: 0, 1, 2, 3 and what I'd like to do is transform each into "Not seen", "Seen", "Completed and passed" and "Completed and failed" respectively.
Because I'm a beginner in SQL I'm having trouble identifying if my issues are because of bad logic, bad coding or both.
Edit: Pruned the code a little to make it simpler for this question.
What you're trying to do is a kind of pivot, so you can do it without the correlated subqueries.
SELECT u.username AS 'ID'
,u.firstname AS 'Names'
,u.lastname AS 'Lastnames'
,u.email AS 'Email'
,c.fullname AS 'Course'
,(LEFT(g.finalgrade,2)) AS 'Final grade'
,ELT(MAX(IF(r.id = 6, completion_state, NULL))+1,
'Not seen', 'Seen', 'Completed and passed', 'Completed and failed') AS `Activity 1`
,ELT(MAX(IF(r.id = 7, completion_state, NULL))+1,
'Not seen', 'Seen', 'Completed and passed', 'Completed and failed') AS `Activity 2`
,ELT(MAX(IF(r.id = 8, completion_state, NULL))+1,
'Not seen', 'Seen', 'Completed and passed', 'Completed and failed') AS `Activity 3`
FROM prefix_user u LEFT JOIN prefix_grade_grades g ON u.id = g.userid
INNER JOIN prefix_course_modules_completion cmc ON cmc.userid = u.id
INNER JOIN prefix_course_modules cm ON cmc.coursemoduleid = cm.id
INNER JOIN prefix_course c ON cm.course = c.id
INNER JOIN prefix_resource r ON c.id = r.course
WHERE (u.id > 7) AND (u.firstaccess > 0) AND (c.id = 5)
GROUP BY u.id
ORDER BY u.firstname ASC
Yuo should also group by u.id rather than u.username. This will allow you to select other columns that are functionally dependent on this primary key without using aggregation functions.

SQL query returns column with data from another column

I have a table Notices connected to tables Likes and Comments. When I return the notices for a user I also create columns: number_of_likes, number_of_comments and liked_by_me. The query is working correctly when the user making the query hasn't liked a notice (liked_by_me = 0) . But if they have (liked_by_me = 1) the value I get for number_of_likes is wrong and is the same as number_of_comments.
Example:
1)
- liked by me = false
- likes = 1
- comments = 5
Returned values:
- liked_by_me = 0
- number_of_likes = 1
- number_of_comments = 5
2)
- liked by me = true
- likes = 2
- comments = 5
Returned values:
- liked_by_me = 1
- number_of_likes = 5
- number_of_comments = 5
Here is the query I am using:
SELECT notices.*
, count(comment.id) as number_of_comments
, count(like1.user_id) as number_of_likes
, like2.user_id IS NOT NULL AS liked_by_me
, boards.name as board_name
FROM notices
LEFT JOIN comments as comment
ON (comment.notice_id = notices.id)
LEFT JOIN likes as like1
ON (like1.notice_id = notices.id)
LEFT JOIN likes as like2
ON (like2.notice_id = notices.id
AND like2.user_id = $1)
LEFT JOIN boards
ON (boards.id = notices.board_id)
LEFT OUTER JOIN board_users
ON (board_users.board_id = notices.board_id)
WHERE board_users.user_id = $1
GROUP BY notices.id
, boards.name
, like2.user_id
, userId
Any help would be appreciated. I have been on this for hours and I don't think I will be able to find the problem.
Thanks!
Solution:
Here is the working query
SELECT notices.*,
(SELECT COUNT(user_id) from likes WHERE likes.notice_id = notices.id) AS number_of_likes,
(SELECT user_id IS NOT NULL from likes WHERE likes.notice_id = notices.id AND likes.user_id = $1) AS liked_by_me,
count(comments.id) as number_of_comments, boards.name as board_name
FROM notices LEFT JOIN comments ON (comments.notice_id = notices.id)
LEFT JOIN boards ON (boards.id = notices.board_id)
LEFT OUTER JOIN board_users ON (board_users.board_id = notices.board_id)
WHERE board_users.user_id = $1 GROUP BY notices.id, boards.name", user);
You will have to use subeselects.
Excellent article on this problem: The GROUPing pitfall
TL;DR: Basically, you have to realize, that all your comments and likes are being multiplicated by one another. Try to display the result of the query without the group clause to see, that duplicate likes/comments are being counted.
EDIT: I didn't test this, but it's how the query might look:
(that is if user can only like one notice once, otherwise you would have to group current user likes too)
SELECT
notices.*,
comments.number_of_comments,
likes.number_of_likes
current_user_likes.user_id IS NOT NULL AS liked_by_me
boards.name AS board_name
FROM notices
LEFT JOIN (
SELECT
COUNT(*) AS number_of_comments,
notice_id
FROM comments
GROUP BY notice_id
) AS comments ON comments.notice_id = notices.id
LEFT JOIN (
SELECT
COUNT(*) AS number_of_likes,
notice_id
FROM likes
GROUP BY notice_id
) AS likes ON likes.notice_id = notices.id
LEFT JOIN likes AS current_user_likes
ON current_user_likes.notice_id = notices.id
AND current_user_likes.user_id = $1
LEFT JOIN boards ON boards.id = notices.board_id
INNER JOIN board_users
ON board_users.board_id = notices.board_id
AND board_users.user_id = $1;

MySQL Unknown column u.id in on clause but column exists

I've got this tables:
users {id = int, name = varchar, pwd = char}
company {id = int, token = char, name = varchar}
user_company {id = int, id_usr = int, id_company = int, name_usr = varchar}
I'm trying to get the pwd from users and find out if the user is in the company with the token X from user_company
When I use this query
SELECT u.pwd,h.name_usr
FROM users u, company c
LEFT JOIN users_company h ON c.id = h.id_company AND u.id = h.id_usr
WHERE u.user_name = 'user#domain.com'
AND c.token = 'f30ea71e7a9d9f0a6710bb46537c0bde'
LIMIT 1;
I keep on getting 'Unknown column u.id in on clause' although u.id exists. What am I doing wrong? Thanks
SELECT u.pwd,h.nombre_usr
FROM company c LEFT JOIN users_company h ON c.id = h.id_company
LeFt join users u on u.id = h.id_usr
WHERE u.user_name = 'user#domain.com'
AND c.token = 'f30ea71e7a9d9f0a6710bb46537c0bde'
LIMIT 1;
The Error was beacause of Join Query . The way it was written is wrong. We can not apply join two tables with single table at same time.
sI've got the solution. Thank you all for the help.
SELECT u.pwd,h.name_usr
FROM users u
LEFT JOIN company c ON c.token = 'f30ea71e7a9d9f0a6710bb46537c0bde'
LEFT JOIN users_company h ON c.id = h.id_company AND u.id = h.id_usr
WHERE u.user_name = 'user#domain.com'
LIMIT 1;

join clause not showing all results

this is a very weird issue.
I have 3 tables, all with key relations on agent_id.
Now this query :
select DATE_FORMAT(created_on,"%d/%m/%y") as date
from logs
where agent_id = "18"
and log_status="1"
and date_format(created_on,"%m/%y") = "12/12"
group by YEAR(created_on), MONTH(created_on), DAY(created_on) desc
Does exactly what it suppoused to, it returns 3 rows of :
date
31/12/12
30/12/12
26/12/12
However when I join 2 other tables it excludes the date of 31/12/12. This is the query :
select DATE_FORMAT(l.created_on,"%d/%m/%y") as date
from logs l
join logs_rewards lr on l.id = lr.related_log_id
join agents a on a.id = l.agent_id
where l.log_status = "1"
and DATE_FORMAT(l.created_on,"%m/%y") = '12/12'
AND l.agent_id = '18'
group by YEAR(l.created_on), MONTH(l.created_on), DAY(l.created_on) desc
This returns :
date
30/12/12
26/12/12
And I can't figure out why that happens. Any help?
I've tried changing AND l.agent_id = '18' to AND a.id = '18' but same result.
This is because data are not mapped in all the 3 tables.
Below query will work
select DATE_FORMAT(l.created_on,"%d/%m/%y") as date
from logs l
left join logs_rewards lr on l.id = lr.related_log_id
left join agents a on a.id = l.agent_id
where l.log_status = "1"
and DATE_FORMAT(l.created_on,"%m/%y") = '12/12'
AND l.agent_id = '18'
group by YEAR(l.created_on), MONTH(l.created_on), DAY(l.created_on) desc
Maybe you want to replace JOIN with LEFT JOIN to get all results from logs.

SQL calculating time between assignments

I have to write an SQL statement which contain a field that contain two different values consecutively but in the way I have wrote it, it return always null because it is interpreted as having the two value in the same time!
My conditions should be : (ci.field = 'Group' and ci.oldString = 'Triage' ) and (ci.field='assignee' and ci.newString is not NULL)
That means calculate time between: when the issue is assigned to group named Triage and when the issue is assigned to a person.
How can I fix it?
My SQL statement:
select TIMEDIFF(a.created,b.created)
from
(select g.created, g.issueid as groupid1
from changegroup g
join changeitem ci on (ci.groupid = g.id)
join jiraissue ji on (ji.id = g.issueid)
join project p on (p.id = ji.project)
join priority pr on (pr.id = ji.priority)
where ci.field = 'Group'
and ci.oldString = 'Triage'
and ci.field='assignee'
and ci.newString is not NULL
and p.pname = 'Test'
and pr.pname='P1'
and ji.created between '2011-08-11 14:01:00' and '2011-08-12 14:11:00'
) a
left join (
select ji.created, ji.id as groupid2
from jiraissue ji
join changegroup g on (g.issueid = ji.id)
join project p on (p.id = ji.project)
where p.pname = 'Test'
and ji.created between '2011-08-11 14:01:00' and '2011-08-12 14:11:00'
) b ON (a.groupid1 = b.groupid2);
This is the table from which I should retrieve data
See my comment about the quality of your question but a hint at how to solve this goes like (assuming you can make sure this doesn't create 1-n joins)
select groupid_orsomething_else, TIMEDIFF(a.created, b.created)
from yourtable
left join
(select groupid_orsomething_else, created
from yourtable
where field = 'Group' and oldstring is 'Triage'
) a
on a.groupid_orsomething_else = yourtable.groupid_orsomething_else
left join
(select groupid_orsomething_else, created
from yourtable
where field = 'assignee' and oldstring is null) b
on b.groupid_orsomething_else = yourtable.groupid_orsomething_else