SQL Server的 排序規則(collation)衝突和解決方案

什麼是排序規則(collation)

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

2010121145642

這些排序規則有什麼作用呢?讓我們先來看看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

常見的解決方案

知道了什麼是排序規則衝突,我們接下來分析衝突的解決方案,以數據庫級別的排序規則爲例,一般來說,解決方案有下面幾種

  1. 把SQL實例刪了重建 ——大多數情況下等於沒說-_-|||
  2. 修改數據庫的排序規則 ——參考阿牛兄的這篇文章
  3. 在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

謝謝!


作者:Lance ZhangLance Zhang's Tech Blog
出處:http://blodfox777.cnblogs.com/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。

發佈了139 篇原創文章 · 獲贊 21 · 訪問量 82萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章