我有一个带有文本字段的表。我想选择文本全部大写的行。此代码按预期工作,并返回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提供程序调用区分大小写的比较。
答案 0 :(得分: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
- 现在检查一下。所有三个变量都被视为系统的默认排序规则:
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
的规则。)
文件清楚地说明了
是一个可以应用于数据库定义或列定义以定义排序规则的子句,还是应用于字符串表达式以应用排序规则转换。
稍后你会找到
- 创建或更改数据库
- 创建或更改表格列
- 转换表达式的排序规则
醇>
如果您想控制比较是完成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好得多。
答案 1 :(得分:6)
请尝试使用BINARY_CHECKSUM
功能,无需UDF
功能:
SELECT txt
FROM (SELECT 'ABC' AS txt UNION SELECT 'cdf') t
WHERE
BINARY_CHECKSUM(txt)= BINARY_CHECKSUM(UPPER(txt))
答案 2 :(得分:1)
我认为您对排序规则的运作方式感到困惑。如果要强制区分大小写的排序规则,则可以在where谓词中执行,而不是使用类似的函数。标量函数对于性能而言非常糟糕。
以下是如何使用排序规则来完成此类事情。
SELECT txt
FROM (SELECT 'ABC' AS txt UNION SELECT 'cdf') t
WHERE txt collate SQL_Latin1_General_CP1_CS_AS = UPPER(txt)
答案 3 :(得分:1)
这就是我的所作所为: 我更改了函数以执行比较,而不是设置排序规则,然后返回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
答案 4 :(得分:1)
我同意 @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 无法用变量或表达式表示。
答案 5 :(得分:1)
正如@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)
请注意,持久列也可以编入索引,并且比调用标量函数具有更好的性能。
答案 6 :(得分:1)
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'
只要在整理时使用,变量就会显示整理特征。
在你的情况下,你只返回普通变量,
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)
要定义多个collate,您必须使用不同的collate定义多个表。 TVF只能返回静态表模式,因此只能有一个collate定义。
因此,TVF不是执行任务的正确方法。
答案 7 :(得分:1)
MSDN明确定义COLLATE:
是否可以应用于数据库定义或列的子句 定义以定义排序规则或字符串 表达以应用归类演员。
你能在这里看到关于变量的词吗?
如果您需要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