如何在 SQL 服务器中获取区分大小写的排序规则版本?

How to get a case sensitive version of a collation in SQL Server?

有没有办法获得区分大小写的排序规则版本以用于查询?

假设查询可以用于具有不同排序规则的数据库,其中一些不区分大小写,并且可以具有不同的区域性。 (例如多个客户端)

但是,此查询应始终以区分大小写的方式运行,同时尽可能不更改排序规则区域性和其他属性。

比如一个DB恰好用了SQL_Latin1_General_CP1_CI_AS(这里的CI代表Case Insensitive),我想用SQL_Latin1_General_CP1_CS_AS(CS代表Case Sensitive)。

简单查询示例:

DECLARE @Title nvarchar(2) = 'qQ'

--Case insensitive (following DB collation)
SELECT REPLACE(@Title, 'q', 'o') --Result: 'oo'

--Case sensitive, but fixed to a collation
SELECT REPLACE(@Title COLLATE SQL_Latin1_General_CP1_CS_AS, 'q', 'o') --Result: 'oQ'

在查询中修复这样的排序规则可能会导致在迁移代码或以后更改数据库排序规则时出现问题。

是否有内置函数来获取当前排序规则的区分大小写版本,或者是否有可用于此目的的解决方法?

排序规则不一定由数据库默认值决定:它们也可以按字符串字段设置。

不,除了使用 Dynamic SQL 将 COLLATE 子句写入查询之外,我从未见过(而且我已经看过)进行动态归类的方法。或者,如果您需要考虑的选项数量很少,您 可以 可以尝试如下操作:

SELECT ...
FROM   ...
WHERE (@CaseSensitive = 1 AND [Field] LIKE N'%' + @Name + N'%' COLLATE Something_CS_AS)
OR (@CaseSensitive = 0 AND [Field] LIKE N'%' + @Name + N'%')

此外,大小写(甚至重音、假名或宽度)敏感和不敏感之间没有 直接 等价。虽然大多数时候不区分大小写的归类有区分大小写的对应物,但有 15 个仅不区分大小写的归类:

;WITH CaseS AS
(
  SELECT [name]
  FROM   sys.fn_helpcollations()
  WHERE  [name] LIKE N'%[_]cs[_]%'
)
SELECT CaseI.*
FROM   sys.fn_helpcollations() CaseI
LEFT JOIN CaseS
       ON CaseI.name = REPLACE(CaseS.[name], N'_CS_', N'_CI_')
WHERE  CaseI.[name] LIKE N'%[_]ci[_]%'
AND    CaseS.[name] IS NULL;

Returns:

name                                  description
SQL_1xCompat_CP850_CI_AS              ...
SQL_AltDiction_CP850_CI_AI            ...
SQL_AltDiction_Pref_CP850_CI_AS       ...
SQL_Danish_Pref_CP1_CI_AS             ...
SQL_Icelandic_Pref_CP1_CI_AS          ...
SQL_Latin1_General_CP1_CI_AI          ...
SQL_Latin1_General_CP1253_CI_AI       ...
SQL_Latin1_General_CP437_CI_AI        ...
SQL_Latin1_General_CP850_CI_AI        ...
SQL_Latin1_General_Pref_CP1_CI_AS     ...
SQL_Latin1_General_Pref_CP437_CI_AS   ...
SQL_Latin1_General_Pref_CP850_CI_AS   ...
SQL_Scandinavian_Pref_CP850_CI_AS     ...
SQL_SwedishPhone_Pref_CP1_CI_AS       ...
SQL_SwedishStd_Pref_CP1_CI_AS         ...

Fixing a collation like this in the query could cause problems when migrating the code,

为什么?您打算将代码迁移到哪里?如果是另一个 RDBMS,那么您已经需要应对数据类型差异、SQL 方言差异、"best practices" 差异等。那么为什么还要担心排序规则呢?除非你确定你将迁移到另一个 RDBMS,否则你应该通过使用你当前的平台来使你的系统尽可能最好地工作,而不是由于以下原因而处于不太理想的状态仅使用最低评论分母功能。

or changing the DB collation at a latter date.

你为什么要这样做?同样,任何具有显式 COLLATION 设置的字符串字段都不受数据库默认设置的影响。


如果您正在寻找 strict 大小写(以及所有内容,包括重音等)对 equivalence 的敏感性(我们 不是 谈论范围搜索或排序),那么您可以使用二进制排序规则(即以 _BIN_BIN2 结尾的排序规则)。请记住,二进制归类可能不会按照您期望的方式排序,因为它们不是基于 "dictionary" 的排序,至少就在所有语言中表现相同的单个二进制归类而言。他们也不在语言之间进行等价(即将 "a" 等同于带有重音的 "a")。

自从最初发布此答案后,我发现上面的段落实际上是错误的建议。如果目标是区分大小写,请不要使用二进制排序规则。它过于严格,在许多情况下不会给出准确的结果。详情和例子请看:No, Binary Collations are not Case-Sensitive.

此外,请不要使用仅以 _BIN 结尾的二进制排序规则,因为自 SQL Server 2005 发布以来它们已经过时,并且应该只当需要保持与也使用 _BIN 归类的另一个系统的向后兼容性时使用。如果您需要二进制排序规则,请使用以 _BIN2 结尾的排序规则。详情和例子请看:Differences Between the Various Binary Collations (Cultures, Versions, and BIN vs BIN2).


更新

我能够想出一个函数来获取传入排序规则的区分大小写的版本(如果存在)。但是,此函数只会帮助创建正确的 Dynamic SQL;它不能在查询中内联使用以动态设置 COLLATE 子句(主要是因为不能这样做)。有两个参数:

  • @CollationName——如果你传递这个,你会得到它的区分大小写的版本,如果存在的话。 @DatabaseName 参数将被忽略。
  • @DatabaseName——如果您不知道确切的排序规则,请将 @CollationName 保留为 NULL 并将其传入,它将查找该数据库的默认排序规则。
  • 如果两个参数都是 NULL 那么它将查找函数所在数据库的默认排序规则。
  • 如果传入或查找的排序规则已经区分大小写,将返回该名称
  • 待办事项(当我有时间时):查找没有默认的数据库的服务器默认排序规则(它们将 NULL 作为默认排序规则名称)

函数有两个版本:第一个是 TVF(因为它们更快)和标量 UDF(因为它们有时更容易交互)。

Table-值函数:

USE [Test];
SET ANSI_NULLS ON;

IF (OBJECT_ID(N'dbo.GetCaseSensitiveCollation') IS NOT NULL)
BEGIN
  DROP FUNCTION dbo.GetCaseSensitiveCollation;
END;

GO
CREATE FUNCTION dbo.GetCaseSensitiveCollation
(
  @CollationName sysname,
  @DatabaseName sysname
)
RETURNS TABLE
--WITH SCHEMABINDING
--     Cannot schema bind table valued function 'dbo.GetCaseSensitiveCollation'
--     because it references system object 'sys.fn_helpcollations'.
AS RETURN

  WITH collation(name) AS
  (
    SELECT CONVERT(sysname, COALESCE(@CollationName,
                DATABASEPROPERTYEX(COALESCE(@DatabaseName, DB_NAME()), 'Collation')))
  )
  SELECT col.[name]
  FROM   sys.fn_helpcollations() col
  CROSS JOIN collation
  WHERE  col.[name] = CASE WHEN collation.[name] LIKE N'%[_]CS[_]%' 
                               THEN collation.[name]
                           ELSE REPLACE(collation.[name], N'_CI_', N'_CS_')
                      END;
GO

示例:

-- Get CS Collation for the specified Collation
SELECT [name] AS [BySpecificCollation]
FROM dbo.GetCaseSensitiveCollation(N'Indic_General_100_CI_AS_KS_WS', NULL);

-- Get CS Collation based on database default for the specified database
SELECT [name] AS [ByDefaultCollationForDB]
FROM dbo.GetCaseSensitiveCollation(NULL, N'msdb');

-- Get CS Collation based on database default for database that the function exists in
SELECT [name] AS [CurrentDB]
FROM Test.dbo.GetCaseSensitiveCollation(NULL, NULL);

-- Get CS Collation based on database default for the current database
USE [ReportServer];
SELECT [name] AS [CurrentDB]
FROM Test.dbo.GetCaseSensitiveCollation(NULL, DB_NAME());

标量用户定义函数:

USE [Test];
SET ANSI_NULLS ON;

IF (OBJECT_ID(N'dbo.GetCaseSensitiveCollation2') IS NOT NULL)
BEGIN
  DROP FUNCTION dbo.GetCaseSensitiveCollation2;
END;
GO
CREATE FUNCTION dbo.GetCaseSensitiveCollation2
(
  @CollationName sysname,
  @DatabaseName sysname
)
RETURNS sysname
--WITH SCHEMABINDING
--     Cannot schema bind table valued function 'dbo.GetCaseSensitiveCollation2'
--     because it references system object 'sys.fn_helpcollations'.
AS
BEGIN
  DECLARE @NewCollationName sysname;

  ;WITH collation(name) AS
  (
    SELECT CONVERT(sysname, COALESCE(@CollationName,
                DATABASEPROPERTYEX(COALESCE(@DatabaseName, DB_NAME()), 'Collation')))
  )
  SELECT @NewCollationName = col.[name]
  FROM   sys.fn_helpcollations() col
  CROSS JOIN collation
  WHERE  col.[name] = CASE WHEN collation.[name] LIKE N'%[_]CS[_]%'
                                THEN collation.[name]
                           ELSE REPLACE(collation.[name], N'_CI_', N'_CS_')
                      END;

  RETURN @NewCollationName;
END;
GO

示例:

/* Get CS Collation for the specified Collation */
SELECT dbo.GetCaseSensitiveCollation2(N'Indic_General_100_CI_AS_KS_WS', NULL)
                 AS [BySpecificCollation];
-- Indic_General_100_CS_AS_KS_WS

/* Get CS Collation based on database default for the specified database */
SELECT dbo.GetCaseSensitiveCollation2(NULL, N'msdb') AS [ByDefaultCollationForDB];
-- SQL_Latin1_General_CP1_CS_AS

/* Get CS Collation based on database default for the current database */
USE [ReportServer];
SELECT Test.dbo.GetCaseSensitiveCollation2(NULL, DB_NAME()) AS [CurrentDB];
-- Latin1_General_CS_AS_KS_WS

/* Get CS Collation based on database default for database where the function exists */
SELECT Test.dbo.GetCaseSensitiveCollation2(NULL, NULL) AS [DBthatFunctionExistsIn];
-- SQL_Latin1_General_CP1_CS_AS