Select users that have n roles for reporting functions SQL - sql

I have an application that uses a standard Role, User and UserRoleJoin architecture.
The application needs to send reports to users based on what group of roles they may be in. For example
Role:
ID Name
1 Lead
2 Mech
3 Elec
4 Auditor
5 Assigner
UserRole:
UserID RoleID
1 1
1 2
1 4
2 1
2 4
I want to be able to send one report to all users that are Leads, Mech and Auditors or a different report to all users that are leads and auditors.
The first report would only be sent to user 1 while the second report would be sent to user 2
Should I have a different table that handle roles to reports associations or should I deal with these groups by a select query?

There are several ways. I would be inclined to use group by and having:
select ur.UserId
from UserRole ur join
Role r
on ur.RoleID = r.Id
where name in ('Leads', 'Mech', 'Auditors')
group by ur.UserId
having count(*) = 3;
The advantage of this approach is that the logic is all in the having clause. You can make it more flexible, such as all Leads and Mechs who are not Auditors.

If those groups are stable enough they deserve a way to persist them in the DB, kind of Group(ID PK, Name), RoleGroup(GroupID , RoleID)
declare #grpName varchar(50) = 'my_group';
select ur.UserId
from UserRole ur
join RoleGroup rg on rg.RoleID = ur.RoleID
join Group g on rg.GroupID = g.ID and g.Name = #grpName
group by ur.UserId
having count(*) = (select count(*) n
from RoleGroup rg
join Group g on rg.GroupId = g.ID and g.Name = #grpName);

Related

How to sum up max values from another table with some filtering

I have 3 tables
User Table
id
Name
1
Mike
2
Sam
Score Table
id
UserId
CourseId
Score
1
1
1
5
2
1
1
10
3
1
2
5
Course Table
id
Name
1
Course 1
2
Course 2
What I'm trying to return is rows for each user to display user id and user name along with the sum of the maximum score per course for that user
In the example tables the output I'd like to see is
Result
User_Id
User_Name
Total_Score
1
Mike
15
2
Sam
0
The SQL I've tried so far is:
select TOP(3) u.Id as User_Id, u.UserName as User_Name, SUM(maxScores) as Total_Score
from Users as u,
(select MAX(s.Score) as maxScores
from Scores as s
inner join Courses as c
on s.CourseId = c.Id
group by s.UserId, c.Id
) x
group by u.Id, u.UserName
I want to use a having clause to link the Users to Scores after the group by in the sub query but I get a exception saying:
The multi-part identifier "u.Id" could not be bound
It works if I hard code a user id in the having clause I want to add but it needs to be dynamic and I'm stuck on how to do this
What would be the correct way to structure the query?
You were close, you just needed to return s.UserId from the sub-query and correctly join the sub-query to your Users table (I've joined in reverse order to you because to me its more logical to start with the base data and then join on more details as required). Taking note of the scope of aliases i.e. aliases inside your sub-query are not available in your outer query.
select u.Id as [User_Id], u.UserName as [User_Name]
, sum(maxScore) as Total_Score
from (
select s.UserId, max(s.Score) as maxScore
from Scores as s
inner join Courses as c on s.CourseId = c.Id
group by s.UserId, c.Id
) as x
inner join Users as u on u.Id = x.UserId
group by u.Id, u.UserName;

Query Across Join Exclusively

I am trying to write a query to determine who, in my company, has roles that I specify, and no others.
The tables are User, UserRole, and Role. An (incorrect) example is below and I have tried a few different ways like this, but they all seem to return a user when they just contain the roles.
select U.Username from User U
join UserRole UR on U.UserID = UR.UserID
join Role R on UR.RoleID = R.RoleID
where R.RoleName in ('Role1', 'Role2', 'Role3')
Example User table
User ID
UserName
1
Joe
2
Bob
Example UserRole Table
UserID
RoleID
1
1
1
2
1
3
2
2
2
3
Example Role Table
RoleID
RoleName
1
Admin
2
SysAdmin
3
Manager
For example, I want to query for everyone that only has the SysAdmin, and manager roles. Although Joe has those roles I don't want him to be included in the result.
I feel like there is something simple that I am missing. However, after doing research online, I am unable to find a similar scenario.
If I understood your requirements:
select U.Username
from User U
join UserRole UR on U.UserID = UR.UserID
join Role R on UR.RoleID = R.RoleID
where R.RoleName in ('Role1', 'Role2', 'Role3')
GROUP BY U.Username
HAVING COUNT(R.RoleName)=3
The above is untested but should give you enough hints to solve your problem

select a column from another table mulipletimes problem

User
USER_ID USERNAME
1 -
2 Chris
3 Dave
4 Vlad
Issue
Creator RESOLVER VERIFIER
2 3 4
2 3 1
3 1 1
expected output:
Creator RESOLVER VERIFIER
Chris Dave Vlad
Chris Dave -
Dave - -
current code I have:
SELECT creatorid.username, resolverid.username, verifierid.username
FROM issue
JOIN user creatorid ON issue.creator = creatorid.user_id
JOIN user resolverid ON issue.resolver = resolverid.user_id
JOIN user verifierid ON issue.verifier = verifierid.user_id
do i have to join the table 3 times to get the corresponding username of the user_id in issue table or is there is a simpler way of doing this? Asking as this is a simplified version of the tables, the User and Issue table contains a lot of other columns. Thanks
Because of the join, you will see each issue three times which is not what you want. You could use three scalar subqueries to get around that:
select i.id,
(select username from users u1 where u1.user_id = i.creator) as creator,
(select username from users u2 where u2.user_id = i.resolver) as resolver,
(select username from users u3 where u3.user_id = i.verifier) as verifier
from issue i;
This isn't going to be fast though.
Another option is to aggregate all user_id / username pairs into a JSON object, then use that in a sub-query:
select i.id, -- other columns from the issue table
u.names ->> i.creator::text as creator,
u.names ->> i.resolver::text as resolver,
u.names ->> i.verifier::text as verifier
from issue i
join lateral (
select jsonb_object_agg(user_id, username) as names
from users u
where u.user_id in (i.creator, i.resolver, i.verifier)
) u on true;
The traditional way to do this is:
select i.*, uc.username, ur.username, uv.username
from issue i left join
users uc
on uc.user_id = i.creator left join
users ur
on ur.user_id = i.resolver left join
users uv
on uv.user_id = i.verifier;

sql: many to many relationship join

I'm very new to SQL so if there are multiple possibilities I'd like to see them all (and hear which possibilities are better than others). I'm using sqlite3.
I have the following 3 tables: user, channel, subscriptions
user:
user_id name
1 Johnny
2 Stacy
3 Allana
channel:
channel_id channel_name
1 ESPN
2 Disney
subscriptions:
user_id channel_id
1 1
2 2
3 1
3 2
What SQL command do I need to perform to get the following table? I basically want to see who is subscribed to which channels by names (so exactly what's laid out in the subscriptions table but mapping numbers to names based on the other tables).
user_id channel_id
Johnny ESPN
Stacy Disney
Allana ESPN
Allana Disney
I've tried the following but I'm getting nothing in the return statement:
select user.name, channel.channel_name from user, channel, subs where user.user_id == subs.user_id and channel.channel_id == subs.channel_id
Try this out and let me know in case you face any difficulty.
select a.name,c.channel_name
from
user a
left join
subscriptions b
on a.user_id = b.user_id
left join
channel c
on b.channel_id = c.channel_id;
or (in the format u asked in comments)
select u.name,c.channel_name
from
user u
left join
subscriptions s
on u.user_id = s.user_id
left join
channel c
on s.channel_id = c.channel_id;
Haven't tested it but try this:
select
u.name
,c.channel
from
user_id u
inner join subscriptions s
on u.user_id=s.user_id
inner join channel c
on s.channel_id=c.channel_id

Given a specific user I want to find all users having at least all of the roles this specific user has

Suppose I have user_role table having user and role columns.
| User | Role |
---------------
| 100 | 10 |
| 101 | 10 |
| 101 | 11 |
| 102 | 11 |
I want to write a query that will return users with same or lesser roles. For example:
Return user 100 for user 100
Return user 100,101,102 for user 101
Return user 102 for user 102
Business requirement: Suppose User X belongs to Asia group only. So X should have access permission to users who belongs to Asia group only. But say Y belongs to Asia and Europe groups. So Y should have access permission to users who belongs to:
Asia group only
Europe group only
Asia and Europe group only
Now, X should not access the data of Y as X does not belong to all the groups that Y belongs to. Similarly, say Z belongs to Asia, Europe and America. So, Z should access all the data of X, Y and Z but the reverse is not true.
My initial SQL:
select distinct(user) from user_role where role in
(select role from user_role where user=?);
Above query returns all the users sharing at least one common groups and not all common groups.
Can anybody please help me with a SQL example?
This can be done with much more less effort. The idea is left join roles on roles of particular user and then filter only those users for which all roles are found in that particular user's roles:
;with c as(select roleid from userroles where userid=100)
select r.userid from userroles r left join c on r.roleid = c.roleid
group by r.userid
having sum(case when c.roleid is null then 1 else 0 end) = 0
Fiddle http://sqlfiddle.com/#!6/bca579/7
Try this:
-- Create a CTE that will help us know the number of roles any user have.
;WITH CTE (UserId, RoleId, NumberOfRoles)
AS (
SELECT T1.UserId, RoleId, NumberOfRoles
FROM UsersToRoles T1 INNER JOIN
(
-- Derived table is needed so that we can have
-- the user, he's roleId and he's number of roles in the CTE
SELECT UserId, COUNT(RoleId) As NumberOfRoles
FROM UsersToRoles
GROUP BY UserId
) T2 ON(T1.UserId = T2.UserId)
)
-- We join the CTE with itself on the RoleId to get only users that have the same roles,
-- and on the NumberOfRoles to ensure that the users we get back have at least the nomber of roles as the user we testing.
SELECT DISTINCT T1.UserId
FROM CTE T1
INNER JOIN CTE T2 ON(T1.RoleId = T2.RoleId AND T1.NumberOfRoles <= T2.NumberOfRoles)
WHERE T2.UserId = #UserId
Play with it yourself in this sql fiddle
CTE, or Common Table Expressions is a concept introduced in Sql Server 2008. basically, you define a select statement that the rest of your sql can refer to as if it was a view.
In this case, you could have this CTE written as a view and it would give you the same result.