Проблемите с набора от знаци са доста чести, нека се опитам да дам някои общи бележки.
По принцип трябва да вземете предвид четири различни настройки за набор от знаци.
1 и 2:NLS_CHARACTERSET
и NLS_NCHAR_CHARACTERSET
Пример:AL32UTF8
Те са дефинани във вашата база данни, можете да ги разпитвате с
SELECT *
FROM V$NLS_PARAMETERS
WHERE PARAMETER IN ('NLS_CHARACTERSET', 'NLS_NCHAR_CHARACTERSET');
Тези настройки определят кои знаци (в какъв формат) могат да се съхраняват във вашата база данни - нито повече, нито по-малко. Изисква известно усилие (вижте Миграция на набор от символи и/или помощник за мигриране на база данни Oracle за Unicode), ако трябва да го промените в съществуваща база данни.
3:NLS_LANG
Пример:AMERICAN_AMERICA.AL32UTF8
Тази стойност е дефинирана само на вашия клиент. NLS_LANG няма нищо общо с възможността за съхраняване на знаци в база данни. Използва се, за да уведоми Oracle какъв набор от знаци използвате от страна на клиента. Когато зададете стойност NLS_LANG (например на AL32UTF8), тогава просто казвате на базата данни на Oracle "моят клиент използва набор от знаци AL32UTF8" - това не означава непременно, че вашият клиент наистина използва AL32UTF8! (вижте по-долу №4)
NLS_LANG може да бъде дефиниран от променливата на средата NLS_LANG
или от системния регистър на Windows в HKLM\SOFTWARE\Wow6432Node\ORACLE\KEY_%ORACLE_HOME_NAME%\NLS_LANG
(за 32 бита), респ. HKLM\SOFTWARE\ORACLE\KEY_%ORACLE_HOME_NAME%\NLS_LANG
(за 64 бита). В зависимост от вашето приложение може да има и други начини да посочите NLS_LANG, но нека се придържаме към основите. Ако стойността NLS_LANG не е предоставена, Oracle я задава по подразбиране на AMERICAN_AMERICA.US7ASCII
Форматът на NLS_LANG е NLS_LANG=language_territory.charset
. {charset } част от NLS_LANG е не показано във всяка системна таблица или изглед. Всички компоненти на дефиницията NLS_LANG са по избор, така че следните дефиниции са валидни:NLS_LANG=.WE8ISO8859P1
, NLS_LANG=_GERMANY
, NLS_LANG=AMERICAN
, NLS_LANG=ITALIAN_.WE8MSWIN1252
, NLS_LANG=_BELGIUM.US7ASCII
.
Както е посочено по-горе, частта {charset} на NLS_LANG
не е наличен в базата данни в нито една системна таблица/изглед или която и да е функция. Строго погледнато това е вярно, но можете да изпълните тази заявка:
SELECT DISTINCT CLIENT_CHARSET
FROM V$SESSION_CONNECT_INFO
WHERE (SID, SERIAL#) = (SELECT SID, SERIAL# FROM v$SESSION WHERE AUDSID = USERENV('SESSIONID'));
Трябва да върне набор от знаци от текущия ви NLS_LANG
настройка - въз основа на моя опит обаче стойността често е NULL или Unknown
, т.е. не е надежден.
Намерете още много полезна информация тук:NLS_LANG FAQ
Имайте предвид, че някои технологии не използват NLS_LANG
, настройките там нямат никакъв ефект, например:
-
ODP.NET управляваният драйвер не е
NLS_LANG
чувствителен. Той е чувствителен само към .NET локал. (вижте Доставчик на данни за Ръководство за разработчици на .NET) -
OraOLEDB (от Oracle) винаги използва UTF-16 (вижте Специфични характеристики на доставчика на OraOLEDB)
-
Базиран на Java JDBC (например SQL Developer) има свои собствени методи за работа с набори от символи (вижте Ръководство за разработчици на база данни JDBC – Поддръжка за глобализация за повече подробности)
4:"Истинският" набор от знаци на вашия терминал, вашето приложение или кодирането на .sql
файлове
Пример:UTF-8
Ако работите на Windows терминал (т.е. с SQL*plus), можете да разпитвате кодовата страница с команда chcp
, в Unix/Linux еквивалентът е locale charmap
или echo $LANG
. Можете да получите списък с всички идентификатори на кодови страници на Windows от тук:Идентификатори на кодова страница. Забележка, за UTF-8 (chcp 65001
) има някои проблеми, вижте тази дискусия.
Ако работите с .sql
файлове и редактор като TOAD или SQL-Developer, трябва да проверите опциите за запис. Обикновено можете да изберете стойности като UTF-8
, ANSI
, ISO-8859-1
и др.ANSI
означава ANSI кодовата страница на Windows, обикновено CP1252
, можете да проверите във вашия регистър на адрес HKLM\SYSTEM\ControlSet001\Control\Nls\CodePage\ACP
или тук:Справочник за API за поддръжка на национални езици (NLS)
[Microsoft премахна тази справка, приемете я под формата на уеб-архив Справка за API за поддръжка на национални езици (NLS)]
Как да задам всички тези стойности?
Най-важният момент е да съвпадне NLS_LANG
и вашия "реален" набор от символи на вашия терминал, респ. приложение или кодирането на вашия .sql
файлове
Някои често срещани двойки са:
-
CP850 ->
WE8PC850
-
CP1252 или ANSI (в случай на "западен" компютър) ->
WE8MSWIN1252
-
ISO-8859-1 ->
WE8ISO8859P1
-
ISO-8859-15 ->
WE8ISO8859P15
-
UTF-8 ->
AL32UTF8
Или изпълнете тази заявка, за да получите още:
SELECT VALUE AS ORACLE_CHARSET, UTL_I18N.MAP_CHARSET(VALUE) AS IANA_NAME
FROM V$NLS_VALID_VALUES
WHERE PARAMETER = 'CHARACTERSET';
Някои технологии ви улесняват живота, напр. ODP.NET (неуправляван драйвер) или ODBC драйвер от Oracle автоматично наследява набора от знаци от NLS_LANG
стойност, така че условието отгоре винаги е вярно.
Изисква ли се да се зададе стойност на NLS_LANG на клиента, равна на базата данни NLS_CHARACTERSET
стойност?
Не, не е задължително! Например, ако имате база данни набор от символи NLS_CHARACTERSET=AL32UTF8
и наклиентата набор от символи NLS_LANG=.ZHS32GB18030
тогава ще работи без проблем (при условие, че вашият клиент наистина използва GB18030), въпреки че тези набори от знаци са напълно различни. GB18030 е набор от знаци, често използван за китайски, като UTF-8
той поддържа всички символи на Unicode.
Ако имате, например NLS_CHARACTERSET=AL32UTF8
и NLS_LANG=.WE8ISO8859P1
също ще работи (отново, при условие че вашият клиент наистина използва ISO-8859-P1). Въпреки това, базата данни може да съхранява знаци, които вашият клиент не може да покаже, вместо това клиентът ще покаже заместител (напр. ¿
).
Както и да е, полезно е да имате съвпадащи стойности NLS_LANG и NLS_CHARACTERSET, ако е подходящо. Ако те са равни, можете да сте сигурни, че всеки знак, който може да бъде съхранен в базата данни, също може да бъде показан и всеки знак, който въвеждате във вашия терминал или записвате във вашия .sql файл, също може да бъде съхранен в базата данни и не се заменя с заместител.
Допълнение
Толкова много пъти можете да прочетете съвети като „Наборът от знаци NLS_LANG трябва да бъде същият като набора от знаци на вашата база данни“ (също тук на SO). Това просто не е вярно и е популярен мит!
Ето доказателството:
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.
И двата символа на клиента и базата данни са AL32UTF8
, обаче знаците не съвпадат. Причината е моят cmd.exe
и по този начин също SQL*Plus използват Windows CP1252. Следователно трябва да задам съответно 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.
Помислете и за този пример:
CREATE TABLE ARABIC_LANGUAGE (
LANG_CHAR VARCHAR2(20),
LANG_NCHAR NVARCHAR2(20));
INSERT INTO ARABIC_LANGUAGE VALUES ('العربية', 'العربية');
Ще трябва да зададете две различни стойности за NLS_LANG
за едно изявление - което не е възможно.