Problemen met de tekenset komen vrij vaak voor, ik zal proberen wat algemene opmerkingen te geven.
In principe moet je rekening houden met vier verschillende tekenset-instellingen.
1 en 2:NLS_CHARACTERSET
en NLS_NCHAR_CHARACTERSET
Voorbeeld:AL32UTF8
Ze zijn alleen defined gedefinieerd in uw database, u kunt ze ondervragen met
SELECT *
FROM V$NLS_PARAMETERS
WHERE PARAMETER IN ('NLS_CHARACTERSET', 'NLS_NCHAR_CHARACTERSET');
Deze instellingen bepalen welke tekens (in welk formaat) in uw database kunnen worden opgeslagen - niet meer en niet minder. Het vereist enige inspanning (zie Character Set Migration en/of Oracle Database Migration Assistant for Unicode) als u het op een bestaande database moet wijzigen.
3:NLS_LANG
Voorbeeld:AMERICAN_AMERICA.AL32UTF8
Deze waarde wordt alleen gedefinieerd op uw cliënt. NLS_LANG heeft niets te maken met de mogelijkheid om tekens in een database op te slaan. Het wordt gebruikt om Oracle te laten weten welke tekenset u aan de clientzijde gebruikt. Wanneer u de NLS_LANG-waarde instelt (bijvoorbeeld op AL32UTF8), zegt u gewoon tegen de Oracle-database "mijn klant gebruikt tekenset AL32UTF8" - het betekent niet noodzakelijk dat uw klant echt AL32UTF8 gebruikt! (zie hieronder #4)
NLS_LANG kan worden gedefinieerd door omgevingsvariabele NLS_LANG
of via het Windows-register op HKLM\SOFTWARE\Wow6432Node\ORACLE\KEY_%ORACLE_HOME_NAME%\NLS_LANG
(voor 32 bit), resp. HKLM\SOFTWARE\ORACLE\KEY_%ORACLE_HOME_NAME%\NLS_LANG
(voor 64-bits). Afhankelijk van uw toepassing zijn er misschien andere manieren om NLS_LANG te specificeren, maar laten we het bij de basis houden. Als de NLS_LANG-waarde niet wordt opgegeven, stelt Oracle deze standaard in op AMERICAN_AMERICA.US7ASCII
Indeling van NLS_LANG is NLS_LANG=language_territory.charset
. De {tekenset } een deel van NLS_LANG is niet weergegeven in elke systeemtabel of -weergave. Alle componenten van de NLS_LANG-definitie zijn optioneel, dus de volgende definities zijn allemaal geldig:NLS_LANG=.WE8ISO8859P1
, NLS_LANG=_GERMANY
, NLS_LANG=AMERICAN
, NLS_LANG=ITALIAN_.WE8MSWIN1252
, NLS_LANG=_BELGIUM.US7ASCII
.
Zoals hierboven vermeld, het {charset}-gedeelte van NLS_LANG
is niet beschikbaar in de database in een systeemtabel/view of enige functie. Strikt genomen is dit waar, maar u kunt deze query uitvoeren:
SELECT DISTINCT CLIENT_CHARSET
FROM V$SESSION_CONNECT_INFO
WHERE (SID, SERIAL#) = (SELECT SID, SERIAL# FROM v$SESSION WHERE AUDSID = USERENV('SESSIONID'));
Het zou een tekenset moeten retourneren uit je huidige NLS_LANG
instelling - maar op basis van mijn ervaring is de waarde vaak NULL of Unknown
, d.w.z. niet betrouwbaar.
Vind hier meer zeer nuttige informatie:NLS_LANG FAQ
Let op, sommige technologieën maken geen gebruik van NLS_LANG
, instellingen daar hebben geen effect, bijvoorbeeld:
-
ODP.NET Managed Driver is niet
NLS_LANG
gevoelig. Het is alleen gevoelig voor .NET-landinstellingen. (zie Data Provider for .NET Developer's Guide) -
OraOLEDB (van Oracle) gebruikt altijd UTF-16 (zie OraOLEDB Provider Specific Features)
-
Op Java gebaseerde JDBC (bijvoorbeeld SQL Developer) heeft zijn eigen methoden om met tekensets om te gaan (zie Database JDBC Developer's Guide - Globalization Support voor meer details)
4:De "echte" tekenset van uw terminal, uw applicatie of de codering van .sql
bestanden
Voorbeeld:UTF-8
Als u op een Windows-terminal werkt (d.w.z. met SQL*plus) kunt u de codepagina opvragen met het commando chcp
, op Unix/Linux is het equivalent locale charmap
of echo $LANG
. U kunt hier een lijst krijgen van alle Windows-codepagina-ID's:Codepagina-ID's. Let op, voor UTF-8 (chcp 65001
) er zijn enkele problemen, zie deze discussie.
Als u werkt met .sql
bestanden en een editor zoals TOAD of SQL-Developer moet je de opslagopties aanvinken. Meestal kunt u waarden kiezen zoals UTF-8
, ANSI
, ISO-8859-1
, enz.ANSI
betekent de Windows ANSI-codepagina, meestal CP1252
, kunt u uw register inchecken op HKLM\SYSTEM\ControlSet001\Control\Nls\CodePage\ACP
of hier:National Language Support (NLS) API-referentie
[Microsoft heeft deze referentie verwijderd, neem het van web-archief National Language Support (NLS) API-referentie]
Hoe stel je al deze waarden in?
Het belangrijkste punt is om NLS_LANG
. te matchen en uw "echte" tekenset van uw terminal, resp. applicatie of de codering van uw .sql
bestanden
Enkele veelvoorkomende paren zijn:
-
CP850 ->
WE8PC850
-
CP1252 of ANSI (in het geval van "westerse" pc) ->
WE8MSWIN1252
-
ISO-8859-1 ->
WE8ISO8859P1
-
ISO-8859-15 ->
WE8ISO8859P15
-
UTF-8 ->
AL32UTF8
Of voer deze query uit om meer te krijgen:
SELECT VALUE AS ORACLE_CHARSET, UTL_I18N.MAP_CHARSET(VALUE) AS IANA_NAME
FROM V$NLS_VALID_VALUES
WHERE PARAMETER = 'CHARACTERSET';
Sommige technologieën maken u het leven gemakkelijker, b.v. ODP.NET (unmanged driver) of ODBC-driver van Oracle neemt automatisch de tekenset over van NLS_LANG
waarde, dus voorwaarde van boven is altijd waar.
Is het vereist om de NLS_LANG-waarde van de client gelijk te stellen aan de database NLS_CHARACTERSET
waarde?
Nee, niet noodzakelijk! Als u bijvoorbeeld de database . heeft tekenset NLS_CHARACTERSET=AL32UTF8
en de klant tekenset NLS_LANG=.ZHS32GB18030
dan werkt het zonder enig probleem (mits je cliënt echt GB18030 gebruikt), hoewel deze karaktersets totaal verschillend zijn. GB18030 is een tekenset die veel wordt gebruikt voor Chinees, zoals UTF-8
het ondersteunt alle Unicode-tekens.
Als u bijvoorbeeld NLS_CHARACTERSET=AL32UTF8
. heeft en NLS_LANG=.WE8ISO8859P1
het zal ook werken (opnieuw, op voorwaarde dat uw klant echt ISO-8859-P1 gebruikt). De database kan echter tekens opslaan die uw client niet kan weergeven, in plaats daarvan zal de client een tijdelijke aanduiding weergeven (bijv. ¿
).
Hoe dan ook, het is nuttig om overeenkomende NLS_LANG- en NLS_CHARACTERSET-waarden te hebben, indien van toepassing. Als ze gelijk zijn, kunt u er zeker van zijn dat elk teken dat in de database is opgeslagen, ook kan worden weergegeven en elk teken dat u in uw terminal invoert of in uw .sql-bestand schrijft, ook in de database kan worden opgeslagen en niet wordt vervangen door een tijdelijke aanduiding.
Aanvulling
Je kunt zo vaak adviezen lezen als "De NLS_LANG-tekenset moet hetzelfde zijn als je database-tekenset" (ook hier op SO). Dit is gewoon niet waar en een populaire mythe!
Hier is het bewijs:
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.
Zowel client- als databasetekensets zijn AL32UTF8
, maar de karakters komen niet overeen. De reden is, mijn cmd.exe
en dus ook SQL*Plus gebruikt Windows CP1252. Daarom moet ik NLS_LANG dienovereenkomstig instellen:
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.
Bekijk ook dit voorbeeld:
CREATE TABLE ARABIC_LANGUAGE (
LANG_CHAR VARCHAR2(20),
LANG_NCHAR NVARCHAR2(20));
INSERT INTO ARABIC_LANGUAGE VALUES ('العربية', 'العربية');
U moet twee verschillende waarden instellen voor NLS_LANG
voor een enkele verklaring - wat niet mogelijk is.