SQL GRANT and REVOKE Permissions
Controlling access to your database goes far beyond creating users and roles. The real power lies in deciding exactly what each user or role is allowed to do and on which specific objects. This is where the GRANT and REVOKE statements come into play. Together, they form the backbone of SQL GRANT REVOKE permission management, giving you fine-grained control over who can read, modify, or manage your data.
This guide covers everything you need to confidently manage permissions in SQL: granting and revoking privileges at the table level, restricting access down to individual columns, applying permissions across entire schemas, and avoiding the most common pitfalls along the way. All examples are practical and applicable to major database systems like PostgreSQL, MySQL, SQL Server, and Oracle.
How SQL Permissions Work
SQL follows a whitelist model for permissions. By default, a newly created user has no access to any data. Every permission must be explicitly granted. This design ensures that no user accidentally ends up with more access than intended.
The two core statements are:
GRANTadds permissions, allowing a user or role to perform specific actions.REVOKEremoves permissions, taking away previously granted access.
Permissions can be applied at multiple levels of granularity:
| Level | What It Controls |
|---|---|
| Database | Connection, creation of schemas |
| Schema | Usage, creation of objects within a schema |
| Table | SELECT, INSERT, UPDATE, DELETE on entire tables |
| Column | SELECT, INSERT, UPDATE on specific columns only |
Think of permissions as layers. You can grant broad access at the schema level and then fine-tune restrictions at the table or column level depending on your security requirements.
GRANT: Adding Permissions
The GRANT statement gives a user or role permission to perform one or more operations on a database object.
Basic Syntax
GRANT privilege_type [, privilege_type, ...]
ON object_name
TO user_or_role;
Granting Table-Level Permissions
The most common use case is granting specific privileges on a table.
Grant read access:
GRANT SELECT ON employees TO alice;
Now alice can run SELECT queries against the employees table, but she cannot insert, update, or delete rows.
Grant multiple privileges at once:
GRANT SELECT, INSERT ON employees TO alice;
This allows alice to both read and add new rows to the employees table.
Grant full data manipulation privileges:
GRANT SELECT, INSERT, UPDATE, DELETE ON employees TO data_editor;
Available Privilege Types
Different database systems support slightly different privilege names, but the most common ones are universal:
| Privilege | What It Allows |
|---|---|
SELECT | Read rows from a table or view |
INSERT | Add new rows to a table |
UPDATE | Modify existing rows in a table |
DELETE | Remove rows from a table |
TRUNCATE | Remove all rows from a table (PostgreSQL) |
REFERENCES | Create foreign keys referencing a table |
TRIGGER | Create triggers on a table |
ALL PRIVILEGES | Every available privilege on the object |
Granting to Roles vs. Users
You can grant permissions directly to a user or to a role. Granting to a role is almost always the better practice because it keeps your permission management centralized and reusable.
-- Grant to a role (recommended)
CREATE ROLE reporting_team;
GRANT SELECT ON employees TO reporting_team;
GRANT SELECT ON departments TO reporting_team;
GRANT SELECT ON salaries TO reporting_team;
-- Then assign the role to users
GRANT reporting_team TO alice;
GRANT reporting_team TO bob;
GRANT reporting_team TO charlie;
If you later need to add access to a new table for the entire reporting team, you update the role once:
GRANT SELECT ON projects TO reporting_team;
-- All three users instantly gain access
GRANT ALL PRIVILEGES
The ALL PRIVILEGES shorthand grants every applicable permission on the specified object.
GRANT ALL PRIVILEGES ON employees TO db_admin;
This gives db_admin full control over the employees table: SELECT, INSERT, UPDATE, DELETE, and any other table-level privilege supported by your database system.
Use ALL PRIVILEGES sparingly. In production environments, it is almost always better to grant only the specific privileges a user or role actually needs. The principle of least privilege exists to reduce the blast radius if an account is compromised or misused.
WITH GRANT OPTION
By default, a user who receives a privilege cannot pass that privilege to other users. The WITH GRANT OPTION clause changes this behavior.
GRANT SELECT ON employees TO team_lead WITH GRANT OPTION;
Now team_lead can grant SELECT on employees to other users:
-- Connected as team_lead
GRANT SELECT ON employees TO new_intern;
-- Success: team_lead is allowed to delegate this privilege
Without WITH GRANT OPTION:
GRANT SELECT ON departments TO team_lead;
-- Connected as team_lead
GRANT SELECT ON departments TO new_intern;
-- ERROR: permission denied for table departments
WITH GRANT OPTION creates a chain of trust. If you revoke the privilege from team_lead, the privileges they delegated to others may also be revoked (this behavior is called cascading revoke and varies by database system). Use this feature carefully and document who has grant options.
GRANT Examples Across Database Systems
PostgreSQL:
GRANT SELECT, INSERT ON employees TO app_service;
GRANT USAGE ON SEQUENCE employees_id_seq TO app_service;
In PostgreSQL, if a table has an auto-incrementing column using a sequence, you must also grant USAGE on the sequence for INSERT to work properly.
MySQL:
GRANT SELECT, INSERT ON company_db.employees TO 'app_service'@'localhost';
MySQL requires you to specify the database with the table name and the user's host.
SQL Server:
USE company_db;
GRANT SELECT, INSERT ON dbo.employees TO app_service;
SQL Server uses the schema.table format (commonly dbo as the default schema).
Oracle:
GRANT SELECT, INSERT ON hr.employees TO app_service;
Oracle uses the owner.table format.
REVOKE: Removing Permissions
The REVOKE statement removes previously granted privileges from a user or role. Its syntax mirrors GRANT.
Basic Syntax
REVOKE privilege_type [, privilege_type, ...]
ON object_name
FROM user_or_role;
Examples
Revoke a single privilege:
REVOKE INSERT ON employees FROM alice;
alice can no longer insert rows into employees, but any other privileges she has (like SELECT) remain intact.
Revoke multiple privileges:
REVOKE INSERT, UPDATE, DELETE ON employees FROM alice;
Revoke all privileges:
REVOKE ALL PRIVILEGES ON employees FROM alice;
REVOKE Across Database Systems
PostgreSQL:
REVOKE SELECT ON employees FROM reporting_team;
MySQL:
REVOKE SELECT ON company_db.employees FROM 'alice'@'localhost';
SQL Server:
REVOKE SELECT ON dbo.employees FROM alice;
Oracle:
REVOKE SELECT ON hr.employees FROM alice;
Common Mistake: Revoking From a User When the Privilege Came From a Role
A frequent source of confusion is attempting to revoke a privilege directly from a user when that user actually receives the privilege through a role.
Scenario:
-- Setup
CREATE ROLE read_only;
GRANT SELECT ON employees TO read_only;
CREATE USER alice WITH PASSWORD 'Alice!2024';
GRANT read_only TO alice;
Wrong approach:
REVOKE SELECT ON employees FROM alice;
This revokes any direct SELECT grant on employees from alice, but she still has SELECT through the read_only role. She can still query the table.
Correct approach (option 1): Revoke the role from the user:
REVOKE read_only FROM alice;
Correct approach (option 2): Revoke the privilege from the role:
REVOKE SELECT ON employees FROM read_only;
-- This affects ALL users who have the read_only role
Choose the approach based on whether you want to affect just one user or everyone with that role.
Verifying Current Permissions
After granting or revoking permissions, you should verify the current state.
PostgreSQL:
-- Check table-level privileges
SELECT grantee, privilege_type, table_name
FROM information_schema.table_privileges
WHERE table_name = 'employees';
Output:
grantee | privilege_type | table_name
------------+----------------+------------
data_editor| SELECT | employees
data_editor| INSERT | employees
data_editor| UPDATE | employees
alice | SELECT | employees
MySQL:
SHOW GRANTS FOR 'alice'@'localhost';
Output:
+--------------------------------------------------------------+
| Grants for alice@localhost |
+--------------------------------------------------------------+
| GRANT USAGE ON *.* TO `alice`@`localhost` |
| GRANT SELECT ON `company_db`.`employees` TO `alice`@`localhost` |
+--------------------------------------------------------------+
SQL Server:
SELECT
dp.name AS principal_name,
o.name AS object_name,
p.permission_name,
p.state_desc
FROM sys.database_permissions p
JOIN sys.objects o ON p.major_id = o.object_id
JOIN sys.database_principals dp ON p.grantee_principal_id = dp.principal_id
WHERE o.name = 'employees';
Column-Level Permissions
Sometimes granting access to an entire table is too broad. You might want a user to see employee names and departments but not their salaries or personal information. Column-level permissions solve this.
Syntax
GRANT privilege_type (column1, column2, ...)
ON table_name
TO user_or_role;
Practical Example
Consider an employees table with these columns:
CREATE TABLE employees (
id SERIAL PRIMARY KEY,
first_name VARCHAR(50),
last_name VARCHAR(50),
email VARCHAR(100),
department VARCHAR(50),
salary DECIMAL(10, 2),
ssn VARCHAR(11)
);
The salary and ssn columns contain sensitive data. You want the support team to see basic employee information but not financial or personal details.
-- Grant SELECT on only the non-sensitive columns
GRANT SELECT (id, first_name, last_name, email, department)
ON employees
TO support_team;
Testing the permissions:
-- Connected as a member of support_team
SELECT id, first_name, department FROM employees;
Output:
id | first_name | department
----+------------+------------
1 | Emma | Engineering
2 | Frank | Marketing
-- Trying to access a restricted column
SELECT id, first_name, salary FROM employees;
-- ERROR: permission denied for column "salary" of relation "employees"
-- SELECT * also fails because it includes restricted columns
SELECT * FROM employees;
-- ERROR: permission denied for column "salary" of relation "employees"
Column-Level INSERT and UPDATE
You can also restrict which columns a user can write to.
-- Allow updating only the department and email, not salary
GRANT UPDATE (department, email) ON employees TO hr_assistant;
Testing:
-- Connected as hr_assistant
UPDATE employees SET department = 'Sales' WHERE id = 1;
-- Success
UPDATE employees SET salary = 999999 WHERE id = 1;
-- ERROR: permission denied for column "salary" of relation "employees"