25 noiembrie 2011

Oracle și turnul Babel

Se întâmplă uneori, și sunt convins că ați pățit-o și voi, să interogați o tabelă în Oracle, iar rezultatul să fie neașteptat. Deși una din coloane ar fi trebuit să conțină, să zicem, numele clientului, pe ecran apare "pomul de Crăciun": globulețe, bastonașe și alte ornamente. E clar, baza de date "ne vorbește" într-o limbă pe care noi nu o înțelegem. Ori de câte ori se întâmplă asta, nu pot să nu mă gândesc la istorioara aceea biblică, cea cu turnul Babel. Cică fiii lui Noe s-au gândit să construiască un turn, care să ajungă până la cer, însă Dumnezeu le-a venit de hac și, n-o să vă vină să credeți, le-a încurcat limbile. Hei, nu organul anatomic, ci limba vorbită! Dom'le, fantastică strategie! A funcționat extraordinar de bine, iar, în final, construcția a fost abandonată.

Lăsând de-o parte latura mistică a povestioarei, dacă stăm și ne gândim bine, cam așa s-a întâmplat și cu evoluția tehnologiilor informatice. Desigur, nu discutăm de abandonarea proiectului, dar probleme au fost și sunt în continuare.

Ce ne povestesc bunicii...

Noi ăștia mai tineri n-am prins vremurile acelea când se lucra cu cartele perforate, sisteme cu lămpi și alte ciudățenii, care astăzi ar fi de neimaginat. Da, vremurile acelea au existat, iar harababura în sistemele de reprezentare a informației era maximă. Comunicarea între dispozitive de calcul diferite era aproape imposibilă, deoarece sistemele interne de reprezentare a datelor erau total diferite. Mai mult, chiar și dispozitive produse de același fabricant nu știau să comunice între ele (exemplul IBM-ului este reprezentativ).

Figura 1 - Dispozitiv cu cartele perforate, marca IBM

Nevoia de standardizare era enormă, astfel că anii '60 vin cu sistemul de codificare a caracterelor, denumit ASCII (American Standard Code for Information Interchange). Este uimitor cum a reușit acest standard să supraviețuiască până astăzi și, sincer să fiu, nu-i prevăd un sfârșit prea curând. Ne putem, oare, imagina succesul WWW, a email-ului și altor astfel de tehnologii fără ASCII? Aș afirma că nu!

În paralel cu ASCII, sistemul EBCDIC (Extended Binary Coded Decimal Interchange Code) a fost folosit, în special pe dispozitivele dezvoltate de cei de la IBM. Acest sistem e o variantă extinsă a celui folosit pe dispozitivele cu cartele perforate produse de IBM și, deși nu aduce nici un avantaj notabil față de ASCII, a continuat să fie utilizat, pentru simplul motiv că adoptarea noului standard ASCII nu s-a putut realiza în timp util și nici eficient din punctul de vedere al costurilor implicate.

Ce-i cu ASCII ăsta?

În primul rând, ASCII este un sistem de codificare a caracterelor. Deși poate nu este evident pentru toată lumea, un calculator habar n-are ce-i aia "A", "B" și alte astfel de caractere. Calculatorul operează cu biți, deci cu un sistem de calcul binar. Prin urmare, este nevoie de o convenție, prin care să-i spui: "Uite, acu eu îți trimit o secevență de biți, dar fii atent că ea reprezintă un text, care trebuie interpretat folosind o codificare. Dacă primul octet reprezintă cifra 65 atunci interpretează-l ca fiind 'A', dacă e 122 atunci interpretează-l ca fiind 'z' și așa mai departe". Ei bine, ASCII vine cu această codificare, care nu ar fi, în sine, foarte diferită de alte astfel de codificări, dar marele avantaj este acela că e standardizată. Este o primă premisă a inter-operabilității.

ASCII definește 128 de caractere, din care 33 sunt neimprimabile, ele numindu-se și caractere de control. Să nu uităm, discutăm de anii '60, când astfel de caractere erau necesare pentru a trimite diferite comenzi dispozitivelor informatice.

Dacă ar fi să facem un calcul simplu, observăm că ar fi necesari 7 biți pentru a codifica un caracter în ASCII (2^7=128). Mai mult decât suficient, nu-i așa? În fond, după cum este și denumit, discutăm de un sistem de codificare a alfabetului englezesc, iar 128 de simboluri sunt îndestulătoare.

Toate bune și frumoase până când, draguții de americani s-au gândit să valorifice și potențialul pieței mondiale, adică să vândă calculatoare și aplicații informatice și în alte țări. Și cerere era, slavă Domnului, dar cum se întâmplă întotdeauna, clientul e pretențios și face nazuri. Neamțu' a început a se plânge că n-are "umlaut", francezul, naționalist convins, a zis că el nu-și bate joc de limba sa și fără posibilitatea de a scrie cu "é" nici nu stă la discuții. Ca să nu mai vorbim de ruși, greci, chinezi și alții, pentru care grafia latină e total nepotrivită. E clar, ceva trebuia făcut!

Soluția imediată a fost extinderea codificării ASCII. După cum se știe, majoritatea calculatoarelor operează cu octeți (bytes). ASCII-ul folosind doar 7 biți, rămânea întodeauna un bit nefolosit. Așa că, ce s-au gandit băieții? Dacă folosim și al optulea bit, atunci ajungem la un spațiu de manevră de 256 de caractere. Lăsăm codificarea ASCII așa cum este ea, iar restul de 128 de coduri le folosim pentru a atribui simboluri specifice altor limbi. Evident, nu se pot codifica simbolurile din toate limbile folosind doar 128 de poziții, dar se pot furniza codificări ASCII extinse pentru fiecare regiune în parte. Astfel, nemții vor folosi o variantă de ASCII extins, iar grecii alta.

Deși, genială prin simplitate, soluția a condus la apariția a numeroase extensii, astăzi standardizate sub ISO-8859-x. În sistemele Windows le putem găsi sub denumirea de "coduri de pagină ANSI". Apropos, sunt multe, peste 150. Trebuia și Microsoft să mai adauge puțină dezordine, nu-i așa? Marea problemă a acestor codificări este că, tot ce depășește limita de 128, e specific acelei codificări și, prin urmare, există o mare probabilitate ca respectivele caractere să nu aibă corespondent în celelalte sisteme de codificare sau, în cel mai bun caz, să aibă alte coduri.

Acum, haideți să ne imaginăm cum comunică un calculator configurat să lucreze în limba greacă cu un calculator din România. Dacă ambele calculatoare se rezumă la a folosi caractere ASCII standard, atunci toate bune și frumoase, dar dacă vreo unul din cele două calculatoare are "impertinența" să trimită vreun caracter specific limbii native, se ajunge la tot felul de aiureli. Caracterul românesc va apărea cu totul diferit pe calculatorul din Grecia, iar textul grecesc va fi reprezentat eronat pe calculatorul din România. Pe alocuri, se pot face tot felul de artificii, conversii de la un set de caractere la altul, dar, în final, e evident că nu poți reprezenta un text decât folosind setul de simboluri specifice acelei codificări.

De dragul divagației, imaginați-vă ce chin pe un biet profesor de rusă, utilizator fidel de Windows 95, în cazul în care își propune cumva să-și tehnoredacteze cursurile pe care le predă. E clar că îi trebuie un editor "deștept", care să-i permită să introducă și text în românește, dar și text în rusă, simultan. Apropos, se mai predă limba rusă pe la vreo școală din România? Mă gandesc că ar fi o idee de înființat una, unde părinții să-și poată trimite copii obraznici. Măcar o lună!

Așa nu se mai poate!

Mediul globalizat și, în special, Internet-ul au ridicat, inevitabil, ștacheta. S-au creat premisele dezvoltării unui set de caractere universal, mai întâi UCS (Universal Character Set), urmat la scurt timp de Unicode. Discutăm de proiecte ambițiose, care își propun să codifice caracterele din toate limbile, inclusiv cele care nu se mai folosesc curent, dar pentru care există reguli clare de scriere (hieroglife, scrieri arhaice etc.).

În contextul acestui articol, nu are sens să insistăm foarte mult pe diferențele dintre UCS și Unicode, mai ales că ele au avut o evoluție în tandem. În orice caz, diferența de bază este important a fi subliniată. UCS este standard-ul prin care se propune o schemă de codificare: fiecare caracter universal primește un cod unic. Unicode folosește aceeași codificare, dar e mai mult decât atât. Asociază fiecărui caracter, în plus, o serie de atribute care-l descriu mai bine și fac posibile tot felul de "jonglerii": sortări în limba nativă, comparații etc., dar și reguli de compunere a caracterelor pentru a obține simbolul dorit. Spre exemplu, în Unicode, "ț" se poate obține prin combinarea caracterului "t" cu cel care desemenază o sedilă.

Un termen des întâlnit în jargonul IT-iștilor este și cel de UTF. Nu e excepțional să auzi afirmații de genul: "ți-am trimis un fișier în UTF" sau "hai să folosim UTF". Cunosc programatori WEB pentru care "utf-8" e sinonim cu Unicode. Prin urmare, câteva explicații lămuritoare sunt necesare. Unicode e standardul ce conține descrierea întregului sistem: codificări, atribute, reguli etc. UTF vine de la "UCS Transformation Format" și descrie modul în care este reprezentat un caracter Unicode, relativ la formatul intern al calculatorului: octeți, aliniament, semnificația biților pe diferite arhitecturi etc. În engleză e ceea ce se întâlnește sub denumirea de "encoding". Standardul Unicode descrie trei tipuri de reprezentare: UTF-8, UTF-16 și UTF-32. Dat fiind că Unicode conține un registru foarte larg de caractere (peste un milion), pentru reprezentarea unui singur caracter nu este suficient, în principiu, un singur octet. Patru octeți, adică 32 de biți, ar asigura reprezentarea oricărui caracter din Unicode. Este, de altfel, și schema de reprezentare cea mai simplă, denumită UTF-32. A doua variantă este UTF-16, prin care cele mai comune caractere pot fi reprezentate pe 16 biți (2 octeți), restul urmând a fi referite printr-o combinație de două coduri pe 16 biți. Discutăm, deci, de o schemă variabilă: un caracter poate ocupa doi octeți sau patru. În sfârșit, o altă combinație posibilă este UTF-8 prin care, asemănător UTF-16, fiecare caracter poate fi reprezentat folosind un număr variabil de octeți, dar, de data aceasta, unu, doi, trei sau patru. 
Figura 2 - Exemple de reprezentare a caracterelor Unicode

Fiecare "encoding" are avantajele și dezavantajele sale. UTF-32, dat fiind că nu operează cu lungimi variabile de reprezentare pentru caractere diferite, e cel mai simplu de implementat. Marele său dezavantaj este, în schimb, risipa sub aspectul alocării. Chiar și un banal caracter ASCII este reprezentat pe 32 de biți. UTF-16 e mai puțin "gurmand". Marea majoritate a caracterelor încap, bine mersi, pe doi octeți. Restul, sunt reprezentate pe 32 de biți, însă aceste caractere au, oricum, o frecvență mică. UTF-16 este recomandat pentru limbile asiatice, care, dat fiind că utilizează tot felul de caractere ciudate, au asociate coduri numerice mari, ce oricum ar necesita doi octeți pentru a putea fi reprezentate. În sfârșit, UTF-8 are marele avantaj că e compatibil cu ASCII, toate caracterele acestuia fiind reprezentate în acest sistem folosind tot opt biți. Acest lucru, îi conferă și un alt avantaj, acela că pentru texte bazate puternic pe ASCII, este cel mai compact. Dezavantajul este legat de efortul necesar de recompunere a octeților, atunci când sunt implicate caractere reprezentate pe mai mult de un byte.

Font-uri Unicode

Standardul Unicode definește caracterele la un nivel abstract. Le asociază coduri unice, denumiri, atribute etc., dar nu le atribuie și simbolul sau reprezentarea grafică. Acest lucru este delegat către librăria de "font"-uri. Prin urmare, ori de câte ori operăm cu texte Unicode, este foarte important să folosim și o colecție de font-uri care să aibă definite reprezentările grafice pentru diferite coduri Unicode.

Din păcate, din câte știu eu, nu există nici o astfel de colecție care să aibă definite toate caracterele Unicode, prin urmare, s-ar putea să fie necesar a încerca mai multe astfel de colecții prin care să se poată evalua în ce măsură ele răspund nevoilor dumneavoastră.

În ceea ce mă privește, pentru tehnoredactarea de diverse documente am folosit cu succes "Arial Unicode MS" pe platformele Windows și "DejaVu Sans" pe *NIX. Acestea sunt "font"-uri cu lățime variabilă, care nu sunt recomandate pentru scrierea de cod sursă. Pentru acest scop, întodeauna opțiunea trebuie să se îndrepte spre un font cu lățime fixă. "DejaVu Sans Mono" ar putea fi o variantă bună, deși totalul caracterelor Unicode implementate este relativ mic. O colecție de font-uri Unicode cu o acoperire mai mare ar fi "GNU Unifont". Aceasta ar fi în regulă de folosit pentru scrierea codului sursă, însă dezavantajul major este acela că folosește sistemul "bitmap", prin urmare aceste font-uri nu arată foarte bine la dimensiuni diferite de cele standard.

Orice colecție de font-uri veți folosi, este important să rețineți că acestea au limitările lor, iar înainte de a suspecta o problemă în codificarea Unicode, verificați dacă librăria de font-uri folosită are implementate caracterele pe care doriți să le afișați.

Seturi de caractere în Oracle

Mai țineți minte sloganul folosit la lansarea Oracle 8i? Era ceva de genul: "the internet changes everything", adică "internet-ul schimbă totul". Litera "i" din numerotarea versiunii vine de la "internet". A-ți propune să deservești internet-ul implică deschidere globală și, în acest context, posibilitatea de a opera cu diferite seturi de caractere. Prin urmare, Oracle a investit serios în această direcție. Nu mă credeți? Haideți să vedem câte seturi de caractere avem disponibile.
SQL> compute sum of total_cs on report
SQL> break on report
SQL> select isdeprecated, count(value) total_cs
  2    from v$nls_valid_values
  3   where parameter='CHARACTERSET' group by isdeprecated;

ISDEPRECATED   TOTAL_CS
------------ ----------
TRUE                 25
FALSE               222
             ----------
sum                 247 
După cum se poate observa, sunt multe. Atât de multe încât, în 11g, lista de seturi de caractere disponibile în DBCA este filtrată, iar doar câteva din categoria celor recomandate sunt, implicit, afișate.

Oracle permite configurarea unui set de caractere pentru baza de date și, începând cu Oracle 8i, a unui set de caractere suplimentar, denumit "set de caractere național", folosit pentru a stoca date asociate tipurilor: NCHAR, NVARCHAR2, NCLOB. Pentru a afla ce seturi de caractere sunt configurate pentru o bază de date Oracle, se poate utiliza următoarea interogare:
SQL> select * from v$nls_parameters
  2   where parameter in ('NLS_CHARACTERSET', 'NLS_NCHAR_CHARACTERSET');

PARAMETER              VALUE    
---------------------- ---------
NLS_CHARACTERSET       WE8DEC   
NLS_NCHAR_CHARACTERSET AL16UTF16 
Parametrul NLS_CHARACTERSET ne dă setul de caractere al bazei de date, iar NLS_NCHAR_CHARACTERSET pe cel național. Denumirea seturilor de caractere folosite de Oracle e puțin ciudată, dar, în principiu, următoarea convenție este utilizată:

<REGIUNE><NUMĂR BIȚI NECESARI PENTRU A REPREZENTA UN CARACTER><DENUMIRE SET DE CARACTERE>[S|C] 

,unde [S|C] sunt opționale și indică ori de câte ori un anumit set de caractere e relevant doar pe server [S] sau doar pe client [C]. Excepție de la regulă  fac: UTF8 și UTFE.

În cazul de mai sus, setul de caractere configurat pentru baza de date este pentru regiunea "Western Europe", fiind necesari 8 biți pentru a reprezenta un caracter. Denumirea standard a setului de caractere este DEC. În ceea ce privește setul de caractere național, regiunea este "ALL", deci Unicode. Conform convenției sunt necesari 16 biți pentru a stoca un caracter, iar denumirea setului de caractere e UTF16. Din punctul meu de vedere, denumirea acestui set de caractere e aleasă "nefericit". AL16UTF16 se vrea a fi compatibil cu standardul Unicode, de la versiunile 3.1 în sus, standarde în care apare definit conceptul de "caractere suplimentare" reprezentate pe 32 de biți, prin două perechi de câte 16 biți, denumite coduri surogat. Deci, un caracter poate ocupa 4 octeți, prin urmare, după umila mea părere, AL32UTF16 ar fi denumirea corectă. Spre exemplu, în figura 2 este reprezentat caracterul cu codul U+10400, care în AL16UTF16 ar avea următoarea reprezentare:
 SQL> select dump(unistr('\D801\DC00'), 1017) from dual;

DUMP(UNISTR('\D801\DC00'),1017)                
-----------------------------------------------
Typ=1 Len=4 CharacterSet=AL16UTF16: d8,^A,dc,^@
Se observă că avem de-a face cu 4 octeți și, conform standardului, discutăm de reprezentarea unui singur caracter.

Setul de caractere al bazei de date este cel folosit pentru a stoca tot ce înseamnă tipuri CHAR, VARCHAR2 și CLOB. În plus, codul SQL și PL/SQL este reprezentat tot în setul de caractere al bazei de date. Dat fiind că există limitarea conform căreia cuvintele cheie din limbajul SQL și PLSQL să fie compatibile ASCII, AL16UTF16 nu poate fi folosit pentru a desemna setul de caractere al bazei de date. Altfel, un caracter ASCII ar fi reprezentat pe 2 octeți, iar Oracle se așteaptă la o reprezentare pe 8 biți. Pe de altă parte, setul de caractere AL32UTF8 poate fi utilizat fără probleme, deoarece este compatibil ASCII.

Setul de caractere național reprezintă o modalitate suplimentară prin care datele de tip NCHAR, NVARCHAR2 și NCLOB pot fi stocate, folosindu-se un alt sistem de codificare a caracterelor. Din nou strâmb din nas și spun că denumirea de "set național" nu mi se pare potrivită. Spre exemplu, într-o bază de date 11g există doar două opțiuni valide pentru configurarea setului de caractere național: AL16UTF16 și UTF8, ambele fiind Unicode. Ce-o fi național aici, eu unul nu înțeleg! Aș fi ales, mai degrabă, denumirea de "set de caractere universal", pentru a puncta înzestrarea Unicode, dar, în fine, să nu ne cramponăm de denumiri. Îi zicem "național" și gata! De asemenea, nu am reușit să aflu motivul pentru care AL32UTF8 nu poate fi folosit ca set național, în timp ce UTF8 e o opțiune validă. Diferența notabilă dintre cele două seturi de caractere este faptul că AL32UTF8 are implementat conceptul de caractere suplimentare, spre deosebire de UTF8, care e compatibil cu Unicode până la versiunea 3.1 și nu are habar de astfel de caractere. Mă gândesc că, mai mult ca sigur sunt și diferențe de implementare a celor două standarde, prin urmare probabil ca limitarea ține de "bucătăria internă" Oracle.

Configurarea clientului Oracle

Aplicațiile comunică cu bazele de date folosind librării client. Remarcați că am folosit termenul "comunică" tocmai pentru a scoate în evidență faptul că este nevoie de un "limbaj" comun pentru ca cele două părți să se poată facă înțelese. Clientul trebuie să-i "vorbească" bazei de date într-o limbă pe care aceasta o poate înțelege și invers, baza de date trebuie să-i comunice clientului într-o limbă pe care acesta o cunoaște. Altfel spus, clientul și serverul Oracle trebuie să opereze cu seturi de caractere compatibile.

Pentru a înțelege mai bine despre ce este vorba, imaginați-vă un absolvent de Harvard, poliglot, vorbitor de engleză, germană, franceză și spaniolă, încercând să poarte o discuție cu țața Floarea de la noi. Degeaba vorbește omul fluent patru limbi, dacă nu știe românește, degeaba!

În general, înaintea oricărei conversații, părțile implicate stabilesc de comun acord limba în care comunică. Acest lucru nu este întodeauna evident, dar un turist, spre exemplu, va încerca să se asigure că urmează să comunice într-una din limbile cunoscute de el, prin întrebări de tipul: "Do you speak English?" sau "Parlez-vous français?" etc. În Oracle, clientul îi comunică server-ului cu ce set de caractere operează, prin intermediul NLS_LANG, care poate fi o cheie în "registry", dacă discutăm de Windows, sau o variabilă de sistem.

NLS_LANG are următorul format: <Limbă>_<Teritoriu>.<Set de caractere>. În contextul discuției noastre, de interes este ultima parte a definiției NLS_LANG, cea legată de setul de caractere. Acesta trebuie să fie, cu mici excepții  pe care le vom puncta la momentul potrivit, cel configurat pe calculatorul client. Ok, repeteți după mine: MU-SAI, CEL CON-FI-GU-RAT PE CLI-ENT! O configurare eronată, cum ar fi inițializarea NLS_LANG astfel încât să coincidă cu setul de caractere al bazei de date, deși clientul folosește altceva, poate conduce la informație textuală coruptă în baza de date. Revenind la exemplul simpaticului absolvent de Harvard, dacă la întrebarea "Habla español?", țața Floarea răspunde "Si!" pentru că își amintește ea ceva de prin telenovelele vizionate recent, dar, de fapt, ea nu știe o boabă de spaniolă, vă imaginați penibilul unei astfel de conversații, unde domnu' educat începe a-i vorbi într-o spaniolă impecabilă, iar saraca tanti, tot ce poate spune e "Si, si!". În concluzie, fiți corecți când specificați NLS_LANG.

Oricum, ușor de zis, mai greu de făcut. A determina setul de caractere utilizat de client presupune, implicit, a ști cum să-l determini în sistemul de operare instalat pe respectivul calculator. Pe sistemele bazate pe Unix trebuie procedat într-un fel, pe Windows altfel și așa mai departe. Mai mult, trebuie ținut cont și de aplicația care se conectează la baza de date. Degeaba sistemul de operare știe Unicode, dacă aplicația nu e scrisă astfel încât să utilizeze Unicode. Dacă mai adăugăm în peisaj și faptul că Windows operează cu seturi de caractere diferite pentru aplicațiile GUI (non-Unicode), denumite coduri de pagină ANSI și alte seturi de caractere pentru cele tip consolă, denumite seturi de caractere OEM, atunci ne dăm seama că e loc pentru erori și probleme de inter-operabilitate.

Haideți să vedem, în mare, ce avem de făcut în Windows și, apoi, într-un sistem de operare bazat pe Unix, cum ar fi, de pildă, Oracle Linux sau RedHat Linux.

În Windows, dacă discutăm de o aplicație GUI ce nu știe Unicode, ne raportăm la setul de caractere al sistemului de operare, denumit cod de pagina ANSI. Pentru a determina acest cod, tragem o ochiadă la următoarea cheie din "registry":

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Nls\CodePage\ 

Mai departe, folosim tabelul de corespondență de mai jos:

Cod pagină Set caractere Oracle
1250 EE8MSWIN1250
1251 CL8MSWIN1251
1252 WE8MSWIN1252
1253 EL8MSWIN1253
1254 TR8MSWIN1254
1255 IW8MSWIN1255
1256 AR8MSWIN1256
1257 BLT8MSWIN1257
1258 VN8MSWIN1258
874 TH8TISASCII
932 JA16SJIS
936 ZHS16GBK
949 KO16MSWIN949

Spre exemplu, dacă folosim sqlplusw.exe, care este o aplicație GUI compatibilă "cod pagină ANSI", iar acest cod este 1252, atunci putem inițializa NLS_LANG cu "AMERICAN_AMERICA.WE8MSWIN1252".

Dacă folosim varianta consolă a SqlPLUS-ului, atunci e altă mâncare de pește. Trebuie să ne raportăm la așa-numitele coduri de pagină OEM. În principiu, deschidem frumușel o consolă "Command Window" și determinăm codul OEM configurat, folosind comanda "chcp". Apoi, folosim tabelul de mai jos:

Cod OEMSet caractere Oracle
437US8PC437
720AR8DOS720
737EL8PC737
850WE8PC850
852EE8PC852
857TR8PC857
858WE8PC858
861IS8PC861
862IW8PC1507
865N8PC865
866RU8PC866

Terminalul "Command Window" știe și de coduri de pagina ANSI, ceea ce-i conferă mai multă flexibilitate. Totuși, câteva configurări adiționale sunt necesare. Comanda "chcp" trebuie folosită pentru a inițializa codul de pagina ANSI și, în plus, "font"-ul Lucida Console trebuie selectat, dat fiind că cele de tip "raster" sunt extrem de limitate. Figura de mai jos, prezintă cele două cazuri.
Figura 3 - Exemplu de configurare a terminalului Windows

Se observă că, în primul caz simbolul Euro nu este afișat corect, pe când, în cel de-al doilea caz, avem un rezultat mulțumitor.

Dacă aplicația știe ce-i aia Unicode, atunci se poate utiliza UTF8 sau AL32UTF8 în configurarea NLS_LANG. Chiar dacă Windows utilizează intern UTF16, nu configurați NLS_LANG să folosească AL16UTF16.

Configurarea în sistemele bazate pe UNIX ar trebui să fie mai simplă, dat fiind că aceste sisteme de operare au Unicode-ul în "carne și-n oase". Putem verifica ce set de caractere avem configurat cu ajutorul comenzii "locale". Pe sistemul meu Oracle Linux 5.4, obțin următorul rezultat:
[oracle@fox ~]$ locale
LANG=en_US.UTF-8
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_PAPER="en_US.UTF-8"
LC_NAME="en_US.UTF-8"
LC_ADDRESS="en_US.UTF-8"
LC_TELEPHONE="en_US.UTF-8"
LC_MEASUREMENT="en_US.UTF-8"
LC_IDENTIFICATION="en_US.UTF-8"
LC_ALL= 
Conform variabilei LANG de mai sus, sistemul folosește un sistem de codificare a caracterelor bazat pe UTF8. Formatul de definire a acestei variabile seamănă izbitor de bine cu NLS_LANG. E clar de unde s-au inspirat băieții de la Oracle, nu? Dat fiind că sistemul de fața știe Unicode, este în regulă să folosim UTF8 sau AL32UTF8 pentru inițializarea variabilei NLS_LANG.

Atenție, dacă NLS_LANG nu este definit pe client, atunci, implicit, setul de caractere folosit va fi US7ASCII. Evident, acest lucru nu este recomandat, prin urmare este întotdeauna bine sa-l inițializăm, conform instrucțiunilor de mai sus. Excepție fac librăriile JDBC, care de la versiunea 10g ignoră NLS_LANG și folosesc în loc configurările de la nivelul mașinii virtuale Java. Deoarece Java folosește nativ Unicode, JDBC-ul va converti întotdeauna din setul de caractere al bazei de date în UCS-2 și invers. Apropos, dat fiind că SQLDeveloper-ul furnizat de Oracle este scris în Java, el este prin definiție compatibil Unicode, și nu are nevoie de nici o setare de NLS_LANG, ceea ce-l face utilitarul perfect pentru a testa probleme de afișare/conversie a caracterelor.

Import/Export SRL

Utilitarele clasice de export/import reprezintă, de fapt, aplicații client. Ele se conectează la baza de date și, fie extrag, în cazul exportului, fie introduc date, în cazul importului. Discutăm, deci de aplicații cu care putem muta tot felul de informații dintr-o bază de date în alta. Asta înseamnă că, a exporta dintr-o bază de date cu un anumit set de caractere într-o altă baza de date cu un alt set de caractere, este un caz cât se poate de plauzibil.

Mai întâi, haideți să vedem ce se întâmplă în cazul exportului. Pai, avem de-a face, pe de o parte, cu setul de caractere al bazei de date din care efectuăm exportul și, pe de altă parte, setul de caractere al clientului, dat de NLS_LANG. Când cele două sunt diferite, avem de-a face cu o conversie de la setul de caractere al bazei de date la cel al clientului, iar utilitarul "exp" ne va indica acest lucru prin afișarea unor mesaje ca cele de mai jos:
Export done in AL32UTF8 character set and AL16UTF16 NCHAR character set
server uses WE8DEC character set (possible charset conversion)
Mesajele de mai sus ne spun că exportul s-a realizat folosind pe client setul de caractere AL32UTF8, în timp ce baza de date folosește setul de caractere WE8DEC. Dacă pe client se folosește același set de caractere cu cel al bazei de date, a doua linie din listingul de mai sus nu va mai fi afișată.Situația este prezentată în figura de mai jos:
Figura 4 - Exemplu de export cu seturi de caractere diferite

Este important de reținut faptul că setul de caractere folosit la export, adică cel de pe client definit prin NLS_LANG, este stocat în fișierul generat (dump), astfel încât să se poată ști în ce sistem de codificare sunt reprezentate datele. Această informație este extrem de importantă pentru utilitarul "imp".

Ajungem, prin urmare, la faza a doua, cea de import. Presupunem că avem un fișier de tip "dump" generat de "exp". În ceea ce privește utilitarul de import, avem de-a face cu, posibil, trei seturi de caractere distincte:
  1. setul de caractere în care datele din fișierul de export sunt stocate
  2. setul de caractere specific sesiunii de import, dat de NLS_LANG
  3. setul de caractere al bazei de date țintă
Astfel, pot apărea conversii de la un set de caractere la altul, iar acest lucru este semnalizat de utilitarul de import prin mesaje asemănătoare celor de mai jos:
import done in WE8MSWIN1252 character set and AL16UTF16 NCHAR character set
import server uses WE8DEC character set (possible charset conversion)
export client uses AL32UTF8 character set (possible charset conversion)
În traducere liberă, ar veni cam așa: calculatorul de pe care rulăm utilitarul de import operează cu setul de caractere WE8MSWIN1252, baza de date țintă, cea în care importăm datele, folosește WE8DEC, iar fișierul de tip "dump" are reprezentate datele în AL32UTF8. Figura de mai jos prezintă acest caz:
Figura 5 - Exemplu de import cu seturi de caractere diferite
După cum se poate observa, există o mare problemă: conversia de la AL32UTF8 la WE8MSWIN1252 poate conduce la o pierdere de informație. E și normal, dat fiind că e vorba de o conversie de la un set de caractere Unicode cu peste un milion de caractere reprezentate, la un set de caractere pe 8 biți, mult mai limitat. Prin urmare, atunci când efectuăm operații de export/import trebuie să reducem cât mai mult aceste conversii.

Ceva mai devreme, atunci când povesteam despre configurarea clientului Oracle, spuneam răspicat că trebuie să fim cinstiți și să nu inducem în eroare comunicarea client-server, printr-o inițializare greșită a variabilei/cheii NLS_LANG. Ziceam că ea trebuie, musai, să reflecte setul de caractere folosit pe calculatorul client. Așa cum se întâmplă de multe ori, există și excepții, iar utilitarele "exp" și "imp" intră în această categorie.

Regula de aur, în acest caz, ar fi ca, pentru export să se inițializeze NLS_LANG astfel încât să reflecte setul de caractere al bazei de date, chiar dacă, pe client, există configurat un cu totul și cu totul alt set de caractere. În exemplul din figura 4, NLS_LANG ar trebui inițializat cu AMERICAN_AMERICA.WE8DEC. Astfel, între server și client dispare operația de conversie. Există vreun risc, în acest caz? Răspunsul este nu, pentru că fișierul tip "dump" generat de "exp" este unul binar și, prin urmare, nu contează cu ce set de caractere operează sistemul de operare gazdă. În cazul importului, NLS_LANG ar trebui configurat astfel încât să reflecte setul de caractere în care sunt reprezentate datele din fișierul tip "dump". Dacă se respectă regula anterioară de la export, atunci discutăm, practic, de setul de caractere al bazei de date sursă. Dacă acesta este diferit de cel al bazei de date țintă, cea în care importăm datele, vom avea o singură conversie, de la setul de caractere din fișierul tip "dump" la cel al bazei de date destinație. Putem afla setul de caractere stocat în fișierul de tip "dump", prin simularea unui import cu clauza "show=y". Există și metoda brută, cea de a folosi un editor hexazecimal prin care să aflăm codul setului de caractere stocat direct în fișier, însă e ceva mai complicat și nu vom intra în detalii. Cei interesați pot consulta nota 48644.1, ce poate fi accesată pe portalul de suport al celor de la Oracle.

Pentru utilitarele DataPump, regulile de mai sus nu se aplică. Acestea, oricum, nu intră în categoria aplicațiilor client, ele rulând în contextul serverului. Totuși, dacă utilitarele "expdp" sau "impdp" folosesc un fișier de parametri, NLS_LANG trebuie configurat astfel încât să reflecte setul de caractere (encoding-ul) acestui fișier.

Aceeași situație intervine și în cazul utilitarului "SQL*Loader". Acesta este folosit pentru a încărca date din fișiere text ce respectă un anumit format, deci îl putem asimila în categoria aplicațiilor client, folosite în operațiile de import. De obicei, "SQL*Loader" folosește un așa-numit fișier de control, care conține regulile după care se efectuează importul, precum și descrierea formatului datelor stocate în fișierul sau fișierele de intrare: separator de coloane, sepratorul de linii etc. În acest caz, NLS_LANG trebuie să reflecte "encoding"-ul fișierului de control, iar setul de caractere specific datelor din fișierul/fișierele de intrare trebuie specificat prin clauza CHARACTERSET, precizată, la rândul ei, în fișierul de control. Dacă nu se utilizează un fișier de control, atunci NLS_LANG trebuie configurat astfel încât să reflecte setul de caractere al datelor de intrare.

Cum detectăm setul de caractere al unui fișier text?

Dacă discutăm de "encoding"-uri specifice anumitor fișiere, problema care se pune este cea de detectare a acestora. De pildă, să presupunem că de la un client primim un fișier text cu date ce trebuie încărcate în baza de date folosind "SQL*Loader". Pentru a-l încărca fără probleme, ar trebui să știm "encoding"-ul acestui fișier, pentru a putea configura corect NLS_LANG sau pentru a specifica setul de caractere corespunzător în fișierul de control, prin utilizarea clauzei CHARACTERSET. Deci, această informație este critică. Așa că, Oracle furnizează un utilitar propriu de detectare a setului de caractere specific unui fișier dat. Este vorba de LCSSCAN. Iată un exemplu simplist:
~$ lcsscan file=german.txt


 Language and Character Set File Scanner v2.1

 (c) Copyright 2003, 2004 Oracle Corporation.  All rights reserved.


german.txt:     GERMAN  WE8ISO8859P1;
Există pe platformele *NIX, utilitarul "enca", dar, din punctul meu de vedere, este destul de limitat. Spre exemplu, dacă îi furnizez un fișier codificat în utf-8, nu știe să-l detecteze.
~$ enca test.txt
enca: Cannot determine (or understand) your language preferences.
Please use `-L language', or `-L none' if your language is not supported
(only a few multibyte encodings can be recognized then).
Run `enca --list languages' to get a list of supported languages. 
În schimb, comanda "file", disponibilă pe platformele *NIX, își face treaba foarte bine:
~$ file test.txt
test.txt: UTF-8 Unicode text 
Există și metoda "trail and error". Spre exemplu, se încarcă fișierul în Firefox, după care "ne jucăm" cu diferite seturi de caractere, până când se obține reprezentarea corectă a textului. Din păcate, denumirile seturilor de caractere din Firefox, nu coincid cu cele specifice Oracle, dar sunt destul de intuitive. Spre exemplu, "ISO/IEC 8859-1 [Latin 1]" corespunde setului Oracle "WE8ISO8859P1", iar "Windows CP-1252" corespunde setului "WE8MSWIN1252".

Cum alegem setul de caractere pentru baza de date?

Decizia cu privire la setul de caractere ce trebuie configurat pentru baza de date este importantă și e bine a fi luată corect de la început. Schimbarea ulterioară a setului de caractere, în cele mai multe cazuri, nu este ușor de realizat, iar uneori se poate dovedi chiar o misiune imposibilă.

Așa cum deja știm, atunci când instalăm o bază de date nouă, trebuie să-i asociem un set de caractere și, în plus, unul național. Cu cel național terminăm repede. Nu avem de ales decât între AL16UTF16 și UTF8. Oracle recomandă AL16UTF16, fiind standardul care este actualizat regulat cu noile versiuni furnizate de Unicode Consortium. Ocupă el mai mult, cel puțin 16 biți chiar și pentru un caracter ASCII, dar avem avantajul că e numa' bun pentru limbajele asiatice, ale căror caractere ar fi necesitat oricum 2 octeți. Dom'le, au viziune băieții aștia de la Oracle! Au anticipat atât de bine avântul glorios al economiei chineze, încât, a nu fi pregătit să stochezi tot felul de denumiri chinezești, ar putea fi încadrat la capitolul "ignoranță". Deci, nu vrem să fim etichetați ca ignoranți și, în principiu, lăsăm opțiunea implicită AL16UTF16 pentru setul național. Îndrăznim să o modificăm pe UTF8, doar atunci când la baza noastră de date se conectează aplicații antice, ce folosesc în continuare un client Oracle 8i sau, Doamne ferește, unul mai vechi. Anticariatele astea de clienți nu se înțeleg prea bine cu AL16UTF16 și atunci, ca ultimă soluție, alegem varianta UTF8. Avantajul ar fi că avem un sistem de reprezentare mai compact, în schimb nu există conceptul de caractere suplimentare și rămânem la standardul Unicode 3.1, dat fiind că UTF8 în Oracle e încremenit la această versiune.

Ne frecăm mulțumiți palmele și trecem mai departe la configurarea setului de caractere al bazei de date. Ei, aici e scrâșnirea dinților și scărpinarea scăfârliei (proprii!). Ce se anunța inițial ca o simplă selecție în DBCA, se poate transforma în ore bune de discuții cu clientul, colegii de suferință programatori, conducerea și alții.

În primul rând, pornim analiza de la scopul acestei noi baze de date. Ce facem cu ea? Cine o utilizează și cum? Dacă aceasta trebuie să deservească o nouă aplicație, atunci vom afla foarte multe răspunsuri analizând specificațiile acesteia. Vom ști care sunt utilizatorii țintă, dacă avem de-a face cu nivele de prezentare în limbi diferite și câte și mai câte. Oricum, dat fiind că, deocamdată, elementul de interes este alegerea setului de caractere al bazei de date, ne concentrăm pe determinarea oportunității alegerii unui set care implementează Unicode sau a unui sistem de codificare a caracterelor specific doar unei anumite regiuni.

Contează și limbajul în care este scrisă aplicația. Dacă e Java, atunci Oracle recomandă AL32UTF8, deoarece, nativ, acest limbaj de programare operează cu UCS-2 (o variantă de UTF-16, deci Unicode), iar conversia de la AL32UTF8 la UCS-2 se realizează foarte eficient în librăriile JDBC.

Pe de altă parte, un set de caractere pe 8 biți, specific doar unei anumite regiuni, se poate dovedi, prin simplitatea sa, mai eficient. Spre exemplu, dacă avem o aplicație .NET pentru un client din Germania, care se conectează la baza de date de pe calculatoare Windows și care nu operează decât în contextul limbii germane, atunci setul de caractere WE8MSWIN1252 se poate dovedi o alegere potrivită. Dat fiind că discutăm de clienți Windows cu o configurație pe limba germană, codul de pagină ANSI ar fi 1252, deci putem inițializa NLS_LANG cu aceeași valoare a setului de caractere, cea a bazei de date. Asta înseamnă că, între client și server, nu va exista nici un fel de conversie între seturi de caractere.

În altă ordine de idei, mai nou, Oracle recomandă Unicode pentru toate noile baze de date. Raționamentul din spatele acestei recomandări e că diferențele de performanță între utilizarea unui set de caractere Unicode și a unuia pe 8 biți nu sunt notabile și atunci e mai bine să fim pregătiți pentru a stoca date în orice limbă, decât să ne dăm seama ulterior că se impune schimbarea setului de caractere. Sincer, eu nu am cunoștință de teste de performanță comparative care să arate gradul în care un set de caractere ar fi mai bun decât celălalt. Cred că ar fi și destul de greu de cuantificat, dat fiind că intervin mai mulți factori în ecuație: conversii în setul de caractere al clientului, ce tipuri de caractere sunt majoritar folosite etc. Ideea de a opta pentru Unicode în baza de date fără a mai sta prea mult pe gânduri sună bine, însă nu e chiar așa de simplu. Există tot felul de implicații care trebuie luate în considerare. Iată câteva dintre ele:
  • modelarea tabelelor: în contextul Unicode nu se mai poate pleca de la premisa că dimensiunea maximă a unei coloane de tip VARCHAR2 ar fi de 4000 de caractere. Cifra de 4000 este exprimată în octeți, ceea ce înseamnă că, presupunând că am stoca doar caractere ciudate pe 32 de biți, ar rezulta un maxim de 1000 de caractere. Analog, putem extrapola raționamentul și pentru tipul CHAR. Consecința imediată este o mai mare atenție acordată modelării tabelelor. Dacă ne asteptăm la mai mult de 1000 de caractere într-o coloană de tip VARCHAR2, atunci ar trebui să luăm în considerare tipul CLOB. 
  • dimensiunea bazei de date: dat fiind că, în AL32UTF8, un caracter poate fi reprezentat pe mai mult de un octet, atunci ne putem astepta la o dimensiune mai mare a bazei de date, cu consecințe în necesarul de spațiu pe disc și, eventual, de memorie.
  • considerente ce țin de integrare: dacă avem de-a face cu sisteme de interfațare bazate pe replicare (view-uri materializate), iar baza de date sursă operează cu un set de caractere pe 8 biți, în timp ce cea destinație cu Unicode, atunci pot apărea tot felul de surprize, datorită felului diferit în care dimensiunile coloanelor de tip VARCHAR2 și CHAR se comportă. Chiar și un simplu "CREATE TABLE AS SELECT" (CTAS) efectuat printr-un "dblink" ne poate rezerva surprize. De asemenea, trebuie luate în considerare și alte aspecte, cum ar fi, de pildă, posibilitatea de a efectua operații de transport de "tablespace"-uri. Aceste operații nu se pot realiza decât dacă baza de date sursă are același set de caractere, inclusiv cel național, ca și baza de date destinație.
  • codul SQL și PLSQL: trebuie acordată o mai mare atenție felului în care datele sunt inserate în tabele, precum și acelor funcții care prelucrează text, dat fiind că, în cazul Unicode, aritmetica octeților e diferită de cea a caracterelor. Prin urmare funcții ca de pildă LENGTHB sau SUBSTRB sunt susceptibile a se comporta diferit în cazul unei baze de date Unicode, față de una cu un set de caractere pe 8 biți. De asemenea, în instrucțiuni SQL s-ar putea ca operații cu LIKE2, LIKE4 și LIKEC să se dovedească a fi utile. Inclusiv procedurile de livrare a codului PL/SQL ar putea suferi modificări, dacă acestea folosesc SqlPLUS. Unele companii au chiar reguli stricte prin care nu permit înglobarea de texte Unicode, în clar, în codul PLSQL, ci obligă ca acestea să fie reprezentate folosind funcții de tipul UNISTR.
  • gradul de incertitudine: pentru baze de date consolidate, care deservesc mai multe aplicații și, eventual, sunt planificate pentru a deservi, în viitor, noi sisteme informatice este, în general, o bună idee să se meargă pe varianta Unicode, dat fiind că nu se poate determina de la început specificul tuturor aplicațiilor ce urmează a fi dezvoltate.
Dacă, totuși, decizia înclină spre Unicode, atunci apare, pe bună dreptate, nedumerirea legată de setul de caractere național, care și el este tot Unicode și devine, în acest caz, superfluu. Din punctul meu de vedere, în bazele de date Oracle configurate cu un set de caractere Unicode, setul național își pierde din relevanță. Desigur, putem să ne imaginăm că suntem paranoici și, dacă baza de date folosește AL32UTF8, iar setul național este AL16UTF16, vom opta pentru a stoca textele specifice limbilor asiatice în tipuri NCHAR, NVARCHAR2 sau NCLOB, dat fiind că AL16UTF16, în acest caz, ar fi mai eficient decât AL32UTF8. În practică, nu cred că-și bate cineva capul cu asemenea optimizări de finețe.

Pe de altă parte, dacă decidem să nu optăm pentru un set de caractere Unicode pentru baza de date, atunci, probabil că va trebui să acordăm o mai mare atenție tipurilor NCHAR, NVARCHAR2 și NCLOB, atunci când modelăm design-ul bazei de date.

Surprize, surprize...

Dacă Andreea Marin ar fi DBA și ar face o emisiune despre seturi de caractere Oracle (chiar, oare cum ar fi?) ar avea de unde să-și selecteze colecția de surprize, numai bune de uimit auditoriul. Nu știu cine s-ar uita la o astfel de emisiune TV, în afară de câțiva "oracliști" ciudați, incluzându-mă aici și pe mine, dar ce mai contează!

Text Unicode în codul sursă

Să intre în scenă surpriza numărul unu! Dacă se lucrează cu o bază de date cu un set de caractere regional, pe 8 biți să zicem, și dorim să populăm o tabelă ce conține coloane de tip NCHAR, NVARCHAR2 sau NCLOB prin intermediul unor instrucțiuni SQL-INSERT, trebuie să avem mare grijă la valorile, în clar, furnizate clauzei VALUES. Iată un exemplu:
SQL> create table nchar_test ( col nvarchar2(100) );

Table created.

SQL> insert into nchar_test values ('diacritice: șțăâî');

1 row created.

SQL> select * from nchar_test;

COL
--------------------
diacritice: ¿¿¿âî 
După cum se poate observa, valoarea inserată nu este cea la care ne așteptam. Problema este legată de faptul că, limbajul SQL și PL/SQL nu operează decât cu setul de caractere al bazei de date, prin urmare, instrucțiunea SQL-INSERT din exemplul nostru este interpretată în contextul acestui set de caractere. Rezultă că, valorile în clar care sunt reprezentate în Unicode, se vor "corupe".

Începând cu versiunea 10.2 a clientului Oracle avem o funcționalitate nouă ce ne permite rezolvarea acestei situații. Pe client, putem defini variabila de sistem ORA_NCHAR_LITERAL_REPLACE cu valoarea "true" și, mai departe, se folosește prefixarea valorii în clar cu N', ca în exemplul de mai jos:
 [oracle@owl ~]$ export ORA_NCHAR_LITERAL_REPLACE=true
[oracle@owl ~]$ sqlplus talek/***

SQL*Plus: Release 11.2.0.1.0 Production on Sun Dec 4 16:01:32 2011

Copyright (c) 1982, 2009, Oracle.  All rights reserved.


Connected to:
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Production
With the Partitioning, Automatic Storage Management, OLAP, Data Mining
and Real Application Testing options

SQL> insert into nchar_test values (N'diacritice cu noua sintaxa: șțăâî');

1 row created.

SQL> select * from nchar_test;

COL
----------------------------------------
diacritice: ¿¿¿âî
diacritice cu noua sintaxa: șțăâî 
Dacă se folosește SqlDeveloper, atunci aceeași funcționalitate se obține adăugând următoarea linie în fișierul de configurare ./sqldeveloper/bin/sqldeveloper.conf:
AddVMOption -Doracle.jdbc.convertNcharLiterals=true

Un altfel de "WHEN OTHERS THEN NULL"...

Dacă lucrăm cu o bază de date cu un set de caractere Unicode, cum ar fi AL32UTF8, trebuie să ținem seama că anumite funcții ce operează cu text ne pot rezerva surprize. Pentru exemplificare, să luăm ca exemplu, funcția REGEXP_REPLACE:
SQL> create table test (col varchar2(4000));

Table created.

SQL> insert into test values (rpad('a', 4000, 'a'));

1 row created.

SQL> select length(col) from test;

LENGTH(COL)
-----------
       4000

SQL> update test set col=regexp_replace(col, 'a$', unistr('\20ac'));

1 row updated.

SQL> select length(col) from test;

LENGTH(COL)
-----------
       3999 
Practic, avem o tabelă cu o coloană definită ca VARCHAR2(4000), ce conține un sir lung de 4000 de caractere de "a". Dorim să înlocuim ultimul caracter din acest șir cu simbolul Euro, care poate fi reprezentat, elegant, folosind funcția UNISTR și codul Unicode corespunzător. Instrucțiunea UPDATE funcționează fără nici o problemă, în schimb, șirul rezultat este mai mic, având acum doar 3999 de "a"-uri. Cum este posibil așa ceva? Păi, simbolul Euro în AL32UTF8 nu poate fi reprezentat pe un singur byte, prin urmare, dat fiind că dimensiunea maximă a sirului este de 4000 de octeți, acesta nu are cum să încapă. În acest caz, nu este "aruncată" nici o eroare, așa cum ne-am fi așteptat probabil și, mai grav, șirul asupra căruia am operat este modificat prin înlăturarea ultimului caracter. Deci, atenție la astfel de cazuri!

Aceeași Mărie, dar cu altă pălărie

În Unicode, există conceptul de caractere compuse. Spre exemplu, "ñ" se poate obține prin combinarea lui "n" cu "~". Totuși, există și caracterul "ñ", definit de sine stătător. Aceaste cazuri pot crea perplexități. Iată un exemplu:
create table test_like (col nvarchar2(100));
insert into test_like values ('espa' || unistr('\006e\0303') || 'iol');
Avem o tablă cu un câmp de tip NVARCHAR2, numai bun de stocat text unicode. Avem și un SQL-INSERT cu care introducem în tabelă textul "españiol". Funcția UNISTR('\006e\0303') returnează caracterul "ñ", obținut prin operația de compunere. Toate bune și frumoase, până cand cineva dorește să selecteze toate înregistrările care se termină în "ñiol". Utilizatorul nostru nu știe cum au fost introduse datele și, evident, nici nu ar trebui să-l intereseze acest lucru. Pentru a interoga, folosește codul unicode asociat caracterului de sine stătător "ñ":
SQL> select * from test_like where col like '%' || unistr('\00f1') || 'iol';

no rows selected
Funcția UNISTR('\00f1') returnează caracterul "ñ", dar utilizând codul de sine stătător. Se poate observa că interogarea de mai sus nu returnează nici un rând, deși, teoretic, ar trebui. În fond și la urma urmei, discutăm de același înțeles semantic. Pentru a evita astfel de cazuri este bine a se utiliza LIKEC, așa cum este exemplificat mai jos:
SQL> select * from test_like where col likec '%' || unistr('\00f1') || 'iol';

COL
------------------
españiol 
Operatorul LIKEC știe să opereze cu astfel de reprezentări echivalente ale aceluiași caracter și ne poate salva în astfel de situații. Analog, funcția INSTRC s-ar putea să fie cea potrivită în locul INSTR-ului.

Atenție la UTL_FILE

Dacă avem de încărcat date din fișiere externe și dorim să folosim UTL_FILE, atunci trebuie să ținem cont de faptul că acest pachet nu efectuează nici un fel de conversie între "encoding"-ul fișierului sursă și setul de caractere al bazei de date. Ar fi și destul de dificil, dat fiind că UTL_FILE rulează în contextul serverului și nu există nici un NLS_LANG care să-i poată spune care ar fi setul de caractere client, în acest caz, cel al fișierului de încărcat. Un exemplu lămuritor se impune:
SQL> create directory test_dir as '/home/oracle';

Directory created.

SQL> grant read on directory test_dir to talek;

Grant succeeded.

SQL> grant execute on utl_file to talek;

Grant succeeded.
Instrucțiunile de mai sus pregătesc terenul pentru testul nostru. Dintr-un utilizator cu suficiente privilegii, creăm un director Oracle și dăm drepturi de citire utilizatorului TALEK. De asemenea, îi dăm și drepturi să poată folosi pachetul UTL_FILE. Mai departe, în directorul "/home/oracle" de pe server cream un fișier numit "german.txt" și folosim un "encoding" pe 8 biți, cum ar fi cel desemnat de codul de pagină ANSI 1252. Fișierul arata ca în figura de mai jos:
Figura 7 - Editarea unui fișier cu un set de caractere ANSI 1252, în Vim

Am folosit editorul Vim și am configurat explicit "encoding"-ul "cp1252". Evident, puteți folosi editorul dumneavoastră preferat, dar important este să alegeți setul de caractere corespunzător.

Acum, haideți să testăm ce se întâmplă când încercăm să încărcăm acest fișier într-o bază de date ce are ca set de caractere AL32UTF8.
SQL> connect talek/***
Connected.

SQL> create table test_utl_file (text varchar2(500));

Table created.

SQL> declare
  2    l_file utl_file.file_type;
  3    l_text raw(500);
  4  begin
  5    l_file := utl_file.fopen('TEST_DIR', 'german.txt', 'R');
  6    utl_file.get_raw(l_file, l_text, 500);
  7    insert into test_utl_file values(utl_raw.cast_to_varchar2(l_text));
  8    utl_file.fclose(l_file);
  9  end;
 10  /

PL/SQL procedure successfully completed.

SQL> select * from test_utl_file;

TEXT
----------------------
Deutsch f▒r Anf▒nger.  
După cum se poate observa, textul stocat în tabela noastră nu arată cum ar trebui. Ce s-a întâmplat? Ei bine, baza noastră de date folosește AL32UTF8, de unde rezultă că textul stocat în tabelă este interpretat ca fiind în acest set de caractere. Prin urmare, textul stocat ca WE8MSWIN1252 nu are nici o semnificație în UTF8. O conversie manuală, în schimb, rezolvă problema.
SQL> select utl_raw.cast_to_varchar2(
  2  utl_raw.convert(utl_raw.cast_to_raw(text), 'AMERICAN_AMERICA.AL32UTF8', 'AMERICAN_AMERICA.WE8MSWIN1252')
  3  ) text from test_utl_file;

TEXT
------------------------
Deutsch für Anfänger.
Reținem, deci că, ori de câte ori folosim UTL_FILE, trebuie să ne ocupăm noi de partea de conversie, dat fiind că aceasta nu va fi efectuată automat. Evident, "encoding"-ul fișierului sursă trebuie știut/detectat în prealabil.

Dacă XML nu e, nimic nu e!

Tehnologiile XML sunt la modă. Mulți programatori investesc mult timp și efort în a le învăța temeinic, astfel că, pentru unii, e mai ușor să-și imagineze persistența datelor în fișiere XML, decât relațional, sub formă de tabele. Desigur, și "framework"-ul îi ajută prin facilități de serializare a obiectelor în format XML, ceea ce nu-i rău deloc. Oracle nu putea sta deoparte și oferă XDB, o întregă infrastructură pentru stocarea de conținut XML.

Un fișier XML are de obicei pe prima linie o clauză de "encoding" prin care se specifică ce set de caractere este utilizat pentru reprezentarea informației din respectivul XML, cum ar fi de pildă:

<?xml version="1.0" encoding="UTF-8"?>

Dacă se merge pe varianta stocării acestor XML-uri în coloane VARCHAR2 sau CLOB atunci este posibil ca setul de caractere specificat în clauza "encoding" a XML-ului să nu-l reflecte pe cel real, deoarece astfel de coloane folosesc întotdeauna setul de caractere al bazei de date, care poate fi diferit de cel declarat în XML. Spre exemplu, într-o bază de date WE8MSWIN1252, dacă se introduce într-o coloană CLOB un XML care are declarat un "encoding" utf-8, acest lucru nu înseamnă că acel XML va fi reprezentat în UTF-8, ci tot în setul de caractere al bazei de date, în acest caz WE8MSWIN1252. Dacă logica aplicației se bazează pe setul de caractere declarat în XML, iar acesta nu este compatibil cu cel al bazei de date, atunci ne putem aștepta la probleme.

CLOB-ul e mai gras în Unicode

Tipul CLOB, spre deosebire de NCLOB, folosește setul de caractere definit pentru baza de date. Spre exemplu, daca avem o bază de date WE8MSWIN1252, datele asociate coloanelor de tip CLOB vor folosi acest set de caractere. Totuși, dacă discutăm de o bază de date ce folosește un set de caractere multi-byte, cum ar fi de pildă AL32UTF8, atunci setul de caractere folosit pentru CLOB-uri este fie AL16UTF16, fie o versiune Oracle internă de UCS-2. Efectul imediat este faptul că, atunci când se ia în considerare migrarea de la un set de caractere "single-byte" la unul "multi-byte", trebuie să se țină cont că informația textuală stocată în CLOB-uri va necesita mai mult spațiu pe disc, dat fiind că, în AL16UTF16 sau UCS-2, chiar și un banal caracter ASCII va necesita doi octeți.

Și totuși, cum schimbăm setul de caractere asociat bazei de date?

Da, se poate ajunge și aici! Spre exemplu, o bază de date care a fost utilizată bine-mersi cu un set de caractere regional pe 8 biți poate să nu mai fie adecvată, de îndată ce apar noi provocări legate de globalizare și de necesitatea de a stoca date în limbi diferite. Sau, se poate să aveți de-a face cu o bază de date care a fost "lansată la apă" de la bun început cu o configurare nefericită legată de setul de caractere asociat. De pildă, o bază de date accesată, în special, de pe clienți Windows, cu un set de caractere WE8DEC, în loc de WE8MSWINxxxx. Spun că această configurație este nefericit aleasă, deoarece intervin conversii inutile între setul de caractere al clientului și cel al bazei de date, iar WE8DEC are mult mai puține simboluri definite față de WE8MSWINxxxx. De altfel, începând cu Oracle 11g, setul de caractere WE8DEC nu se mai regăsește în lista celor recomandate. Asta nu însemnă că Oracle nu mai asigură suport pentru el, doar că nu se știe ce se va întâmpla cu el prin versiunile viitoare.

În principiu, atunci când discutăm de schimbarea setului de caractere al bazei de date, avem la dispoziție două posibilități "mari și late":
  • creăm o nouă bază de date cu setul de caractere dorit, exportăm vechea bază de date și o importăm în baza de date nou creată.
  • folosim scriptul CSALTER furnizat de Oracle (pentru versiunile de la 10g încolo).

Ambele metode au avantaje și dezavantaje. Soluția export/import funcționează în majoritatea cazurilor, conversia între cele două seturi de caractere realizându-se automat (atenție la NLS_LANG în cazul utilitarelor exp/imp clasice). Dezavantajul major este legat de timpul și efortul necesare unui astfel de proces.

Varianta CSALTER pare a fi mult mai convenabilă, dat fiind că ea poate fi aplicată direct, fără a fi necesară crearea unei baze de date noi. Totuși, această metodă nu poate fi folosită decât dacă setul de caractere destinație este un super-set strict. Ce înseamnă acest lucru? Înseamnă că setul de caractere destinație trebuie să aibă definite toate caracterele din setul sursă (posibil și altele adiționale), iar acestea trebuie să aibă fix aceeași codificare. Spre exemplu, setul WE8MSWIN1252 este un super-set strict pentru WE8ISO8859P1, deoarce conține toate caracterele celui din urmă, cu păstrarea acelorași coduri. În schimb, WE8MSWIN1252 este un super-set pentru WE8DEC, dar nu este strict, deoarece, deși conține toate caracterele setului WE8DEC, unele dintre acestea au alte codificări. Faptul că WE8MSWIN1252 nu este un super-set strict pentru WE8DEC, nu înseamnă neaparat că nu se poate utiliza soluția CSALTER într-o posibilă migrare, ci că, pentru a o putea folosi, baza de date nu trebuie să folosească nici unul din caracterele WE8DEC care se regăsește în WE8MSWIN1252 cu o altă codificare.

Evident, există și cazul în care migrarea la un nou set de caractere nu este posibilă fără pierdere de informații, datorită incompatibilității între seturile de caractere sursă și destinație.

După cum se poate observa, metoda de folosit, precum și posibilele implicații ale schimbării setului de caractere trebuie bine cântărite înaintea luării unei astfel de decizii. Prin urmare, băieții de la Oracle s-au gândit să dea o mână de ajutor bietului DBA, descumpănit în fața unei decizii atât de dificile, așa că furnizează un așa-numit "CS Scanner". Acesta este un utilitar care analizează informația textuală din baza de date și ne poate spune cât de fezabilă este soluția de migrare spre un nou set de caractere. Nu am să plictisesc cititorul cu tot felul de detalii legate de instalarea și utilizarea acestui utilitar, dat fiind că acestea sunt deja consemnate în documentație, însă câteva aspecte trebuie subliniate:
  • Analiza informației textuale se referă atât la dicționarul bazei de date, cât și la obiectele create de utilizatori. Acest lucru este necesar, deoarece Oracle permite crearea de obiecte cu denumiri ce folosesc și caractere diferite de cele ASCII-standard. Nu recomand acest lucru, dar astfel de cazuri pot fi întâlnite.
  • Textele "problematice" sunt împărțite în trei categori: convertibile, trunchiate și corupte (în engleză, "Convertible", "Truncation" și "Lossy"). Cele convertibile se referă la faptul că avem de-a face cu coduri diferite în contextul noului set de caractere. Prin urmare, ele sunt disponibile în noul set, dar sub o nouă codificare. Truncherile apar în special atunci când se trece de la un set de caractere "single-byte" la unul "multi-byte", iar în urma conversiei limita de lungime definită la nivelul coloanei ar fi depășită. În sfârșit, caracterele corupte sunt cele care nu au corespondent în setul de caractere destinație.
  • sunt oferite și informații legate de oportunitatea refacerii anumitor indecși;
Haideți să vedem un exemplu concret. Avem o bază de date WE8DEC și dorim să evaluăm impactul migrării la un set de caractere Unicode, cum ar fi AL32UTF8.
~$ csscan fromchar=WE8DEC tochar=AL32UTF8 table='(TALEK.MONEY,TALEK.UMLAUT,TALEK.TAB_WITH_CLOB)' array=1024000 process=1


Character Set Scanner v2.2 : Release 11.2.0.1.0 - Production on Thu Dec 15 13:53:16 2011

Copyright (c) 1982, 2009, Oracle and/or its affiliates.  All rights reserved.


Username: / as sysdba

Password: 

Connected to:
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Production
With the Partitioning, Automatic Storage Management, OLAP, Data Mining
and Real Application Testing options

Enumerating table to scan...

. process 1 scanning TALEK.UMLAUT[AAAdUOAAEAAAlMAAAA]
. process 1 scanning TALEK.TAB_WITH_CLOB[AAAdV+AAEAAAlKYAAA]
. process 1 scanning TALEK.MONEY[AAAdUJAAEAAAlL4AAA]

Creating Database Scan Summary Report...

Creating Individual Exception Report...

Scanner terminated successfully.  
Pentru a nu aștepta foarte mult, am limitat analiza la nivelul a trei tabele, specificate prin parametrul "table". Parametrii "fromchar" și "tochar" consider că nu au nevoie de nici o explicație, iar descrierile pentru "array" și "process" le las cititorului ca "temă pentru acasă".

Implicit, obținem următoarele rapoarte: "scan.txt", care ar fi raportul de sinteză, "scan.out", cu listingul de mai sus și "scan.err" cu o descriere detaliată a textelor cu probleme. Raportul de sinteză este cel care ar trebui întâi consultat. Haideți să vedem ce găsim interesant în el. În primul rând, ne oferă o estimare legată de necesitătile de spațiu, sub aspectul alocării:
[Database Size]

Tablespace                           Used            Free           Total       Expansion
------------------------- --------------- --------------- --------------- ---------------
SYSTEM                            888.44M           1.56M         890.00M            .00K
SYSAUX                            956.00M          74.00M       1,030.00M            .00K
UNDOTBS1                           14.25M         270.75M         285.00M            .00K
USERS                           1,199.19M          52.06M       1,251.25M           3.81M
EXAMPLE                            78.44M          22.81M         101.25M            .00K
OSTP_DAT                       13,527.44M         692.56M      14,220.00M            .00K
TMP                                  .00K            .00K            .00K            .00K
OSTP_TEST                           2.13M         197.88M         200.00M            .00K
DEV_ODI_TEMP                         .00K            .00K            .00K            .00K
DEV_ODI_USER                       14.19M         185.81M         200.00M            .00K
------------------------- --------------- --------------- --------------- ---------------
Total                          16,680.06M       1,497.44M      18,177.50M           3.81M 
Se poate observa că ar mai fi necesari aproximativ 3.81M în tablespace-ul USERS. Discutăm de o conversie la un set de caractere "multi-byte", prin urmare ne putem aștepta la astfel de situații. Confirmarea o găsim în secțiunea "Database Scan Parameters", care conține parametrii utilizați pentru efectuarea analizei:
[Database Scan Parameters]

Parameter                      Value                                           
------------------------------ ------------------------------------------------
CSSCAN Version                 v2.1                                            
Instance Name                  poc                                             
Database Version               11.2.0.1.0                                      
Scan type                      Selective tables                                
Scan CHAR data?                YES                                             
Database character set         WE8DEC                                          
FROMCHAR                       WE8DEC                                          
TOCHAR                         AL32UTF8                                        
Scan NCHAR data?               NO                                              
Array fetch buffer size        1024000                                         
Number of processes            1                                               
Capture convertible data?      NO                                              
------------------------------ ------------------------------------------------ 
Apoi, avem un rezumat, hai să-i spunem "cheia de boltă". Acesta ar trebui să ne dea o primă indicație dacă putem efectua conversia în condiții de siguranță și cum:
[Scan Summary]

Some character type application data are not convertible to the new character set
Mai sus avem de-a face cu scenariul cel mai pesimist, în care conversia la noul set de caractere nu este posibilă, decât cu pieredere de informație. Iată și distribuția textelor pe categorii:
[Application Data Conversion Summary]

Datatype                    Changeless      Convertible       Truncation            Lossy
--------------------- ---------------- ---------------- ---------------- ----------------
VARCHAR2                             1                0                1                1
CHAR                                 0                0                0                0
LONG                                 0                0                0                0
CLOB                                 0            1,001                0                0
VARRAY                               0                0                0                0
--------------------- ---------------- ---------------- ---------------- ----------------
Total                                1            1,001                1                1
Total in percentage              0.100%          99.701%           0.100%           0.100% 
Listingul de mai sus poate fi interpretat în felul următor: un text de tip VARCHAR2 dintr-un record nu necesită nici o conversie, fapt indicat de coloana "Changeless". Acest lucru este ideal, dat fiind că soluția CSALTER nu funcționează decât cu astfel de texte. Mai departe, avem o valoare VARCHAR2, pe undeva printr-o tabelă, care, odată cu trecerea la noul set de caractere, va depăși limita maximă de lungime definită pentru respectiva coloană. Indicația în acest sens este dată de coloana "Truncation". Nu se poate să nu observăm și rubrica "Lossy". Aceasta ne spune că avem o valoare, pe undeva, care conține caractere "ciudate", ce nu pot fi convertite la noul set de caractere. În sfârșit, mai putem observa că avem 1001 texte (sună poetic, nu?) de tip CLOB care necesită conversia la noul set de caractere. Detalii cu privire la textele "problematice" pot fi regăsite în raportul "scan.err".
[Application data individual exceptions]

User  : TALEK
Table : MONEY
Column: SYMBOL
Type  : VARCHAR2(10)
Number of Exceptions         : 1         
Max Post Conversion Data Size: 3         

ROWID              Exception Type      Size Cell Data(first 30 bytes)     
------------------ ------------------ ----- ------------------------------
AAAdUJAAEAAAlL/AAA lossy conversion         <80>                             
------------------ ------------------ ----- ------------------------------

User  : TALEK
Table : UMLAUT
Column: COL
Type  : VARCHAR2(1)
Number of Exceptions         : 1         
Max Post Conversion Data Size: 2         

ROWID              Exception Type      Size Cell Data(first 30 bytes)     
------------------ ------------------ ----- ------------------------------
AAAdUOAAEAAAlMHAAA exceed column size     2 <fc>
------------------ ------------------ ----- ------------------------------
Deja parcă văd o sprânceană ridicată a nedumerire. Încercăm să convertim dintr-un amărât set de caractere pe 8 biți, WE8DEC, în AL32UTF8, care, vorba ceea, este Unicode și, în principiu, ar trebui să conțină toate caracterele. Cazul acesta este o indicație clară că există caractere invalide chiar și în setul de caractere sursă, cel existent și pe care dorim să-l schimbăm. Putem testa acest lucru cu utilitarul "csscan", furnizând parametrului "tochar" aceeași valoare cu cea a setului de caractere sursă:
~$ csscan fromchar=WE8DEC tochar=WE8DEC table='(TALEK.MONEY,TALEK.UMLAUT,TALEK.TAB_WITH_CLOB)' array=1024000 process=1
Iată ce obținem:
[Application Data Conversion Summary]

Datatype                    Changeless      Convertible       Truncation            Lossy
--------------------- ---------------- ---------------- ---------------- ----------------
VARCHAR2                             2                0                0                1
CHAR                                 0                0                0                0
LONG                                 0                0                0                0
CLOB                             1,001                0                0                0
VARRAY                               0                0                0                0
--------------------- ---------------- ---------------- ---------------- ----------------
Total                            1,003                0                0                1
Total in percentage             99.900%           0.000%           0.000%           0.100% 
Cum se poate ajunge la astfel de situații? Ei bine, cel mai adesea, cauza principală o reprezintă o configurație greșită a variabilei NLS_LANG. Spre exemplu, să presupunem că de pe un calculator cu Windows, ce folosește codul de pagină ANSI 1252, ne conectăm la o bază de date WE8DEC. Mai departe, să presupunem că domnu' utilizator a auzit el ceva despre NLS_LANG, dar pentru că nu știe exact cu ce se mănâncă, l-a configurat cu valoarea "AMERICAN_AMERICA.WE8DEC", astfel încât să se potrivească cu setul de caractere al bazei de date. În acest caz, între client și server nu intervine nici o conversie. La un moment dat, să presupunem că utilizatorul nostru simpatic vrea să insereze simbolul euro în baza de date. Poate să facă acest lucru, deoarece codul de pagina ANSI 1252 are definit acest simbol. Din păcate, însă, setul de caractere WE8DEC nu știe ce-i aia "Euro", iar dat fiind că între client și server nu intervine nici o conversie, codul asociat simbolului Euro va fi stocat așa cum este el în baza de date, chiar dacă respectivul cod este invalid. La momentul interogării de pe același calculator client, problema nu va fi depistată deoarce, din nou, nici o conversie nu are loc, codul caracterului este returnat așa cum este el, iar în contextul ANSI 1252 este valid, prin urmare, afișat. Evident, problemele apar atunci când alte calculatoare client, cu alte seturi de caractere configurate, vor încerca să acceseze aceste date.

Atenție, chiar dacă se încearcă o conversie direct la WE8MSWIN1252 unde simbolul Euro ar fi recunoscut, tot am avea probleme deoarece Oracle nu ar ști cum să convertească acest cod din WE8DEC în WE8MSWIN1252.
~$ csscan fromchar=WE8DEC tochar=WE8MSWIN1252 table='(TALEK.MONEY,TALEK.UMLAUT,TALEK.TAB_WITH_CLOB)' array=1024000 process=1

*** listing trunchiat ***

[Application Data Conversion Summary]

Datatype                    Changeless      Convertible       Truncation            Lossy
--------------------- ---------------- ---------------- ---------------- ----------------
VARCHAR2                             2                0                0                1
CHAR                                 0                0                0                0
LONG                                 0                0                0                0
CLOB                             1,001                0                0                0
VARRAY                               0                0                0                0
--------------------- ---------------- ---------------- ---------------- ----------------
Total                            1,003                0                0                1
Total in percentage             99.900%           0.000%           0.000%           0.100% 
Din nou, rubrica "Lossy" este dovada concretă. Dar, dacă se elimină textul invalid, obținem un rezultat mulțumitor, în sensul că, toate textele își păstrează aceeași reprezentare, prin urmare putem utiliza soluția CSALTER.

Mai observați ceva interesant? Deodată, toate textele stocate în coloane de tip CLOB au devenit "Changeless", adică fără modificări de reprezentare. Pai, înainte, când încercam să convertim la AL32UTF8, ele erau afișate ca fiind "convertibile". Ia să vedem ce-i cu aceste CLOB-uri. La o primă "inspecție", s-ar părea că ele conțin doar caracterul "a", deci ASCII standard.
SQL> select count(1) from tab_with_clob where regexp_like(col, 'a*');

  COUNT(1)
----------
      1001 
Teoretic, aceste texte ar trebui să fie reprezentate la fel, atât în WE8MSWIN1252, cât și în AL32UTF8. O fi ceva domle' cu CLOB-urile astea. Exact! Am precizat deja că în Unicode CLOB-urile sunt mai grase. Atunci când facem conversia dintr-un set de caractere "single-byte" în unul tot "single-byte", atunci valorile de tip CLOB sunt stocate în setul de caractere al bazei de date. Este cazul conversiei de la WE8DEC la WE8MSWIN1252. În cazul conversiei la AL32UTF8 discutăm de un set de caractere "multi-byte", iar în acest caz, CLOB-urile sunt stocate în AL32UTF8 sau UCS-2. De aceea, toate aceste CLOB-uri vor necesita conversii.

În momentul în care "csscan"-ul ne dă undă verde, putem trece la schimbarea setului de caractere, folosind scriptul CSALTER. Nu uitați să faceți un backup al bazei de date înainte, eventual să-l și testați, dat fiind că rularea scriptului de schimbare a bazei de date nu este reversibilă. De asemenea, țineți cont de faptul că, pentru rularea cu succes a acestui script, utilitarul "csscan" trebuie să fi fost rulat în modul "full", cu cel puțin șapte zile înainte.

Concluzii

"Teoria" seturilor de caractere nu este una foarte bine cunoscută. Există tot felul de mituri, bâjbâieli și greșeli de configurare/utilizare a sistemelor implicate. Într-un fel, acest lucru este explicabil și prin faptul că toate aceste concepte se află undeva la granița dintre sisteme. Nu putem spune că ele aparțin exclusiv bazelor de date sau sistemelor de operare sau, mai știu eu căror componente. Administratorul bazei de date trebuie să-și lărgească puțin orizontul de abordare și să se uite și în ograda sistemului de operare, a terminalului pe care îl folosește, a aplicațiilor ce se conectează la baza de date și așa mai departe. Evident, și elementele de configurare a seturilor de caractere specifice mediului Oracle trebuie stăpânite, astfel încât să evităm greșelile de configurare a NLS_LANG-ului sau alegerea nepotrivită a setului de caractere al bazei de date.

0 commentarii: