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)
但是,这会返回 ABC
和 cdf
。
为什么会这样,我该如何让它发挥作用?
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
,将NULL
或0
设置为两行。
这是 - 肯定的! - 性能比 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
子句只能用于数据库定义、列定义或字符串/字符表达式,也就是说它用于数据库对象,即表、列、索引
collation_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
不转换任何列或变量。它定义了collate的特性。
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'
所以变量只有在与 collate 一起使用时才会表现出 collate 特性。
在您的情况下,您只返回普通变量,
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 只能返回静态表模式,因此只能有一个 collate 定义。
因此,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 无法按预期工作的主要内容,如果未能解决你的问题,请参考以下文章
使用广播应用地图转换时,pyspark Udf 未按预期工作?