Search code examples
phpmysqlconcatenationgroup-concat

How to get mysql Group_Concat to work with additional CONCAT information?


I'm working on a web app with mysql involving a user role table where permission levels are stored as well as role ids and associated to usernames and their ids (redundancy to continue supporting legacy code).

I want to be able to display every user's role as a comma seperated list in plain English with minimal hit to the DB (>1k users) as well as their permission level.

Permissions are stored as bits in the ismanager and ishead columns and roleids are keyed to define_roles (id, rolename)

Here's what I've got works (but doesn't show user levels)

<?
$rolenames = array();
foreach($db->query("SELECT id, rolename FROM define_roles") as $row)
{
    $rolenames[$row['id']] = $row['rolename'];
}

foreach($db->query("SELECT DISTINCT userid, username, 
                        GROUP_CONCAT(DISTINCT roleid  ORDER BY roleid) AS idlist
                        FROM user_roles 
                        GROUP BY userid 
                        ORDER BY username ASC") as $row)
{
    $rolestring = '';
    //echo $row['idlist'];
    foreach(explode(',',$row['idlist']) as $id)
    {
        $rolestring .= " ".$rolenames[$id].",";
    }
    $rolestring = rtrim($rolestring,',');

    echo "<tbody>
            <tr>
                <td><font size='1'>" . $row['username'] . "</font></td>
                <td><font size='1'>" .$rolestring. "</font></td>
            </tr>";
}
?>

Output:

  • TestUser Sales
  • UserTest2 Sales, Mailroom, Janitorial

What I want to see is

  • TestUser Sales(Manager)
  • UserTest2 Sales, Mailroom (Head), Janitorial(Manager)

The best I could come up with (though it doesn't work) is:

$rolenames = array();
foreach($db->query("SELECT id, rolename FROM define_roles") as $row)
{
    $rolenames[$row['id']] = $row['rolename'];
    $rolenames[$row['id']."10"] = $row['rolename']." (Manager)";
    $rolenames[$row['id']."01"] = $row['rolename']." (Head)";
    $rolenames[$row['id']."11"] = $row['rolename']." (Head)";
}

foreach($db->query("SELECT DISTINCT userid, username, 
                        GROUP_CONCAT
                        (
                            CONCAT(roleid,ismanager,ishead) ORDER BY roleid
                        ) AS idlist
                        FROM user_roles 
                        GROUP BY userid 
                        ORDER BY username ASC") as $row)

Solution

  • Firstly, you should probably normalize your database so that you have a separate table linking the users and defined roles. But that's a separate thing and I appreciate you may not be able to do that.

    If I've understood what's in your tables correctly, the answer is this, I think:

    SELECT 
        userid, 
        username, 
        GROUP_CONCAT(DISTINCT 
            CONCAT(
                (SELECT d.rolename FROM define_roles AS d
                WHERE d.id = roleid)
            ), 
            IF(ismanager = 1, " (Manager)", ""),
            IF(ishead = 1, " (Head)", "")
        ) AS roles
    FROM user_roles
    GROUP BY userid;
    

    You won't need the initial sql where you looked up the rolenames from define_roles.

    [EDIT]: Sorry, updated with brackets around the inner SELECT.