字符串的排序规则和数据类型不兼容
Posted
技术标签:
【中文标题】字符串的排序规则和数据类型不兼容【英文标题】:Collation and datatype incompatibility on strings 【发布时间】:2021-05-24 18:28:03 【问题描述】:在涉及排序规则和数据类型差异的情况下,我对系统的行为感到非常困惑。
作为一个最小示例,我将相同的 Unicode 值输入到两个不同表的单个列中。在一个表中,该列是varchar
和某种排序规则,而在另一个表中它是nvarchar
和另一个排序规则。代码和结果:
create table cn(code nvarchar(max) collate Latin1_General_CI_AS)
create table cv(code varchar(max) collate SQL_Latin1_General_CP1253_CI_AI)
insert cn select N'3VT18021δ'
insert cv select N'3VT18021δ'
select * from cn
select * from cv
--1.
select * from cn inner join cv on cn.code=cv.code
-- Cannot resolve the collation conflict between "SQL_Latin1_General_CP1253_CI_AI" and "Latin1_General_CI_AS" in the equal to operation.
--2.
select * from cn inner join cv on cn.code=cv.code collate SQL_Latin1_General_CP1253_CI_AI
-- returns one row
--3.
select * from cn inner join cv on cn.code =cv.code collate Latin1_General_CI_AS
-- returns 0 rows
--4.
select * from cn inner join cv on cn.code collate SQL_Latin1_General_CP1253_CI_AI =cv.code
-- returns one row
--5.
select * from cn inner join cv on cn.code collate Latin1_General_CI_AS =cv.code
-- returns one row
我的笔记:
案例一:排序规则不同,我明白了
案例 2 和 5:返回(正确)一行。为什么要整理字段 自己整理有什么好处?
案例 3 和 4:为什么将一个排序规则转换为另一个排序规则有效 时间,而不是另一个?
当然,所有这些都因数据类型的不同而变得更加复杂。
【问题讨论】:
【参考方案1】:排序规则是数据类型的一部分。如果您使用不同的排序规则,并且许多约束在使用不同的排序规则(PRIMARY KEY、UNIQUE、CHECK...)时的行为不同,则字符的内部表示可能会有所不同。
在运算符(=、LIKE、+)和某些函数(CONCAT...)中混合不同的排序规则会系统地导致错误,直到您为此操作施加特定的排序规则。 因此,有一个 COLLATE 关键字充当运算符来消除可以使用哪种排序规则的歧义。
SQL Server 区分两种排序规则。
-
名称以 SQL_ 开头的技术排序规则
出于功能目的的语义排序,名称以语言名称开头
技术排序规则只能用于恢复具有特定编码的导入数据...例如,您可以拥有严格等同于 IBM EBCDIC 的排序规则,但它是为 SQL Server 表操作保留此排序规则的愚蠢想法!
语义排序广泛用于促进应用程序功能...您想要 CI 还是 CS(案例行为)、AI 或 AS(变音行为)、WS(宽行为,例如 2 = ² ) 等...
使用这个查询:
select CAST(code AS VARBINARY(max)) from cn;
select CAST(code AS VARBINARY(max)) from cv;
你会发现最后一个字符没有相同的代码。这就是为什么使用 Latin1_General_CI_AS 排序规则时结果没有行...
您将看到以 2 个字节编码的 NVARCHAR(max) 数据类型的“B403”字符无法转换为每个字符 1 个字节的 PAGE CODE CP1253...
事实上,带有 SQL_Latin1_General_CP1253_CI_AI 的 VARCHAR 中的 B4 字节是“ä”而不是“δ”
换句话说,尝试将 1 个字节放入 2 个字节中很容易……只需添加一些零即可。但是,相反,只有当右边的字节被清零时,才能尝试将 2 个字节合二为一......
【讨论】:
【参考方案2】:案例 2 和 5:返回(正确)一行。为什么将字段与自己的排序规则进行排序有什么好处?
当您在子句中的值上显式使用COLLATE
时,表达式的双方都会显式转换为该排序规则,因此不会发生冲突。
案例 3 和 4:为什么一次将一个排序规则转换为另一个有效,而另一个则不行?
您的一列是varchar
,因此当它从一种排序规则更改为另一种排序规则时,它的值会发生变化。具体来说,这是当您 COLLATE
表中的值 cv
到排序规则 Latin1_General_CI_AS
时。由于'δ'
不是varchar
的排序规则中可用的字符,它会更改为'd'
和'3VT18021d'
不 等于N'3VT18021δ'
。您可以通过以下方式看到这一点:
SELECT code COLLATE Latin1_General_CI_AS
FROM cv;
您需要先将值显式转换为nvarchar
:
select *
from cn
inner join cv on cn.code = CONVERT(nvarchar(MAX),cv.code) collate Latin1_General_CI_AS;
--Returns one row now
编辑:解释为什么查询 3不返回数据,而查询 5 确实返回数据,这是因为 COLLATE
s 的定位以及隐式转换发生的时间。
cn.code =cv.code collate Latin1_General_CI_AS --3
cn.code collate Latin1_General_CI_AS =cv.code --5
对于查询 3,COLLATE
表达式位于 cv.code
上,即 varchar
。结果,值的排序规则已更改first,并且字符'δ'
丢失。然后由于数据类型优先,它被隐式转换为nvarchar
。
但是,对于查询 5,COLLATE
位于 cn.code
和 nvarchar
上。因此,当值的排序规则发生更改时,不会丢失任何字符。由于cv.code
没有明确的COLLATE
,而是首先将其转换为nvarchar
(由于数据类型优先)并然后进行整理;不会丢失字符。
【讨论】:
很好,只剩下一点:如果双方都基于排序规则进行更改,为什么案例 5 不 像 3 那样更改 varchar 数据?这是否意味着关于“数据转换”collate
尊重它在哪一边?
“如果双方都基于排序规则进行更改,为什么案例 5 不会像 3 那样更改 varchar
数据?” 我不明白你的意思在这里再问。
如果您问为什么第四个查询有效,我的回答中涵盖了 @GeorgeMenoutis ,cn.code
是 nvarchar
不是 varchar
。 N'3VT18021δ'
将在任一排序规则中返回正确的值,'3VT18021δ'
不会。
我已经为你更新了答案,@GeorgeMenoutis。
实际上,@GeorgeMenoutis。以上是关于字符串的排序规则和数据类型不兼容的主要内容,如果未能解决你的问题,请参考以下文章