I know this is an old post, but for those who are curious, if one does not want to follow the normal route and rebuild your dataset to get all data and columns converted, here is a script to allow you to backup your indexes, keys and constraints and convert all tables and columns and restore your keys, indexes and constraints. Note, this should always only be done in testing environment before attempting any production runs.
USE [PUT DATABASE NAME HERE];
GO
DECLARE @DBName NVARCHAR(255) = '[PUT DATABASE NAME HERE]';
DECLARE @NewCollation NVARCHAR(255) = 'Latin1_General_CI_AS';
-- Create temporary tables to store constraints, indexes, and foreign keys
IF OBJECT_ID('tempdb..#Constraints') IS NOT NULL DROP TABLE #Constraints;
CREATE TABLE #Constraints (
TableName NVARCHAR(128), -- Use NVARCHAR(128) instead of sysname
ConstraintName NVARCHAR(128),
ConstraintSQL NVARCHAR(MAX)
);
IF OBJECT_ID('tempdb..#Indexes') IS NOT NULL DROP TABLE #Indexes;
CREATE TABLE #Indexes (
TableName NVARCHAR(128), -- Use NVARCHAR(128) instead of sysname
IndexName NVARCHAR(128),
IndexSQL NVARCHAR(MAX)
);
-- Create a temporary table to log errors
IF OBJECT_ID('tempdb..#ErrorLog') IS NOT NULL DROP TABLE #ErrorLog;
CREATE TABLE #ErrorLog (
Step NVARCHAR(100),
ErrorMessage NVARCHAR(MAX),
ErrorTime DATETIME DEFAULT GETDATE()
);
-- Change the database collation
BEGIN TRY
EXEC('ALTER DATABASE ' + @DBName + ' COLLATE ' + @NewCollation);
END TRY
BEGIN CATCH
INSERT INTO #ErrorLog (Step, ErrorMessage) VALUES ('Database Collation Change', ERROR_MESSAGE());
END CATCH
-- Capture primary keys and unique constraints (including the SQL to recreate them)
INSERT INTO #Constraints
SELECT
OBJECT_NAME(k.parent_object_id) AS TableName,
k.name AS ConstraintName,
'ALTER TABLE ' + QUOTENAME(OBJECT_SCHEMA_NAME(k.parent_object_id)) + '.' + QUOTENAME(OBJECT_NAME(k.parent_object_id)) +
' ADD CONSTRAINT ' + QUOTENAME(k.name) + ' PRIMARY KEY (' + STUFF((SELECT ', ' + QUOTENAME(c.name)
FROM sys.index_columns ic
JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
WHERE ic.object_id = k.parent_object_id AND ic.index_id = k.unique_index_id
ORDER BY ic.key_ordinal
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, '') + ')'
FROM sys.key_constraints k
WHERE k.type = 'PK' -- Primary keys
-- Capture unique constraints (same logic for recreating them)
INSERT INTO #Constraints
SELECT
OBJECT_NAME(k.parent_object_id) AS TableName,
k.name AS ConstraintName,
'ALTER TABLE ' + QUOTENAME(OBJECT_SCHEMA_NAME(k.parent_object_id)) + '.' + QUOTENAME(OBJECT_NAME(k.parent_object_id)) +
' ADD CONSTRAINT ' + QUOTENAME(k.name) + ' UNIQUE (' + STUFF((SELECT ', ' + QUOTENAME(c.name)
FROM sys.index_columns ic
JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
WHERE ic.object_id = k.parent_object_id AND ic.index_id = k.unique_index_id
ORDER BY ic.key_ordinal
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, '') + ')'
FROM sys.key_constraints k
WHERE k.type = 'UQ' -- Unique constraints
-- Capture all indexes (Clustered, Non-Clustered, Unique, Non-Unique)
INSERT INTO #Indexes
SELECT
OBJECT_NAME(i.object_id) AS TableName,
i.name AS IndexName,
'CREATE ' + CASE WHEN i.is_unique = 1 THEN 'UNIQUE ' ELSE '' END +
i.type_desc + ' INDEX ' + QUOTENAME(i.name) +
' ON ' + QUOTENAME(OBJECT_SCHEMA_NAME(i.object_id)) + '.' + QUOTENAME(OBJECT_NAME(i.object_id)) +
' (' + STUFF((SELECT ', ' + QUOTENAME(c.name)
FROM sys.index_columns ic
JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id
ORDER BY ic.key_ordinal
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, '') + ')'
FROM sys.indexes i
WHERE OBJECT_SCHEMA_NAME(i.object_id) = 'dbo'
AND i.type_desc IN ('NONCLUSTERED', 'CLUSTERED', 'XML') -- Including all types of indexes
AND i.is_primary_key = 0 -- Exclude primary keys
AND i.is_unique_constraint = 0 -- Exclude unique constraints
-- Drop primary keys, unique constraints, and indexes (Clustered, Non-Clustered, Unique, Non-Unique)
DECLARE @DropConstraintsSQL NVARCHAR(MAX) = '';
DECLARE @DropIndexesSQL NVARCHAR(MAX) = '';
-- Drop primary keys and unique constraints
SELECT @DropConstraintsSQL = @DropConstraintsSQL + 'ALTER TABLE ' + QUOTENAME(OBJECT_SCHEMA_NAME(k.parent_object_id)) + '.' + QUOTENAME(OBJECT_NAME(k.parent_object_id)) +
' DROP CONSTRAINT ' + QUOTENAME(k.name) + ';' + CHAR(10)
FROM sys.key_constraints k
WHERE k.type IN ('PK', 'UQ') -- Primary keys and Unique constraints
AND OBJECT_SCHEMA_NAME(k.parent_object_id) = 'dbo';
-- Drop all indexes (Clustered, Non-Clustered, Unique, Non-Unique)
SELECT @DropIndexesSQL = @DropIndexesSQL + 'DROP INDEX ' + QUOTENAME(i.name) + ' ON ' +
QUOTENAME(OBJECT_SCHEMA_NAME(i.object_id)) + '.' + QUOTENAME(OBJECT_NAME(i.object_id)) + ';' + CHAR(10)
FROM sys.indexes i
WHERE OBJECT_SCHEMA_NAME(i.object_id) = 'dbo'
AND i.type_desc IN ('NONCLUSTERED', 'CLUSTERED', 'XML') -- Including all types of indexes
AND i.is_primary_key = 0
AND i.is_unique_constraint = 0;
-- Execute drop constraints and indexes
BEGIN TRY
EXEC(@DropConstraintsSQL);
EXEC(@DropIndexesSQL);
END TRY
BEGIN CATCH
INSERT INTO #ErrorLog (Step, ErrorMessage) VALUES ('Drop Constraints and Indexes', ERROR_MESSAGE());
END CATCH;
-- Alter column collations (for dbo schema)
DECLARE @collate NVARCHAR(100);
DECLARE @table NVARCHAR(255);
DECLARE @column_name NVARCHAR(255);
DECLARE @column_id INT;
DECLARE @data_type NVARCHAR(255);
DECLARE @max_length INT;
DECLARE @row_id INT;
DECLARE @sql NVARCHAR(MAX);
DECLARE @sql_column NVARCHAR(MAX);
DECLARE @is_nullable BIT;
SET @collate = @NewCollation; -- 'Latin1_General_CI_AS'; --
DECLARE local_table_cursor CURSOR FOR
SELECT [name]
FROM sysobjects
WHERE OBJECTPROPERTY(id, N'IsUserTable') = 1
OPEN local_table_cursor
FETCH NEXT FROM local_table_cursor
INTO @table
WHILE @@FETCH_STATUS = 0
BEGIN
DECLARE local_change_cursor CURSOR FOR
SELECT ROW_NUMBER() OVER (ORDER BY c.column_id) AS row_id
, c.name AS column_name
, t.name AS data_type
, c.max_length
, c.column_id
, c.is_nullable
FROM sys.columns c
JOIN sys.types t ON c.system_type_id = t.system_type_id
LEFT OUTER JOIN sys.index_columns ic ON ic.object_id = c.object_id AND ic.column_id = c.column_id
LEFT OUTER JOIN sys.indexes i ON ic.object_id = i.object_id AND ic.index_id = i.index_id
WHERE c.object_id = OBJECT_ID(@table)
ORDER BY c.column_id
OPEN local_change_cursor
FETCH NEXT FROM local_change_cursor
INTO @row_id, @column_name, @data_type, @max_length, @column_id, @is_nullable
WHILE @@FETCH_STATUS = 0
BEGIN
IF (@max_length = -1) OR (@max_length > 4000)
SET @max_length = 4000;
-- Preserve the nullability of the column
IF (@data_type LIKE '%char%' OR @data_type LIKE '%text%')
BEGIN
-- Check if column is nullable or not and preserve it
IF @is_nullable = 1
BEGIN
SET @sql = 'ALTER TABLE [' + @table + '] ALTER COLUMN [' + @column_name + '] ' + @data_type +
'(' + CAST(@max_length AS NVARCHAR(100)) + ') COLLATE ' + @collate + ' NULL';
END
ELSE
BEGIN
SET @sql = 'ALTER TABLE [' + @table + '] ALTER COLUMN [' + @column_name + '] ' + @data_type +
'(' + CAST(@max_length AS NVARCHAR(100)) + ') COLLATE ' + @collate + ' NOT NULL';
END
PRINT @sql -- For debugging: Output the generated SQL
BEGIN TRY
EXEC sp_executesql @sql
END TRY
BEGIN CATCH
PRINT 'ERROR: Some index or constraint relies on the column ' + @column_name + '. No conversion possible.'
PRINT @sql
END CATCH
END
FETCH NEXT FROM local_change_cursor
INTO @row_id, @column_name, @data_type, @max_length, @column_id, @is_nullable
END
CLOSE local_change_cursor
DEALLOCATE local_change_cursor
FETCH NEXT FROM local_table_cursor
INTO @table
END
CLOSE local_table_cursor
DEALLOCATE local_table_cursor
GO
-- After changing column collations, recreate primary keys, unique constraints, and indexes
DECLARE @RecreateConstraintsSQL NVARCHAR(MAX) = '';
DECLARE @RecreateIndexesSQL NVARCHAR(MAX) = '';
-- Recreate primary keys and unique constraints
SELECT @RecreateConstraintsSQL = @RecreateConstraintsSQL + ConstraintSQL + ';' + CHAR(10)
FROM #Constraints;
-- Recreate all indexes (Clustered, Non-Clustered, Unique, Non-Unique)
SELECT @RecreateIndexesSQL = @RecreateIndexesSQL + IndexSQL + ';' + CHAR(10)
FROM #Indexes;
-- Execute recreate constraints and indexes
BEGIN TRY
PRINT 'Recreating Constraints:';
PRINT @RecreateConstraintsSQL; -- Debugging output to see the SQL
EXEC(@RecreateConstraintsSQL);
PRINT 'Recreating Indexes:';
PRINT @RecreateIndexesSQL; -- Debugging output to see the SQL
EXEC(@RecreateIndexesSQL);
END TRY
BEGIN CATCH
INSERT INTO #ErrorLog (Step, ErrorMessage) VALUES ('Recreate Constraints and Indexes', ERROR_MESSAGE());
END CATCH;
-- Display error log
SELECT * FROM #ErrorLog;
-- Cleanup
DROP TABLE #ErrorLog;
DROP TABLE #Constraints;
DROP TABLE #Indexes;
DROPed and reCREATEd.COLLATEisn't SARGable.