Answers:
SQL Server中的排序规则确定用于匹配和排序字符数据的规则。通常,您将首先根据比较语义和数据使用者所需的排序顺序来选择排序规则。
人们通常不会发现二进制排序规则会产生他们期望的排序和比较行为。因此,尽管它们提供了最佳的性能(尤其是纯代码点BIN2版本),但大多数实现并未使用它们。
在原始性能方面(仅适用于非Unicode字符串),第二个是向后兼容SQL归类。使用Unicode数据时,这些归类使用Windows归类具有相同性能特征来代替。这里有一些细微的陷阱,因此这些天您需要有充分的理由选择SQL排序规则(除非在美国系统上工作,因为它仍然是默认设置)。
通常,由于复杂的Unicode比较和排序规则,Windows排序规则是最慢的。但是,它们提供了与SQL Server中Windows的完全兼容性,并定期维护以跟上Unicode标准的更改。对于包含Unicode数据的现代用途,通常建议使用Windows排序规则。
TL; DR
如果只需要区分大小写的比较和排序语义,则应选择_CS_
(对于区分大小写)变体,无论哪种基本排序规则都能为用户的语言和文化提供预期的行为。例如,这两个都是区分大小写的归类:
-- Latin1-General, case-sensitive, accent-sensitive
Latin1_General_CS_AS
-- Latin1-General, case-sensitive, accent-sensitive for Unicode Data,
-- SQL Server Sort Order 51 on Code Page 1252 for non-Unicode Data
SQL_Latin1_General_CP1_CS_AS
您可以使用sys.fn_helpcollations查看这些定义。
除排序规则外,四个表完全相同;一种二进制文件,一种区分大小写,一种不区分大小写和一种SQL区分大小写:
CREATE TABLE #Example_BIN
(
string nvarchar(50)
COLLATE Latin1_General_BIN
NOT NULL
);
CREATE TABLE #Example_CS
(
string nvarchar(50)
COLLATE Latin1_General_CS_AI
NOT NULL
);
CREATE TABLE #Example_CI
(
string nvarchar(50)
COLLATE Latin1_General_CI_AI
NOT NULL
);
CREATE TABLE #Example_SQL
(
string varchar(50) -- Note varchar
COLLATE SQL_Latin1_General_CP1_CS_AS
NOT NULL
);
每个表的样本数据相同:
INSERT #Example_BIN
(string)
VALUES
(N'A'),
(N'a'),
(N'B'),
(N'b'),
(N'C'),
(N'c');
INSERT #Example_CS
SELECT EB.string
FROM #Example_BIN AS EB;
INSERT #Example_CI
SELECT EB.string
FROM #Example_BIN AS EB;
INSERT #Example_SQL
SELECT EB.string
FROM #Example_BIN AS EB;
现在我们要查找大于“ a”的字符串:
SELECT EB.string AS BIN
FROM #Example_BIN AS EB
WHERE EB.string > N'a'
ORDER BY EB.string;
SELECT EC.string AS CS
FROM #Example_CS AS EC
WHERE EC.string > N'a'
ORDER BY EC.string;
SELECT EC2.string AS CI
FROM #Example_CI AS EC2
WHERE EC2.string > N'a'
ORDER BY EC2.string;
SELECT ES.string AS SQL
FROM #Example_SQL AS ES
WHERE ES.string > 'a' -- not Unicode
ORDER BY ES.string;
╔═════╗
║ BIN ║
╠═════╣
║ b ║
║ c ║
╚═════╝
╔════╗
║ CS ║
╠════╣
║ A ║
║ b ║
║ B ║
║ c ║
║ C ║
╚════╝
╔════╗
║ CI ║
╠════╣
║ B ║
║ b ║
║ C ║
║ c ║
╚════╝
╔═════╗
║ SQL ║
╠═════╣
║ B ║
║ b ║
║ C ║
║ c ║
╚═════╝
但是请注意,如果我们将Unicode文字与SQL归类一起使用,则隐式转换规则将导致Windows归类比较:
SELECT ES.string AS SQL
FROM #Example_SQL AS ES
WHERE ES.string > N'a'
ORDER BY ES.string;
...并且SQL排序规则结果更改:
╔═════╗
║ SQL ║
╠═════╣
║ A ║
║ B ║
║ b ║
║ C ║
║ c ║
╚═════╝
鉴于这是一个现有的数据库已经定义了表,因此,除了对DML操作的潜在性能影响(实际上已经存在)之外,更改数据库归类的操作还会产生一些非常严重的影响。这会对性能和功能产生非常实际的影响,并且此更改不仅没有达到预期的目标(至少不是始终如一),而且就行为(或在创建新表时将改变行为)的可能性而言,数据如何排序和相等。
保罗已经在回答中提供了很好的解释,并举例说明了不同类型的归类在性能和行为上的差异,因此在此我不再赘述。但是,有几点需要一些额外的细节,并且与更改现有数据库的排序规则的当前方案(与设置新数据库的排序规则相对)有关,还有其他几点要添加。
二进制排序规则不仅区分大小写:它们对所有内容都敏感!因此,通过使用二进制排序规则(以_BIN
或结尾_BIN2
),您的比较现在也对重音敏感,对假名敏感,对宽度敏感以及对面筋敏感(至少现在看来这是趋势;-))。这是进行此更改的理想影响吗?最终用户是否期望这种行为改变?
归类不仅影响比较,还影响排序。二进制排序规则将基于每个字节的ASCII
或UNICODE
字节值(分别取决于VARCHAR
或NVARCHAR
)进行排序。因此,通过选择二进制排序规则,您将放弃特定于语言/文化的加权规则,该规则根据该文化的字母对每个字符(甚至某些语言的字符,例如匈牙利语,由2个字母组成)进行排序。因此,如果“ ch”自然应该在 “ k” 之后,那么使用二进制排序规则就不会发生这种情况。同样,这是否是进行此更改的理想效果?最终用户是否期望这种行为改变?
除非您对应用程序有特定的向后兼容性要求,否则应使用collation BIN2
而不是BIN
排序规则,当然前提是首先要使用二进制排序规则。该BIN2
排序规则在SQL Server 2005中引入,并根据MSDN页的使用原则为BIN和BIN2排序规则:
SQL Server中以前的二进制排序规则(以“ _BIN”结尾的排序规则)对Unicode数据执行了不完整的代码点到代码点比较。较旧的SQL Server二进制排序规则将第一个字符作为WCHAR进行比较,然后逐字节进行比较。
...
您可以迁移到[_BIN2]二进制排序规则,以利用真正的代码点比较,并且应使用新的二进制排序规则来开发新的应用程序。
还应该注意的是,_BIN2
排序规则很方便地匹配StringComparison EnumerationOrdinal
选项的行为,以便使用该选项在.NET代码中进行的比较和排序将产生与SQL Server中正在执行的相同操作相同的结果(当使用的归类,当然)。_BIN2
出于与_BIN2
归类有关的类似原因,除非您对保持向后兼容的行为有特定要求,否则应倾向于使用Windows归类,而不要使用SQL Server特定的归类(即,SQL_
现在考虑以开头的归类)有点“烂” ;-))。
当使用Unicode数据(即N
,将数据类型指定为NChar
或的应用程序代码中带有前缀或传入SQL Server的字符串NVarChar
)时,我看不到使用一种排序规则与另一种排序规则如何对插入或更新NCHAR
or NVARCHAR
字符串字段有所不同。
当使用非Unicode数据,或者插入或更新非Unicode字段时,如果需要翻译或无法映射正在插入/更新的任何字符,则特定的排序规则(数据库或字段)可能起着很小的作用。甚至是一个单词?),由排序规则定义的代码页指定。当然,每当使用非Unicode数据或数据类型时,都会存在此潜在问题,并且此问题并不特定于更改数据库排序规则的情况。该更改将影响字符串文字(如果数据库排序规则与字段的排序规则不同,则可能已经成为问题)。但是,即使不对数据库排序规则进行任何更改,来自其他数据库或来自SQL Server外部的数据(任何客户端代码)也可以包含任何字符,并且可以具有任何特定的编码。
很重要!!!更改数据库默认排序规则时,为任何现有表中任何现有字符串字段指定的排序规则都不会更改,但是任何新字段都将具有数据库默认排序规则(除非通过该COLLATE
子句覆盖)。这将以三种方式影响您的查询:
1)如果对任何现有字段中的任何一个查询将JOIN联接到任何新字段中,则会收到排序规则不匹配错误:
USE [master];
GO
IF (DB_ID(N'ChangeCollationTest') IS NOT NULL)
BEGIN
PRINT 'Dropping [ChangeCollationTest] DB...';
ALTER DATABASE [ChangeCollationTest]
SET SINGLE_USER
WITH ROLLBACK IMMEDIATE;
DROP DATABASE [ChangeCollationTest];
END;
GO
PRINT 'Creating [ChangeCollationTest] DB...';
CREATE DATABASE [ChangeCollationTest]
COLLATE SQL_Latin1_General_CP1_CI_AS;
GO
USE [ChangeCollationTest];
GO
CREATE TABLE [CollateTest-SQL_Latin1_General_CP1_CI_AS]
(Col1 NVARCHAR(50) COLLATE DATABASE_DEFAULT, Col2 NVARCHAR(50));
SELECT *
FROM sys.columns sc
WHERE sc.[object_id] = OBJECT_ID(N'[CollateTest-SQL_Latin1_General_CP1_CI_AS]');
-- "collation_name" for both fields shows: SQL_Latin1_General_CP1_CI_AS
GO
USE [master];
GO
ALTER DATABASE [ChangeCollationTest]
COLLATE Latin1_General_BIN2;
GO
USE [ChangeCollationTest];
GO
CREATE TABLE [CollateTest-Latin1_General_BIN2]
(Col1 NVARCHAR(50) COLLATE DATABASE_DEFAULT, Col2 NVARCHAR(50));
SELECT *
FROM sys.columns sc
WHERE sc.[object_id] = OBJECT_ID(N'[CollateTest-Latin1_General_BIN2]');
-- "collation_name" for both fields shows: Latin1_General_BIN2
GO
SELECT *
FROM dbo.[CollateTest-SQL_Latin1_General_CP1_CI_AS] ctSQL
INNER JOIN dbo.[CollateTest-Latin1_General_BIN2] ctWIN
ON ctWIN.Col1 = ctSQL.Col1;
返回值:
Msg 468, Level 16, State 9, Line 4
Cannot resolve the collation conflict between "SQL_Latin1_General_CP1_CI_AS" and
"Latin1_General_BIN2" in the equal to operation.
2)与字符串文字或变量进行比较的现有表的现有字段(设置为先前的默认排序规则)上的谓词/过滤器不会出错,但是由于SQL Server需要将排序规则的等式化,因此它们肯定会对性能产生影响。并自动将字符串文字或变量转换为字段的排序规则。启用“包括实际执行计划”(Control-M),然后执行以下操作(假设您已经运行了上面显示的查询):
SELECT *
FROM dbo.[CollateTest-SQL_Latin1_General_CP1_CI_AS] ctSQL
WHERE ctSQL.Col1 = N'a';
-- Unspecified collations on string literals and variables assume the database default
-- collation. This mismatch doesn't cause an error because SQL Server adds a
-- "[Col1]=CONVERT_IMPLICIT(nvarchar(4000),[@1],0)" but it can hurt performance.
SELECT *
FROM dbo.[CollateTest-Latin1_General_BIN2] ctWIN
WHERE ctWIN.Col1 = N'a';
-- No CONVERT_IMPLICIT; plan shows "[Col1]=[@1]".
3) AND,就隐式转换而言,请注意转换的是字符串文字(具有数据库默认排序规则的隐式排序规则Latin1_General_BIN2
),而不是表中的字段。关于此过滤器是否区分大小写(旧排序规则)或区分大小写(新排序规则)的任何猜测?运行以下命令以查看:
INSERT INTO dbo.[CollateTest-SQL_Latin1_General_CP1_CI_AS] (Col1)
VALUES (N'a'), (N'A');
SELECT ctSQL.Col1
FROM dbo.[CollateTest-SQL_Latin1_General_CP1_CI_AS] ctSQL
WHERE ctSQL.Col1 = N'a';
返回值:
Col1
----
a
A
天哪!该查询不仅会由于造成轻微(或更重要的影响)的性能影响CONVERT_IMPLICIT()
,而且甚至不会以所需的区分大小写的方式运行。
如此,如果排序规则在已经有表的数据库上更改,那么是的,性能和功能都会受到影响。
如果排序规则是在新的数据库上设置的,那么Paul已经通过解释二进制排序规则来解决了这一问题,尽管它虽然很快,却可能不会以人们期望或期望的方式排序。
还应注意,您始终可以按条件指定排序规则。该COLLATE子句可以加入WHERE
条件ORDER BY
,大多数接受字符串的任何地方。
示例1(WHERE条件):
SELECT tmp.col AS [SQL-CaseSensitive]
FROM (VALUES ('a'), ('A'), ('b'), ('B')) tmp(col)
WHERE tmp.col > 'a' COLLATE SQL_Latin1_General_CP1_CS_AS;
SELECT tmp.col AS [Windows-CaseSensitive]
FROM (VALUES ('a'), ('A'), ('b'), ('B')) tmp(col)
WHERE tmp.col > 'a' COLLATE Latin1_General_CS_AI;
返回值:
SQL-CaseSensitive
-----------------
b
B
Windows-CaseSensitive
-----------------
A
b
B
示例2(ORDER BY):
SELECT tmp.col AS [Windows-CaseSensitive]
FROM (VALUES ('a'), ('A'), ('b'), ('B')) tmp(col)
ORDER BY tmp.col COLLATE Latin1_General_CS_AI;
SELECT tmp.col AS [Windows-Binary]
FROM (VALUES ('a'), ('A'), ('b'), ('B')) tmp(col)
ORDER BY tmp.col COLLATE Latin1_General_BIN2;
返回值:
Windows-CaseSensitive
-----------------
a
A
b
B
Windows-Binary
-----------------
A
B
a
b
示例3(IF语句):
IF ('A' = 'a') SELECT 1 AS [DatabaseDefault-CaseInsensitive?];
-- if the DB is not case-sensitive or binary, returns 1
IF ('A' = 'a' COLLATE Latin1_General_BIN2) SELECT 2 AS [Windows-Binary];
返回值:
DatabaseDefault-CaseInsensitive?
--------------------------------
1
{nothing}
示例4(与函数输入参数关联):
SELECT UNICODE(N'🂡') AS [UCS-2],
UNICODE(N'🂡' COLLATE Latin1_General_100_CI_AS_SC) AS [UTF-16];
-- This character is a Unicode supplemental character and is not part of the
-- default UCS-2 encoding. In order for built-in functions to handle these
-- characters correctly, either the DB default collation needs to end in
-- "_SC" (available as of SQL Server 2012), or use as shown here.
-- See the character in more detail here: http://unicode-table.com/en/1F0A1/
返回值:
UCS-2 UTF-16
------ -------
55356 127137
UCS-2值55,356是部分正确的,因为它是“代理对”中两个值中的第一个。但是,除非明确给出_SC
排序规则,否则该UNICODE()
函数只能将每个字符视为双字节值,并且不知道如何正确处理双双字节代理对。
更新
即使使用了上述所有示例,区分大小写比较的一个方面(通常被忽略,而被二进制比较/排序规则所否定)仍然是Unicode的归一化(组成和分解)。
示例5(二进制比较不区分大小写时):
区分大小写的真实比较允许将字符与另一个字符组合在一起,从而形成已经作为另一个Unicode代码点存在的另一个字符。区分大小写的比较关注可显示字符,而不是用于创建该字符的代码点。
SELECT 'Equal' AS [Binary],
NCHAR(0x00FC) AS [ü],
N'u' + NCHAR(0x0308) AS [u + combining diaeresis]
WHERE NCHAR(0x00FC) COLLATE Latin1_General_100_BIN2
= N'u' + NCHAR(0x0308) COLLATE Latin1_General_100_BIN2
-- No result as they are a different number of code points,
-- as well as being different code points.
SELECT 'Equal' AS [Case-Sensitive],
NCHAR(0x00FC) AS [ü],
N'u' + NCHAR(0x0308) AS [u + combining diaeresis]
WHERE NCHAR(0x00FC) COLLATE Latin1_General_100_CS_AS -- ü
= N'u' + NCHAR(0x0308) COLLATE Latin1_General_100_CS_AS -- u + combining diaeresis
-- Result set returned, even being a different number of code points AND Accent Sensitive,
-- due to normalization
返回值:
Binary ü u + combining diaeresis
------- --- -------------------------
{nothing}
Case-Sensitive ü u + combining diaeresis
--------------- --- -------------------------
Equal ü ü
真正的区分大小写的比较还允许宽字符等于其非宽等效项。
IF (N'sofia' = N'sofia' COLLATE Latin1_General_100_BIN2)
SELECT 'Values are the same' AS [Binary]
ELSE
SELECT 'Values are different' AS [Binary];
IF (N'sofia' = N'sofia' COLLATE Latin1_General_100_CS_AS)
SELECT 'Values are the same' AS [Case-Sensitive]
ELSE
SELECT 'Values are different' AS [Case-Sensitive];
返回值:
Binary
---------------
Values are different
Case-Sensitive
---------------
Values are the same
_BIN
和_BIN2
)归类不区分大小写!