MySQL JOIN from 3 relative tables - sql

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.

Related

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)

SQL: a better way to include & exclude on the same field than, both, WHERE and NOT EXISTS?

I have this query which needs to, both, include and exclude on the same role_id field, so I'm calling the same table for both the NOT EXISTS() subquery and the INNER JOIN.
SELECT
u.fname
,u.lname
,c.country
,c.postal
FROM [user] u
INNER JOIN company c
ON (u.company_id = c.id)
INNER JOIN users_roles ur
ON (u.id = ur.user_id)
WHERE ur.role_id = 3
AND NOT EXISTS (SELECT 1
FROM users_roles
WHERE role_id = 4
AND user_id= u.id)
ORDER BY c.country, c.postal
This approach works, but seems kinda clunky, so I'm wondering whether there's a more standard (performant) approach that I'm not aware of?
I recommend running performance analysis on both queries, but you can also do this with a left outer join.
SELECT
u.fname
,u.lname
,c.country
,c.postal
FROM [user] u
INNER JOIN company c
ON (u.company_id = c.id)
INNER JOIN users_roles ur
ON (u.id = ur.user_id)
LEFT OUTER JOIN users_roles ur2
ON (u.id = ur2.user_id)
AND role_id = 4
WHERE ur.role_id = 3
AND ur2.user_id IS NULL
ORDER BY c.country, c.postal
This will end up with NULL in place of values for the ur2 table if no matching rows in the table are found; if you assert in the WHERE clause that the value is NULL, it will exclude anything that did have a match.
This one makes just one join, it uses grouping, sums the role_ids and checks on the aggregate result, the execution time on sqlfiddle is 6ms vs 24ms
SELECT
u.fname
,u.lname
,c.country
,c.postal
,sum(ur.role_id)
FROM [user] u
INNER JOIN company c
ON (u.company_id = c.id)
INNER JOIN users_roles ur
ON (u.id = ur.user_id)
and role_id in(3,4)
group BY u.fname, u.lname, c.country, c.postal
having sum(ur.role_id) = 3
go
You could join user_roles just once and it might help performance a tiny bit. I cannot make up my mind right now which query I like more.
SELECT
u.fname
,u.lname
,c.country
,c.postal
FROM [user] u
INNER JOIN company c
ON (u.company_id = c.id)
CROSS APPLY (
SELECT SUM (IIF(role_id = 3, 1, 0)) AS role3Count, SUM (IIF(role_id = 4, 1, 0)) AS role4Count
FROM users_roles ur
WHERE role_id BETWEEN 3 AND 4 AND u.id = ur.user_id
) ur
WHERE role3Count > 0 AND role4Count = 0
ORDER BY c.country, c.postal
This has the chance of being slightly faster because a single index seek on users_roles with range role_id BETWEEN 3 AND 4 is enough to retrieve the roles. In your query, there were two seeks. On the other hand this query does some aggregates and computations. You'd need to measure.
I'm not convinced this is a nicer query. It is more complex than yours. I'll propose it anyway. You decide.

List all friends for a userid SQL query

I have 2 tables a user table (user_id, fname, lname, dob, etc) and a are_friends table
(userA_id, userB_id). I have been trying to do this query for a while now, I need it to list all friends for a user_id.
What I have got so far,
SELECT
U.user_id,
U.fname,
U.lname
FROM are_friends A, user U
WHERE
A.user_id = U.user_id
AND (
A.user_id = 1
OR A.user_id IN (SELECT userB_id FROM are_friends WHERE userA_id = 1)
);
Any help will be much appreciated.
Try using an INNER JOIN like this:
SELECT u2.user_id, u2.fname, u2.lname
FROM user u
INNER JOIN are_friends f ON f.userA_id = u.user_id
INNER JOIN user u2 ON u2.user_id = f.userB_id
WHERE u.user_id = 1
You can change the WHERE clause to specifically get the friends of another user id.

SQL Get List of Users not in Roles

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

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.