Multiple joins with multiple conditions with multiple tables - mysql

at first my tables:
game
+----+--------------+
| id | game |
+----+--------------+
| 1 | Game1 |
| 2 | Game2 |
| 4 | Game4 |
+----+--------------+
group_game
+---------+----------+
| game_id | group_id |
+---------+----------+
| 1 | 33 |
| 1 | 45 |
| 4 | 33 |
+---------+----------+
groups
+----+------------+----
| id | group_name | ...
+----+------------+----
| 33 | Group33 | ...
| 45 | Group45 | ...
+----+------------+----
users
+---------+----------+----
| user_id | username | ...
+---------+----------+----
| 1 | User1 | ...
| 2 | User2 | ...
+---------+----------+----
users_groups
+---------+----------+
| user_id | group_id |
+---------+----------+
| 1 | 33 |
| 1 | 45 |
| 2 | 45 |
+---------+----------+
What I want to do
Now I want to check wether the current user is in a group which plays "Game4" and if yes the output should be the id and the name of the group.
the current user is "User1" with the ID 1 (table users)
"User1" is in a group with the ID 33 (table users_groups)
The Group-ID 33 belongs to "Group33" (table groups)
The Group with the ID 33 plays the Game with the ID 4 (table group_game)
The Game with the ID belongs to the game "Game4" (table game)
CONCLUSION: Yes, the user is in a group which plays Game4, so output the name of the group ("Group33")
My current code for that (which gives me no rows)
$user_id = $_SESSION["user_id"];
$Game4= "Game4";
$gruppen_dayz = $db->prepare("
SELECT g.group_id, g.group_name
FROM groups g
LEFT JOIN users_groups ug
ON g.group_id = ug.group_id
LEFT JOIN group_game gg
ON g.group_id = gg.group_id
LEFT JOIN game ga
ON ga.id = gg.game_id
WHERE ga.game = ? AND ug.user_id = ?
");
$gruppen_dayz->bind_param('ii', $Game4, $user_id);
I don't know exactly how I should build this query :/

Your where clause is being applied after the joins generate the dataset, negating the left join aspect of your joins. To solve this move the criteria to the join itself so the limits are applied before the joins. Otherwise, the NULL values generated in your left joins are then excluded by your where clause.
There may be other elements as well, this is just the first component I saw.
$user_id = $_SESSION["user_id"];
$Game4= "Game4";
$gruppen_dayz = $db->prepare("
SELECT g.group_id, g.group_name
FROM groups g
LEFT JOIN users_groups ug
ON g.group_id = ug.group_id
AND ug.user_id = ?
LEFT JOIN group_game gg
ON g.group_id = gg.group_id
LEFT JOIN game ga
ON ga.id = gg.game_id
AND ga.game = ?
");
$gruppen_dayz->bind_param('ii', $Game4, $user_id);
---UPDATE ----
Upon further investigation I believe your joins are wrong. Groups doesn't have a group_ID field according to your table structure. Walking though the rest now...
SELECT g.group_id, g.group_name
FROM groups g
LEFT JOIN users_groups ug
ON g.id = ug.group_id
LEFT JOIN group_game gg
ON g.id = gg.group_id
LEFT JOIN game ga
ON ga.id = gg.game_id
WHERE ga.game = ? AND ug.user_id = ?
I might rewrite it as...
SELECT G.Id, G.Group_name
FROM USERS_GROUPS UG
INNER JOIN GROUPS G
on UG.Group_ID = G.ID
INNER JOIN GROUP_GAME GG
on GG.Group_ID = G.ID
INNER JOIN Game GA
on GA.ID = GG.Game_ID
WHERE ga.game = ? AND ug.user_id = ?
I see no value or need for left joins based on your criteria.

Related

Get list of groups user hasn't Joined, List of groups user didn't create

The user (Ben) has joined group 2 and group 3. How can I write this in a select query... I want to select from groups I haven't joined and groups I didn't create.
users_tbl table
user_id username
| 1 | ben
| 2 | betty
| 3 | tim
| 4 | jimmy
| 5 | sammy
user_groups table
user_id group_id
| 1 | 2
| 1 | 3
group_tbl table
group_id user_id
| 1 | 5
| 2 | 4
| 3 | 5
I am able to get the list of groups I didn't create using this query...
SELECT * FROM group_tbl LEFT JOIN users_tbl ON users_tbl.user_id = group_tbl.user_id WHERE group_tbl.user_id != ? ORDER BY RAND() LIMIT 10
How can I get the list of groups users hasn't joined?
You can do it if you do a LEFT join of group_tbl to users_tbl and return the unmatched rows of group_tbl:
SELECT g.*
FROM group_tbl g LEFT JOIN user_groups u
ON u.group_id = g.group_id AND u.user_id = 1
WHERE u.user_id IS NULL
Or with NOT EXISTS:
SELECT g.*
FROM group_tbl g
WHERE NOT EXISTS (
SELECT 1
FROM user_groups u
WHERE u.group_id = g.group_id AND u.user_id = 1
)
See the demo.
Results:
group_id
user_id
1
5

join pivot table in mysql

How to join different tables with pivote table
I have 4 tables like
users
id | name |
-------------
1 | abc |
2 | ccc |
user_profile
id | user_id | email |
-------------------------------
1 | 1 | abc#gmail.com
2 | 2 | ccc#gmail.com
skills
id | skill_name |
--------------------------
1 | java |
2 | php |
user_skills
user_id | skill_id |
---------------------------
1 | 1 |
1 | 2 |
2 | 1 |
The result should be
name | email | skills |
----------------------------------
abc |abc#gmail.com | java, php |
ccc |ccc#gmail.com | java |
I am able to join multiple tables but I have problem joining pivote
I have tried below with query
SELECT users.name,user_profiles.email, group_concat(programs.name)
from users
JOIN user_profiles on user_profiles.user_id = users.id
LEFT JOIN user_skills on user_skills.user_id = users.id
LEFT JOIN skills on user_skills.skill_id = skills.id
GROUP BY users.id
Can anyone help me on this please??Thanks
You need GROUP_CONCAT to generate the CSV list of skills:
SELECT
u.name,
up.email,
GROUP_CONCAT(s.skill_name) AS skills
FROM users u
INNER JOIN user_profile up
ON u.id = up.user_id
LEFT JOIN user_skills us
ON u.id = us.user_id
INNER JOIN skills s
ON us.skill_id = s.id
GROUP BY
u.id, u.name, up.email;
Demo
Note that I group by both the user's id and name, because perhaps two users happen to have the same name. Follow the link below for a running SQLFiddle.
Your query should work. Perhaps the problem is the reference to programs rather than skills:
select u.name, up.email, group_concat(s.name)
from users u join
user_profiles up
on up.user_id = u.id left join
user_skills us
on us.user_id = u.id left join
skills s
on us.skill_id = s.id
group by u.name, up.email;

MySQL Relational Division Query Performance

I have students that are associated many-to-many with groups via a join table groups_students. Each group has a group_type, which can either be a permission_group or not (boolean on group_types table).
I also have users, which are also associated many-to-many with groups via groups_users.
I want to return all students for which a particular user is associated with ALL the student's permission groups.
I've been lead to believe this requires relational division and here's where I am with it:
SELECT DISTINCT gs.student_id
FROM groups_students AS gs
INNER JOIN groups ON groups.id = gs.group_id
INNER JOIN groups_users gu ON gu.group_id = groups.id
INNER JOIN group_types ON group_types.id = groups.group_type_id
WHERE group_types.permission_group = 1
AND gu.user_id = 37
AND NOT EXISTS (
SELECT * FROM groups_students AS gs2
WHERE gs2.student_id = gs.student_id
AND NOT EXISTS (
SELECT gu2.group_id
FROM groups_users AS gu2
WHERE gu2.group_id = gs2.group_id AND gu2.user_id = gu.user_id
)
)
This works, but on my live database with over 20,000 rows in groups_students, it takes over 3 seconds.
Can I make it faster? I read about doing relational division with COUNT but I couldn't relate it to my scenario. Am I able to make cheap gains to bring this query well under half a second execution time or am I looking at a major restructure?
Edit - English language description: Students belong to classes (groups), and users have permission to view certain classes. I need to know the students for which a particular user has permission to view all the (permission) classes for.
EXPLAIN for the slow query:
+----+--------------------+-------------+--------+--------------------------------------------------------------+--------------------------------------------------+---------+-----------------------------+------+--------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------------+-------------+--------+--------------------------------------------------------------+--------------------------------------------------+---------+-----------------------------+------+--------------------------------+
| 1 | PRIMARY | gu | ref | index_groups_users_on_user_id,index_groups_users_on_group_id | index_groups_users_on_user_id | 5 | const | 1181 | Using where; Using temporary |
| 1 | PRIMARY | groups | eq_ref | PRIMARY | PRIMARY | 4 | my_db.gu.group_id | 1 | |
| 1 | PRIMARY | group_types | ALL | PRIMARY | NULL | NULL | NULL | 3 | Using where; Using join buffer |
| 1 | PRIMARY | gs | ref | index_groups_students_on_group_id_and_student_id | index_groups_students_on_group_id_and_student_id | 4 | my_db.groups.id | 9 | Using where; Using index |
| 2 | DEPENDENT SUBQUERY | gs2 | ref | index_groups_students_on_student_id_and_group_id | index_groups_students_on_student_id_and_group_id | 4 | my_db.gs.student_id | 8 | Using where; Using index |
| 3 | DEPENDENT SUBQUERY | gu2 | ref | index_groups_users_on_user_id,index_groups_users_on_group_id | index_groups_users_on_group_id | 5 | my_db.gs2.group_id | 99 | Using where |
+----+--------------------+-------------+--------+--------------------------------------------------------------+--------------------------------------------------+---------+-----------------------------+------+--------------------------------+
SQL Fiddle
"I want to return all students for which a particular user is associated with ALL the student's permission groups."
I don't really follow your query; it seems so complicated for this purpose. Instead, I think of it as follows:
Generate all students and their permissions
Generate all permissions for user 37
(outer) Join these together on permissions
Be sure that all permissions for a particular student are in the u37 group
The resulting query is:
select student_id
from (SELECT gs.student_id, g.id as group_id
FROM groups_students gs INNER JOIN
groups g
ON g.id = gs.group_id INNER JOIN
groups_users gu
ON gu.group_id = g.id INNER JOIN
group_types gt
ON gt.id = g.group_type_id
where gt.permission_group = 1
) s left outer join
(select g.id as group_id
from groups_users gu INNER JOIN
groups g
on gu.group_id = g.id INNER JOIN
group_types gt
ON gt.id = g.group_type_id
where gu.user_id = 37 and gt.permission_group = 1
) u37
on s.group_id = u37.group_id
group by s.student_id
having count(*) = count(u37.group_id);
Note: You can do this without the subqueries. Despite their overhead, I think they make the query much more understandable.
A simpler version of Gordon's idea...
SELECT gs.student_id
FROM groups_students gs
JOIN groups g
ON g.id = gs.group_id
JOIN group_types gt
ON gt.id = g.group_type_id
LEFT
JOIN groups_users gu
ON gu.group_id = gs.group_id
AND gu.user_id = 37
WHERE gt.permission_group
GROUP
BY student_id
HAVING COUNT(student_id) = COUNT(user_id)
I don't understand why you use subqueries. They are generally slow and should be avoided if possible. Maybe I do not understand your requirements correctly, but I would come up with something like this:
SELECT DISTINCT gs.student_id
FROM groups_students AS gs
INNER JOIN groups ON groups.id = gs.group_id
INNER JOIN groups_users gu ON gu.group_id = groups.id
INNER JOIN group_types ON group_types.id = groups.group_type_id
LEFT JOIN groups_students AS gs2 ON gs2.student_id = gs.student_id
LEFT JOIN groups_users AS gu2 ON gu2.group_id = gs2.group_id AND gu2.user_id = gu.user_id
WHERE group_types.permission_group = 1
AND gu.user_id = 37
AND gs2.student_id IS NULL
AND gu2.group_id IS NULL
You can force something to not exist by using a left join and checking, that the right table-column (use the primary key) contains null.

Trouble with join statement for selecting data across tables with MYSQL

I'm trying to display events that a user has created and events that he has signed up for. I have three tables for this.
//events
| event_id | event_title | event_details | event_timestamp | userid
1 title1 test 1234 1
2 title2 testing2 123 2
//registration_items : event_id references events.event_id
| id | event_id | task_name
1 2 task 1
//registration_signup : id references registration_items.id
| id | userid | timestamp
1 1 1234
Here's the current query I have. Right now it only displays the event the user created. It should display both created events and ones he signed up for
select events.*, registration_items.*, registration_signup.*, users.username from events
INNER JOIN users on users.userid = events.userid
LEFT JOIN registration_items ON registration_items.event_id = events.event_id
LEFT JOIN registration_signup ON registration_signup.id = registration_items.id
WHERE events.userid = '$user_id' OR registration_signup.userid = '$user_id' ORDER BY events.event_timestamp DESC
For userid1 the output should be
Title
title1 (the user created this)
title2 (the user signed up for this)
For userid2 the output should be
Title
title2
select events.*, registration_items.*, registration_signup.*, users.username
from events
INNER JOIN users on users.userid = events.userid
LEFT JOIN registration_items ON registration_items.event_id = events.event_id
LEFT JOIN registration_signup ON registration_signup.id = registration_items.id
WHERE registration_signup.userid = '$user_id'
union
select events.*, registration_items.*, registration_signup.*, users.username
from events
INNER JOIN users on users.userid = events.userid
INNER JOIN registration_items ON registration_items.event_id = events.event_id
INNER JOIN registration_signup ON registration_signup.id = registration_items.id
WHERE events.userid = '$user_id'
ORDER BY events.event_timestamp DESC
I am not sure if it's correct to guess that you have a typo in the sample data. Because your query works the moment you change the user id to 1 for both events, signed up events.. So please take a look at this reference demo and comment if it's not a typo...
SQLFIDDLE DEMO
query: (your query..)
select u.userid,e.event_id as id,
ri.event_id as evt, e.event_title,
ri.task_name,
rs.timestamp,
u.name from events e
INNER JOIN users u on
u.userid = e.userid
LEFT JOIN registration_items ri ON
ri.event_id = e.event_id
LEFT JOIN registration_signup rs ON
rs.id = ri.id
WHERE e.userid = '1' or
rs.userid = '1'
ORDER BY e.event_timestamp DESC
;
Results:
| USERID | ID | EVT | EVENT_TITLE | TASK_NAME | TIMESTAMP | NAME |
------------------------------------------------------------------
| 1 | 1 | 1 | title1 | task 1 | 1234 | john |
| 1 | 2 | 2 | title2 | task 1.2 | 3456 | john |

LEFT JOIN 3 columns to get username

I have three columns I need to join which comes from 3 different tables,
Contributions table:
+-----------+---------------------+
| record_id | contributor_user_id |
+-----------+---------------------+
| 1 | 2 |
+-----------+---------------------+
| 1 | 5 |
+-----------+---------------------+
Members table:
+--------------+---------+
| username | user_id |
+--------------+---------+
| Test | 1 |
+--------------+---------+
| Test2 | 5 |
+--------------+---------+
| Test3 | 6 |
+--------------+---------+
Records table:
+---------+-----------+
| user_id | record_id |
+---------+-----------+
| 28 | 1 |
+---------+-----------+
For what I need to return is the username and user_id for displaying the record owner. Also, display the username and the user_id, but this can be multiple (more than 1+ user). I've tried this:
SELECT usr.username,
usr.user_id,
rec.record_id,
contrib.record_id,
contrib.contributor_user_id
FROM
(
records rec
INNER JOIN members usr ON rec.user_id = usr.user_id
# this returns records as NULL
LEFT OUTER JOIN contributions contrib ON rec.record_id = contrib.record_id AND contrib.contributor_user_id = usr.user_id
# this works, but I need the username to be displayed too
LEFT OUTER JOIN contributions contrib ON rec.record_id = contrib.record_id
)
WHERE rec.record_id = 1
Try nesting the join for contributing users inside of the left join to contributions.
SELECT u.username, u.user_id, r.record_id, u2.username as ContributorName, u2.user_id as ContributorId
FROM records r
INNER JOIN members u
ON r.user_id = u.user_id
LEFT JOIN contributions c
INNER JOIN members u2
ON c.contributor_user_id = u2.user_id
ON r.record_id = c.record_id
WHERE r.record_id = 1
SELECT
usr.username AS record_owner
, usr.user_id AS record_owner_id
, rec.record_id
, con.contributor_user_id AS contributor_id
, contributors.username AS contributor_name
FROM
records rec
INNER JOIN
members usr
ON rec.user_id = usr.user_id
LEFT OUTER JOIN
contributions con
ON rec.record_id = con.record_id
INNER JOIN
members contributors
ON con.contributor_user_id = contributors.user_id
WHERE
rec.record_id = 1