【问题标题】:How to get a case sensitive version of a collation in SQL Server?如何在 SQL Server 中获取区分大小写的排序规则版本?
【发布时间】:2015-05-09 08:16:26
【问题描述】:

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

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

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

例如,如果数据库恰好使用 SQL_Latin1_General_CP1_CI_AS(CI 代表不区分大小写),我想使用 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】:

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

    不,除了使用动态 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;
    

    返回:

    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 设置的字符串字段都不受数据库默认值的影响。


    如果您正在寻找严格大小写(以及包括口音等所有内容)对等价的敏感性(我们在谈论关于范围搜索或排序),那么您可以使用二进制排序规则(即以_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)


    更新

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

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

    【讨论】:

    • 我们有大量结构相同但排序规则不同的旧客户端数据库。其中一些不区分大小写。不必为每个人定制 SP 就太好了。如果没有办法做到这一点,我们可能不得不动态地构建和执行查询。或者,更有可能的是,有生成变体 SP 的脚本。我只是希望有一个更优雅的解决方案:)
    • @Oxians 我看到您已经接受(谢谢),但我在顶部添加了与“动态”查询相关的建议。请务必测试几个案例,因为该模式通常不如动态 SQL。
    • @Oxians 我找到了一种方法来获取特定排序规则的区分大小写形式(如果存在)。查看我的答案的更新部分:)。
    • 非常好,谢谢!我们最终选择了动态 SQL 生成(为 SP 创建/更改生成 SQL)。这些函数在未来可能会很有用,因为我们现在寻求更原始的解决方案(配置表中的备用排序规则名称)
    猜你喜欢
    • 2014-05-10
    • 2011-05-11
    • 2011-03-18
    • 2011-06-01
    • 1970-01-01
    • 2011-05-23
    • 2019-06-19
    • 2012-06-26
    • 2016-12-19
    相关资源
    最近更新 更多