Cannot Resolve the Collation Conflict (Error 468)

🚨Part of the SQL Server Errors series, the exact messages and what actually causes them.

The errorMsg 468  ·  Level 16  ·  State 9
Cannot resolve the collation conflict between “SQL_Latin1_General_CP1_CI_AS” and “Latin1_General_CI_AS” in the equal to operation.
Add COLLATE DATABASE_DEFAULT to the comparison, not to the column. The error is telling you two sides of a comparison store text under different rules and it will not pick one for you. Changing a column’s collation is a schema change with index and constraint fallout; changing the comparison is a one-line edit to the query that failed.

Nothing is broken when you see 468. Both columns are valid, both databases are healthy, and the same query very likely works somewhere else. What has happened is that a comparison has one side stored under one set of sorting and equality rules and the other side under a different set, and SQL Server refuses to guess which one should win.

It refuses for a good reason. Picking silently would mean deciding whether 'Smith' = 'SMITH' is true, and getting that wrong changes which rows come back rather than producing an error. An error you can fix in a minute is better than a result set that is quietly wrong.


Where the Second Collation Came From

Three sources account for almost every 468, and they are worth telling apart because the fix is the same but the reason it keeps happening is not.

Where it comes fromWhat is actually going on
Across two databasesEach database has its own collation, set when it was created and often inherited from whatever the server default was that day. A join between them compares text under two sets of rules. Restored databases are the usual culprit, because a restore keeps the collation it was backed up with, not the collation of the instance it lands on.
A temp table#temp tables live in tempdb, so their character columns take tempdb’s collation, which follows the instance. Load a temp table from a user database whose collation differs from the instance, then join it back, and you get 468 from a query that touches only one user database. This is the one that surprises people.
A literal or a variableLess common, and usually only when a variable is declared in one database’s context and compared in another, or a linked server is in the path.

Two queries tell you which one you are in. The first shows every database collation on the instance, including tempdb:

SELECT name, collation_name
FROM   sys.databases
ORDER  BY name;

The second shows the collation of the columns actually in the comparison, which is what matters when two columns in the same database disagree:

SELECT  OBJECT_NAME(c.object_id) AS table_name,
        c.name                   AS column_name,
        c.collation_name
FROM    sys.columns AS c
WHERE   c.collation_name IS NOT NULL
  AND   OBJECT_NAME(c.object_id) IN ('Customers', 'Orders')
ORDER   BY table_name, column_name;

The Fix, and Why It Goes on the Comparison

COLLATE DATABASE_DEFAULT on one side of the comparison resolves it. It tells SQL Server to evaluate that expression using the collation of the current database, so both sides agree for the length of that comparison and nothing else changes:

SELECT  c.customer_id, o.order_id
FROM    Sales.dbo.Customers AS c
JOIN    Archive.dbo.Orders  AS o
        ON o.customer_code = c.customer_code COLLATE DATABASE_DEFAULT;

DATABASE_DEFAULT rather than a named collation is the deliberate part. Naming Latin1_General_CI_AS works today and hard-codes an assumption that survives until the query runs somewhere with a different default. DATABASE_DEFAULT keeps working after a restore to another instance, which is exactly the journey that produced the conflict in the first place.

For the temp table case, fix it where the table is declared rather than at every join to it:

CREATE TABLE #staging
(
    customer_code varchar(20) COLLATE DATABASE_DEFAULT NOT NULL,
    amount        decimal(18,2) NOT NULL
);

One declaration, and every subsequent join to that table stops caring.


What Not To Do

  • Do not ALTER COLUMN to change the collation because one query failed. It rewrites the column, and it is blocked outright while an index, a computed column, a check constraint or a foreign key depends on it. You end up dropping and recreating objects during a change window to fix a query you could have fixed in one line.
  • Do not rebuild the database collation to match. Changing a database’s collation does not change the collation of columns already in it, so the conflict survives the operation that was supposed to end it.
  • Do not sprinkle a named collation everywhere. It clears today’s error and moves the problem to the next instance, where the name no longer matches the default.
  • Do not wrap the column instead of the comparison if you can avoid it. WHERE c.code COLLATE DATABASE_DEFAULT = @x applies a function to the column and gives up the index seek. Collate the other side where you have the choice, for the same reason implicit conversions hurt.

Common Questions

Is 468 a sign something is corrupt or misconfigured?
No. Every object involved is valid. Two of them store text under different rules, which is a design consequence of how the databases were created or restored, not a fault. The instance is healthy and the error is doing its job.
Why does the same query work in one environment and fail in another?
Because collation follows the database, and a database keeps the collation it was created or restored with. Dev, test and production frequently differ for no better reason than which server default applied on the day each was built. That is also why 468 tends to appear first in whichever environment was restored most recently.
Why does a query that only touches one database still raise it?
A temp table. #temp columns take tempdb’s collation, which follows the instance, so joining a temp table back to a user database with a different collation compares two collations without ever naming a second database. Declaring the column COLLATE DATABASE_DEFAULT in the CREATE TABLE removes it.
Does COLLATE DATABASE_DEFAULT change how my data is stored?
No. It affects only the evaluation of that expression, for that statement. Nothing is written, no column definition changes, and the plan is the only thing that differs.
Will this slow the query down?
It can, if you collate the indexed column, because applying it to a column makes the predicate non-sargable and costs the seek. Put COLLATE on the other side of the comparison where you have a choice. Collating a temp table column at declaration avoids the question entirely.
Should I just standardise every database on one collation?
It is the real fix if you own the estate and can afford it, and it is a migration rather than an afternoon: the database collation and every existing character column both have to change, and code that relied on case sensitivity has to be retested. Until that is scheduled, COLLATE DATABASE_DEFAULT at the comparison is the correct answer rather than a workaround.

Related Scripts

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *