无法在 Oracle 数据库中保存特定的特殊字符 (ü)

Unable to save a specific special character (ü) in Oracle database

我正在尝试通过 Python 2 (Flask) API 从 Python 3 (Django) 应用程序中获取特殊字符(例如:ñ、ü、é)并进入 Oracle 数据库 (Oracle 19c Enterprise)。这取代了 Java 具有直接数据库访问权限并且能够毫无问题地保存特殊字符的应用程序。

为了使 API 调用中包含特殊字符,我发现我必须对值进行 URL 编码:

>>> comment = "Joëlle Küsel Núñez-Chaillüé"
>>> urllib.parse.quote(comment).encode('utf-8')
b'Jo%C3%ABlle%20K%C3%BCsel%20N%C3%BA%C3%B1ez-Chaillu%CC%88%C3%A9'

该字符串被发送到 Python 2 API。我最初的尝试涉及在将其发送到 Oracle 之前在 Python 2 中对其进行解码,但是,任何将值传递到 cx_Oracle 的尝试都会导致 'ascii' codec can't encode characters in position 3-4: ordinal not in range(128)

我试图通过简单地将 URL 编码的字符串直接传递给 Oracle 来解决这个问题:

>>> comment_blob = cx.cursor().var(cx_Oracle.BLOB)
>>> comment_blob.setvalue(0, bytes(u'{}'.format(comment.decode('utf-8'))))

在 Oracle 中,我尝试解码值:

v_comment := UTL_RAW.CAST_TO_VARCHAR2( parameter );
v_comment := convert(utl_url.unescape(v_comment), 'WE8ISO8859P15');

这适用于除 ü 以外的所有内容,在某些情况下它会被后面的字符覆盖(但在粘贴到任何其他文本编辑器中时会正确显示),有时会更改为完全不同的字符。例如:

Joëlle Küsel Núñez-Chaillüé
Joëlle Kÿsel Núñez-Chaillüé

字符 9 从 ü 变为 ÿ,虽然最后两个字符在这里看起来是正确的,但在 SQL Developer 中,它们被合并为一个字符(通过旧 Java 应用程序插入的值在 SQL Developer 中正确显示。

知道为什么我只有 ü 字符有问题吗?

如果您有较旧的 cx_Oracle 模块,那么您需要在创建连接时指定一个字符集,例如:

connection = cx_Oracle.connect("hr", userpwd, "dbhost.example.com/orclpdb1", encoding="UTF-8")

如果升级到cx_Oracle8,那么这个字符集就是默认的,所以encoding选项可以省略。参见 Setting the Client Character Set

另外两个注意事项:

我想出了一个有效(虽然不理想)的解决方法来解决我的问题。在我的 Python 3 (Django) 应用程序中,我将字符串转换为字符代码列表。我通过 Python 2 API 传递代码列表而不对其进行任何操作,然后使用 PL/SQL 将其转换回常规字符。

Django 代码:

comment = str([ord(c) for c in comment]).strip('[]').replace(' ', '')

# Send comment to API as a parameter

Python 2 API:

# Call PL/SQL function passing in the list of character codes as a string

cursor.callfunc(
    'save_comment', cx_Oracle.STRING, [comment]
)

甲骨文:

FUNCTION save_comment(p_comment VARCHAR2)
    RETURN VARCHAR2
IS
    v_comment           VARCHAR2(500) := '';
    v_remain            VARCHAR2(500);
    v_char              VARCHAR2(3);
    v_char_code         NUMBER;
BEGIN

    -- Comment comes in as list of character codes
    -- '74,111,235,108,108,101,32,75,252,115,101,108,32,78,250,241'
    v_remain := p_comment;

    WHILE v_remain IS NOT NULL LOOP

        -- Pop the first character code from the list
        IF v_remain LIKE '%,%' THEN
            v_char := substr(v_remain, 1, instr(v_remain, ',')-1);
            v_remain := substr(v_remain, instr(v_remain, ',') + 1);
        ELSE
            v_char := v_remain;
            v_remain := NULL;
        END IF;

        -- Turn character code from VARCHAR2 into NUMBER
        v_char_code := to_number(v_char);

        -- Convert Python char code to Oracle char code
        IF v_char_code >= 192 THEN
            v_char_code := v_char_code + 49856;
        ELSIF v_char_code > 160 THEN
            v_char_code := v_char_code + 49664;
        END IF;

        -- Add this character to the comment
        v_comment := v_comment || chr(v_char_code);
    END LOOP;

    RETURN v_comment;
END;

这个方便的 ASCII 字符代码列表是一个很好的资源:
https://www.techonthenet.com/ascii/chart.php