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.