How to simplify a query between Many to Many Relationship - sql

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

Related

sql join Many-To-Many - 3 tables

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

SQL Subquery column equals operation

I am trying to do a SQL query for user with certain permission enabled flag. I know, I can do this:
select u.ID, u.Name,
(select p.Value
from Permissions p
where p.UserID = u.ID AND p.Key = 'CanEdit') as IsPermissionEnabled
from Users u
But it's not exactly what I need, can I do something like this:
select u.ID, u.Name,
((select p.Value
from Permissions p
where p.UserID = u.ID AND p.Key = 'CanEdit') = 'True')
as IsPermissionEnabled
from Users u
It didn't work for me. So, how to change my query to make it work?
Surely you should just join to the table to get p.Value.
Then you can do with it whatever you like:
SELECT
u.ID,
u.Name,
p.Value as IsPermissionEnabled
FROM Users u
LEFT OUTER JOIN Permissions p
ON p.UserID = u.ID
AND p.Key = 'CanEdit';
Try this query
select u.ID, u.Name, case when p.value>0 then 'True' else '' end
as IsPermissionEnabled
from Users u
left join permission p on p.UserID = u.ID and p.key='CanEdit'
In SQL Server, you need an explicit case statement. So, you can write the query as:
select u.ID, u.Name,
(case when (select p.Value
from Permissions p
where p.UserID = u.ID AND p.Key = 'CanEdit'
) = 'True'
then 1 else 0
end) as IsPermissionEnabled
from Users u;
A join ( inner or left) with a Case can be used to return the desired data with IsPermissionEnabled. Assuming intent is to treat IsPermissionEnabled as boolean, the case statement can set it to a bit value of 0 or 1 as below.
select u.USERID , u.Username, IsPermissionEnabled = case p.[Key] when 'CanEdit' then 1 else 0 end from [Permissions] p inner join [User] u on u.USERID = p.userid

Giving default values for columns in join condition in oracle SQL Query

I have 3 tables
users(userid,UserName)
1,Ram
2,Krishna
3,Madhu
4,Deepak
roles(roleid,roleName)
101,Agent
102,User
103,Manager
user_role_map(userId,roleId)
1,102
2,103
3,101
I need to get following details in my query. If there is no record in user_role_map for a user i need to get RoleName as NONE
UserID, UserName, RoleName
1,Ram,User
2,Krishna,Manager
4,Deepak,NONE
Below query is not returning UserID 4
SELECT u.UserID
, u.UserName
, r.RoleName
FROM users u, roles r, user_role_map ur
WHERE u.UserID = ur.UserID
AND r.roleid = ur.roleid
try this - not tested but it looks right to me
select
u.UserID, u.UserName, IsNull(r.roleName, 'NONE')
from
users u
left join user_role_map ur on
ur.UserId = u.UserId
left join roles r on
r.roleid=ur.roleid

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

How to place an ORDER BY clause in SQL between UNIONS

I want to implement simple SQL query that will return a sorted list. The problem is that I get syntax errors for placing the ORDER BY clause anywhere I put it.
SELECT
fr.FunctionRoleID, fr.FunctionRoleInternalName
FROM
users u
JOIN
UserRoles ur ON ur.UserID = u.UserID
JOIN
Roles_FunctionRoles rfr ON rfr.RoleID = ur.RoleID
JOIN
FunctionRoles fr ON fr.FunctionRoleID = rfr.FunctionRoleID
WHERE
u.UserName = #UserName
AND u.Active = 1
UNION
SELECT
fr.FunctionRoleID, fr.FunctionRoleInternalName
FROM
Roles r
JOIN
Roles_FunctionRoles rfr ON rfr.RoleID = r.RoleID
JOIN
FunctionRoles fr ON fr.FunctionRoleID = rfr.FunctionRoleID
WHERE
r.RoleName = 'Authenticated Users'
AND #UserName IS NOT NULL
AND LEN(#UserName) > 0
What I want to insert:
ORDER BY fr.DisplayName ASC
EDIT
If I create a subquery using
SELECT *
FROM
(
[my initial query]
)
ORDER BY
[COLUMN NAME] ASC
I get the following error message:
Incorrect syntax near 'ORDER'. Expected 'AS', 'ID' or 'QUOTED_id'
In most databases, you can only place an order by at the end of a union.
Because the union abstracts away individual table aliases, you only have to list the column name. So omit the fr. :
ORDER BY DisplayName
The ORDER BY clause needs to be placed after the last SELECT statement of the Union.
select * from(
*what you have there*
) as foo
order by DisplayName ASC
I'm not in front of an IDE so the syntax may be off a bit but that's the idea.
e: yeah, figured I'd jack up the syntax...alias added :)
Your not selecting DisplayName so you cannot use it to ORDER BY a set derived from a UNION. If you want to order by it and omit it from the results;
;WITH T (FunctionRoleID, FunctionRoleInternalName, DisplayName) AS (
SELECT
fr.FunctionRoleID, fr.FunctionRoleInternalName, fr.DisplayName
FROM users u
JOIN UserRoles ur ON ur.UserID = u.UserID
JOIN Roles_FunctionRoles rfr ON rfr.RoleID = ur.RoleID
JOIN FunctionRoles fr ON fr.FunctionRoleID = rfr.FunctionRoleID
WHERE
u.UserName = #UserName
AND
u.Active = 1
UNION
SELECT
fr.FunctionRoleID, fr.FunctionRoleInternalName, fr.DisplayName
FROM
Roles r
JOIN Roles_FunctionRoles rfr ON rfr.RoleID = r.RoleID
JOIN FunctionRoles fr ON fr.FunctionRoleID = rfr.FunctionRoleID
WHERE
r.RoleName = 'Authenticated Users'
and #UserName is not null and LEN(#UserName) > 0
)
SELECT
FunctionRoleID, FunctionRoleInternalName
FROM T
ORDER BY DisplayName
Try
SELECT * FROM (
SELECT
fr.FunctionRoleID, fr.FunctionRoleInternalName
FROM
users u
JOIN UserRoles ur ON ur.UserID = u.UserID
JOIN Roles_FunctionRoles rfr ON rfr.RoleID = ur.RoleID
JOIN FunctionRoles fr ON fr.FunctionRoleID = rfr.FunctionRoleID
WHERE
u.UserName = #UserName
AND
u.Active = 1
UNION
SELECT
fr.FunctionRoleID, fr.FunctionRoleInternalName
FROM
Roles r
JOIN Roles_FunctionRoles rfr ON rfr.RoleID = r.RoleID
JOIN FunctionRoles fr ON fr.FunctionRoleID = rfr.FunctionRoleID
WHERE
r.RoleName = 'Authenticated Users'
and #UserName is not null and LEN(#UserName) > 0 ) a
ORDER BY a.FunctionRoleInternalName ASC
Basically, you are selecting against the result of the UNION and then performing the ORDER BY...note that I use "FunctionRoleInternalName"...you can change that to "DiaplayName" only of you use that as a column ALIAS in the UNION queries...e.g. "FunctionRoleInternalName AS DisplayName"
For UNION, ORDER BY goes at the end and applies to the combined result of both queries; you can't order by a column that is not selected by both queries in the union.
what you need to do is select fr.DisplayName in both queries; then you can order by it.
If you don't want the display name to be one of the output columns, nest the whole thing in an outer query that retrieves just the columns you want.