Problémy se znakovou sadou jsou docela běžné, zkusím uvést nějaké obecné poznámky.
V zásadě musíte zvážit čtyři různá nastavení znakové sady.
1 a 2:NLS_CHARACTERSET
a NLS_NCHAR_CHARACTERSET
Příklad:AL32UTF8
Jsou definovány pouze ve vaší databázi, můžete je vyzvídat pomocí
SELECT *
FROM V$NLS_PARAMETERS
WHERE PARAMETER IN ('NLS_CHARACTERSET', 'NLS_NCHAR_CHARACTERSET');
Tato nastavení definují, které znaky (v jakém formátu) mohou být uloženy ve vaší databázi – nic více, nic méně. Vyžaduje to určité úsilí (viz migrace znakové sady a/nebo nástroj Oracle Database Migration Assistant pro Unicode), pokud jej musíte změnit ve stávající databázi.
3:NLS_LANG
Příklad:AMERICAN_AMERICA.AL32UTF8
Tato hodnota je definována pouze na vašem klientovi. NLS_LANG nemá nic společného se schopností ukládat znaky do databáze. Používá se k tomu, aby Oracle věděl, jakou znakovou sadu používáte na straně klienta. Když nastavíte hodnotu NLS_LANG (například na AL32UTF8), stačí říct databázi Oracle "můj klient používá znakovou sadu AL32UTF8" - nemusí to nutně znamenat, že váš klient skutečně používá AL32UTF8! (viz níže #4)
NLS_LANG lze definovat pomocí proměnné prostředí NLS_LANG
nebo registrem Windows na adrese HKLM\SOFTWARE\Wow6432Node\ORACLE\KEY_%ORACLE_HOME_NAME%\NLS_LANG
(pro 32 bit), resp. HKLM\SOFTWARE\ORACLE\KEY_%ORACLE_HOME_NAME%\NLS_LANG
(pro 64 bitů). V závislosti na vaší aplikaci mohou existovat jiné způsoby, jak určit NLS_LANG, ale zůstaňme u základů. Pokud není zadána hodnota NLS_LANG, Oracle ji nastaví jako výchozí AMERICAN_AMERICA.US7ASCII
Formát NLS_LANG je NLS_LANG=language_territory.charset
. {znaková sada } část NLS_LANG není zobrazeny v jakékoli systémové tabulce nebo zobrazení. Všechny součásti definice NLS_LANG jsou volitelné, takže všechny následující definice jsou platné:NLS_LANG=.WE8ISO8859P1
, NLS_LANG=_GERMANY
, NLS_LANG=AMERICAN
, NLS_LANG=ITALIAN_.WE8MSWIN1252
, NLS_LANG=_BELGIUM.US7ASCII
.
Jak je uvedeno výše v části {charset} NLS_LANG
není k dispozici v databázi v žádné systémové tabulce/pohledu nebo jakékoli funkci. Přesně vzato je to pravda, ale můžete spustit tento dotaz:
SELECT DISTINCT CLIENT_CHARSET
FROM V$SESSION_CONNECT_INFO
WHERE (SID, SERIAL#) = (SELECT SID, SERIAL# FROM v$SESSION WHERE AUDSID = USERENV('SESSIONID'));
Měl by vrátit znakovou sadu z vašeho aktuálního NLS_LANG
nastavení - nicméně na základě mých zkušeností je hodnota často NULL nebo Unknown
, tj. není spolehlivý.
Další velmi užitečné informace naleznete zde:NLS_LANG FAQ
Všimněte si, že některé technologie nevyužívají NLS_LANG
, tamní nastavení nemají žádný vliv, například:
-
Spravovaný ovladač ODP.NET není
NLS_LANG
citlivý. Je citlivý pouze na národní prostředí .NET. (viz Poskytovatel dat pro .NET Developer's Guide) -
OraOLEDB (od společnosti Oracle) vždy používá UTF-16 (viz specifické funkce poskytovatele OraOLEDB)
-
JDBC založený na Javě (například SQL Developer) má své vlastní metody pro práci se znakovými sadami (další podrobnosti viz Příručka pro vývojáře databáze JDBC – Podpora globalizace)
4:„Skutečná“ znaková sada vašeho terminálu, vaší aplikace nebo kódování .sql
soubory
Příklad:UTF-8
Pokud pracujete na terminálu Windows (tj. s SQL*plus), můžete kódovou stránku prozkoumat příkazem chcp
, v Unixu/Linuxu je ekvivalentem locale charmap
nebo echo $LANG
. Zde můžete získat seznam všech identifikátorů kódových stránek Windows:Identifikátory kódových stránek. Poznámka pro UTF-8 (chcp 65001
) jsou nějaké problémy, viz tato diskuze.
Pokud pracujete s .sql
soubory a editor, jako je TOAD nebo SQL-Developer, musíte zkontrolovat možnosti uložení. Obvykle můžete zvolit hodnoty jako UTF-8
, ANSI
, ISO-8859-1
, atd.ANSI
znamená kódovou stránku Windows ANSI, obvykle CP1252
, můžete zkontrolovat ve svém registru na adrese HKLM\SYSTEM\ControlSet001\Control\Nls\CodePage\ACP
nebo zde:National Language Support (NLS) API Reference
[Microsoft odstranil tuto referenci, vezměte ji z webového archivu Reference API národní jazykové podpory (NLS)]
Jak nastavit všechny tyto hodnoty?
Nejdůležitější je shoda NLS_LANG
a vaši „skutečnou“ znakovou sadu vašeho terminálu, resp. aplikace nebo kódování vašeho .sql
soubory
Některé běžné páry jsou:
-
CP850 ->
WE8PC850
-
CP1252 nebo ANSI (v případě "západního" PC) ->
WE8MSWIN1252
-
ISO-8859-1 ->
WE8ISO8859P1
-
ISO-8859-15 ->
WE8ISO8859P15
-
UTF-8 ->
AL32UTF8
Nebo spusťte tento dotaz a získejte další:
SELECT VALUE AS ORACLE_CHARSET, UTL_I18N.MAP_CHARSET(VALUE) AS IANA_NAME
FROM V$NLS_VALID_VALUES
WHERE PARAMETER = 'CHARACTERSET';
Některé technologie vám usnadňují život, např. ODP.NET (unmanaged driver) nebo ODBC driver od Oracle automaticky zdědí znakovou sadu z NLS_LANG
hodnotu, takže podmínka shora je vždy pravdivá.
Je nutné nastavit hodnotu NLS_LANG klienta na stejnou hodnotu jako databáze NLS_CHARACTERSET
hodnotu?
Ne, ne nutně! Například pokud máte databázi znaková sada NLS_CHARACTERSET=AL32UTF8
a klient znaková sada NLS_LANG=.ZHS32GB18030
pak to bude fungovat bez problémů (za předpokladu, že váš klient skutečně používá GB18030), ačkoli tyto znakové sady jsou zcela odlišné. GB18030 je znaková sada běžně používaná pro čínštinu, například UTF-8
podporuje všechny znaky Unicode.
Pokud máte například NLS_CHARACTERSET=AL32UTF8
a NLS_LANG=.WE8ISO8859P1
bude také fungovat (opět za předpokladu, že váš klient skutečně používá ISO-8859-P1). Databáze však může ukládat znaky, které váš klient není schopen zobrazit, místo toho klient zobrazí zástupný symbol (např. ¿
).
Každopádně je výhodné mít odpovídající hodnoty NLS_LANG a NLS_CHARACTERSET, pokud je to vhodné. Pokud jsou stejné, můžete si být jisti, že jakýkoli znak, který může být uložen v databázi, lze také zobrazit a jakýkoli znak, který zadáte do terminálu nebo zapíšete do souboru .sql, může být také uložen v databázi a nebude nahrazen zástupným symbolem.
Dodatek
Tolikrát si můžete přečíst radu jako "Znaková sada NLS_LANG musí být stejná jako znaková sada databáze" (také zde na SO). To prostě není pravda a je to populární mýtus!
Zde je důkaz:
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.
Znakové sady klienta i databáze jsou AL32UTF8
, ale postavy se neshodují. Důvodem je můj cmd.exe
a tedy i SQL*Plus používají Windows CP1252. Proto musím odpovídajícím způsobem nastavit NLS_LANG:
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.
Zvažte také tento příklad:
CREATE TABLE ARABIC_LANGUAGE (
LANG_CHAR VARCHAR2(20),
LANG_NCHAR NVARCHAR2(20));
INSERT INTO ARABIC_LANGUAGE VALUES ('العربية', 'العربية');
Pro NLS_LANG
byste museli nastavit dvě různé hodnoty pro jeden příkaz – což není možné.