sql join Many-To-Many - 3 tables - sql

I have 3 tables in sqlserver :
tbl_Users - User_ID, User_Name
tbl_Roles - Role_ID, Role_Name
tbl_Users_Roles_MTM - User_ID, Role_ID
A user can have multiple roles assigned to him, and that will show in the Many-To-Many table.
In my stored-procedure I need Role_Name which are NOT assigned to a specific User_ID (which is given as a parameter).
I guess I should use an INNER JOIN (or a LEFT one...).
There are numerous entries in SO and other forums with questions nearly similar to this but not quite. I experimented a lot but by now I completely lost my hands and feet!
Thank you all.
EDIT :
With the help of the good people of SO, I got it to work :
SELECT r.Role_Name
FROM tbl_Roles r
WHERE NOT EXISTS(
SELECT 1
FROM tbl_Users_Roles_MTM ur
WHERE ur.User_ID = #User_ID
AND ur.Role_ID = r.Role_ID);
SO people are awesome!!!!!

Try this query:
SELECT r.Role_Name
FROM tbl_Roles r
WHERE NOT EXISTS (
SELECT 1
FROM tbl_Users_Roles_MTM ur
WHERE ur.User_ID = #User_ID
AND ur.Role_ID = r.Role_ID);

You can do this:
SELECT *
FROM Roles
WHERE Role_Name = #rolename
AND NOT EXISTS(SELECT 1
FROM tbl_Roles AS r
INNER JOIN tbl_Users_Roles_MTM AS ur ON ur.Role_ID = r.Role_ID
WHERE r.role_Name = #rolename
AND ur.User_ID = #User_ID);
The NOT EXISTS predicate will ensure that the role is not assigned to any user.

SELECT r.*
FROM tbl_Roles r
LEFT JOIN tbl_Users_Roles_MTM ur
ON r.Role_ID = ur.Role_ID
AND ur.User_ID = #User_ID
WHERE ur.User_ID IS NULL

I would do something like this
select roles.Role_Name
from tbl_Roles as roles
left join tbl_Users_Roles_MTM as userRoles
on userRoles.Role_ID = roles.Role_ID
and userRoles.[User_ID] = #User_ID
where userRoles.Role_ID is null
Basically left join and then select only those rows, where it has not been joined

Related

Exclude record if duplicate record has certain value

I have a table of users who have certain roles. A user is entered into the table one time for each role they have. I need to get a count of all users who have certain roles but I need to exclude any duplicate record that also has another role. Below is what is populated in my table
Name Role
Steve ROLE_8
Steve ROLE_9
Steve ROLE_1
And this is the query I have to select users who have certain roles. What I need to do is check to see if a user has ROLE_1 but also check if there is another instance of that user who has a role that I do not wish to include and exclude that user from the return set.
SELECT COUNT(DISTINCT c.user_id)
FROM users c
WHERE email_addr != ''
AND email_addr IS NOT NULL
AND EXISTS
(SELECT r.role_id
FROM ROLE r, user_role ur
WHERE c.user_id = ur.user_id
AND ur.role_id = r.role_id
AND (r.name = 'ROLE_1'
OR r.name = 'ROLE_2'
OR r.name = 'ROLE_3'
OR r.name = 'ROLE_4'
OR r.name = 'ROLE_5'
OR r.name = 'ROLE_6'))
Using the DISTINCT in your COUNT makes the EXISTS unnecessary - you can just join to the user_role table. At that point you just need to exclude those users who also have one of the roles that you don't want:
SELECT
COUNT(DISTINCT U.user_id)
FROM
Users U
INNER JOIN User_Role UR ON UR.user_id = U.user_id AND
INNER JOIN Role R ON
R.role_id = UR.role_id AND
R.name IN ('ROLE_USER_ADMIN', 'ROLE_1'...)
WHERE
U.email_addr IS NOT NULL AND U.email_addr <> '' AND
NOT EXISTS
(
SELECT *
FROM
User_Role UR2
INNER JOIN Role R2 ON
R2.role_id = UR2.role_id AND
R2.name IN ('Some_Excluded_Role')
WHERE
UR2.user_id = U.user_id
)
If you want to exclude any user who has any role outside of your list then you can do the following:
SELECT
COUNT(DISTINCT U.user_id)
FROM
Users U
INNER JOIN User_Role UR ON UR.user_id = U.user_id AND
INNER JOIN Role R ON
R.role_id = UR.role_id AND
R.name IN ('ROLE_USER_ADMIN', 'ROLE_1'...)
WHERE
U.email_addr IS NOT NULL AND U.email_addr <> '' AND
NOT EXISTS
(
SELECT *
FROM
User_Role UR2
INNER JOIN Role R2 ON
R2.role_id = UR2.role_id AND
R2.name NOT IN ('ROLE_USER_ADMIN', 'ROLE_1'...)
WHERE
UR2.user_id = U.user_id
)
I believe this will get you what you're looking for (without knowing exactly what you expect the results to look like). The concept being to first find the ones that match the 'good' roles and then filter with the other sub-select the ones that also have 'bad' roles.
SELECT COUNT(DISTINCT c.[user_id])
FROM users AS c
INNER JOIN user_role AS ur
ON ur.[user_id] = c.[user_id]
WHERE c.email_addr != ''
AND c.email_addr IS NOT NULL
AND ur.role_id IN
(SELECT sub_r.role_id
FROM [role] AS sub_r
WHERE sub_r.name IN ('ROLE_USER_ADMIN', 'ROLE_1')
)
AND c.[user_id] NOT IN
(SELECT sub2_ur.[user_id]
FROM user_role AS sub2_ur
INNER JOIN [role] AS sub2_r
ON sub2_r.role_id = sub2_ur.role_id
WHERE sub2_r.name IN ('ROLE_NOT_TO_USE','ANOTHER_NOT_TO_USE')
AND sub2_ur.[user_id] = c.[user_id]
)
If I wanted to get a dataset in which I'm including users that have either role 1, 2, or 3 and I wanted to exclude users that had role 4 and 5, I would do the following, if "Name" is unique:
SELECT COUNT(DISTINCT Name)
FROM ROLE
WHERE Role IN ('ROLE_1','ROLE_2','ROLE_3')
AND Name NOT IN (SELECT DISTINCT Name FROM ROLE WHERE Role IN ('ROLE_4','ROLE_5');

How to simplify a query between Many to Many Relationship

I was wondering how to simplify this query, here(http://sqlfiddle.com/#!3/2789c/4) you have the complete example
SELECT distinct (R.[roleId])
FROM [Role] R
LEFT JOIN [userRole] U ON R.[roleId] = U.[roleId]
WHERE R.RoleID NOT IN(
SELECT [roleId]
from [dbo].[userRole]
WHERE userId = 2)
I want to get all the roles that are not assigned to an specific user. I think the inner select could be erase.
Update 1
After your great help, I could use only one SELECT http://sqlfiddle.com/#!3/2789c/87
SELECT R.[roleID]
FROM [Role] R
LEFT JOIN [userRole] U
ON R.[roleID] = U.[roleID] AND U.userId = #userID
WHERE U.userId IS NULL
As simple as it gets:
select roleId
from Role
except
select roleId
from userRole
where userId = 2
SELECT R.roleId
FROM [Role] R
LEFT JOIN [userRole] U ON R.roleId = U.roleId
group by r.roleId
having sum(case when U.userId = 2 then 1 else 0 end) = 0
SQLFiddle demo
You can also try this
Select distinct role.roleID from role , userrole
except
select roleId from userrole where userID=2
OR
SELECT R.roleId
FROM [Role] R
LEFT JOIN [userRole] U ON R.roleId = U.roleId
except
select roleId from userrole where userID=2

Script for getting posts with unique role

I want to write a script which will show all messages owned by ONLY users with RoleID = 4. So I tried to write something like this:
SELECT DT.DiscussionThreadID, DT.Message FROM DiscussionThread DT
INNER JOIN Users U on U.UserID = DT.CreatedBy
INNER JOIN UserRoles UR on UR.UserID = U.RoleID
WHERE UR.RoleID = 4
Example of UserRoles table:
UserID RoleID
1 1
1 2
1 4
2 4
3 3
3 4
I expected to see only messages posted by user with UserID = 2 - he don't have additional roles except of RoleID = 4. But my script returns all posts. Can someone help me?
SELECT DT.DiscussionThreadID, DT.Message
FROM DiscussionThread DT
INNER JOIN Users U on U.UserID = DT.CreatedBy
INNER JOIN UserRoles UR on UR.RoleID = U.RoleID
group by DT.DiscussionThreadID, DT.Message
having count(Distinct roleID) = 1
and max(roleID)=4
If you want better performance, do the role check before the join to DiscussionThread. You can also dispense with the users table, since fields from the table are not being used:
SELECT DT.DiscussionThreadID, DT.Message
FROM DiscussionThread DT inner join
(select userId
from UserRoles UR
group by userId
having COUNT(distinct roleId) = 1) and max(roleId) = 4
) ur
on UR.UserID = U.UserID
group by DT.DiscussionThreadID, DT.Message

user role permission query

I have 5 tables: user > user_has_role > role > role_has_permission > permission
An user can have 0 or * roles.
And a role can have 0 or * permissions
now i have a query which will get all permissions belonging to a user.
This work but now i want that if the user has the role "super_admin" then all permissions get joined. But without specifing them in the table role_has_permission.
This is query:
SELECT p.name,r.name role
FROM user_has_role AS ur
JOIN role AS r
ON ur.role_id = r.id
JOIN role_has_permission AS rp
ON r.id = rp.role_id
LEFT JOIN permission AS p
ON rp.permission_id = p.id
WHERE ur.user_id = [USER_ID]
You could just do a union:
SELECT p.name,r.name role
FROM user_has_role AS ur
JOIN role AS r
ON ur.role_id = r.id
JOIN role_has_permission AS rp
ON r.id = rp.role_id
LEFT JOIN permission AS p
ON rp.permission_id = p.id
WHERE ur.user_id = [USER_ID]
UNION
SELECT p.name, 'super_admin'
FROM permission
WHERE EXISTS (SELECT * FROM user_has_role, role
WHERE ur.user_id = [USER_ID] and role.role_id = user_has_role.role_id and
role.name = 'super_admin')

SQL join help for friend list

I have three database tables: users, user_profiles and friends:
users
id
username
password
user_profiles
id
user_id
full_name
friends
id
usera_id
userb_id
What would be a query which finds the friends list of any user and also joins the table users and user_profiles to get the profile and user information of that friend?
Use:
SELECT f.username,
up.*
FROM USERS f
JOIN USER_PROFILES up ON up.user_id = f.id
JOIN FRIENDS fr ON fr.userb_id = f.id
JOIN USERS u ON u.id = fr.usera_id
WHERE u.username = ?
...assuming userb_id is the friend id.
This may not be the best way to do it, but this felt like the logical way:
select a.id , a.friend_id ,
Users.username
from
( SELECT id , IF(usera_id = 1, userb_id , usera_id) friend_id
FROM friends
where usera_id = 1 OR userb_id = 1 ) a
left join Users on a.friend_id = Users.id
this uses a mySQL function so probably wont work in Oracle/MSSQL
Falling back on bad habits (not using JOIN notation in the FROM clause):
SELECT a.id, a.username, a.full_name,
b.id, b.username, b.full_name
FROM friends AS f, users AS ua, users AS ub,
user_profiles AS a, user_profiles AS b
WHERE f.usera_id = ua.id
AND f.userb_id = ub.id
AND a.user_id = ua.id
AND b.user_id = ub.id
The key point is using table aliases (all those 'AS' clauses) and referencing the same table more than once when necessary.
Someone could write this with JOIN instead.
Some modification to eugene y's answer, will this work?
SELECT * FROM users u
JOIN friends f ON (f.userb_id = u.id OR f.usera_id = u.id)
JOIN user_profiles p ON u.id = p.user_id
WHERE u.id = ?