0

I have four tables Users, Roles, User_Role, and Email. I need to develop a query that retrieves the user information (username, lastname, and firstname), the users email address (from the email table) and an aggregated list of all the roles (which is connected to the User tables with a many-to-many table user_role). I developed the query below which has one problem. The aggregated list has duplicate role name entries.

SELECT DISTINCT ON (u.username)
    u.username AS "Username", u.lastname AS "Last Name", u.firstname AS "First Name", e.name AS "Email Address"
    , string_agg(r.name, ';'  ORDER BY r.name) AS "Roles"
FROM    users u
    LEFT OUTER JOIN user_role ur ON (u.id=ur.user_id)
    LEFT OUTER JOIN roles r ON (ur.role_id = r.id)
    INNER JOIN email e ON e.user_id = u.id
WHERE   u.active = 1 AND length(r.name) > 0 AND r.active = 1 AND ur.active = 1
GROUP BY u.username, u.lastname, u.firstname, e.name
ORDER BY u.username

If I comment out the Inner email join and associated email fields (see below) I get the correct role names in the string_agg.

SELECT DISTINCT ON (u.username)
    u.username AS "Username", u.lastname AS "Last Name", u.firstname AS "First Name",
--  e.name AS "Email Address"
    , string_agg(r.name, ';'  ORDER BY r.name) AS "Roles"
FROM    users u
    LEFT OUTER JOIN user_role ur ON (u.id=ur.user_id)
    LEFT OUTER JOIN roles r ON (ur.role_id = r.id)
--  INNER JOIN email e ON e.user_id = u.id
WHERE   u.active = 1 AND length(r.name) > 0 AND r.active = 1 AND ur.active = 1
GROUP BY u.username, u.lastname, u.firstname, 
--  e.name
ORDER BY u.username
Cœur
  • 37,241
  • 25
  • 195
  • 267
RWBear
  • 233
  • 5
  • 16

1 Answers1

0

This would probably solve the problem. It's an educated guess. Not enough information in the question.

SELECT DISTINCT ON (u.username)
        u.username AS "Username"
      , u.lastname AS "Last Name"
      , u.firstname AS "First Name"
      , e.emails AS "Email Addresses"
      , string_agg(r.name, ';'  ORDER BY r.name) AS "Roles"
FROM    users u
LEFT    JOIN user_role ur ON u.id = ur.user_id
LEFT    JOIN roles     r  ON ur.role_id = r.id
LEFT    JOIN (
   SELECT user_id, string_agg(name, ', ') AS emails
   FROM   email
   GROUP  BY 1
   ) e  ON e.user_id = u.id
WHERE  u.active = 1
AND    r.name  ''
AND    r.active = 1
AND    ur.active = 1
GROUP  BY u.username, u.lastname, u.firstname, e.emails 
ORDER  BY u.username

Aside: r.name <> '' does the same as length(r.name) > 0, just faster.

Whether or not this should be a LEFT JOIN for emails instead of a JOIN is an unrelated question.
But I guess it should be, so I changed that, too.

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228