Ryan
Ryan

Reputation: 6866

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

Upvotes: 2

Views: 105

Answers (5)

Pez
Pez

Reputation: 178

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.

Upvotes: 1

Conrad Frix
Conrad Frix

Reputation: 52675

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
                    ) 

Upvotes: 2

Branko Dimitrijevic
Branko Dimitrijevic

Reputation: 52147

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.

Upvotes: 0

Hugh Jones
Hugh Jones

Reputation: 2694

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

Upvotes: 0

marc_s
marc_s

Reputation: 755321

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.

Upvotes: 3

Related Questions