Skip to content

Est. 2011ยทMicrosoft Partner 7033487ยทDelivery under 3 minยทSupport 7 days a week

Your vault is empty.

Free Fix 208

Error 208: Invalid Object Name – Schema, Database Context and Collation

11 min read Updated October 4, 2026 SQL Server

Fix it now

Error 208 is “Invalid object name” and it very rarely means the table has been dropped. Far more often the session is in a different database than you think, the caller’s default schema is not dbo, the collation makes names case-sensitive, or the caller cannot see the object at all. Qualifying names with the schema removes most of it in one move.

Run in the session that failed, not in a fresh window of your own

SELECT DB_NAME() AS CurrentDb, SCHEMA_NAME() AS DefaultSchema,
       SUSER_SNAME() AS Login, USER_NAME() AS DbUser;
SELECT OBJECT_ID('dbo.YourTable') AS ResolvesTo;
SELECT s.name AS SchemaName, o.name AS ObjectName, o.type_desc
FROM sys.objects AS o
JOIN sys.schemas AS s ON s.schema_id = o.schema_id
WHERE o.name LIKE '%YourTable%';
  1. Read the first result. If DB_NAME() is master or tempdb, that is the whole answer and a USE or a corrected initial catalog fixes it.
  2. A NULL from OBJECT_ID means this session cannot resolve the name – which is the same conclusion the query reached, and includes the case where the caller has no permission on it.
  3. Qualify every reference with its schema. dbo.YourTable resolves identically for every caller; an unqualified name does not.
  4. If the object is in another database, use three parts. Across servers, use four and check the linked server exists before blaming the name.
  5. If it is a temporary table, confirm it was created in the same session and has not gone out of scope.

Run this as the account that failed, using EXECUTE AS USER = 'AppUser'; and REVERT; if you cannot log in as it. Metadata is filtered by permission, so your own session is not a fair test.

If the name resolves for the account that failed, you are done. If not, the next section explains how the engine turns a name into an object.

Why it happens

An unqualified name is not a complete address. When the engine sees YourTable, it looks first in the default schema of the database user running the statement, and then in dbo. That means the same query can succeed for one login and fail for another purely because their users were created with different default schemas. The failure is deterministic and repeatable, which is what makes it so confusing when only some people report it.

Resolution is also scoped to the current database. A job step, a connection string or an application that opens on master and never issues a USE fails on every unqualified name, even though the objects plainly exist somewhere on the instance. The tool you are testing in almost always has the right database selected, which is why the same statement works interactively and fails in production.

Then there is visibility. Microsoft documents metadata visibility as limited to securables a user owns or has been granted some permission on: a catalogue query for an object the caller has no rights to returns an empty result set rather than an error. The same rule reaches the built-in functions, and the OBJECT_ID documentation says so explicitly – a metadata-emitting function such as OBJECT_ID can return NULL if the user has no permission on the object. That is why a diagnosis run as sysadmin proves nothing about what the application account can see.

Finally, procedures behave differently from ad hoc batches. Microsoft’s CREATE PROCEDURE reference states that a procedure can reference tables that do not yet exist, that only syntax checking is performed at creation time, and that objects are resolved when the procedure is first executed. So a procedure that references a missing table is created quite happily and fails later – by design, so that procedures can be created in any order.

The caller has a different default schema

You have this one if The identical unqualified query works for a sysadmin and fails for an application login.

  1. Check it: SELECT name, default_schema_name FROM sys.database_principals WHERE type IN ('S','U','G');
  2. Set it if it is wrong: ALTER USER AppUser WITH DEFAULT_SCHEMA = dbo;
  3. Better, qualify the names in the code so the default schema stops mattering at all.

An EXECUTE AS block changes the effective user, and therefore the default schema, for everything inside it. That is useful for testing and surprising when it happens in production code.

The session is in the wrong database

You have this one if SELECT DB_NAME(); returns master, tempdb or something unrelated.

  1. Add USE YourDatabase; at the top of the batch, or set the correct database as the initial catalog in the connection string.
  2. For an Agent job step, set the database on the step itself rather than relying on the service account’s default.
  3. Check the login’s default database: SELECT name, default_database_name FROM sys.server_principals;

The database collation is case-sensitive

You have this one if SELECT * FROM dbo.Customers fails while dbo.customers works, or the reverse.

  1. Check it: SELECT name, collation_name FROM sys.databases; A collation containing CS is case-sensitive, and object names follow it.
  2. Match the case exactly as the object was created, which you can read from sys.objects.
  3. Do not change the database collation to work around this. It affects comparisons, sorting and every column created afterwards.

The caller cannot see the object

You have this one if The object exists and the name is right, but the failure happens only for one account, and granting a permission makes it disappear.

  1. Test what the caller can see: EXECUTE AS USER = 'AppUser'; SELECT OBJECT_ID('dbo.YourTable'); REVERT; A NULL there is the answer.
  2. Grant the minimum needed, on the schema rather than object by object where that fits: GRANT SELECT ON SCHEMA::dbo TO AppUser;
  3. Check for a DENY higher up. A DENY at any level beats a GRANT.

Metadata visibility is deliberate: telling an unauthorised caller that an object exists is itself a disclosure. It does mean your own session is a misleading place to diagnose from.

It is a temporary object, a synonym or a view over something that moved

You have this one if The failure is intermittent, or the object is reached through a synonym or a view rather than directly.

  1. A local temporary table lives only in the session that created it. A new session, or a batch after the creating one ended, will not find it.
  2. Check synonyms for a target that no longer exists: SELECT name, base_object_name FROM sys.synonyms;
  3. For a view whose underlying table changed, run EXEC sp_refreshview 'dbo.YourView';. It updates the persisted metadata for a view not created with SCHEMABINDING.

Full reference

Symptom to cause

What you see Where to look
Works for you, fails for one application account That user’s default schema, or its permissions
Fails everywhere, object visible in Object Explorer The connection is in the wrong database
Fails only when the case of the name differs A case-sensitive database collation
Fails inside a procedure that was created without complaint Deferred name resolution: the name is checked at first execution
207 rather than 208 The object resolved but a column name did not
4104 rather than 208 A multi-part identifier could not be bound: usually an alias or prefix not in scope
209 More than one source in the query exposes that column name
1087 A table variable was referenced without being declared in the same batch
2714 The mirror image: an object of that name already exists

The four-part address, in full

Form Resolves how
YourTable Default schema of the current database user, then dbo, in the current database
dbo.YourTable Named schema, current database. Identical for every caller
OtherDb.dbo.YourTable Named schema in a named database on this instance
SALESSRV.OtherDb.dbo.YourTable Through a linked server registered in sys.servers
#Temp tempdb, scoped to the session that created it
##Temp tempdb, global, and gone when the last session referencing it ends

Testing as somebody else

The only honest way to reproduce a permission-shaped 208

EXECUTE AS USER = 'AppUser';

SELECT DB_NAME() AS CurrentDb, SCHEMA_NAME() AS DefaultSchema, USER_NAME() AS DbUser;
SELECT OBJECT_ID('dbo.YourTable') AS ResolvesTo;
SELECT TOP 1 * FROM dbo.YourTable;

REVERT;

Because metadata is filtered by permission, this block is the difference between guessing and knowing. A NULL from OBJECT_ID inside the impersonation, where your own session returns a value, tells you the account cannot see the object – and the fix is a grant, not a rename. Remember EXECUTE AS also switches the default schema to that user’s, which is convenient here and a trap in production code that assumes otherwise.

Deferred name resolution, precisely

Microsoft’s wording is worth having exactly: a procedure can reference tables that do not yet exist; at creation time only syntax checking is performed; the procedure is not compiled until it is executed for the first time; and only during compilation are all objects referenced in the procedure resolved. So a syntactically correct procedure referencing a missing table is created successfully and fails at execution time. That is by design, so procedures can be created in any order, and it is why a deployment can look clean and break on first use.

Why qualifying names is worth doing everywhere

  • It removes the default schema question entirely, so behaviour stops depending on who is connected.
  • It makes the statement mean the same thing in a job step, an application and your query window.
  • It removes an ambiguity the engine would otherwise resolve per user, which helps plan reuse.
  • It makes 208 mean what it says: the object genuinely is not there, rather than not there for you.
  • It costs four characters.

When it really is missing

Once schema, database, collation and permission are all ruled out, search the instance rather than the database: SELECT s.name, o.name, o.type_desc FROM sys.objects AS o JOIN sys.schemas AS s ON s.schema_id = o.schema_id WHERE o.name LIKE '%YourTable%'; run in each candidate database. If nothing turns up anywhere, check whether a deployment dropped it, whether a synonym is pointing at something that has gone, and whether the object was ever created outside a transaction that was later rolled back.

Every code this article covers

Code What it points at Source
208 Invalid object name: the name could not be resolved in the current database and schema scope, or the caller cannot see it Microsoft Learn
207 Invalid column name: the object resolved but a column in the statement did not Microsoft Learn
4104 The multi-part identifier could not be bound, usually a table prefix or alias that is not in scope Microsoft Learn
209 Ambiguous column name: more than one source in the query exposes that column Microsoft Learn
1087 Must declare the table variable: it was referenced without being declared in the same batch Microsoft Learn
2714 There is already an object of that name in the database, which is the mirror image of 208 Microsoft Learn

Confirm the fix worked

  1. SELECT OBJECT_ID('dbo.YourTable'); returns a value in the failing session, not just in yours.
  2. The original statement runs under the account that failed, using EXECUTE AS USER if you cannot log in as it.
  3. SELECT DB_NAME(); returns the intended database in the application connection, not only in your query window.
  4. Every reference in the changed code is schema-qualified.
  5. If the object is reached through a view or synonym, that view has been refreshed and the synonym’s target exists.

Questions people ask about this

Should I always write the schema name?

Yes. Qualifying with the schema removes the default schema question entirely and makes the statement mean the same thing for every caller, in every context. It also helps plan reuse, because the engine no longer has to resolve the name per user.

Why does a procedure compile and then fail at run time?

Deferred name resolution. Microsoft’s CREATE PROCEDURE reference states that only syntax checking happens at creation, that the procedure is not compiled until first executed, and that objects are resolved only during that compilation. It is by design so procedures can be created in any order.

Why do I get 208 instead of a permission denied message?

Metadata visibility is filtered by permission: a caller with no rights on an object gets an empty result from the catalogue views, and OBJECT_ID can return NULL for them. Test with EXECUTE AS USER rather than from your own session, which sees everything.

Does it cost anything to fix?

No. This is a naming, context or permission problem and it behaves identically on every edition. There is nothing to buy.

My temporary table disappeared. Why?

A local temporary table lives only in the session that created it. A different session, or a batch after the creating one has ended, will not find it. If you need it across batches, use a global temporary table or a permanent one in a working schema.

Related error codes

Was this article helpful?

Your feedback helps us improve our documentation.

Related articles

Free Fix SSIS Error 0xC020801C: Cannot Acquire Connection From Connection Manager Free Fix Error 8909 and DBCC CHECKDB Allocation Errors: Repair Without Losing Rows Free Fix Error 3414 and 3417: Recovery Failed and the Instance Will Not Start License Error Error 1827: SQL Server Express Has Hit Its Licensed Database Size Limit
โ† Back to Knowledge Base