1

需要将 SQL nchar 转换为 .NET 转换为 char。
更具体地说,将 nchar UNICODE 转换为 char ASCII。

复杂之处在于 SQL char 使用完整字节。
不是 128 的纯
ASCII。TSQL 函数 ASCII 返回 0-255。

理想情况下,会有一个 FormByte 的 NormalizationForm。
它不会是一个精确的文本值 - 而是一个接近的逻辑值或?。
SQL 将使用 FormByte 从 nchar 转换为 char。
规范化表格

编码解码对我不起作用,我尝试了所有口味。

在 SQL 中,许多 char(字节)映射到 63。63 是 ?。
不仅仅是超过 255 的字符映射到 63。130
到 140 都映射到 63。

字符 160-255 全部返回 160-255

并非所有超过 255 都映射到 63。
例如,许多变音符号映射到 ASCII。

TSQL 具有 UNICODE 和 ACSII 函数。
所以我只是将所有 Unicode 字符加载到 char 和 nchar 列中。

SQL 返回的 char 对于 29 个字符是错误的。
并且为坏字符返回的 ASCII() 没有意义 - 所有控制字符都在 130 - 160 范围内。
检查了不正确的 29 二进制文件,存储的是 ASCII() 返回的内容。
对于 27,从 char 返回的内容是 nchar,对于 2,它甚至不是正确的 nchar。它们都应该映射到 ? 或 ACSII 等价物。
“和”映射到“(但会采用?)'和'映射到'
-(短划线)和-(短划线)映射到
-...

我知道你不相信我。
将 'Œ' 插入 char 列并选择它 - 它将返回 'Œ'。
你甚至可以搜索它 - char = 'Œ' 返回 true。
选择 ASCII('Œ') 返回 140,这就是实际存储的内容(检查二进制文件)。
140 / 8C 的 UNICODE 定义是部分行向后。
我检查了该字符的二进制值,它是 8C (140)。
返回的是 unicode 'Œ' Int16 338。
似乎 SQL 正在执行一些输入输出映射并弄错了。

ASCII 函数对于未映射到 ? 的 575 个 unicode 字符是正确的。
char 值与 ACSII 匹配,它们都有意义。
EG 12 个不同形式的 u 都映射到 u。
32163 个字符不是 ? 映射到 ? (63)。

下面是返回错误值的 29 个字符。
列顺序:
char
nchar
ASCII(char)
UNICODE(nchar)

     sqlCharASCIIbackToString did not match  Œ Œ 140 338
     sqlCharASCIIbackToString did not match  œ œ 156 339
     sqlCharASCIIbackToString did not match  Š Š 138 352
     sqlCharASCIIbackToString did not match  š š 154 353
     sqlCharASCIIbackToString did not match  Ÿ Ÿ 159 376
     sqlCharASCIIbackToString did not match  Ž Ž 142 381
     sqlCharASCIIbackToString did not match  ž ž 158 382
     sqlCharASCIIbackToString did not match  ƒ Ƒ 131 401
     sqlCharASCIIbackToString did not match  ƒ ƒ 131 402
     sqlCharASCIIbackToString did not match  ˆ ˆ 136 710
     sqlCharASCIIbackToString did not match  ˜ ˜ 152 732
     sqlCharASCIIbackToString did not match  – – 150 8211
     sqlCharASCIIbackToString did not match  — — 151 8212
     sqlCharASCIIbackToString did not match  ‘ ‘ 145 8216
     sqlCharASCIIbackToString did not match  ’ ’ 146 8217
     sqlCharASCIIbackToString did not match  ‚ ‚ 130 8218
     sqlCharASCIIbackToString did not match  “ “ 147 8220
     sqlCharASCIIbackToString did not match  ” ” 148 8221
     sqlCharASCIIbackToString did not match  „ „ 132 8222
     sqlCharASCIIbackToString did not match  † † 134 8224
     sqlCharASCIIbackToString did not match  ‡ ‡ 135 8225
     sqlCharASCIIbackToString did not match  • • 149 8226
     sqlCharASCIIbackToString did not match 
     … … 133 8230
     sqlCharASCIIbackToString did not match  ‰ ‰ 137 8240
     sqlCharASCIIbackToString did not match  ‹ ‹ 139 8249
     sqlCharASCIIbackToString did not match  › › 155 8250
     sqlCharASCIIbackToString did not match  € € 128 8364
     sqlCharASCIIbackToString did not match  ™ ™ 153 8482
     sqlCharASCIIbackToString did not match  ˜ ≈ 152 8776
     count63 =  32163 countMis =  29 countCorrect =  575

运行以下 .NET 以查看 SQL 返回的“Œ”

char char338 = (char)338;
System.Diagnostics.Debug.WriteLine(char338);
sqlCmd.CommandText = "select [char] from [charNchar] where [char] = @char;";
sqlCmd.Parameters.Add("@char", SqlDbType.Char).Value = char338;
string string338= sqlCmd.ExecuteScalar().ToString();
char338 = string338.ToCharArray()[0];
System.Diagnostics.Debug.WriteLine(char338 + " " + ((Int16)char338).ToString());

上面的代码返回 338。SQL
正在返回一个大于 byte 的值,数据类型应该存储为 byte。
如果我搜索 (char)140 那么?63 被退回。

有趣的是在 char 上搜索 'Œ' 与 N'Œ' 会产生不同的结果。
即在左侧搜索 (140) Œ。
在右边搜索 (338) Œ char 搜索什么也没找到。
Nchar 使用任一输入查找两个结果。

  SELECT [int16],RTRIM([char]) as [char], ASCII([char]) as 'ASCII'
                ,RTRIM([nchar]) as [nchar], UNICODE([nchar]) as 'UNICODE'
  FROM [test].[dbo].[charNchar]
  where [char] = 'Œ'
  SELECT [int16],RTRIM([char]) as [char], ASCII([char]) as 'ASCII'
                ,RTRIM([nchar]) as [nchar], UNICODE([nchar]) as 'UNICODE'
  FROM [test].[dbo].[charNchar]
  where [char] = N'Œ'
  SELECT [int16],RTRIM([char]) as [char], ASCII([char]) as 'ASCII'
                ,RTRIM([nchar]) as [nchar], UNICODE([nchar]) as 'UNICODE'
  FROM [test].[dbo].[charNchar]
  where [nchar] = 'Œ'
  SELECT [int16],RTRIM([char]) as [char], ASCII([char]) as 'ASCII'
                ,RTRIM([nchar]) as [nchar], UNICODE([nchar]) as 'UNICODE'
  FROM [test].[dbo].[charNchar]
  where [nchar] = N'Œ'


int16  char                                               ASCII       nchar                                              UNICODE
------ -------------------------------------------------- ----------- -------------------------------------------------- -----------
338    Œ                                                  140         Œ                                                  338

int16  char                                               ASCII       nchar                                              UNICODE
------ -------------------------------------------------- ----------- -------------------------------------------------- -----------
338    Œ                                                  140         Œ                                                  338
339    œ                                                  156         œ                                                  339

int16  char                                               ASCII       nchar                                              UNICODE
------ -------------------------------------------------- ----------- -------------------------------------------------- -----------
338    Œ                                                  140         Œ                                                  338
339    œ                                                  156         œ                                                  339

int16  char                                               ASCII       nchar                                              UNICODE
------ -------------------------------------------------- ----------- -------------------------------------------------- -----------
338    Œ                                                  140         Œ                                                  338
339    œ                                                  156         œ                                                  339

≈ search 找不到任何四个查询。检查图表,这是 8776 的正确字符,数学几乎等于。 

〜是粘贴到SSMS中的零宽度,但它好像粘贴到从蓝色变为黑色的FROM中。 

我错过了什么吗 - 这对我来说似乎是一个错误。
这不仅仅是他错误的价值,而是一个无效的价值。
返回一个 Int16。
假设我想使用字节来存储字符以节省空间 - 它会在 SQL char 上中断,因为 29 个字符不会作为字节返回。

这是我使用的代码:

public void SQLchar()
{

    SqlConnection sqlCon = new SqlConnection(connString);  
    try
    {         
        sqlCon.Open();
        SqlCommand sqlCmd = sqlCon.CreateCommand();
        SqlDataReader rdr;
        sqlCmd.CommandText = "delete charNchar";
        sqlCmd.ExecuteNonQuery();
        for(Int16 i = 0; i < Int16.MaxValue; i ++)
        {
            sqlCmd.CommandText = "insert into charNchar (int16,char,nchar) values (@int16, @char, @nchar);";
            sqlCmd.CommandType = System.Data.CommandType.Text;
            sqlCmd.Parameters.Clear();
            sqlCmd.Parameters.Add("@int16", SqlDbType.Int).Value = i;
            sqlCmd.Parameters.Add("@char", SqlDbType.Char).Value = (char)i;
            sqlCmd.Parameters.Add("@nchar", SqlDbType.NChar).Value = (char)i;
            sqlCmd.ExecuteNonQuery();
        }
        string sqlChar;
        string sqlNChar;
        Int16 sqlCharASCII;
        Int16 sqlNCharUnicode;
        string sqlCharASCIIbackToString;
        sqlCmd.CommandText = "select char,nchar,ASCII(char),UNICODE(nchar) from charNchar order by int16;";
        rdr = sqlCmd.ExecuteReader();
        Int16 count63 = 0;
        Int16 countMis = 0;
        Int16 countCorrect = 0;
        while (rdr.Read())
        {
            sqlChar = rdr.IsDBNull(0) ? "dbNull" : rdr.GetString(0);
            sqlNChar = rdr.IsDBNull(1) ? "dbNull" : rdr.GetString(1);
            sqlCharASCII = rdr.IsDBNull(2) ? Int16.Parse("-1") : (Int16)rdr.GetInt32(2);
            sqlNCharUnicode = rdr.IsDBNull(3) ? Int16.Parse("-1") : (Int16)rdr.GetInt32(3);
            if(sqlCharASCII == 63 && sqlNCharUnicode != 63)
            {
                count63 ++;
                continue;  // ?
            }
            if (sqlCharASCII < 0)
            {
                System.Diagnostics.Debug.WriteLine("ASCII(char) null for " + sqlChar + " " + sqlNChar);
            }
            else
            {
                sqlCharASCIIbackToString = ((char)sqlCharASCII).ToString();
                if (string.CompareOrdinal(sqlChar, sqlCharASCIIbackToString) != 0)
                {
                    countMis++;
                    System.Diagnostics.Debug.WriteLine(" sqlCharASCIIbackToString did not match " + sqlCharASCIIbackToString + " " + sqlChar + " " + sqlNChar + " " + sqlCharASCII + " " + sqlNCharUnicode);
                }
                else
                {
                    countCorrect++;
                }
            }
        }
        rdr.Close();
        System.Diagnostics.Debug.WriteLine("count63 =  " + count63.ToString() + " countMis =  " + countMis.ToString() + " countCorrect =  " + countCorrect.ToString());
    }
    catch (Exception Ex)
    {
        System.Diagnostics.Debug.WriteLine(Ex.Message);
    }
    finally 
    {
        sqlCon.Close();
    }
}

至于为什么。
在 .NET 中解析字符串数据,该数据是 FK。
与其往返 SQL 来获取 FK 的 ID,不如使用 .NET 字典来提高速度。
Dictionary 是从值中获取键的反向查找。
解析器具有 char 的 Int16,因为它已被解析器使用。
因此,如果 char 的 ASCII 错误,则反向查找将失败。
我想我可以对不正确的 ASCII 结果进行硬编码修复。
但我想了解这里发生了什么,然后再走一条以补丁开始的道路。
char 有一些基本缺陷吗?
可以只使用 nchar,但我们更喜欢 char。
应用程序的性质是我们想要匹配。
u 的 6 个变音符号都匹配 ascii u 是一件好事。

4

3 回答 3

11

您严重混淆了代码点值和编码字节值。

代码点 U+0152(338 或 Œ)在 Windows-1252 中编码为字节 0x8C 或十进制的 140,这就是命名错误的ASCII()函数返回给您的内容。巧合的是,Windows-1252 中的许多代码点的编码方式使得被编码的代码点具有与该代码点的编码字节值相同的值。

Windows-1252 只能编码:

0-127
160-255

而这些不整齐地在一个范围内:

338,339,352,353,376,381,382,402,
710,732,8211,8212,8216,8217,8218,
8220,8221,8222,8224,8225,8226,
8230,8240,8249,8250,8364,8482

第二批中的所有代码点都不会以字节值<->代码点值编码,这正是您所期望的。

Windows-1252 无法对范围 128-159 进行编码,因此尝试转换该范围内的任何内容(例如 130 或 140)只会被编码为?0x3F。无论如何,该范围几乎是无用的 C1 控制字符。

它也没有利用它拥有的全部 256 个字符空间,它只编码 251 个不同的字符。因此,您不能将其用作伪字节,因为 5 个字节是无效的 Windows-1252。如果这就是你试图做的,它不会工作。


实际上尚不清楚您甚至要尝试做什么高级别的事情,所以我会猜测一下。

如果您想不区分重音匹配,那么只需使用不区分重音的排序规则。然后ü, ú, ùetc 将全部匹配u。与编码无关。

CREATE TABLE Mytable (
    Mycolumn NVARCHAR(10) COLLATE Latin1_General_CI_AI
)

INSERT INTO Mytable (myColumn) VALUES( 'ü' ), ('ú'), ( 'ù' )

SELECT Mycolumn
FROM Mytable
WHERE Mycolumn = 'u'

--Results

MYCOLUMN
ü
ú
ù

这是一个演示http://sqlfiddle.com/#!3/67752/2


要将 SQLAscii 转换为'Œ',请尝试以下操作:

public static char Windows1252CPtoChar(int cp)
{
    Encoding win1252 = Encoding.GetEncoding("Windows-1252"); //this could be made static
    return win1252.GetString(new byte[] { (byte)cp })[0];
}

public static void Main(string[] args) {
    Console.WriteLine(Windows1252CPtoChar(140) == 'Œ');
}

所以而不是:

sqlCharASCIIbackToString = ((char)sqlCharASCII).ToString();

sqlCharASCIIbackToString = (Windows1252CPtoChar(sqlCharASCII)).ToString();
于 2013-04-16T11:01:43.227 回答
2

因此,我将在这里假设您尝试做的是利用您正在获取 unicode 数据并将其存储到数据库中的 varchar 字段中的事实......您需要将输入编码作为ASCII字符串

string bad = Encoding.ASCII.GetString(Encoding.Unicode.GetBytes(input));
于 2013-04-13T01:37:51.157 回答
0

我冒昧地猜测您遇到了涉及在本地系统或 SQL 服务器系统上使用的代码页的转换异常(这会影响所谓的“高 ASCII”十进制 128-255 字符的解释方式)以及列/表/数据库上使用的排序规则。SQL Server 将尝试根据使用的代码页和排序规则将“无效”字符转换为有效字符。

当欧洲客户尝试使用在欧洲代码页处于活动状态的系统上设置的文件加载带有“ü”的名称时,我们遇到了一个问题。我们的 SQL Server 设置与代码页 437 活动(OEM CP 设置,IIRC)存储并返回“÷”。(十进制 246)一旦 CHAR 数据的代码页问题得到解决,一切都变得很糟糕。我不记得输入和输出设置的细节,抱歉。

编辑:这篇Stackoverflow 文章很好地讨论了一些问题,包括“?” 事物。

于 2013-04-15T19:50:47.217 回答