Multiple SQL Server databases with the exact same schema somehow ended up having different collations. How do I change them all to be the same with a scripted approach without any manual clicking around?
declare #rename_models table (
wrong nvarchar(256) COLLATE SQL_Latin1_General_CP1_CI_AS, -- tried overriding collation, but this conflicts with some of the databases
correct nvarchar(256) COLLATE SQL_Latin1_General_CP1_CI_AS
);
The query I run against a models table:
select code as to_be_deleted from models where code in (select wrong from #rename_models);
Throws this for some databases:
MESSAGE
"Msg 468, Level 16, State 9, Line 140
Cannot resolve the collation conflict between ""SQL_Latin1_General_CP1_CI_AS"" and ""Latin1_General_CI_AS"" in the equal to operation.
You can use the COLLATE keyword in your Select Query.
Casting the collation of an expression.
You can use the COLLATE clause to apply a character expression to a
certain collation. Character literals and variables are assigned the
default collation of the current database. Column references are
assigned the definition collation of the column.
References :
COLLATE
I am trying to execute the following query on two different databases with difent collations
select * from sourcedb.DBO.PKtable
except
select * from destinationdb.DBO.PKtable
It is clear that both the tables have the same columns and the primary key
But when executing, encountered the following exception
Msg 468, Level 16, State 9, Line 17
Cannot resolve the collation conflict between "SQL_Latin1_General_CP1_CI_AS" and "Latin1_General_CS_AS" in the EXCEPT operation.
I cannot use COLLATE keyword because.. I will be using the above query at runtime and the table name and columns vary, hence cannot predict the columns of the table.
I have tried to change the collation of the destination database to same as the source database using the following command
ALTER DATABASE destinationDB SET SINGLE_USER WITH ROLLBACK IMMEDIATE
ALTER DATABASE destinationDB COLLATE <<sourceCollation>>;
ALTER DATABASE destinationDB SET MULTI_USER
The collation is set to the database and I can see it from the sys tables.
Even then when I execute the query mentioned above, getting the same error
EDIT1: Basically I'm trying to get the records which have the same primary key but difference in the row.
Another possible workaround (apart from dynamic sql) might be to use a temporary table. (This may suit if this is a one off type of operation).
-- create empty #PKTable with required columns and collation.
select * INTO #PKTable from sourcedb.DBO.PKtable where 1=0
-- fill table with data
insert #PKTable select * from destinationdb.DBO.PKtable
-- compare
select * from sourcedb.DBO.PKtable
except
select * from #PKTable
-- remove temp table
drop table #PKTable
Alter Collation
I need to change the collation of one of our databases on a particular server from Latin1_General_CI_AS to SQL_Latin1_General_CP1_CI_AI so that it matches the rest of our databases.
The Problem
However, when I attempt to do this, I get the following error:
ALTER DATABASE failed. The default collation of database 'XxxxxXxxxxx' cannot be set to SQL_Latin1_General_CP1_CI_AI. (Microsoft SQL Server, Error: 5075)
My Research
My googling on the topic has revealed a number of articles which indicate that I need to export all the data, drop the database, re-create it with the correct collation, then re-import the data.
For example: Problem with database collation change (SQL Server 2008)
Obviously this is a significant task, especially since primary-foreign key relationships must be preserved, and our database is quite large (over ten million data rows).
My Question
Is there a way to change the collation of an existing SQL Server 2012 database which does not require exporting and re-importing all the data?
Alternatively, is there some tool or script(s) which can automate this process in a reliable manner?
The following works for me on SQL Server 2012:
ALTER DATABASE CURRENT COLLATE SQL_Latin1_General_CP1_CI_AI;
The accepted answer in the linked question is not entirely correct, at least not for SQL Server 2012. It says:
Ahh, this is one of the worst problems in SQL Server: you cannot change the collation once an object is created (this is true both for tables and databases...).
But I was just able to change the default collation and I have tables that are populated. The MSDN page for ALTER DATABASE states in the "Remarks" section, under "Changing the Database Collation":
Before you apply a different collation to a database, make sure that the following conditions are in place:
You are the only one currently using the database.
No schema-bound object depends on the collation of the database.
If the following objects, which depend on the database collation, exist in the database, the ALTER DATABASE database_name COLLATE statement will fail. SQL Server will return an error message for each object blocking the ALTER action:
User-defined functions and views created with SCHEMABINDING.
Computed columns.
CHECK constraints.
Table-valued functions that return tables with character columns with collations inherited from the default database collation.
So, I would suggest making sure that the database is in Single-User mode, and that if you have any of those four items, that you:
drop them
change the collation
and then re-add them
BUT, at that point all that has been changed is the Database's default Collation. The Collation of any existing columns in user tables (i.e. non-system tables) will still have the original Collation. If you want existing string columns -- CHAR, VARCHAR, NCHAR, NVARCHAR, and the deprecated TEXT and NTEXT -- to take on the new Collation, you need to change each of those columns individually. And, if there are any indexes defined on those columns, then those indexes will need to be dropped first (disabling is not enough) and created again after the ALTER COLUMN (other dependencies that would prevent the ALTER COLUMN would have already been dropped in order to get the ALTER DATABASE to work). The example below illustrates this behavior:
Test Setup
USE [tempdb];
SET NOCOUNT ON;
CREATE TABLE dbo.ChangeCollationParent
(
[ChangeCollationParentID] INT NOT NULL IDENTITY(1, 1)
CONSTRAINT [PK_ChangeCollationParent] PRIMARY KEY,
ExtendedASCIIString VARCHAR(50) COLLATE Latin1_General_CI_AS NULL,
UnicodeString NVARCHAR(50) COLLATE Latin1_General_CI_AS NULL
);
CREATE TABLE dbo.ChangeCollationChild
(
[ChangeCollationChildID] INT NOT NULL IDENTITY(1, 1)
CONSTRAINT [PK_ChangeCollationChild] PRIMARY KEY,
[ChangeCollationParentID] INT NULL
CONSTRAINT [FK_ChangeCollationChild_ChangeCollationParent] FOREIGN KEY
REFERENCES dbo.ChangeCollationParent([ChangeCollationParentID]),
ExtendedASCIIString VARCHAR(50) COLLATE Latin1_General_CI_AS NULL,
UnicodeString NVARCHAR(50) COLLATE Latin1_General_CI_AS NULL
);
INSERT INTO dbo.ChangeCollationParent ([ExtendedASCIIString], [UnicodeString])
VALUES ('test1' + CHAR(200), N'test1' + NCHAR(200));
INSERT INTO dbo.ChangeCollationParent ([ExtendedASCIIString], [UnicodeString])
VALUES ('test2' + CHAR(170), N'test2' + NCHAR(170));
INSERT INTO dbo.ChangeCollationChild
([ChangeCollationParentID], [ExtendedASCIIString], [UnicodeString])
VALUES (1, 'testA ' + CHAR(200), N'testA ' + NCHAR(200));
INSERT INTO dbo.ChangeCollationChild
([ChangeCollationParentID], [ExtendedASCIIString], [UnicodeString])
VALUES (1, 'testB ' + CHAR(170), N'testB ' + NCHAR(170));
SELECT * FROM dbo.ChangeCollationParent;
SELECT * FROM dbo.ChangeCollationChild;
Test 1: Change column Collation with no dependencies
ALTER TABLE dbo.ChangeCollationParent
ALTER COLUMN [ExtendedASCIIString] VARCHAR(50) COLLATE SQL_Latin1_General_CP1_CI_AI NULL;
ALTER TABLE dbo.ChangeCollationParent
ALTER COLUMN [UnicodeString] NVARCHAR(50) COLLATE SQL_Latin1_General_CP1_CI_AI NULL;
ALTER TABLE dbo.ChangeCollationChild
ALTER COLUMN [ExtendedASCIIString] VARCHAR(50) COLLATE SQL_Latin1_General_CP1_CI_AI NULL;
ALTER TABLE dbo.ChangeCollationChild
ALTER COLUMN [UnicodeString] NVARCHAR(50) COLLATE SQL_Latin1_General_CP1_CI_AI NULL;
SELECT * FROM dbo.ChangeCollationParent;
SELECT * FROM dbo.ChangeCollationChild;
The ALTER COLUMN statements above complete successfully.
Test 2: Change column Collation with dependencies
-- First, create an index:
CREATE NONCLUSTERED INDEX [IX_ChangeCollationParent_ExtendedASCIIString]
ON dbo.ChangeCollationParent ([ExtendedASCIIString] ASC);
-- Next, change the Collation back to the original setting:
ALTER TABLE dbo.ChangeCollationParent
ALTER COLUMN [ExtendedASCIIString] VARCHAR(50) COLLATE Latin1_General_CI_AS NULL;
This time, the ALTER COLUMN statement received the following error:
Msg 5074, Level 16, State 1, Line 60
The index 'IX_ChangeCollationParent_ExtendedASCIIString' is dependent on column 'ExtendedASCIIString'.
Msg 4922, Level 16, State 9, Line 60
ALTER TABLE ALTER COLUMN ExtendedASCIIString failed because one or more objects access this column.
ALSO, please be aware that the Collation of some string columns in database-scoped system catalog views (e.g. sys.objects, sys.columns, sys.indexes, etc) will change to the new Collation. If your code has JOINs to any of these string columns (i.e. name), then you might start getting Collation mismatch errors until you change the Collation on the joining columns in your user tables.
UPDATE:
If changing the Collation for the entire Instance is the desire, or an option, then there is an easier method that bypasses all of these restrictions. It is undocumented and hence unsupported (so if it doesn't work, Microsoft isn't going to help). However, it changes the Collation at all levels: Instance, all Database's, and all string columns in all User Tables. It does this, and avoids all of the typical restrictions, by simply updating the meta-data of the tables, etc to have the new Collation. It then drops and recreates all indexes that have string columns. There are also a few nuances to this method that might have impact, but are fixable. This method is the -q command-line switch of sqlservr.exe. I have documented all of the behaviors, including listing all of the potentially affected areas by doing such a wide-sweeping Collation change, in the following post:
Changing the Collation of the Instance, the Databases, and All Columns in All User Databases: What Could Possibly Go Wrong?
For anyone else stumbling to this problem, the solution is to set DB in single_user mode before change the collation and then set again the multi_user mode after it.
Make sure to not close the connection before setting the multi_user mode!
/* block all other users from connecting to the db */
ALTER DATABASE YorDbName SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
/* modify your db collate */
ALTER DATABASE CURRENT COLLATE SQL_Latin1_General_CP1_CI_AI;
/* allow again all other users to connect to the db */
ALTER DATABASE YorDbName SET MULTI_USER;
My application's database mydb has different collation than tempdb. I get a lot of exceptions comparing string values between tempdb temporary tables and mydb persistent tables.
It was decided to create mydb with same collation as tempdb.
The collation must be set automatically using script. I've tried this:
DECLARE #SQLCollation sql_variant
SELECT #SQLCollation = DATABASEPROPERTYEX('tempdb', 'Collation')
ALTER DATABASE mydb COLLATE #SQLCollation -- doesn't work
ALTER DATABASE mydb COLLATE Latin1_General_CI_AS -- works, but doesn't suit me because I have to type in collation myself in this SQL
So how do I set the same collation for mydb as for tempdb?
You can't just change DB collation with ALTER DATABASE. This only changes system databases (object names etc). (Dalex's answer)
You have to follow the steps detailed in the answers to Changing SQL Server Database sorting.
Another option to use the COLLATE Database_Default to coerce collation without knowing what it is. See SQL Server error "Implicit conversion of because the collation of the value is unresolved due to a collation conflict." and SQL Server - is there a way to mass resolve collation conflicts
Changing collation of the db will not change the collation of already existing tables in the db.
Another option would be to specify the collation to use when you create your temp table.
create table #TempTable
(
Name varchar(10) collate database_default
)
Your comparisons will then work just fine asuming that all your tables character fields have the same collation as the database.
DECLARE #SQLCollation NVARCHAR(1000)
SELECT #SQLCollation = 'ALTER DATABASE MyDb COLLATE '+CAST(DATABASEPROPERTYEX('Tempdb', 'Collation') as NVARCHAR(1000))
exec (#sqlcollation)
I have a case sensitive SERVER (SQL_Latin1_General_CP1_CS_AS) but the Database is Case insensitive (SQL_Latin1_General_CP1_CI_AS).
If I try to create the following stored procedure on the database, I get the error "Must declare the scalar variable "#test"."
CREATE PROCEDURE [dbo].[sp_Test] (#TEST int) as
begin
SELECT #test
end
GO
But as I stated the database itself is not case sensitive. Im assuming this is documented somewhere that stored procedures follow the sensitivity of the server but I cannot find a reference anywhere. Can anyone point me to where I would find some docs about this? (Yes I tried google, but im not finding anything)
You are right. Database collation does not control variables name case sensitivity - server collation does.
Any other object name (e.g. table, view, column) follows database collation rules. In your situation, that means case insensitive, since your database is CI (case insensitive).
From the SQL Server Books Online:
COLLATE (Transact-SQL)
The collation of an identifier depends on the level at which it is defined.
Identifiers of instance-level objects, such as logins and database names, are assigned the default collation of the instance.
Identifiers of objects within a database, such as tables, views, and column names, are assigned the default collation of the database.
For example, two tables with names different only in case may be created in a database with case-sensitive collation, but may not be created in a database with case-insensitive collation. For more information, see Database Identifiers.
The identifiers for variables, GOTO labels, temporary stored procedures, and temporary tables are in the default collation of the server instance.
Variables, GOTO labels, temporary stored procedures, and temporary tables can be created when the connection context is associated with one database, and then referenced when the context has been switched to another database.
You can check your server collation using:
SELECT SERVERPROPERTY('collation');
SQL_Latin1_General_CP1_CI_AS
(1 row(s) affected)
See also
MSDN forums: Why are my SP's throwing a case error when pushing to a db using BIN collation?
Case sensitive variables in SQL Server
Case sensitive variable names in SQL Server?