如何在SQL Server中获取一个区分大小写的排序规则版本?

问题描述 投票:3回答:1

有没有办法在查询中使用区分大小写的排序规则版本?

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

但是,此查询应始终以区分大小写的方式运行,如果可能,则不会更改排序规则文化和其他属性。

例如,如果DB恰好使用SQL_Latin1_General_CP1_CI_AS(CI代表Case Insensitive),我想使用SQL_Latin1_General_CP1_CS_AS(CS表示区分大小写)。

简单的查询示例:

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'

在查询中修复这样的排序规则可能会在迁移代码时导致问题,或者在后一个日期更改数据库排序规则。

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

sql sql-server tsql collation case-sensitive
1个回答
5
投票

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

不,我从未见过使用动态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'%')

此外,Case(或甚至Accent,Kana或Width)敏感且不敏感之间没有直接等价。虽然大多数情况下对于不区分大小写的排序规则存在区分大小写的对应关系,但有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;

返回:

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         ...

在查询中修复这样的排序规则可能会在迁移代码时导致问题,

为什么?您计划将代码迁移到哪里?如果是另一个RDBMS,那么您已经需要应对数据类型差异,SQL方言差异,“最佳实践”差异等等。那么为什么要担心整理呢?除非您确定要迁移到另一个RDBMS,否则您应该尽可能地利用当前平台尽可能地使系统正常工作,而不是由于存在于最佳状态仅使用最低评论分母功能。

或在后一天更改数据库排序规则。

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


如果您正在寻找严格的Case(以及包括Accent等在内的所有内容)对等效性的敏感性(我们不是在讨论范围搜索或排序),那么您可以使用二进制排序规则(即以_BIN_BIN2结尾的一个)。请记住,二进制排序规则可能不会按照您预期的方式排序,因为它们不是基于“字典”的排序,至少不是单个二进制排序规则在所有语言中表现相同。它们也没有在语言之间形成等价(即将“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)


UPDATE

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

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

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

表值函数:

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
© www.soinside.com 2019 - 2024. All rights reserved.