sql >> Base de Datos >  >> RDS >> Oracle

OdbcConnection que devuelve caracteres chinos como ?

Los problemas con el juego de caracteres son bastante comunes, déjame intentar dar algunas notas generales.

En principio hay que considerar cuatro diferentes configuraciones de juego de caracteres.

1 y 2:NLS_CHARACTERSET y NLS_NCHAR_CHARACTERSET

Ejemplo:AL32UTF8

Se definen solo en su base de datos, puede interrogarlos con

    SELECT * 
    FROM V$NLS_PARAMETERS 
    WHERE PARAMETER IN ('NLS_CHARACTERSET', 'NLS_NCHAR_CHARACTERSET');

Estas configuraciones definen qué caracteres (en qué formato) se pueden almacenar en su base de datos, ni más ni menos. Requiere algo de esfuerzo (consulte Migración de juegos de caracteres y/o Asistente de migración de base de datos de Oracle para Unicode) si tiene que cambiarlo en la base de datos existente.

3:NLS_LANG

Ejemplo:AMERICAN_AMERICA.AL32UTF8

Este valor se define solo en su cliente. NLS_LANG no tiene nada que ver con la capacidad de almacenar caracteres en una base de datos. Se utiliza para que Oracle sepa qué conjunto de caracteres está utilizando en el lado del cliente. Cuando establece el valor NLS_LANG (por ejemplo, en AL32UTF8), simplemente le dice a la base de datos de Oracle "mi cliente usa el conjunto de caracteres AL32UTF8". ¡No significa necesariamente que su cliente realmente esté usando AL32UTF8! (ver abajo #4)

NLS_LANG se puede definir mediante la variable de entorno NLS_LANG o mediante el registro de Windows en 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). Dependiendo de su aplicación, puede haber otras formas de especificar NLS_LANG, pero centrémonos en lo básico. Si no se proporciona el valor NLS_LANG, Oracle lo establece por defecto en AMERICAN_AMERICA.US7ASCII

El formato de NLS_LANG es NLS_LANG=language_territory.charset . El {juego de caracteres } parte de NLS_LANG es no se muestra en cualquier tabla o vista del sistema. Todos los componentes de la definición NLS_LANG son opcionales, por lo que las siguientes definiciones son todas válidas:NLS_LANG=.WE8ISO8859P1 , NLS_LANG=_GERMANY , NLS_LANG=AMERICAN , NLS_LANG=ITALIAN_.WE8MSWIN1252 , NLS_LANG=_BELGIUM.US7ASCII .

Como se indicó anteriormente, la parte {charset} de NLS_LANG no está disponible en la base de datos en ninguna tabla/vista del sistema ni en ninguna función. Estrictamente hablando, esto es cierto, sin embargo, puede ejecutar esta consulta:

SELECT DISTINCT CLIENT_CHARSET
FROM V$SESSION_CONNECT_INFO
WHERE (SID, SERIAL#) = (SELECT SID, SERIAL# FROM v$SESSION WHERE AUDSID = USERENV('SESSIONID'));

Debería devolver el conjunto de caracteres de su NLS_LANG actual configuración; sin embargo, según mi experiencia, el valor suele ser NULL o Unknown , es decir, no confiable.

Encuentra más información muy útil aquí:NLS_LANG FAQ

Tenga en cuenta que algunas tecnologías no utilizan NLS_LANG , la configuración allí no tiene ningún efecto, por ejemplo:

  • El controlador administrado ODP.NET no es NLS_LANG sensible. Solo es sensible a la configuración regional de .NET. (consulte la Guía del desarrollador de Data Provider for .NET)

  • OraOLEDB (de Oracle) siempre usa UTF-16 (ver Características específicas del proveedor de OraOLEDB)

  • JDBC basado en Java (por ejemplo, SQL Developer) tiene sus propios métodos para tratar con juegos de caracteres (consulte la Guía del desarrollador de JDBC de base de datos - Soporte de globalización para obtener más detalles)

4:El conjunto de caracteres "reales" de su terminal, su aplicación o la codificación de .sql archivos

Ejemplo:UTF-8

Si trabaja en una terminal de Windows (es decir, con SQL*plus), puede consultar la página de códigos con el comando chcp , en Unix/Linux el equivalente es locale charmap o echo $LANG . Puede obtener una lista de todos los identificadores de páginas de códigos de Windows desde aquí:Identificadores de páginas de códigos. Nota, para UTF-8 (chcp 65001 ) hay algunos problemas, vea esta discusión.

Si trabaja con .sql archivos y un editor como TOAD o SQL-Developer, debe verificar las opciones de guardado. Por lo general, puede elegir valores como UTF-8 , ANSI , ISO-8859-1 , etc.ANSI significa la página de códigos ANSI de Windows, típicamente CP1252 , puede verificar en su Registro en HKLM\SYSTEM\ControlSet001\Control\Nls\CodePage\ACP o aquí:Referencia de API de soporte de idioma nacional (NLS)

[Microsoft eliminó esta referencia, tómela de la referencia de la API de compatibilidad con el idioma nacional (NLS) del archivo web]

¿Cómo establecer todos estos valores?

El punto más importante es hacer coincidir NLS_LANG y su conjunto de caracteres "reales" de su terminal, resp. aplicación o la codificación de su .sql archivos

Algunos pares comunes son:

  • CP850 -> WE8PC850

  • CP1252 o ANSI (en caso de PC "occidental") -> WE8MSWIN1252

  • ISO-8859-1 -> WE8ISO8859P1

  • ISO-8859-15 -> WE8ISO8859P15

  • UTF-8 -> AL32UTF8

O ejecute esta consulta para obtener más:

SELECT VALUE AS ORACLE_CHARSET, UTL_I18N.MAP_CHARSET(VALUE) AS IANA_NAME
FROM V$NLS_VALID_VALUES
WHERE PARAMETER = 'CHARACTERSET';

Algunas tecnologías te hacen la vida más fácil, p. ODP.NET (controlador no administrado) o el controlador ODBC de Oracle hereda automáticamente el conjunto de caracteres de NLS_LANG valor, por lo que la condición anterior siempre es verdadera.

¿Es necesario establecer el valor NLS_LANG del cliente igual a la base de datos NLS_CHARACTERSET? valor?

¡No, no necesariamente! Por ejemplo, si tiene la base de datos juego de caracteres NLS_CHARACTERSET=AL32UTF8 y el cliente conjunto de caracteres NLS_LANG=.ZHS32GB18030 entonces funcionará sin ningún problema (siempre que su cliente realmente use GB18030), aunque estos conjuntos de caracteres son completamente diferentes. GB18030 es un conjunto de caracteres comúnmente utilizado para chino, como UTF-8 admite todos los caracteres Unicode.

Si tiene, por ejemplo, NLS_CHARACTERSET=AL32UTF8 y NLS_LANG=.WE8ISO8859P1 también funcionará (nuevamente, siempre que su cliente realmente use ISO-8859-P1). Sin embargo, la base de datos puede almacenar caracteres que su cliente no puede mostrar; en su lugar, el cliente mostrará un marcador de posición (por ejemplo, ¿ ).

De todos modos, es beneficioso tener valores NLS_LANG y NLS_CHARACTERSET coincidentes, si es adecuado. Si son iguales, puede estar seguro de que cualquier carácter que pueda estar almacenado en la base de datos también se puede mostrar y cualquier carácter que ingrese en su terminal o escriba en su archivo .sql también se puede almacenar en la base de datos y no se sustituye por marcador de posición.

Suplemento

Muchas veces puede leer consejos como "El conjunto de caracteres NLS_LANG debe ser el mismo que el conjunto de caracteres de su base de datos" (también aquí en SO). ¡Esto simplemente no es cierto y es un mito popular!

Aquí está la prueba:

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.

Tanto el juego de caracteres del cliente como el de la base de datos son AL32UTF8 , sin embargo, los caracteres no coinciden. La razón es que mi cmd.exe y por lo tanto también SQL*Plus utiliza Windows CP1252. Por lo tanto, debo establecer NLS_LANG en consecuencia:

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 también este ejemplo:

CREATE TABLE ARABIC_LANGUAGE (
    LANG_CHAR VARCHAR2(20), 
    LANG_NCHAR NVARCHAR2(20));

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

Debería establecer dos valores diferentes para NLS_LANG para una sola declaración, lo cual no es posible.