Home SQL Server Errors 229
Microsoft SQL Server

SQL Server Error 229

The permission was denied on the object

Severity 14 Login, Connection & Permissions
Applies to: SQL Server 2005 to 2022, Azure SQL Database, Azure SQL Managed Instance
The full message
Msg 229, Level 14, State 5, Line 1
The SELECT permission was denied on the object 'Salaries', database 'HR', schema 'dbo'.

Why it happens

Error 229 is a pure permissions problem. You already passed login and you can open the database, so the connection is fine. What you lack is the object-level right the statement needs: SELECT to read, INSERT, UPDATE or DELETE to change data, or EXECUTE to run a stored procedure. SQL Server checks that right on the exact object and, finding none, stops the statement. See the SQL Server security overview for how these rights fit together.

The message names everything you need to fix it: the permission (SELECT), the object (Salaries), the database (HR) and the schema (dbo). It is severity 14, a normal, catchable permission error, not a server fault.

Common triggers are a new login that was never granted access to the table, a report user who can read most tables but not this one, a stored procedure that reads a table under a broken ownership chain, or an explicit DENY that a role carries and that overrides any GRANT the user also has. If the block is on a whole statement class rather than an object, you are looking at error 262 instead.

Examples

SELECT denied on a table

-- Connected as AppUser, which was never granted read on Salaries.
SELECT id, amount
FROM dbo.Salaries;
Result
Msg 229, Level 14, State 5, Line 1
The SELECT permission was denied on the object 'Salaries', database 'HR', schema 'dbo'.

EXECUTE denied on a stored procedure

-- AppUser has no EXECUTE right on the procedure.
EXEC dbo.GetSalaryReport @Year = 2026;
Result
Msg 229, Level 14, State 5, Line 1
The EXECUTE permission was denied on the object 'GetSalaryReport', database 'HR', schema 'dbo'.

How to fix it

Grant the permission the statement needs

The direct fix is to give the login the exact right the message names, on the exact object. Grant SELECT to read, INSERT, UPDATE or DELETE to change data, or EXECUTE to run a procedure.

USE HR;
GO

-- Let AppUser read the table
GRANT SELECT ON dbo.Salaries TO [AppUser];

-- Or let AppUser run the procedure
GRANT EXECUTE ON dbo.GetSalaryReport TO [AppUser];

Add the user to a database role

Instead of granting one object at a time, put the user in a role. db_datareader grants SELECT on every table and view, db_datawriter grants INSERT, UPDATE and DELETE. A custom role lets you group a smaller set of rights and reuse it.

USE HR;
GO

-- Read access to all tables and views
ALTER ROLE db_datareader ADD MEMBER [AppUser];

-- Or a custom role you grant once and reuse
CREATE ROLE ReportReaders;
GRANT SELECT ON dbo.Salaries TO ReportReaders;
ALTER ROLE ReportReaders ADD MEMBER [AppUser];

Fix a procedure that cannot read its own table

When a stored procedure hits 229 while reading a table, and the caller has EXECUTE on the procedure, the problem is usually a broken ownership chain. If the procedure and the table share the same owner (both dbo), SQL Server skips the table check for the caller through ownership chaining, so you only need to grant EXECUTE on the procedure. If the owners differ, run the body under a chosen identity with EXECUTE AS.

-- Same owner (dbo): ownership chaining means EXECUTE alone is enough.
GRANT EXECUTE ON dbo.GetSalaryReport TO [AppUser];

-- Different owners: run the body as an identity that can read the table.
CREATE OR ALTER PROCEDURE dbo.GetSalaryReport
    @Year INT
WITH EXECUTE AS OWNER
AS
    SELECT id, amount FROM dbo.Salaries WHERE year = @Year;

Remove an explicit DENY that overrides the grant

A DENY always beats a GRANT, so a user can be in db_datareader and still be blocked if any role they belong to denies the object. Check for a DENY and revoke it, then let the grant apply.

USE HR;
GO

-- See what AppUser holds on the object
SELECT dp.permission_name, dp.state_desc
FROM sys.database_permissions dp
JOIN sys.objects o ON o.object_id = dp.major_id
WHERE o.name = 'Salaries';

-- Remove the blocking DENY (REVOKE clears the deny row)
REVOKE SELECT ON dbo.Salaries FROM [AppUser];
GRANT SELECT ON dbo.Salaries TO [AppUser];

How to prevent it

Grant access through roles, not to individual users. Put people in db_datareader, db_datawriter or a custom role, and manage membership rather than a long list of per-object grants. This keeps permissions consistent and easy to audit.

Follow least privilege: give each login only the rights it actually uses, and avoid stray DENY statements, which override grants and cause confusing 229 errors long after they were added. For the wider picture, see the SQL Server security guide, and if a login cannot even open the database, see error 15247.

Common questions

How do I fix SQL Server error 229?

Grant the login the exact permission the message names on the exact object, for example GRANT SELECT ON dbo.Salaries TO [AppUser]. To cover many objects at once, add the user to a role such as db_datareader or db_datawriter. If the user should already have it, check for an explicit DENY, which overrides any GRANT.

What is the difference between error 229 and 262?

Error 229 is an object permission error: you lack SELECT, INSERT, UPDATE, DELETE or EXECUTE on a specific table, view or procedure. Error 262 is a statement permission error: you lack the right to run a whole statement class such as CREATE TABLE or CREATE PROCEDURE, which is not tied to any one existing object. See error 262 for that case.

Why does a stored procedure get error 229 when reading a table?

If the caller has EXECUTE on the procedure but still gets 229 on a table, ownership chaining is not covering the read. When the procedure and table share the same owner (both dbo), granting EXECUTE is enough. When the owners differ, add WITH EXECUTE AS so the body runs under an identity that can read the table.

Still stuck on a SQL Server error?

Browse the full SQL Server error library, or get hands-on help from an experienced SQL professional.