# Is a Single `role` Column Enough for User Roles in MySQL?
Have you ever added a `role` column to your `users` table to manage access to your application, only to realise later that your users might need more than one role?
This is a common starting point when building authentication and authorisation systems. You create a users table, add a `role` column, and assign each user a role such as `admin`, `manager`, or `worker`.
At first, everything seems straightforward. But as your application grows, this design can become restrictive.
Let’s look at an example using a real MySQL users table.
1. Understanding the current users table
First, let’s inspect the structure of our existing `users` table:
DESC users;
The output shows that our table contains several fields, including:
id
first_name
last_name
cellphone
country
password
role
created_at
updated_at
status
approved_at
approved_by
deleted_at
The important field for this discussion is:
role varchar(20) NOT NULL
This means every user must have a role value, and the column can store a single string of up to 20 characters.
There is nothing inherently wrong with this approach if every user will always have exactly one role.
However, what happens when a user needs to be both a manager and an administrator?
A single `role` column cannot properly represent that relationship.
2. Checking which roles currently exist
Before changing the database structure, let’s find out which roles are already being used.
We can use the `DISTINCT` keyword:
SELECT DISTINCT role FROM users;
Our output is:
+———+
| role |
+———+
| admin |
| manager |
| worker |
+———+
3 rows in set
We currently have three distinct roles:
– `admin`
– `manager`
– `worker`
This confirms that our application currently recognises three role values.
3. Counting users assigned to each role
Next, let’s find out how many users belong to each role.
SELECT role, COUNT(*) AS total FROM users GROUP BY role;
The result is:
+———+——-+
| role | total |
+———+——-+
| admin | 1 |
| manager | 1 |
| worker | 1 |
+———+——-+
At the moment, each role has one user assigned to it.
This query is particularly useful when managing applications with many users because it gives you a quick overview of how users are distributed across different roles.
4. Viewing users and their assigned roles
We can also retrieve individual users together with their assigned roles:
SELECT id, first_name, last_name, email, role FROM users;
Our current output looks like this:
+—-+————+———–+———————–+———+
| id | first_name | last_name | email | role |
+—-+————+———–+———————–+———+
| 1 | j | 2 | j2@bigfarms.com | admin |
| 2 | j | 3 | j3@bigfarms.com | manager |
| 3 | j | 4 | j4@bigfarms.com | worker |
+—-+————+———–+———————–+———+
As you can see, each user has exactly one assigned role.
We can also retrieve users with a particular role. For example, to find all administrators:
SELECT * FROM users WHERE role = ‘admin’;
This works perfectly well with the current database design.
The limitation only becomes apparent when a user needs multiple roles.
5. The problem with storing multiple roles in one column
Imagine that one of your managers is also responsible for administering the application.
You might be tempted to store both roles in the same column:
admin,manager
Although MySQL allows this because the column is a `VARCHAR`, it introduces problems.
For example:
– Searching for users with a particular role becomes more complicated.
– Enforcing valid role names becomes difficult.
– You cannot easily establish a foreign key relationship between individual roles and users.
– Managing permissions becomes harder.
– Reporting and filtering become less reliable.
A better database design is to separate users and roles.
6. A better approach: Create a roles table
Instead of storing role names directly inside the `users` table, we can create a dedicated `roles` table.
CREATE TABLE roles (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL UNIQUE
);
We can then populate it with our existing roles:
INSERT INTO roles (name)
VALUES
(‘admin’),
(‘manager’),
(‘worker’);
Next, we create a junction table called `user_roles`.
CREATE TABLE user_roles (
user_id INT UNSIGNED NOT NULL,
role_id INT UNSIGNED NOT NULL,PRIMARY KEY (user_id, role_id),
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (role_id) REFERENCES roles(id)
);
This table connects users to roles.
The relationship is now many-to-many:
– One user can have multiple roles.
– One role can belong to multiple users.
For example, a user who is both an administrator and a manager would have two records in `user_roles`.
This is much more flexible than storing multiple role names in a single column.
7. Taking permissions a step further
There is another important consideration when designing role-based access control.
A role such as `admin` or `manager` does not automatically explain what a user is allowed to do.
For example, should a manager be allowed to:
– Create users?
– Approve workers?
– Edit records?
– Delete records?
– View reports?
One approach is to introduce a dedicated `permissions` table.
CREATE TABLE permissions (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE
);
We can then create a `role_permissions` junction table:
CREATE TABLE role_permissions (
role_id INT UNSIGNED NOT NULL,
permission_id INT UNSIGNED NOT NULL,PRIMARY KEY (role_id, permission_id),
FOREIGN KEY (role_id) REFERENCES roles(id),
FOREIGN KEY (permission_id) REFERENCES permissions(id)
);
Now each role can have multiple permissions, and each permission can be assigned to multiple roles.
For example:
| Role | Permission |
| — | — |
| admin | create\_user |
| admin | delete\_user |
| manager | approve\_worker |
| manager | view\_reports |
| worker | view\_assigned\_tasks |
The advantage is that permissions are no longer tightly coupled to role names in your application code.
You can change which permissions belong to a role by updating database records rather than rewriting role-specific permission logic.
## Conclusion
Our current `users` table works because each user has one role: `admin`, `manager`, or `worker`.
However, as an application grows, users may need to perform responsibilities that span multiple roles.
When that happens, a single `role` column becomes restrictive.
Separating roles into their own table and introducing a `user_roles` junction table gives your application a more flexible and scalable foundation.
Adding a permissions system takes the design further by allowing you to control exactly what each role can do.
**The takeaway:** Whenever you design a database column that stores a category, role, tag, or type, ask yourself whether a record might eventually need more than one value. If the answer is yes, consider modelling that relationship with separate tables from the beginning.
Until next time\!
One implementation note: Your existing `users.role` column should not be dropped immediately when migrating to this design. First, create the new tables, migrate the existing `admin`, `manager`, and `worker` assignments into `roles` and `user_roles`, update your application code, and verify everything before removing the old column.
