skip to Main Content

I have to join user_name from user table where as first_name and last_names from user_profile table. Until here everything is fine, but when I try to fetch respective roles assigned from user_role tables it gives multiple rows for single user as 1 user can have multiple roles.

While trying to apply string_agg on role.names (so that multiple roles shown comma separated in single tuple), it gives each role in separate row.

Here is example query I am trying to run in postgresql:

 SELECT users.user_name, user_profiles.first_name, user_profiles.last_name,
(
    SELECT string_agg (roles.name, ',') 
    from roles 
    where roles.id in (
        select user_roles.role_id where users.id = user_roles.user_id
    )
) as name
FROM users 
JOIN user_profiles ON users.id = user_profiles.user_id
JOIN user_roles    ON user_roles.user_id = users.id

2

Answers


  1. You must use GROUP BY clause in order to aggregate more than one record together within a group. That along with the unnecessary (I believe) nested SQL is leading you to wrong results.

    Instead consider the following:

        SELECT users.user_name, user_profiles.first_name, user_profiles.last_name,
            string_agg (roles.name, ',') as name
        FROM users 
          JOIN user_profiles 
            ON users.id = user_profiles.user_id
          JOIN user_roles
            ON user_roles.user_id = users.id
          JOIN roles ON user_roles.role_id = roles.id
        GROUP BY users.user_name, user_profiles.first_name, user_profiles.last_name
    

    Online demo

    Login or Signup to reply.
  2. You forgot a from in your innermost subquery:

    SELECT  users.user_name, 
            user_profiles.first_name, 
            user_profiles.last_name,
            (SELECT string_agg (roles.name, ', ') 
             from  roles 
             where roles.id 
             in (   select u.role_id 
                    from user_roles u --this was missing
                    where users.id = u.user_id)) as name
    FROM users 
        JOIN user_profiles 
            ON users.id = user_profiles.user_id
    --  JOIN user_roles --you're already getting roles in the innermost query
    --      ON user_roles.user_id = users.id;
    

    without it, that subquery was just selecting one, current row’s role_id, instead independently fetching of all roles for the user as you could’ve expected it to. See this demo.

    Note that once this is fixed, you also need to get rid of the last join (commented out in my example) to avoid multiplying users for each of their roles – you’re already getting their roles in the innermost subquery anyways.

    It would be easier to use a regular group by:

    select  users.user_name, 
            user_profiles.first_name, 
            user_profiles.last_name,
            string_agg(roles.name,', ')
    from users 
        inner join user_profiles 
            on users.id=user_profiles.user_id
        inner join user_roles 
            on user_roles.user_id=users.id
        inner join roles 
            on roles.id=user_roles.role_id
    group by 1,2,3;
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search