SQL Get List of Users not in Roles - sql

I have three tables:
Users : UserID, UserName
Roles : RoleID, RoleName
UsersInRoles : UserID, RoleID
How do I get a list of UserIDs and the RolesIDs for which they are NOT in?
I'm using SQL Server 2012.
Thanks for any help you can provide.
Aaron

select users.userid, roles.roleid
from users
cross join roles
left outer join usersinroles on
usersinroles.userid = users.userid and
usersinroles.roleid = roles.roleid
where usersinroles.userid is null
cross join joins each role to each user.
left outer join joins the tables, but doesn't delete the rows that don't match. Instead, it leaves the joined fields as null when there is no match. Getting only the cases where the field is null has the effect of getting only the rows that do not match--the roles that a user doesn't have.

I think #dan1111's solution is better, but this one might be more readable:
SELECT u.Userame,
r.RoleName
FROM Users u
CROSS JOIN Roles r
EXCEPT
SELECT u.Userame,
r.RoleName
FROM Users u
INNER JOIN UsersInRoles ur
on u.UserID = ur.UserID
INNER JOIN Roles r
on ur.RoleID = r.RoleID

Consider using a LEFT JOIN and combine it with an IS NULL condition.

This works using a CROSS JOIN and a LEFT JOIN:
SELECT U.UserId, R.RoleId
FROM Roles R CROSS JOIN Users U
LEFT JOIN UserRoles UR ON U.UserId = UR.UserId AND R.RoleId = UR.RoleId
WHERE UR.UserId IS NULL
And here is the sample Fiddle: http://sqlfiddle.com/#!6/46e48/1

Related

SQL Group By and Having clause and exists clause

These queries fetch records from multiple tables(AspNetUsers,AspNetUserRoles & AspNetRoles). The records will include only those users which have multiple Roles.
I am looking for reasons why 1st query works and the latter did not. Any help would be appreciated.
Query 1:
SELECT
U.Id,
U.UserName
,R.Id
,R.Name AS RoleName
FROM AspNetUsers AS U
JOIN AspNetUserRoles UR
ON U.Id = UR.UserId
JOIN AspNetRoles AS R
ON R.Id = UR.RoleId
WHERE EXISTS (
SELECT UserId,
COUNT() AS NumberofRoles
FROM AspNetUserRoles
GROUP BY UserId
HAVING COUNT() > 1)
Query 2:(Only work if I remove R.Id & R.Name Otherwise it is not working)
SELECT
U.Id,
U.UserName
,R.Id
,R.Name AS RoleName
FROM AspNetUsers AS U
JOIN AspNetUserRoles UR
ON U.Id = UR.UserId
JOIN AspNetRoles AS R
ON R.Id = UR.RoleId
GROUP BY U.Id,U.UserName
The table diagram is attached for better clarity.
The second query doesn't woek because you haven't defined anything known as R.
try this :
SELECT
U.Id,
U.UserName
,R.Id
,R.Name AS RoleName
FROM AspNetUsers AS U
JOIN AspNetUserRoles UR
ON U.Id = UR.UserId
JOIN AspNetRoles AS R
ON R.Id = UR.RoleId
GROUP BY U.Id,U.UserName,R.Id,R.Name
This would be the correct way to fetch records that have multiple Roles.
SELECT
U.Id,
U.UserName
,R.Id AS RoleID
,R.Name AS RoleName
FROM AspNetUsers AS U
JOIN AspNetUserRoles UR
ON U.Id = UR.UserId
JOIN AspNetRoles AS R
ON R.Id = UR.RoleId
WHERE U.Id IN (SELECT UserId FROM AspNetUserRoles GROUP BY UserId HAVING COUNT(*) > 1)

SQL QUERY disturbing Error

I have to write a query which displays the First and Last Name for all users who belong to the Role “Lumo Advantage”.
I wrote it as:
SELECT Users.FirstName, Users.LastName
INNER JOIN Users
ON UserRoles.UserID = Users.UserID
WHERE UserRoles.RoleID =7;
But it is showing error. why? Kindly point out the mistake.
Perhaps because you left out a from clause:
SELECT Users.FirstName, Users.LastName
FROM Users INNER JOIN
UserRoles
ON UserRoles.UserID = Users.UserID
WHERE UserRoles.RoleID = 7;
I would suggest you learn to use table aliases . . . so queries are easier to write and to read:
SELECT u.FirstName, u.LastName
FROM Users u INNER JOIN
UserRoles ur
ON ur.UserID = u.UserID
WHERE ur.RoleID = 7;

SELECT all from left, only from right only where

I have 3 tables, Roles, UsersInRoles and Users
I want to select all roles and a only user records where the user matches the where clause
SELECT r.*, u.UserName
FROM [dbo].[Roles] r
LEFT JOIN [dbo].[UsersInRoles ] ur
ON r.Id = ur.RoleId
LEFT JOIN [dbo].[Users] u
ON u.Id = ur.UserId
WHERE u.UserName = 'admin'
What I want is this:
RoleId, Role, Username
1 Admin Admin
2 Student Null
When I include the where clause, the student role is not returned
Move the filter from your WHERE clause, to the join condition of your Users table:
SELECT r.*, u.UserName
FROM [dbo].[Roles] r
LEFT JOIN [dbo].[UsersInRoles ] ur
ON r.Id = ur.RoleId
LEFT JOIN [dbo].[Users] u
ON u.Id = ur.UserId
AND u.UserName = 'admin'
This will return all records from Roles, with any matching records from UsersInRoles, but only those matching records in Users whose UserName equals 'admin'.
SELECT r.*, u.UserName
FROM [dbo].[Roles] r
LEFT JOIN [dbo].[UsersInRoles ] ur
ON r.Id = ur.RoleId
LEFT JOIN [dbo].[Users] u
ON u.Id = ur.UserId
WHERE (u.UserName = 'admin' or u.UserName is null)

Help me with this SQL Query

3 tables are defined as follows:
Users
User_ID INT
First_Name VARCHAR
Last_Name VARCHAR
Email VARCHAR
Roles
Role_ID INT
Role_Name VARCHAR
Access_Level INT
Roles_Users
User_ID INT
Role_ID INT
Roles_Users is a many-to-many linking table between Users and Roles. I want to pull back the following info:
First_Name, Last_Name, Email, Role_Name
What I have so far is:
SELECT
U.First_Name,
U.Last_Name,
U.Email,
R.Name AS Role_Name
FROM Users U
INNER JOIN Roles_Users RU ON U.User_ID = RU.User_ID
INNER JOIN Roles R ON RU.Role_ID = R.Role_ID
The tricky part (at least for me) is that I want to only pull back the Role_Name with the MIN(Access_Level) for that particular user. So basically the record set I want to pull will have each user only listed once with their lowest access level role name.
I'm sure this is pretty simple but it's just stumping me right now.
Thanks
YOu can use a CTE (Common Table Expression) in conjunction with the ROW_NUMBER windowing function like this:
;WITH MinAccessData AS
(
SELECT
U.First_Name,
U.Last_Name,
U.Email,
R.Name AS Role_Name,
ROW_NUMBER() OVER(PARTITION BY U.User_ID ORDER BY R.Access_Level) AS RowNum
FROM Users U
INNER JOIN Roles_Users RU ON U.User_ID = RU.User_ID
INNER JOIN Roles R ON RU.Role_ID = R.Role_ID
)
SELECT *
FROM MinAccessData
WHERE RowNum = 1
The CTE "partitions" your data by User_ID, e.g. each user gets a "partition". Inside that partition, the roles are ordered by Access_level and the smallest is the first one - so it gets RowNum = 1 - for each user.
So you then select from that CTE all those entries where the RowNum = 1 - this delivers all the entries for each user which have the smallest Access_Level value.
Alternatives without a CTE (just to have another tool in your box)
SELECT
U.First_Name,
U.Last_Name,
U.Email,
R.Name AS Role_Name
FROM Users U
INNER JOIN Roles_Users RU ON U.User_ID = RU.User_ID
INNER JOIN Roles R ON RU.Role_ID = R.Role_ID
INNER JOIN (SELECT
MIN(r.Access_Level) access_level,
ru.UserID,
FROM Roles r
INNER JOIN Roles_Users ru
ON r.Role_ID = ru.Role_ID
GROUP BY UserID
) minAccess
ON ru.UserId = minAccess.UserId
and ru.
ON r.access_level = minAccess .access_level
You can also use a CROSS APPLY
SELECT
U.First_Name,
U.Last_Name,
U.Email,
R.Name AS Role_Name
FROM Users U
CROSS APPLY (SELECT TOP 1
Role_Name
FROM Roles_Users RU ON U.User_ID = RU.User_ID
INNER JOIN Roles R ON RU.Role_ID = R.Role_ID
WHERE u.user_id = ru.user_id
ORDER BY
Access_Level desc
)
Correlated subquery:
SELECT
U.First_Name,
U.Last_Name,
U.Email,
(
select top 1 R.RName from Roles_Users RU ON U.User_ID = RU.User_ID
INNER JOIN Roles R ON RU.Role_ID = R.Role_ID
ORDER BY R.Access_Level
)
AS Role_Name
FROM Users U
In my opinion using a subquery is easier to read and write. In this code the correlated subquery will execute 1x per row returned. I like #Conrad's inner join solution, easiest and probably the most performant, and probably what i would use, just giving this as another option.
Not tested, but it goes something like this
SELECT
U.First_Name,
U.Last_Name,
U.Email,
R.Role_Name
FROM Users U
JOIN Roles_Users RU ON U.User_ID = RU.User_ID
JOIN (
SELECT ROLE_ID, MIN(ROLE_NAME) ROLE_NAME
FROM ROLES
GROUP BY ROLE_ID
HAVING ACCESS_LEVEL = MIN(ACCESS_LEVEL)
) R ON RU.Role_ID = R.Role_ID
SELECT Users.*, Roles.*
FROM
Users
JOIN Roles_Users ON Users.User_ID = Roles_Users.User_ID
JOIN Roles ON Roles.Role_ID = Roles_Users.Role_ID
WHERE
Access_Level = (
SELECT MIN(Access_Level)
FROM
Roles_Users
JOIN Roles ON Roles.Role_ID = Roles_Users.Role_ID
WHERE Users.User_ID = Roles_Users.User_ID
)
NOTE: This will not list users without any role.

MySQL JOIN from 3 relative tables

I have 3 tables
1. role (id, ...)
2. permission (id, ...)
3. role_permission (id, role_id, permission_id)
I want to do the next: i should select all roles and all permissions for this roles with help of one query. My test query:
SELECT `rp`.*, `r`.`role`, `r`.`id` AS `role_id`, `p`.`id` AS
`permission_id`,`p`.`name` AS `permission` FROM `role_permission` AS `rp` RIGHT JOIN
`role` AS `r` ON r.id = rp.role_id RIGHT JOIN `permission` AS `p` ON p.id = rp.permission_id
But this query select only those roles, what have permissions. But i need to select all roles and all permissions (exist row in role_permission or not).Thank you in advance. Sorry for my english.
You want a left join.
select
r.role,
r.id as role_id,
p.id as permission_id
from role
left join role_permission rp on rp.role_id = role.id
left join permission p on p.id = rp.permission_id
This will give you the permissions for all roles with permissions, as well as a null permission_id for roles without permissions.
You need to select all roles in a query, then all permissions in another, cross them and finally left join to role_permissions to produce the full list of roles x permissions
SELECT rp.*, r.role, r.id AS role_id, p.id AS permission_id,p.name AS permission
FROM permission AS p
CROSS JOIN role AS r
LEFT JOIN role_permission AS rp ON p.id = rp.permission_id
AND r.id = rp.role_id
The cross join gives you a full matrix of role x permission combinations, and the LEFT join succeeds even if the row in role_permission doesn't have any entries for a row (role, permission) in the full matrix.