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.