什么是排序規則(collation)
關於SQL Server的排序規則,估計大家都不陌生,在創建數據庫時我們經常要選擇一種排序規則(conllation),一般我們會留意到每一種語言的排序規則都有許多種,比如標准大陸簡體中文Chinese_PRC的排序規則就有數十種之多

這些排序規則有什么作用呢?讓我們先來看看MS官方的解釋:
排序規則指定了表示每個字符的位模式。它還指定了用於排序和比較字符的規則。排序規則具有下面的特征:
- 語言
- 區分大小寫
- 區分重音
- 區分假名
比如在SQL Server 2005中,排序規則名稱由兩部份構成,比如 Chinese_PRC_CI_AI_WS
前半部份是指本排序規則所支持的字符集,如Chinese_PRC 指針對大陸簡體字UNICODE的排序規則。
后半部份即后綴的含義如下:
_BIN
指定使用向后兼容的二進制排序順序。_BIN2
指定使用 SQL Server 2005 中引入的碼位比較語義的二進制排序順序。_Stroke
按筆划排序_CI(CS)
是否區分大小寫,CI不區分,CS區分_AI(AS)
是否區分重音,AI不區分,AS區分_KI(KS)
是否區分假名類型,KI不區分,KS區分_WI(WS)
是否區分全半角,WI不區分,WS區分
既然排序規則如此復雜,那么應用了不同排序規則的列之間默認情況下便不能進行Union、Join、Like等equal操作了,於是便有了排序規則(collation)沖突。
排序規則(collation)沖突
我們知道,SQL Server 從2000 開始,便支持多個排序規則。SQL Server 2000 的數據庫可使用除默認排序規則以外的其他排序規則。此外,SQL Server 2000 還支持為列專門制定排序規則。
這樣一來,我們在寫跨表、跨數據庫、跨服務器操作的T-SQL時,如果equal的字段排序規則不同,便會發生排序規則沖突。
比如我們先見兩個結構相同的表,但字段的排序規則不同:
-- 1. Create TableA.
CREATE TABLE TagsTableA
(
TagName NVARCHAR(64) COLLATE Chinese_PRC_BIN
)
-- 2. Create TableB.
CREATE TABLE TagsTableB
(
TagName NVARCHAR(64) COLLATE Chinese_PRC_CI_AS
)
當表建好之后執行:
-- 3. Try to join them
SELECT * from TagsTableA A INNER JOIN TagsTableB B on A.TagName = B.TagName
便會出下類似下面的問題:
無法解決 equal to 操作中 "Chinese_PRC_BIN" 和 "Chinese_PRC_CI_AS" 之間的排序規則沖突。
常見的場景——臨時表
我們知道,SQL Server的臨時表是保存在Tempdb數據庫中的。而使用臨時表的數據庫與臨時表的排序規則(conllation)不一定相同。所以,當Tempdb的排序規則與當前使用臨時表的數據庫排序規則不同時,便會出現排序規則沖突。
一般來說,我們在創建臨時表時可能不會注意到排序規則,從而留下排序規則沖突的隱患。
比如Openlab V4.0的Blog模塊中的一個存儲過程,便有着這種隱患:
/****** 對?象ó: StoredProcedure [blogs].[up_CreateGetTagIds] 腳本日期: 01/20/2010 19:10:32 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
/*
RETURN VALUES:
Ids
*/
-- =============================================
-- Author: <Lance Zhang>
-- Create date: <2010-01-06>
-- Description: <Make sure all the tag EXISTS in DB, and then get their ids.>
-- 1. Create Temp Table.
-- 2. Insert TagNames into Temp Table.
-- 3. Add new Tags to [Categories] from query Temp Table.
-- 4. Batch Get All Tag Ids from [Categories].
-- 5. Clear and drop Temp Table.
-- =============================================
ALTER PROCEDURE [blogs].[up_CreateGetTagIds]
(
@BlogId INT,
@TagNames XML
)
AS
BEGIN
/******************************* SET CONFIG *************************************************/
SET NOCOUNT ON
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SET NUMERIC_ROUNDABORT OFF
/******************************* DECLARE VARIABLE *************************************************/
/********************************BEGIN TRANSATION**********************************************/
BEGIN TRY
BEGIN TRANSACTION;
-- 1. Create Temp Table.
CREATE TABLE #TagsTable
(
TagName NVARCHAR(64)
)
-- 2. Insert TagNames into Temp Table.
INSERT INTO
#TagsTable
SELECT
TG.Tags.value('@i','NVARCHAR(64)') AS TagName
FROM
@TagNames.nodes('/ts/t') TG(Tags)
-- 3. Add new Tags to [Categories] from query Temp Table.
BEGIN
INSERT INTO
[Categories]
(
[BlogId]
,[ParentId]
,[CategoryType]
,[CategoryName]
,[LoweredCategoryName]
,[Slug]
,[LoweredSlug]
,[Description]
,[CreatedDateUtc]
,[TotalEntities]
,[SortOrder]
,[State]
)
SELECT
@BlogId,
0, -- ParentId, 0 as default.
2, -- CategoryType, 2 as Post Tag.
TT.TagName,
LOWER(TT.TagName),
TT.TagName, -- Slug, use CategoryName as default.
LOWER(TT.TagName), -- LoweredSlug, use LoweredCategoryName as default.
'', -- Description, Empty as default.
GETUTCDATE(),
0, -- TotalEntities, 0 as default.
1, -- SortOrder of PostTags can always be 1.
1 -- State, 1 as Normal.
FROM
#TagsTable TT
WHERE
LOWER(TT.TagName) NOT IN
(
SELECT
C.[LoweredCategoryName]
FROM
[Categories] C WITH( UPDLOCK, HOLDLOCK )
WHERE
[BlogId] = @BlogId
AND [CategoryType] = 2 -- Post Tag.
)
END
-- 4. Batch Get All Tag Ids from [Categories].
BEGIN
SELECT
[CategoryId]
FROM
[Categories] C WITH(NOLOCK)
JOIN
#TagsTable TT
ON
C.[LoweredCategoryName] = LOWER( TT.TagName )
WHERE
C.[BlogId] = @BlogId
AND C.[CategoryType] = 2 -- Post Tag.
AND C.[State] = 1 -- 1 as Normal status.
END
-- 5. Clear and drop Temp Table.
TRUNCATE TABLE
#TagsTable
DROP TABLE
#TagsTable
COMMIT TRANSACTION;
RETURN 1
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0
BEGIN
ROLLBACK TRANSACTION;
RETURN -1
END
END CATCH
END
GO
常見的解決方案
知道了什么是排序規則沖突,我們接下來分析沖突的解決方案,以數據庫級別的排序規則為例,一般來說,解決方案有下面幾種
- 把SQL實例刪了重建 ——大多數情況下等於沒說-_-|||
- 修改數據庫的排序規則 ——參考阿牛兄的這篇文章
- 在T-SQL中使用COLLATE DATABASE_DEFAULT來解決沖突 ——接下來主要討論這個
COLLATE DATABASE_DEFAULT
Collate XXX 操作可以用在字段定義或使用時,它會將字段定義或轉換成XXX 的排序規則格式。而Collate Database_Default 則會將字段定義或轉換成當前數據庫的默認排序規則,從而解決沖突。
比如在下面的代碼中便使用了Collate Database_Default 來解決字段在equal操作中的排序規則沖突:
Insert into Security.Report (Name)
Select C.Path From SSRS.Catalog C
Where C.Path Collate Database_Default Like @ReportPath + '/%'
And C.Path Collate Database_Default Not In (Select Name From Security.Report R)
當然,在創建臨時表時若對字段定義加上Collate Database_Default ,也可以方便地解決潛在的排序規則沖突,比如上一節中提到的存儲過程,只要做如下修改即可。
-- 1. Create Temp Table.
CREATE TABLE #TagsTable
(
TagName NVARCHAR(64) COLLATE DATABASE_DEFAULT
)
結束語
對於專業的SQLer來說,排序規則的應用場景還有很多,例如利用排序規則特點計算漢字筆划和取得拼音首字母等等,更多信息,請查閱MSDN文檔:http://msdn.microsoft.com/zh-cn/library/aa258237(en-us,SQL.80).aspx
引用: http://www.cnblogs.com/blodfox777/archive/2010/01/21/sqlserver-collation-conflict-and-solutions.html
