UDF中的COLLATE无法按预期工作

avs*_*099 6 sql t-sql sql-server collation

我有一个带有文本字段的表.我想选择文本全部大写的行.此代码按预期工作,并返回ABC:

SELECT txt
FROM (SELECT 'ABC' AS txt UNION SELECT 'cdf') t
WHERE 
txt COLLATE SQL_Latin1_General_CP1_CS_AS = UPPER(txt)
Run Code Online (Sandbox Code Playgroud)

然后我创建UDF(如此处所示):

CREATE FUNCTION [dbo].[fnsConvert]
(
      @p NVARCHAR(2000) ,
      @c NVARCHAR(2000)
)
RETURNS NVARCHAR(2000)
AS
    BEGIN
        IF ( @c = 'SQL_Latin1_General_CP1_CS_AS' )
            SET @p = @p COLLATE SQL_Latin1_General_CP1_CS_AS
        RETURN @p    
    END
Run Code Online (Sandbox Code Playgroud)

并运行如下(看起来像我的等效代码):

SELECT txt
FROM (SELECT 'ABC' AS txt UNION SELECT 'cdf') t
WHERE 
dbo.fnsConvert(txt, 'SQL_Latin1_General_CP1_CS_AS') = UPPER(txt)
Run Code Online (Sandbox Code Playgroud)

然而,这ABC也会回归cdf.

为什么会如此,我该如何让它发挥作用?

PS我在这里需要UDF才能从.Net LINQ2SQL提供程序调用区分大小写的比较.

Shn*_*ugo 7

变量不能拥有自己的排序规则.它将始终使用服务器的默认值.检查一下:

- 我声明了三个变量,每个变量都有自己的排序规则 - 至少有一个可能这样认为:

DECLARE @deflt VARCHAR(100) = 'aBc'; --Latin1_General_CI_AS in my system
DECLARE @Arab VARCHAR(100) = 'aBc' COLLATE Arabic_100_CS_AS_WS_SC;
DECLARE @Rom VARCHAR(100) = 'aBc' COLLATE Romanian_CI_AI
Run Code Online (Sandbox Code Playgroud)

- 现在检查一下.所有三个变量都被视为系统的默认排序规则:

SELECT [name], system_type_name, collation_name
FROM sys.dm_exec_describe_first_result_set(N'SELECT @deflt AS Deflt, @Arab AS Arab, @Rom AS Rom'
                                          ,N'@deflt varchar(100), @Arab varchar(100),@Rom varchar(100)'
                                          ,0);

/*
name    system_type_name    collation_name
Deflt   varchar(100)        Latin1_General_CI_AS
Arab    varchar(100)        Latin1_General_CI_AS
Rom     varchar(100)        Latin1_General_CI_AS
*/
Run Code Online (Sandbox Code Playgroud)

- 现在我们检查"aBc"与"ABC"的简单比较

SELECT CASE WHEN @deflt = 'ABC' THEN 'CI' ELSE 'CS' END AS CheckDefault
      ,CASE WHEN @Arab = 'ABC' THEN 'CI' ELSE 'CS' END AS CheckArab
      ,CASE WHEN @Rom = 'ABC' THEN 'CI' ELSE 'CS' END AS CheckRom

/*CI    CI  CI*/
Run Code Online (Sandbox Code Playgroud)

- 但是我们可以为一个给定的动作指定排序规则!

SELECT CASE WHEN @deflt = 'ABC' THEN 'CI' ELSE 'CS' END AS CheckDefault
      ,CASE WHEN @Arab = 'ABC' COLLATE Arabic_100_CS_AS_WS_SC THEN 'CI' ELSE 'CS' END AS CheckArab
      ,CASE WHEN @Rom = 'ABC' COLLATE Romanian_CI_AI THEN 'CI' ELSE 'CS' END AS CheckRom

/*CI    CS  CI*/
Run Code Online (Sandbox Code Playgroud)

- 但是表的列的行为会有所不同:

CREATE TABLE #tempTable(deflt VARCHAR(100)
                       ,Arab VARCHAR(100) COLLATE Arabic_100_CS_AS_WS_SC
                       ,Rom VARCHAR(100) COLLATE Romanian_CI_AI);

INSERT INTO #tempTable(deflt,Arab,Rom) VALUES('aBc','aBc','aBc');

SELECT [name], system_type_name, collation_name
FROM sys.dm_exec_describe_first_result_set(N'SELECT * FROM #tempTable',NULL,0);
DROP TABLE #tempTable;

/*
name    system_type_name    collation_name
deflt   varchar(100)        Latin1_General_CI_AS
Arab    varchar(100)        Arabic_100_CS_AS_WS_SC
Rom     varchar(100)        Romanian_CI_AI
*/
Run Code Online (Sandbox Code Playgroud)

- 这也适用于声明的表变量.比较"知道"指定的排序规则:

DECLARE @TableVariable TABLE(deflt VARCHAR(100)
                            ,Arab VARCHAR(100) COLLATE Arabic_100_CS_AS_WS_SC
                            ,Rom VARCHAR(100) COLLATE Romanian_CI_AI);

INSERT INTO @TableVariable(deflt,Arab,Rom) VALUES('aBc','aBc','aBc');

SELECT CASE WHEN tv.deflt = 'ABC' THEN 'CI' ELSE 'CS' END AS CheckDefault
      ,CASE WHEN tv.Arab = 'ABC' THEN 'CI' ELSE 'CS' END AS CheckArab
      ,CASE WHEN tv.Rom = 'ABC' THEN 'CI' ELSE 'CS' END AS CheckRom
FROM @TableVariable AS tv

/*CI    CS  CI*/
Run Code Online (Sandbox Code Playgroud)

更新一些文档

在此链接您可以阅读有关详细信息.排序规则不会更改该值.它应用一个规则(相关的规则NOT NULL不会更改值,但只是添加规则是否NULL可以设置).

文件清楚地说明了

是可以应用于数据库定义或列定义以定义排序规则的子句,还是应用于排序规则转换的字符串表达式.

稍后你会发现

  1. 创建或更改数据库
  2. 创建或更改表列
  3. 转换表达式的排序规则

更新2:解决方案的建议

如果您想控制比较是CS还是CI,您可以尝试这样做:

DECLARE @tbl TABLE(SomeValueInDefaultCollation VARCHAR(100));
INSERT INTO  @tbl VALUES ('ABC'),('aBc');

DECLARE @CompareCaseSensitive BIT = 0;
DECLARE @SearchFor VARCHAR(100) = 'aBc';

SELECT *
FROM @tbl 
WHERE (@CompareCaseSensitive=1 AND SomeValueInDefaultCollation=@SearchFor COLLATE Latin1_General_CS_AS)
   OR (ISNULL(@CompareCaseSensitive,0)=0 AND SomeValueInDefaultCollation=@SearchFor COLLATE Latin1_General_CI_AS);
Run Code Online (Sandbox Code Playgroud)

随着@CompareCaseSensitive设置为1将只返回aBc,用NULL0将返回两条线.

这是 - 当然! - 性能比UDF好得多.


小智 6

请尝试使用BINARY_CHECKSUMFunction,无需UDF功能:

SELECT txt
FROM (SELECT 'ABC' AS txt UNION SELECT 'cdf') t
WHERE 
BINARY_CHECKSUM(txt)= BINARY_CHECKSUM(UPPER(txt))
Run Code Online (Sandbox Code Playgroud)