[SQL Server] tempdb定序衝突(collation conflict)

延續SQL Server儲存Unicode補充字集的話題,因為同事的目標資料庫是既有資料庫,沒辦法採用新建立資料庫的方式,後來同事直接把資料庫改成支援補充字集的定序(*_SC),一開始很順利,但使用到tempdb,像是join #table 或是union all #table時發現了定序衝突(collation conflict)問題。

來筆記另外兩種解決定序衝突(collation conflict)的作法:

 

模擬直接修改資料庫定序

修改資料庫定序為Chinese_Taiwan_Stroke_90_CI_AS_SC

--修改定序
USE master;  
GO  
ALTER DATABASE db1  
COLLATE Chinese_Taiwan_Stroke_90_CI_AS_SC ;  
GO  

馬上來試試新增補充字集的字進去

use db1
INSERT INTO T1 VALUES(NCHAR(0x28CCE),'3')
SELECT * from t1

補充字是寫進去了,但是同事提到的定序衝突?

試試看把t1 table寫到tempdb #t1,然後join table t1

select * from #t1 a 
  join t1 b
    on a.c2 = b.c2 

果然跳出同事提到的 無法解析 equal to 作業中 "Chinese_Taiwan_Stroke_90_CI_AS_SC" 與 "Chinese_Taiwan_Stroke_CI_AS" 之間的定序衝突

 

union 也有相同情形

select * from #t1 a 
 union all 
select * from t1 b

 


調查定序衝突問題:

1.先查詢tempdb中的temporary table(#t1)定序

USE tempdb 
select object_name(object_id) as tablename, name as columnname,collation_name 
from sys.columns
where collation_name is not null
and object_name(object_id) like '#t1%'

神奇了,怎麼沒有_SC,還是原來資料庫修改前的定序!

記得tempdb中的temporary table的欄位定序應該是參照來源資料!

 

2.趕快來檢查來源資料表t1的欄位設定

USE db1 
select object_name(object_id) as tablename, name as columnname,collation_name 
from sys.columns
where collation_name is not null
and object_name(object_id) like 't1%'

的確有SC啊..

 

3.但透過資料表設計工具觀察,是資料庫預設值

 

4.透過Metadata中的結構描述檢視卻是停在資料庫修改前的定序。

use db1
select TABLE_NAME,COLUMN_NAME,COLLATION_NAME from INFORMATION_SCHEMA.COLUMNS

 


研判定序衝突原因:

初步猜測是來源資料表t1實際還是舊的定序,因此寫入temporary table也是舊的定序,但產生執行計畫時,因為資料庫的預設定序的定義已經修改成新定序, 在產生執行計畫階段就認定有定序衝突(collation conflict)而中止

 


解決定序衝突的方式:

1.有一種解法是在T-SQL後指定Collate來統一。

強制指定成Chinese_Taiwan_Stroke_90_CI_AS_SC或是Chinese_Taiwan_Stroke_CI_AS或是database_default(建議這個,指)都可以解決。

select * from #t1 a 
  join t1 b
    on a.c2 = b.c2 COLLATE Chinese_Taiwan_Stroke_90_CI_AS_SC

select * from #t1 a 
  join t1 b
    on a.c2 = b.c2 COLLATE Chinese_Taiwan_Stroke_CI_AS

select * from #t1 a 
  join t1 b
    on a.c2 = b.c2 COLLATE database_default  

select c1,c2 from #t1 a 
 union all 
select c1 COLLATE Chinese_Taiwan_Stroke_90_CI_AS_SC,c2 COLLATE Chinese_Taiwan_Stroke_90_CI_AS_SC from t1 b  

但因為要改很多支t-sql和stored procedure作罷

 

2.第二種解法是修改資料行定序

use db1
ALTER TABLE T1 
 ALTER COLUMN c1  
   nvarchar(10)COLLATE Chinese_Taiwan_Stroke_90_CI_AS_SC 

ALTER TABLE T1 
 ALTER COLUMN c2   
   varchar(10)COLLATE Chinese_Taiwan_Stroke_90_CI_AS_SC

GO  

 

重新產生temporary table並且執行inner join及union all。

if object_id('tempdb..#t1') is not null drop table #t1

use db1  
select * into #t1 FROM t1 


--innser join
select * from #t1 a 
  join t1 b
    on a.c2 = b.c2 

--union
select * from #t1 a 
 union all 
select * from t1 b

 

執行結果集:

 

小結:

  • 解決了定序衝突的問題,可以不用很多支T-SQL指令或是stored procedure,但要改資料行屬性。
  • 如果table很多,建議就是整個資料庫下的資料表都重建,如果很少,可以使用 INFORMATION_SCHEMA.COLUMNS產生語法修改。

 

參考:

設定或變更資料庫定序

https://msdn.microsoft.com/zh-tw/library/ms175835.aspx

設定或變更資料行定序

https://msdn.microsoft.com/zh-tw/library/ms190920.aspx