字符串的排序规则和数据类型不兼容

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 确实返回数据,这是因为 COLLATEs 的定位以及隐式转换发生的时间。

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.codenvarchar 上。因此,当值的排序规则发生更改时,不会丢失任何字符。由于cv.code 没有明确的COLLATE,而是首先将其转换为nvarchar(由于数据类型优先)并然后进行整理;不会丢失字符。

【讨论】:

很好,只剩下一点:如果双方都基于排序规则进行更改,为什么案例 5 像 3 那样更改 varchar 数据?这是否意味着关于“数据转换”collate 尊重它在哪一边? “如果双方都基于排序规则进行更改,为什么案例 5 不会像 3 那样更改 varchar 数据?” 我不明白你的意思在这里再问。 如果您问为什么第四个查询有效,我的回答中涵盖了 @GeorgeMenoutis ,cn.codenvarchar 不是 varcharN'3VT18021δ' 将在任一排序规则中返回正确的值,'3VT18021δ' 不会。 我已经为你更新了答案,@GeorgeMenoutis。 实际上,@GeorgeMenoutis。

以上是关于字符串的排序规则和数据类型不兼容的主要内容,如果未能解决你的问题,请参考以下文章

怎样修改sqlserver2005系统数据库排序规则

修改MySql中数据表和字段的字符集和排序规则

powerbi 怎么指定排序规则

算法-排序法归类

在sql server 2005中保存其他国家特殊字符

MySQL 排序规则类型是不是需要匹配 PHP 页面字符集类型?