本文转自:http://ju.outofmemory.cn/entry/191163
问题描述
在SQL Server中使用一些复杂的存储过程时,我们需要借用临时表来完成一些逻辑的处理,例如:数据的临时存储、循环处理等等。
临时表创建后,并不是在各个数据库中存在的,而是存在于系统数据库tempdb中。
如今在一个包含临时表的存储过程中,我们遇到了SQL Collation冲突的错误:
错误信息:
Cannot resolve the collation conflict between "Chinese_PRC_CI_AS" and "SQL_Latin1_General_CP1_CI_AS" in the equal to operation.
检查我们的应用数据库和tempdb的Collation,发现这两个数据库的Collation是不一致的。
应用数据库的Collation是:SQL_Latin1_General_CP1_CI_AS
tempdb的Collation是:Chinese_PRC_CI_AS
解决方法
通过GUI方式修改
tempdb作为系统的数据库,它的Collation是不可以被更改的,我们可以更改应用数据库的Collation来解决这个问题。
在数据库的Properties → Options有更改数据库的Collation选项,但修改时会出现错误:
通过SQL脚本修改
SQL脚本也可以修改数据库的Collation,但是要在SINGLE_USER WITH ROLLABACK IMMEDIATE模式下修改,修改完成后再还原为MULTI_USER模式
ALTER DATABASE SampleDb SET SINGLE_USER WITH ROLLBACK IMMEDIATE
GO
ALTER DATABASE SampleDb COLLATE Chinese_PRC_CI_AS
GO
ALTER DATABASE SampleDb SET MULTI_USER
GO
执行SQL后,数据库的Collation变更为Chinese_PRC_CI_AS
验证Collation是否修改成功
要验证Collation是否修改成功,只需要再次执行存储过程,执行后仍然会出现Collation冲突错误:
Cannot resolve the collation conflict between "Chinese_PRC_CI_AS" and "SQL_Latin1_General_CP1_CI_AS" in the equal to operation.
检查数据库中已创建的表字段的Collation属性,仍然是SQL_Latin1_General_CP1_CI_AS。
尝试在数据库中创建一个新的Table
CREATE TABLE Collation_Test
(
[ID] INT IDENTITY
PRIMARY KEY ,
[Name] NVARCHAR(30)
)
GO
查看Collation_Test表字段Name的Collation属性,则是Chinese_PRC_CI_AS
结论:这说明以上修改数据库Collation的SQL脚本,确实成功修改了数据库的Collation属性,新创建的表的字段的Collation属性会继承数据库的Collation属性,但它不会影响已创建的表的字段的Collation属性。
简单地说,修改数据库的Collation,对于已存在的表没有影响,对于新创建的表有影响。
修改表字段的Collation属性
在表字段的属性中可以修改Collation属性,但这种方式每次只能更改一个,我们已经创建了很多的表,使用这种方式不太现实。
那么如何批量地修改表字段的Collation属性呢?
执行下面一段SQL可以解决这个问题:
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);
--期望的Collation
SET @collate = 'Chinese_PRC_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 column_name ,
t.Name data_type ,
c.max_length ,
c.column_id
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
WHILE @@FETCH_STATUS = 0
BEGIN
IF ( @max_length = -1 )
SET @max_length = 4000;
IF ( @data_type LIKE '%char%' )
BEGIN TRY
SET @sql = 'ALTER TABLE ' + @table + ' ALTER COLUMN '
+ @column_name + ' ' + @data_type + '('
+ CAST(@max_length AS NVARCHAR