Oracle
 sql >> Database >  >> RDS >> Oracle

OdbcConnection restituisce i caratteri cinesi come ?

I problemi con il set di caratteri sono abbastanza comuni, vorrei provare a dare alcune note generali.

In linea di principio devi considerarne quattro diverse impostazioni del set di caratteri.

1 e 2:NLS_CHARACTERSET e NLS_NCHAR_CHARACTERSET

Esempio:AL32UTF8

Sono definiti solo sul tuo database, puoi interrogarli con

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

Queste impostazioni definiscono quali caratteri (in quale formato) possono essere archiviati nel database, né più né meno. Richiede un certo sforzo (vedi Migrazione del set di caratteri e/o Oracle Database Migration Assistant per Unicode) se devi modificarlo su un database esistente.

3:NLS_LANG

Esempio:AMERICAN_AMERICA.AL32UTF8

Questo valore è definito solo sul tuo cliente. NLS_LANG non ha nulla a che fare con la capacità di memorizzare i caratteri in un database. Viene utilizzato per far sapere a Oracle quale set di caratteri stai utilizzando sul lato client. Quando imposti il ​​valore NLS_LANG (ad esempio su AL32UTF8), dici semplicemente al database Oracle "il mio client usa il set di caratteri AL32UTF8" - non significa necessariamente che il tuo client stia davvero usando AL32UTF8! (vedi sotto n. 4)

NLS_LANG può essere definito dalla variabile di ambiente NLS_LANG o dal registro di Windows in HKLM\SOFTWARE\Wow6432Node\ORACLE\KEY_%ORACLE_HOME_NAME%\NLS_LANG (per 32 bit), risp. HKLM\SOFTWARE\ORACLE\KEY_%ORACLE_HOME_NAME%\NLS_LANG (per 64 bit). A seconda della tua applicazione potrebbero esserci altri modi per specificare NLS_LANG, ma atteniamoci alle basi. Se il valore NLS_LANG non viene fornito, Oracle lo imposta automaticamente su AMERICAN_AMERICA.US7ASCII

Il formato di NLS_LANG è NLS_LANG=language_territory.charset . Il {set di caratteri } parte di NLS_LANG non mostrato in qualsiasi tabella o vista di sistema. Tutti i componenti della definizione NLS_LANG sono opzionali, quindi le seguenti definizioni sono tutte valide:NLS_LANG=.WE8ISO8859P1 , NLS_LANG=_GERMANY , NLS_LANG=AMERICAN , NLS_LANG=ITALIAN_.WE8MSWIN1252 , NLS_LANG=_BELGIUM.US7ASCII .

Come indicato sopra, la parte {charset} di NLS_LANG non è disponibile nel database in nessuna tabella/vista di sistema o in nessuna funzione. A rigor di termini questo è vero, tuttavia puoi eseguire questa query:

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

Dovrebbe restituire il set di caratteri dal tuo NLS_LANG corrente impostazione - tuttavia, in base alla mia esperienza, il valore è spesso NULL o Unknown , cioè non affidabile.

Trova altre informazioni molto utili qui:Domande frequenti su NLS_LANG

Nota, alcune tecnologie non utilizzano NLS_LANG , le impostazioni non hanno alcun effetto, ad esempio:

  • Il driver gestito ODP.NET non è NLS_LANG sensibile. È solo sensibile alla locale .NET. (vedi Provider di dati per .NET Developer's Guide)

  • OraOLEDB (da Oracle) usa sempre UTF-16 (vedi Funzionalità specifiche del provider OraOLEDB)

  • JDBC basato su Java (ad esempio SQL Developer) ha i propri metodi per gestire i set di caratteri (consultare Database JDBC Developer's Guide - Globalization Support per ulteriori dettagli)

4:Il set di caratteri "reali" del tuo terminale, la tua applicazione o la codifica di .sql file

Esempio:UTF-8

Se lavori su terminale Windows (es. con SQL*plus) puoi interrogare la codepage con il comando chcp , su Unix/Linux l'equivalente è locale charmap o echo $LANG . Puoi ottenere un elenco di tutti gli identificatori di codepage di Windows da qui:Identificatori di codepage. Nota, per UTF-8 (chcp 65001 ) ci sono alcuni problemi, vedi questa discussione.

Se lavori con .sql file e un editor come TOAD o SQL-Developer devi controllare le opzioni di salvataggio. Di solito puoi scegliere valori come UTF-8 , ANSI , ISO-8859-1 , ecc.ANSI indica la codepage ANSI di Windows, in genere CP1252 , puoi controllare il tuo registro in HKLM\SYSTEM\ControlSet001\Control\Nls\CodePage\ACP o qui:Riferimento API National Language Support (NLS)

[Microsoft ha rimosso questo riferimento, prendilo dall'archivio web di riferimento dell'API NLS (National Language Support)]

Come impostare tutti questi valori?

Il punto più importante è abbinare NLS_LANG e il tuo set di caratteri "reali" del tuo terminale, risp. applicazione o la codifica del tuo .sql file

Alcune coppie comuni sono:

  • CP850 -> WE8PC850

  • CP1252 o ANSI (in caso di PC "occidentale") -> WE8MSWIN1252

  • ISO-8859-1 -> WE8ISO8859P1

  • ISO-8859-15 -> WE8ISO8859P15

  • UTF-8 -> AL32UTF8

Oppure esegui questa query per averne di più:

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

Alcune tecnologie ti semplificano la vita, ad es. ODP.NET (driver non gestito) o driver ODBC da Oracle eredita automaticamente il set di caratteri da NLS_LANG valore, quindi la condizione dall'alto è sempre vera.

È necessario impostare il valore NLS_LANG del client uguale al database NLS_CHARACTERSET valore?

No, non necessariamente! Ad esempio, se hai il database set di caratteri NLS_CHARACTERSET=AL32UTF8 e il cliente set di caratteri NLS_LANG=.ZHS32GB18030 quindi funzionerà senza alcun problema (a condizione che il tuo client utilizzi davvero GB18030), sebbene questi set di caratteri siano completamente diversi. GB18030 è un set di caratteri comunemente usato per il cinese, come UTF-8 supporta tutti i caratteri Unicode.

Se hai, ad esempio, NLS_CHARACTERSET=AL32UTF8 e NLS_LANG=.WE8ISO8859P1 funzionerà anche (di nuovo, a condizione che il tuo client utilizzi davvero ISO-8859-P1). Tuttavia, il database potrebbe memorizzare caratteri che il tuo client non è in grado di visualizzare, invece il client visualizzerà un segnaposto (ad es. ¿ ).

Ad ogni modo, è utile avere valori NLS_LANG e NLS_CHARACTERSET corrispondenti, se adatti. Se sono uguali puoi essere certo che qualsiasi carattere che può essere memorizzato nel database può anche essere visualizzato e qualsiasi carattere che inserisci nel tuo terminale o scrivi nel tuo file .sql può anche essere memorizzato nel database e non è sostituito da segnaposto.

Supplemento

Tante volte puoi leggere consigli come "Il set di caratteri NLS_LANG deve essere lo stesso del set di caratteri del tuo database" (anche qui su SO). Questo semplicemente non è vero ed è un mito popolare!

Ecco la 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.

Entrambi i set di caratteri del client e del database sono AL32UTF8 , tuttavia i caratteri non corrispondono. Il motivo è il mio cmd.exe e quindi anche SQL*Plus utilizza Windows CP1252. Pertanto devo impostare NLS_LANG di conseguenza:

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.

Considera anche questo esempio:

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

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

Dovresti impostare due valori diversi per NLS_LANG per una singola affermazione - che non è possibile.