UDF 中的 COLLATE 无法按预期工作

Posted

技术标签:

【中文标题】UDF 中的 COLLATE 无法按预期工作【英文标题】:COLLATE in UDF does not work as expected 【发布时间】:2018-06-02 01:52:52 【问题描述】:

我有一个带有文本字段的表格。我想选择文本全部大写的行。此代码正常工作,并返回 ABC:

SELECT txt
FROM (SELECT 'ABC' AS txt UNION SELECT 'cdf') t
WHERE 
txt COLLATE SQL_Latin1_General_CP1_CS_AS = UPPER(txt)

然后我创建 UDF(建议 here):

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

并按如下方式运行它(这对我来说就像一个等效的代码):

SELECT txt
FROM (SELECT 'ABC' AS txt UNION SELECT 'cdf') t
WHERE 
dbo.fnsConvert(txt, 'SQL_Latin1_General_CP1_CS_AS') = UPPER(txt)

但是,这会返回 ABCcdf

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

PS 我需要 UDF 以便能够从 .Net LINQ2SQL 提供程序调用区分大小写的比较。

【问题讨论】:

【参考方案1】:

我认为您对排序规则的工作方式感到困惑。如果您想强制区分大小写排序规则,您可以在 where 谓词中执行此操作,而不是使用类似的函数。而且标量函数的性能很糟糕。

以下是您可以如何将排序规则用于此类事情。

SELECT txt
FROM (SELECT 'ABC' AS txt UNION SELECT 'cdf') t
WHERE txt collate SQL_Latin1_General_CP1_CS_AS = UPPER(txt)

【讨论】:

我不确定这如何回答我的问题。除了性能部分,你能澄清一下我对整理部分的不理解吗?如果您能解释为什么创建 UDF 的方式不起作用,那将是一个很好的答案,【参考方案2】:

这是我所做的: 我更改了函数来执行比较,而不是设置排序规则,然后返回 1 或 0。

CREATE FUNCTION [dbo].[fnsConvert]
(
      @p NVARCHAR(2000) ,
      @c NVARCHAR(2000)
)
RETURNS BIT
AS

    BEGIN
        DECLARE @result BIT

        IF ( @c = 'SQL_Latin1_General_CP1_CS_AS' )
        BEGIN
            IF @p COLLATE SQL_Latin1_General_CP1_CS_AS = UPPER(@p) 
                SET @result = 1
            ELSE
                SET @result = 0    
        END
        ELSE
            SET @result = 0 

        RETURN @result
    END

然后使用该函数的查询稍有变化。

SELECT txt
FROM (SELECT 'ABC' AS txt UNION SELECT 'cdf') t
WHERE 
dbo.fnsConvert(txt, 'SQL_Latin1_General_CP1_CS_AS') = 1

【讨论】:

【参考方案3】:

变量不能有自己的排序规则。它将始终使用服务器的默认值。检查这个:

--我声明了三个变量,每个变量都有自己的排序规则——至少有人会这么认为:

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

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

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
*/

--现在我们检查“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*/

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

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*/

--但是一个表的列会有不同的表现:

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
*/

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

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*/

更新一些文档

At this link你可以阅读详细信息。排序规则不会改变值。它应用规则(与NOT NULL相关,不会改变值,只是添加规则是否可以设置NULL)。

文档说的很清楚

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

稍后你会发现

    创建或更改数据库 创建或更改表列 强制转换表达式的排序规则

更新 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);

@CompareCaseSensitive 设置为1 将只返回aBc,将NULL0 设置为两行。

这是 - 肯定的! - 性能比 UDF 好得多。

【讨论】:

谢谢您 - 但我真的很感谢您提供确认文档的链接......现在它只是一堆测试。 COLLATE 语句中的COLLATE 子句适用于文字,而不适用于变量。 @avs099 提示一下:我的解决方案也可以很容易地在函数中使用。【参考方案4】:

请尝试使用BINARY_CHECKSUM函数,不需要UDF函数:

SELECT txt
FROM (SELECT 'ABC' AS txt UNION SELECT 'cdf') t
WHERE 
BINARY_CHECKSUM(txt)= BINARY_CHECKSUM(UPPER(txt))

【讨论】:

【参考方案5】:

我同意 @Shnugo,当您创建局部变量时,它将采用默认排序规则

但是,您可以将函数返回的变量值与用户定义的排序规则显式排序,如下所示:

select * from 
(SELECT 'ABC' AS txt UNION SELECT 'cdf') a
where (dbo.fnsConvert(txt, 'SQL_Latin1_General_CP1_CS_AS') 
collate SQL_Latin1_General_CP1_CS_AS)  = UPPER(txt)

另外 collate子句只能用于数据库定义、列定义或字符串/字符表达式,也就是说它用于数据库对象,即表、列、索引

collat​​ion_name 不能用变量或表达式表示。

【讨论】:

【参考方案6】:

正如@Shnugo 所说,排序规则不是变量的属性,但它可以是列定义的属性。

对于 TSQL 之外启用排序规则的比较,您可以使用显式排序规则定义(持久)计算列:

create table Q47890189 (
    txt nvarchar(100),
    colltxt as txt collate SQL_Latin1_General_CP1_CS_AS persisted
)

insert into Q47890189 (txt) values ('ABC')
insert into Q47890189 (txt) values ('cdf')

select * from Q47890189 where txt = UPPER(txt)
select * from Q47890189 where colltxt = UPPER(colltxt)

请注意,持久列也可以被索引,并且比调用标量函数具有更好的性能。

【讨论】:

【参考方案7】:

COLLATE :是一个子句,可应用于数据库定义或列定义以定义排序规则,或应用于字符串表达式以应用排序规则转换。

COLLATE不转换任何列或变量。它定义了collat​​e的特性。

CREATE TABLE [dbo].[OINV]
    [CardCode] [nvarchar](50) NULL
)

如果我有一张带有5175460 rows 的桌子 然后将其转换为另一种数据类型需要时间,因为它的值已转换为新的数据类型。

alter table OINV
alter column CardCode varchar(50)
--1 min 45 sec

alter table OINV
alter column CardCode nvarchar(50) COLLATE SQL_Latin1_General_CP1_CS_AS

如果我不转换数据类型而只想更改排序规则 那么这样做需要 1 毫秒。这意味着它不会将 5175460 行转换为所述整理。 它只是定义该列上的排序规则。

当此列在 where 条件下使用时,该列将显示所述整理的特征。

UDF/TVF 不是执行此操作的方法。最好的方法是更改​​表

另一个例子,

declare @i varchar(60)='ABC'

SELECT txt
FROM (SELECT 'abc' AS txt UNION SELECT 'cdf') t
WHERE 
txt  = @i COLLATE SQL_Latin1_General_CP1_CS_AS

我不能这样声明,

declare @i varchar(60) COLLATE SQL_Latin1_General_CP1_CS_AS='ABC'

所以变量只有在与 collat​​e 一起使用时才会表现出 collat​​e 特性。

在您的情况下,您只返回普通变量,

UDF 这样做的方式,

CREATE FUNCTION testfn (
    @test VARCHAR(100)
    ,@i INT
    )
RETURNS TABLE
AS
RETURN (
        -- insert into @t values(@test)
        SELECT @test COLLATE SQL_Latin1_General_CP1_CS_AS AS a
        )


SELECT *
FROM (
    SELECT 'ABC' AS txt

    UNION

    SELECT 'cdf'
    ) t
OUTER APPLY dbo.testfn(txt, 0) fn
WHERE fn.a = UPPER(txt)

要定义多个排序规则,您必须使用不同的排序规则定义多个表。 TVF 只能返回静态表模式,因此只能有一个 collat​​e 定义。

因此,TVF 不是执行任务的正确方法。

【讨论】:

【参考方案8】:

MSDN 明确定义COLLATE:

是可以应用于数据库定义列的子句 definition 来定义排序规则,或者到一个字符串 表达式应用排序规则。

你能在这里看到一个关于变量的词吗?

如果你需要UDF,只需使用表值函数:

CREATE FUNCTION dbo.test 
(   
    @text nvarchar(max)
)
RETURNS TABLE 
AS
RETURN 
(
    SELECT c COLLATE SQL_Latin1_General_CP1_CS_AS as txt
    FROM (VALUES (@text)) as t(c)
)
GO

并像这样使用它:

;WITH cte AS (
    SELECT N'ABC' as txt
    UNION 
    SELECT N'cdf'
)

SELECT c.txt
FROM cte c
OUTER APPLY dbo.test (c.txt) t
WHERE t.txt = UPPER(c.txt)

输出:

txt
------
ABC

【讨论】:

以上是关于UDF 中的 COLLATE 无法按预期工作的主要内容,如果未能解决你的问题,请参考以下文章

spark add_month 无法按预期工作[重复]

JavaScript 中的雪花 UDF 未按预期计算

使用广播应用地图转换时,pyspark Udf 未按预期工作?

MVC 中的重定向无法按预期工作

ScrollView 中的 ScrollTo 无法按预期工作

Linux swift 项目中的 PromiseKit 无法按预期工作