为啥TSQL把“sofia”和“sofia”一样对待?这是啥字符串编码?

Posted

技术标签:

【中文标题】为啥TSQL把“sofia”和“sofia”一样对待?这是啥字符串编码?【英文标题】:Why did TSQL treat "sofia" as being the same as "sofia"? What string encoding is this?为什么TSQL把“sofia”和“sofia”一样对待?这是什么字符串编码? 【发布时间】:2015-08-03 22:23:52 【问题描述】:

我遇到了一个案例,SQL 服务器可以将“sofia”和“sofia”存储为两个不同的字符串,但是在 TSQL 中进行比较时,无论使用 COLLATE,即使是二进制 Collat​​e,它们都是相同的:

CREATE TABLE #R (NAME NvarchAR(255) COLLATE SQL_Latin1_General_CP1_CI_AS)
INSERT INTO #R VALUES (N'sofia')
INSERT INTO #r VALUES (N'sofia')

SELECT * FROM #r WHERE NAME = N'sofia'

sofia
sofia

(2 row(s) affected)

IF 'sofia' = 'sofia'  COLLATE SQL_Latin1_General_CP1_CI_AS 
SELECT 'Values are the same'
ELSE
SELECT 'Values are different'

-------------------
Values are the same

(1 row(s) affected)

IF 'sofia' = 'sofia'  COLLATE SQL_Latin1_General_CP437_BIN
SELECT 'Values are the same'
ELSE
SELECT 'Values are different'

-------------------
Values are the same

(1 row(s) affected)

I tried to find out the encode of "sofia"

http://***.com/questions/1025332/determine-a-strings-encoding-in-c-sharp

It said:

            // If all else fails, the encoding is probably (though certainly not
            // definitely) the user's local codepage! One might present to the user a
            // list of alternative encodings as shown here: http://***.com/questions/8509339/what-is-the-most-common-encoding-of-each-language
            // A full list can be found using Encoding.GetEncodings();

I iterate through all the encoding returned from Encoding.GetEncodings(), none of them match

Looking into the binary I found an interesting fact: “sofia” itself is encoded with UTF16, but it can be generated from  "SOFIA" UTF16 by filling “1” instead of “0” in the extra byte besides ASCII code (Ex for ‘S’: 83 255 vs 83 0)  It is shown as lower case. In C#, 

“sofia”

                             [0]         83          byte                                    
                             [1]         255        byte
                             [2]         79          byte
                             [3]         255        byte
                             [4]         70          byte
                             [5]         255        byte
                             [6]         73          byte
                             [7]         255        byte
                             [8]         65          byte
                             [9]         255        byte

"SOFIA"

                             [0]         83          byte                                    
                             [1]         0        byte
                             [2]         79          byte
                             [3]         0        byte
                             [4]         70          byte
                             [5]         0        byte
                             [6]         73          byte
                             [7]         0        byte
                             [8]         65          byte
                             [9]         0        byte

"sofia"

                             [0]         115          byte                                    
                             [1]         0        byte
                             [2]         79          byte
                             [3]         0        byte
                             [4]         70          byte
                             [5]         0        byte
                             [6]         105          byte
                             [7]         0        byte
                             [8]         97          byte
                             [9]         0        byte

One can create two different directorie/files with name as C:\sofia\, C:\sofia\ or  sofia.txt, sofia.txt.

Why does the SQL engine think they are the same while storing them with the original streams?

In order to get just the exact I want I had to convert to binary first:

SELECT * FROM #r WHERE CONVERT(VARBINARY(100), Name) = CONVERT(VARBINARY(100), N'sofia')

sofia

(1 row(s) affected)

SELECT * FROM #r WHERE CONVERT(VARBINARY(100), Name) = CONVERT(VARBINARY(100), N'sofia')

sofia

(1 row(s) affected)

但这有很多副作用,比如文化和案例。我如何 TSQL 引擎知道它们是不同的而无需太多成本?

这种字符串编码有正式名称吗?

【问题讨论】:

我很好奇我的回答是否能帮助您解决问题。 【参考方案1】:

这里有两个问题。

第一:存在整理问题。排序规则定义字符的排序和相等性。正如@Kazetsukai 建议的那样,此处提供帮助的特定排序规则属性是宽度敏感度。但是,您不能简单地将_WS 添加到任何排序规则名称并假定它将是有效的排序规则。事实上,SQL_Latin1_General_CP1_CI_AS_WS 不是有效的排序规则。

您可以通过SELECT * FROM fn_helpcollations() WHERE [name] LIKE N'latin%[_]ws'; 获得一组有限的排序规则。该查询的结果表明您可能需要的排序规则是Latin1_General_CI_AS_WS。并且任何以_BIN2 结尾的排序规则都可以使用(尽量不要使用以_BIN 结尾的排序规则,因为这些排序规则已被弃用,就像以SQL_ 开头的排序规则一样)。

但是,出于某种原因,即使使用这些似乎也不起作用:

IF 'sofia' = 'sofia' COLLATE Latin1_General_CI_AS_WS
SELECT 'Values are the same'
ELSE
SELECT 'Values are different'

IF 'sofia' = 'sofia' COLLATE Latin1_General_BIN2
SELECT 'Values are the same'
ELSE
SELECT 'Values are different'

两者的结果都是“值相同”。这让我们:

第二:使用NVARCHAR1 数据时,必须在字符串文字前加上大写N,否则会将字符隐式转换为各自的@首先是 987654331@2 个字符(如果在 Unicode 代码点和字段或操作的排序规则指定的代码页中存在的字符之间没有定义映射,则字符转换为 ?) .

IF N'sofia' = N'sofia' COLLATE Latin1_General_CI_AS_WS
SELECT 'Values are the same'
ELSE
SELECT 'Values are different'

IF N'sofia' = N'sofia' COLLATE Latin1_General_BIN2
SELECT 'Values are the same'
ELSE
SELECT 'Values are different'

为这些文字值加上 N 前缀可以实现预期的行为,两个查询的结果现在都是“值不同”。


1XMLN 前缀类型将数据存储为 UTF-16 Little Endian。默认处理只是 UCS-2 / Base Multilingual Plane (BMP) 字符。但是,如果使用以_SC 结尾的排序规则,那么它可以正确处理带有补充字符的完整 UTF-16。

2CHARVARCHARTEXT(但不要使用最后一种,因为它已被弃用)类型是 8 位 ASCII 扩展代码页。

【讨论】:

【参考方案2】:

我相信您正在寻找的是半角和全角字符之间的区别。根据您的表使用的排序规则,这些将被视为相同或不同。在这种情况下,您使用的是SQL_Latin1_General_CP1_CI_AS,它显然对宽度不敏感。

您可以通过附加_WS according to this 来添加宽度敏感度,因此将排序规则更改为SQL_Latin1_General_CP1_CI_AS_WS 应该将它们视为不相等。

编辑:正如@srutzky 所指出的,您需要找到一个包含 _WS 的排序规则,而不仅仅是将 _WS 添加到排序规则中。

【讨论】:

仅供参考:您不能简单地将_WS 添加到任何排序规则名称并假设它是有效的排序规则。事实上,SQL_Latin1_General_CP1_CI_AS_WS 不是有效的排序规则。试试看它是否有效。

以上是关于为啥TSQL把“sofia”和“sofia”一样对待?这是啥字符串编码?的主要内容,如果未能解决你的问题,请参考以下文章

搜索所有有条件的父亲在Prolog

freeswitch呼叫流程分析

从端口80到端口8000的Apache反向代理配置

freeswitch报错

FreeSWITCH--常用指令

从 python esl 发起会议