Oracle
 sql >> Base de Dados >  >> RDS >> Oracle

OdbcConnection retornando caracteres chineses como ?


Problemas com o conjunto de caracteres são bastante comuns, deixe-me tentar dar algumas notas gerais.

Em princípio, você deve considerar quatro configurações de conjunto de caracteres diferentes.

1 e 2:NLS_CHARACTERSET e NLS_NCHAR_CHARACTERSET


Exemplo:AL32UTF8

Eles são definidos somente em seu banco de dados, você pode interrogá-los com
    SELECT * 
    FROM V$NLS_PARAMETERS 
    WHERE PARAMETER IN ('NLS_CHARACTERSET', 'NLS_NCHAR_CHARACTERSET');

Essas configurações definem quais caracteres (em qual formato) podem ser armazenados em seu banco de dados - nem mais, nem menos. Isso requer algum esforço (consulte Migração de Conjunto de Caracteres e/ou Assistente de Migração de Banco de Dados Oracle para Unicode) se você precisar alterá-lo no banco de dados existente.

3:NLS_LANG


Exemplo:AMERICAN_AMERICA.AL32UTF8

Este valor é definido somente em seu cliente. NLS_LANG não tem nada a ver com a capacidade de armazenar caracteres em um banco de dados. Ele é usado para informar ao Oracle qual conjunto de caracteres você está usando no lado do cliente. Quando você define o valor NLS_LANG (por exemplo, para AL32UTF8), basta informar ao banco de dados Oracle "meu cliente usa o conjunto de caracteres AL32UTF8" - isso não significa necessariamente que seu cliente está realmente usando AL32UTF8! (veja abaixo #4)

NLS_LANG pode ser definido pela variável de ambiente NLS_LANG ou pelo Registro do Windows em HKLM\SOFTWARE\Wow6432Node\ORACLE\KEY_%ORACLE_HOME_NAME%\NLS_LANG (para 32 bits), resp. HKLM\SOFTWARE\ORACLE\KEY_%ORACLE_HOME_NAME%\NLS_LANG (para 64 bits). Dependendo do seu aplicativo, pode haver outras maneiras de especificar NLS_LANG, mas vamos nos ater ao básico. Se o valor NLS_LANG não for fornecido, o Oracle o usará como padrão AMERICAN_AMERICA.US7ASCII

O formato de NLS_LANG é NLS_LANG=language_territory.charset . O {conjunto de caracteres } parte de NLS_LANG é não mostrado em qualquer tabela ou visualização do sistema. Todos os componentes da definição NLS_LANG são opcionais, portanto, as seguintes definições são todas válidas:NLS_LANG=.WE8ISO8859P1 , NLS_LANG=_GERMANY , NLS_LANG=AMERICAN , NLS_LANG=ITALIAN_.WE8MSWIN1252 , NLS_LANG=_BELGIUM.US7ASCII .

Conforme indicado acima, a parte {charset} de NLS_LANG não está disponível no banco de dados em nenhuma tabela/visualização do sistema ou qualquer função. Estritamente falando, isso é verdade, mas você pode executar esta consulta:
SELECT DISTINCT CLIENT_CHARSET
FROM V$SESSION_CONNECT_INFO
WHERE (SID, SERIAL#) = (SELECT SID, SERIAL# FROM v$SESSION WHERE AUDSID = USERENV('SESSIONID'));

Ele deve retornar o conjunto de caracteres do seu NLS_LANG atual configuração - no entanto, com base na minha experiência, o valor geralmente é NULL ou Unknown , ou seja, não confiável.

Encontre mais informações muito úteis aqui:NLS_LANG FAQ

Observe que algumas tecnologias não utilizam NLS_LANG , as configurações não têm nenhum efeito, por exemplo:

  • O driver gerenciado ODP.NET não é NLS_LANG confidencial. É apenas sensível à localidade .NET. (consulte o Guia do desenvolvedor do provedor de dados para .NET)

  • OraOLEDB (da Oracle) sempre usa UTF-16 (consulte Recursos específicos do provedor OraOLEDB)

  • O JDBC baseado em Java (por exemplo, SQL Developer) tem seus próprios métodos para lidar com conjuntos de caracteres (consulte o Guia do Desenvolvedor JDBC de Banco de Dados - Suporte à Globalização para obter mais detalhes)

4:O conjunto de caracteres "real" de seu terminal, seu aplicativo ou a codificação de .sql arquivos


Exemplo:UTF-8

Se você trabalha em um terminal Windows (ou seja, com SQL*plus), pode interrogar a página de código com o comando chcp , no Unix/Linux o equivalente é locale charmap ou echo $LANG . Você pode obter uma lista de todos os identificadores de páginas de código do Windows aqui:Identificadores de página de código. Observação, para UTF-8 (chcp 65001 ) existem alguns problemas, veja esta discussão.

Se você trabalha com .sql arquivos e um editor como TOAD ou SQL-Developer, você deve verificar as opções de salvamento. Normalmente você pode escolher valores como UTF-8 , ANSI , ISO-8859-1 , etc.ANSI significa a página de código Windows ANSI, normalmente CP1252 , você pode verificar no Registro em HKLM\SYSTEM\ControlSet001\Control\Nls\CodePage\ACP ou aqui:Referência da API National Language Support (NLS)

[A Microsoft removeu esta referência, tome-a do arquivo da Web Referência da API do NLS (National Language Support)]

Como definir todos esses valores?


O ponto mais importante é combinar NLS_LANG e seu conjunto de caracteres "real" do seu terminal, resp. aplicativo ou a codificação do seu .sql arquivos

Alguns pares comuns são:

  • CP850 -> WE8PC850

  • CP1252 ou ANSI (no caso de PC "Ocidental") -> WE8MSWIN1252

  • ISO-8859-1 -> WE8ISO8859P1

  • ISO-8859-15 -> WE8ISO8859P15

  • UTF-8 -> AL32UTF8

Ou execute esta consulta para obter mais:
SELECT VALUE AS ORACLE_CHARSET, UTL_I18N.MAP_CHARSET(VALUE) AS IANA_NAME
FROM V$NLS_VALID_VALUES
WHERE PARAMETER = 'CHARACTERSET';

Algumas tecnologias facilitam a sua vida, por ex. ODP.NET (driver não gerenciado) ou driver ODBC da Oracle herda automaticamente o conjunto de caracteres de NLS_LANG valor, então a condição de cima é sempre verdadeira.

É necessário definir o valor NLS_LANG do cliente igual ao banco de dados NLS_CHARACTERSET valor?

Não, não necessariamente! Por exemplo, se você tiver o banco de dados conjunto de caracteres NLS_CHARACTERSET=AL32UTF8 e o cliente conjunto de caracteres NLS_LANG=.ZHS32GB18030 então funcionará sem nenhum problema (desde que seu cliente realmente use GB18030), embora esses conjuntos de caracteres sejam completamente diferentes. GB18030 é um conjunto de caracteres comumente usado para chinês, como UTF-8 ele suporta todos os caracteres Unicode.

Se você tiver, por exemplo, NLS_CHARACTERSET=AL32UTF8 e NLS_LANG=.WE8ISO8859P1 também funcionará (novamente, desde que seu cliente realmente use ISO-8859-P1). No entanto, o banco de dados pode armazenar caracteres que seu cliente não pode exibir, em vez disso, o cliente exibirá um espaço reservado (por exemplo, ¿ ).

De qualquer forma, é benéfico ter valores NLS_LANG e NLS_CHARACTERSET correspondentes, se apropriado. Se forem iguais, você pode ter certeza de que qualquer caractere que possa ser armazenado no banco de dados também pode ser exibido e qualquer caractere que você inserir em seu terminal ou escrever em seu arquivo .sql também poderá ser armazenado no banco de dados e não será substituído por espaço reservado.

Suplemento


Tantas vezes você pode ler conselhos como "O conjunto de caracteres NLS_LANG deve ser o mesmo que o conjunto de caracteres do banco de dados" (também aqui no SO). Isso simplesmente não é verdade e um mito popular!

Aqui está a prova:
C:\>set NLS_LANG=.AL32UTF8

C:\>sqlplus ...

SQL> SET SERVEROUTPUT ON
SQL> DECLARE
  2  CharSet VARCHAR2(20);
  3  BEGIN
  4     SELECT VALUE INTO Charset FROM nls_database_parameters WHERE parameter = 'NLS_CHARACTERSET';
  5     DBMS_OUTPUT.PUT_LINE('Database NLS_CHARACTERSET is '||Charset);
  6     IF UNISTR('\20AC') = '€' THEN
  7             DBMS_OUTPUT.PUT_LINE ( '"€" is equal to U+20AC' );
  8     ELSE
  9             DBMS_OUTPUT.PUT_LINE ( '"€" is not the same as U+20AC' );
 10     END IF;
 11  END;
 12  /

Database NLS_CHARACTERSET is AL32UTF8
"€" is not the same as U+20AC

PL/SQL procedure successfully completed.

Ambos os conjuntos de caracteres do cliente e do banco de dados são AL32UTF8 , no entanto, os caracteres não correspondem. O motivo é que meu cmd.exe e, portanto, o SQL*Plus também usa o Windows CP1252. Portanto, devo definir NLS_LANG de acordo:
C:\>chcp
Active code page: 1252

C:\>set NLS_LANG=.WE8MSWIN1252

C:\>sqlplus ...

SQL> SET SERVEROUTPUT ON
SQL> DECLARE
  2  CharSet VARCHAR2(20);
  3  BEGIN
  4     SELECT VALUE INTO Charset FROM nls_database_parameters WHERE parameter = 'NLS_CHARACTERSET';
  5     DBMS_OUTPUT.PUT_LINE('Database NLS_CHARACTERSET is '||Charset);
  6     IF UNISTR('\20AC') = '€' THEN
  7             DBMS_OUTPUT.PUT_LINE ( '"€" is equal to U+20AC' );
  8     ELSE
  9             DBMS_OUTPUT.PUT_LINE ( '"€" is not the same as U+20AC' );
 10     END IF;
 11  END;
 12  /

Database NLS_CHARACTERSET is AL32UTF8
"€" is equal to U+20AC

PL/SQL procedure successfully completed.

Considere também este exemplo:
CREATE TABLE ARABIC_LANGUAGE (
    LANG_CHAR VARCHAR2(20), 
    LANG_NCHAR NVARCHAR2(20));

INSERT INTO ARABIC_LANGUAGE VALUES ('العربية', 'العربية');

Você precisaria definir dois valores diferentes para NLS_LANG para uma única declaração - o que não é possível.