【问题标题】:ascii function in oracle sql, for non-ascii valueoracle sql中的ascii函数,用于非ascii值
【发布时间】:2020-10-31 14:14:53
【问题描述】:

在 Oracle SQL 中:

当我们将任何非 ascii 字符传递给 ascii 函数时,它会返回一些数字。我们如何解释这个数字。如果我们的字符集为 AL32UTF8,为什么它不返回给定字符的 Unicode 点

select * from nls_database_parameters where parameter = 'NLS_CHARACTERSET';--AL32UTF8

select ascii('Á') from dual;--50049

这个值50049是什么意思?我期待 193

【问题讨论】:

  • 好问题。 dump() 应用于重音 A 确实返回 50049; ascii() 函数只报告存储的内容。如果您将 Á 指定为 national 字符集中,例如使用 n'Á',作为 ascii() 的参数,您将获得预期的返回值。如果您需要对表达式而不是文本文字执行此操作,您可以将表达式包装在 to_nchar() 中,您将再次获得预期的结果。这些都不能解释为什么 Oracle 将字符作为值 50049(十进制)存储在字符集 UTF-8 中。
  • 更多想法。谁知道AL32UTF8编码是什么;不管它是什么,显然它确实将重音 A 编码为十进制 50049。(例如,您可以选择 CHR(50049),它会返回正确的字符;CHR(193) 不会。)您期望 193 的原因是因为即 Unicode 编码字符集中字母的 代码点。碰巧的是,对于两字节字符,UTF16 编码值只是代码点本身。在 UTF8 中并非如此。因此,如果您希望获得代码点而不是编码,那么您会发现使用 UTF16 会更幸运。

标签: oracle non-ascii-characters


【解决方案1】:

这里 - 主要是为了我自己的教育 - 是对重音 A 值 50049 的解释(代码点:Unicode 编码字符集中的 193)。我刚刚从阅读https://www.fileformat.info/info/unicode/utf8.htm 中了解到这是如何工作的

编码的计算源自代码点 193,与与该代码点关联的特定字符无关。

UTF-8 使用相对简单的方案来编码最多 1,141,111 个代码点(或者,现在可能更多;现在让我们只担心代码点达到上限)。

从 1 到 127(十进制)的代码点被编码为单个字节,等于代码点(因此,当以二进制表示时,该字节始终具有前导零)。

从 128 到 2047 的代码点被编码为两个字节。二进制表示的第一个字节总是以 110 开头,第二个字节总是以 10 开头。以 110 开头的字节始终是双字节编码的第一个字节,而以 10 开头的字节始终是“延续”多字节编码的字节(第二、第三或第四)。这些强制前缀是编码方案的一部分(UTF8 编码的“规则”);它们是规则中的硬编码值。

因此:对于从 128 到 2047 的代码点,编码为两个字节,精确格式为 110xxxxxx 和 10xxxxxx 的二进制表示法。第一个字节的最后五位(位),加上第二个字​​节的最后六位(总共:11 位)是代码点的二进制表示(必须编码的从 128 到 2047 的值)。

2047 = 2^11 - 1(这就是 2047 与此处相关的原因)。代码点可以表示为 11 位二进制数(可能带有前导零)。取前 5 位(用 0 向左填充后,长度为 11 位)并将其附加到第一个字节的强制 110 前缀,并获取代码点的最后 6 位并将它们附加到强制前缀 10第二个字节。这给出了给定代码点的 UTF8 编码(以两个字节为单位)。

让我们为代码点 193(十进制)执行此操作。在二进制中,用 0 向左填充,即 00011000001。到目前为止,没什么特别的。

把它分成五位 ||六位:00011 和 000001。

附加强制性前缀:11000011 和 10000001。

用十六进制重写这些:\xC3 和 \x81。把它们放在一起;这是十六进制 C381,或十进制 50049。

【讨论】:

  • Unicode 指定 1.114.112 个码位,UTF-8 能够对所有这些码位进行编码,理论上会支持更多。但是,UTF-16 仅限于 1.114.112 个代码点,这就是当前范围的原因。截至今天,已分配了大约 140.000 个代码点,因此仍有大量空间可用于新的字母表和语言。
  • @WernfriedDomscheit 对 - 我也知道上限并非一成不变。在任何情况下,“标准”字符(例如来自各种字母的字母)都不应混淆。我对这个答案的兴趣(因为我第一次学习编码方案的工作原理)在于代码点和编码之间的转换,然后又回到另一个方向;哪些代码点在 Unicode 中是有效的 - 我对(当时)不太感兴趣。
  • 明白了!!!!所以基本上它是在 UTF-8 编码之后梳理所有的十六进制。并将组合的十六进制转换为十进制。选择 to_number('C381', 'XXXX') from dual; --50049
  • @shobhit - 在这里非常准确(迂腐):您问为什么 ascii 函数不返回字符的 Unicode 点。我不知道你是不是字面上的意思; “点”是100%的技术术语。 Unicode 中 Á 的“代码点”是 193。但 ascii 函数不返回代码点 - 它返回数据库编码 (UTF8) 中的编码值。特别是如果您在“代码页”时代,所有这些“代码点”和“编码”作为不同事物的业务可能会非常混乱。但是,讨论不再是关于 ascii(或 SQL,或计算)。
【解决方案2】:

查看文档:ASCII

ASCII返回char的第一个字符在数据库字符集中的十进制表示。

UTF-8 中字符Á (U+00C1) 的二进制值为xC381,即十进制50049。

193 是代码点。对于 UTF-8,代码点仅对字符 U+0000 - U+007F (0-127) 等于二进制表示。对于 UTF-16BE,代码点仅对字符 U+0000 - U+FFFF (0-65535) 等于二进制表示,

也许你正在寻找

ASCIISTR('Á')

返回\00C1,只需将其转换为十进制值。

前段时间我开发了这个功能,比ASCIISTR更高级,它也适用于多码字符。

CREATE OR REPLACE TYPE VARCHAR_TABLE_TYPE AS TABLE OF VARCHAR2(100);


FUNCTION UNICODECHAR(uchar VARCHAR2) RETURN VARCHAR_TABLE_TYPE IS

    UTF16 VARCHAR2(32000) := ASCIISTR(uchar);
    UTF16_Table VARCHAR_TABLE_TYPE := VARCHAR_TABLE_TYPE();
    sg1 VARCHAR2(4);
    sg2 VARCHAR2(4);
    codepoint INTEGER;

    res VARCHAR_TABLE_TYPE := VARCHAR_TABLE_TYPE();
    i INTEGER;
BEGIN
 
    IF uchar IS NULL THEN
        RETURN VARCHAR_TABLE_TYPE();
    END IF;
    
    SELECT REGEXP_SUBSTR(UTF16, '(\\[[:xdigit:]]{4})|.', 1, LEVEL)
    BULK COLLECT INTO UTF16_Table  
    FROM dual
    CONNECT BY REGEXP_SUBSTR(UTF16, '\\[[:xdigit:]]{4}|.', 1, LEVEL) IS NOT NULL;
    
    i := UTF16_Table.FIRST;
    WHILE i IS NOT NULL LOOP
        res.EXTEND;
        IF REGEXP_LIKE(UTF16_Table(i), '^\\') THEN
            IF REGEXP_LIKE(UTF16_Table(i), '^\\D(8|9|A|B)') THEN
                sg1 := REGEXP_SUBSTR(UTF16_Table(i), '[[:xdigit:]]{4}');
                i := UTF16_Table.NEXT(i);
                sg2 := REGEXP_SUBSTR(UTF16_Table(i), '[[:xdigit:]]{4}');
                codepoint := 2**10 * (TO_NUMBER(sg1, 'XXXX') - TO_NUMBER('D800', 'XXXX')) + TO_NUMBER(sg2, 'XXXX') - TO_NUMBER('DC00', 'XXXX') + 2**16;
                res(res.LAST) := 'U+'||TO_CHAR(codepoint, 'fmXXXXXX');
            ELSE
                res(res.LAST) := 'U+'||REGEXP_REPLACE(UTF16_Table(i), '^\\');
            END IF; 
        ELSE
            res(res.LAST) := 'U+'||LPAD(TO_CHAR(ASCII(UTF16_Table(i)), 'fmXX'), 4, '0');
        END IF;     
        i := UTF16_Table.NEXT(i);
    END LOOP;
    RETURN res;

END UNICODECHAR;

试试https://unicode.org/emoji/charts/full-emoji-list.html#1f3f3_fe0f_200d_1f308的一些例子

UNICODECHAR('?‍☠️')

应该返回

U+1F3F4 
U+200D 
U+2620 
U+FE0F

【讨论】:

  • 当你说“代码点......等于十进制”时,你所说的“十进制”是什么意思?那应该是“编码”(编码值)——事实上,它与基数无关——无论是二进制、十进制、八进制、十六进制还是其他任何东西,不是吗?
  • 好的,我看到了编辑,但我认为它不能解决问题。二进制表示什么?比较是在“代码点”和“编码”之间进行的——这种区别与数字系统无关。
  • @mathguy 你会怎么表达?也许“代码点等于编码值”?我想我想说什么很清楚。
  • 我相信技术术语是“编码” - 在括号中添加“编码值”会更清楚。不幸的是,“编码”在这种情况下至少有两个技术含义——一个是指整体方案,另一个是指单个字符——所以我理解你的犹豫。 “UTF-8编码”是整体方案; “美元符号字符的UTF-8编码”是指单个字符的编码。
猜你喜欢
  • 2011-03-20
  • 2013-09-21
  • 2014-04-09
  • 2014-06-12
  • 2018-08-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-25
相关资源
最近更新 更多