mysql JOINS query - mysql

I have 3 tables, usersonline, mobsters, and gangs.
I'm trying to create a query for the ONLINE USERS page that will run through the records in the usersonline table, linking the IDs to the relevant record in the mobsters table. If the mobster is in a gang, I want it to also grab the record from the gangs table.
Here is my query so far (this doesn't work):
SELECT m.*, g.* FROM usersonline u
INNER JOIN mobsters m ON u.userID = m.id
JOIN gangs g ON g.id = m.gang
ORDER BY u.timestamp DESC
What kind of JOINs should I be using? Is this query even correct?
The gang table is optional but it should always have a record in the mobsters that relates to the usersonline.
Note: My actual query doesn't grab all fields from each table, just did it this way for simplification.

You need a LEFT Join on the Gangs table since "The gang table is optional"
e.g.
SELECT m.*, g.* FROM usersonline u
INNER JOIN mobsters m ON u.userID = m.id
LEFT JOIN gangs g ON g.id = m.gang
ORDER BY u.timestamp DESC

Related

How can I join 4 table?

I have 4 tables, three are many to many relationship:
Student(StudID,GroupId,Name,....)
Lesson(LessID,LessonName,Mark)
StudentLesson(StudID,LessID)
and the relationship between student and Group is One to Many
Student(StudID,Name,....)
Group(GroupId,GroupNumber)
What I want is how select Name, LessonName, Mark, GroupNumber
select S.Name, L.LessonName, L.Mark, G.GroupNumber from Student s
join StudentLesson SL on SL.StudId = S.StudId
join Lesson L on SL.LessID = L.LessID
Join Group G on G.GroupId = S.GroupId
I think the error in this line Join Group G on G.GroupId=S.GroupId, because when I omit it, it works between many to many but between one to many it didn't work.
group is a reserved word, so it needs to be quoted. In MySQL, you can use backticks:
select S.Name, L.LessonName, L.Mark, G.GroupNumber
from Student S
join StudentLesson SL on SL.StudId = S.StudId
join Lesson L on SL.LessID = L.LessID
Join `Group` G on G.GroupId = S.GroupId
Based on Comments: the query is fine; you lack data that matches the results you're after.
There are no students with a groupID
There are no students with a groupID matching GROUPID in the group table.
To prove this out you could simply make the last join a LEFT Join provided you have no where clause with limits on Group.
FROM:
select S.Name,L.LessonName,L.Mark,G.GroupNumber from Student s
join StudentLesson SL on SL.StudId=S.StudId
join Lesson L on SL.LessID =L.LessID
Join Group G on G.GroupId=S.GroupId
TO:
SELECT S.Name, L.LessonName, L.Mark, G.GroupNumber
FROM Student s
INNER JOIN StudentLesson SL on SL.StudId=S.StudId
INNER JOIN Lesson L on SL.LessID =L.LessID
LEFT JOIN Group G on G.GroupId=S.GroupId
This will show you all students w/ lessons and groupNumber if the groupID's match; but i'm betting they will all be NULL.
So are you after all students regardless if they have lessons or groups if so your inner joins should be left. If you're only after students that have lessons and belong to groups then they all need to be inner joins. Just depends on what you're after!
Left join will say include all records from the prior data joins, and only those that match from this join (to group in the example)

Combing a select statement with a count on a different table

I have two tables: users and lessons.
Currently I select all the users using:
SELECT * FROM users WHERE user_type = 1;
Then use PHP to loop through them and count their number of lessons using
SELECT COUNT(*) FROM lessons WHERE student_id=users.user_id;
I would like to combine this into a single query and I'm pretty sure this is possible with a JOIN but it is beyond my basic SQL knowledge.
Do this all in one query. If you want a count per user:
select u.user_id, count(l.student_id)
from users u left join
lessons l
on u.user_id = l.student_id
where u.user_type = 1
group by u.user_id
You can use a join, or a correlated subquery:
select
u.*,
(select count(*) from lessons l where l.student_id = u.user_id) no_lessons
from users u
The upside of the subquery solution is that it does not require aggregation in the outer query. With an index on lessons(student_id), this should be an efficient option.
You can write the following query for this:
SELECT * FROM users u LEFT JOIN lessons l ON u.user_id=l.student_id WHERE u.user_type=1 GROUP BY u.user_id
This will join both the tables (users and lessons) based on the id of both tables and the GROUP BY clause will group all the records of same id as you just want the number of lessons per user.

MYSQL count group by with a left join where there is an entry in a third table

I have a table of groups with details of groups that my users can join and a separate table of group members to show which users have joined which groups. The group_members table just has a column for group_id and a column for user_id. If there is a row with group_id = 3 AND user_id = 10 then we know user 10 is a member of group 3.
I have the following mysql statement that works well to return the details of the groups including the number of members each group has depending upon a couple of conditions in the WHERE clause.
SELECT groups.*, COUNT(group_members.group_id) AS member_count
FROM groups LEFT JOIN
group_members
ON groups.group_id = group_members.group_id
WHERE groups.deleted = 0 AND
groups.trainer_id = ' .$trainer_id .'
GROUP BY groups.group_id
ORDER BY groups.group_name'
The problem is though that users can be deleted in and aren't always removed from the group_members table when this happens. This means that users can show up in the count even though they are no longer actually in the system in a third users table.
So I want to only include group members in the count if they are also present in the users table. I thought I could do this with an INNER JOIN between the group members and users tables. Something like this...
SELECT groups.*, COUNT(users.user_id) AS client_count
FROM groups LEFT JOIN
group_members
ON groups.group_id = group_members.group_id INNER JOIN
users
ON group_members.user_id = users.user_id
WHERE groups.deleted = 0 AND groups.trainer_id = ' .$trainer_id .'
GROUP BY groups.group_id
ORDER BY groups.group_name
The trouble with this is that when a group has no members the group is not showing up in the results. I guess because it is not able to join to the users table.
If anyone could explain to me how I can achieve what I am looking to do I'd be very grateful.
A LEFT JOIN to users` should really fix your problem:
SELECT g.*, COUNT(u.user_id) AS client_count
FROM groups g LEFT JOIN
group_members gm
ON g.group_id = gm.group_id LEFT JOIN
users u
ON gm.user_id = u.user_id
WHERE g.deleted = 0 AND g.trainer_id = ' .$trainer_id .'
GROUP BY g.group_id
ORDER BY g.group_name;
If a group is not being returned, then it does not meet the WHERE filtering conditions. All groups meeting those conditions should be returned if LEFT JOIN is used for both joins.
I would also strongly advise you to use parameters rather than munging query strings, when you call queries from an application language.
You can just do a LEFT JOIN instead of INNER on the users table. The COUNT function skips null entries so your last query should work.

SELECT, 2 counts from 2nd table, RIGHT JOIN on 3rd

I'm trying to gather "followers" for a specific user (#1 in this code).
I'm doing my primary select from followers as the column following will have user #1 and followers.userid will have the userid of the person doing the following.
Next I'm trying to get a count of records from the experiences that have the user id of the follower (how many experiences does this follower have?)
Next, the follower will have rated each experience (1-5 stars) and I want to sum those ratings (experiences.stars) to get an average rating of all experiences.
Lastly, I want to join the followers user record from the users table.
I should end up with
userid, jobs, stars, * from users
SELECT * FROM followers AS F
RIGHT JOIN
(SELECT count(id) FROM experiences AS M WHERE M.userid = F.userid) AS jobs
RIGHT JOIN
(SELECT sum(stars) FROM experiences AS S WHERE S.userid = F.userid) AS stars
RIGHT JOIN
users AS U ON U.userid = F.userid
WHERE F.following = 1 /* #1 = the user # I want the follwers of/for */
I've also tried:
SELECT * FROM followers AS F,
(SELECT count(id) FROM experiences AS M WHERE M.userid = F.userid) AS jobs,
(SELECT sum(stars) FROM experiences AS S WHERE S.userid = F.userid) AS stars
RIGHT JOIN
users AS U ON U.userid = F.userid
WHERE F.following = 1 /* #1 = the user # I want the follwers of/for */
In cPanel, I'm getting an error that I have syntax error at WHERE F.userid in both statements.
A) what am I missing and B) is there a better way to do this?
It seems to me, the query would be easier to follow like so:
SELECT *
FROM followers AS F
LEFT JOIN users AS U ON U.userid = F.userid
LEFT JOIN (SELECT count(id) FROM experiences AS M WHERE M.userid = **F.userid)** AS jobs
LEFT JOIN (SELECT sum(stars) FROM experiences AS S WHERE S.userid = F.userid) AS stars
WHERE F.following = 1 /* #1 = the user # I want the follwers of/for */
;
All those RIGHT JOINs you originally had would only give you followers that had both "types" of experiences.
Also, correlated subqueries can be expensive (and you didn't need two of them...actually, you didn't even need subqueries), so I'd also rework it like so....
SELECT F.*, U.*, count(x.id), sum(x.stars)
FROM followers AS F
LEFT JOIN users AS U ON U.userid = F.userid
LEFT JOIN experiences AS x ON F.userid = x.user_id
WHERE F.following = 1
GROUP BY [all the fields selected in F and U, or just F.userid if server settings allow]
;
Seems like there's a couple of ON clauses missing.
I know that RIGHT outer joins are supported, but why would we write it that way, and not write it as LEFT outer joins. (We typically reserve RIGHT joins to the towers of academia.)
And it's well past time to ditch the old-school comma syntax for join operations. (Yes, it's still supported for backwards compatibility with existing statements. But new development should use the newer JOIN syntax.)
The condition requiring a non-NULL value of F.following would effectively negate the "outerness" of the join, rendering it equivalent to an INNER join. For clarity, we should either write that as an inner JOIN, or if we want an outer join, we should relocate that condition to the appropriate ON clause.
Also, best practice is to qualify all column references; even when they aren't ambiguous to the optimizer, it makes it easier on the future reader (so the future reader doesn't have to confirm which table contains the id column), as well as protecting the query from throwing "ambiguous column" errors in the future if a column named id is added to another table used by the query.
Also, it's not valid to reference columns from F in the outer query inside inline view queries. We can use a correlated subquery, but not as an inline view.
The specification isn't clear. Example data and sample of expected output would go a long ways to clarifying the requirements.
If we want to use correlated subqueries that return a single row, with a single column, we can put those in the SELECT list ...
SELECT f.*
, u.*
, ( SELECT COUNT(m.id)
FROM experiences m
WHERE m.userid = f.userid
) AS jobs
, ( SELECT SUM(s.stars)
FROM experiences s
WHERE s.userid = f.userid
) AS stars
FROM followers f
LEFT
JOIN users u
ON u.userid = f.userid
WHERE f.following = 1 /* #1 = the user # I want the follwers of/for */
ORDER BY ...
We could get an equivalent result using inline views, but that would look quite different.
I would tend to do the aggregation inside the inline view, something along the lines of this:
SELECT f.*
, u.*
, IFNULL(e.jobs,0) AS jobs
, IFNULL(e.stars,0) AS stars
FROM followers f
LEFT
JOIN users u
ON u.userid = f.userid
LEFT
JOIN ( SELECT ef.userid
, COUNT(ee.id) AS jobs
, SUM(ee.stars) AS stars
FROM followers ef
JOIN experiences ee
ON ee.userid = ef.userid
WHERE ef.following = 1 /* argument */
GROUP BY ef.userid
) e
ON e.userid = f.userid
WHERE f.following = 1 /* argument */
ORDER BY ...

MYSQL JOIN syntax How to Join Three Tables

The following query does what I want. It returns all the resuls in the users table and then if there is a match in the details tble, returns the relevant data
users
id|username
details
id|userid|firstname|lastname
$sql = "SELECT u.*, d.*
FROM `users` u
LEFT JOIN `details` d on
u.id = d.userid
ORDER BY $strorder";
However, when I try to join an additonal table where I want to do the same thing--return all the results of the users table and if there is a match in the third table, return the relevant data (total followers of this user)--it only returns one record.
3rd table
follow
id|followerid|followedid
$sql = "SELECT u.*, d.*, COUNT(f.id)
FROM `users` u
LEFT JOIN `details` d on
u.id = d.userid
LEFT JOIN `follow` f on
u.id = f.followedid
ORDER BY $strorder";
Can anyone see what I am doing wrong? Thanks in advance for any suggestions.
Many thanks.
Try to avoid * to select fields, it will be clearer to group your datas (even if mysql is quite permissive with groupings).
When you have an aggregate function (like COUNT, SUM), the other "non aggregated" requested fields should be in a GROUP BY clause.
Mysql don't force you to GROUP BY all the fields, but... I think it's quite a good habit to be "as ANSI as possible" (usefull when you use another DBMS)
SELECT u.id, u.username, d.firstname, d.lastname, count(*) as numberfollowers
FROM user u
LEFT JOIN details d on u.id = d.userid
LEFT JOIN follow f on u.id = f.followedid
GROUP BY u.id, u.username, d.firstname, d.lastname --or just GROUP BY u.id with Mysql
ORDER BY count(*) desc
COUNT being an aggregate function, when selected with other columns, requires you to group your results by those other columns in the select list.
You should rewrite your query with columns that you want to select from users and details and group by those columns.