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