dimanche 29 août 2010

NLS_LANG et problèmes de conversions de données

Qui ne s'est jamais retrouvé avec des caractères invalides insérées dans sa base alors qu'il ne voulait qu'insérer du texte en français?

Ces problèmes d'affichage ou d'insertion sont dûs à une mauvaise définition de la variable d'environnement NLS_LANG côté client. Cette variable indique en fait à Oracle quel est le système d'encodage des caractères utilisé par le client.

Ce qu'il faut savoir tout d'abord c'est que lorsqu'une base et un client utilisent un jeu de caractères différent, la couche Oracle NET effectue une conversion implicite et transparente des données transmises entre les 2 systèmes d'encodage. Supposons par exemple que j'utilise un client Oracle sous windows en français avec un code page WE8MSWIN1252 et que ma base soit défini avec un characterset AL32UTF8, les données insérées seront donc converties en AL32UTF8 et les données de la base affichées côté client seront converties en WE8MSWIN1252.

Si la conversion se fait de manière automatique, comment se fait-il alors qu'on se retrouve parfois avec des caractères invalides?
Tout simplement parce que la variable NLS_LANG utilisé ne reflète pas le réel système d'encodage du client. Imaginons que dans mon exemple précédent le NLS_LANG ne soit pas défini avec le code page 1252 (WE8MSWIN1252 ) mais en AL32UTF8. Lorsque j'insère des données elles sont réellement encodées en code page 1252 mais Oracle considère que c'est du AL32UTF8 (c'est que dit la variable NLS_LANG) et donc Oarcle NET n'effectuera pas de conversion. Je me retrouve donc dans ma base avec des données encodées en WE8MSWIN1252 alors que le jeu de caractère de la base est AL32UTF8.

Ces problèmes de conversion se retrouvent souvent dans les environnements clients en Windows. En effet, Windows utilise 2 jeux de caractères différents: le code page 1252 pour la partie graphique et le code page 850 pour le mode texte (DOS).

Cela veut donc dire que selon qu'on utilise le mode texte ou le mode graphique la variable NLS_LANG doit être défini différemment. Par défaut cette variable est défini dans la base de registre (HKEY_LOCAL_MACHINE\SOFTWARE\ORACLE) et défini avec la valeur FRENCH_FRANCE.WE8MSWIN1252, ce qui signifie que si on se connecte à une base via SQLPLUS sous DOS la conversion se fera avec le code page 1252 alors que mes données sont encodées avec le code page 850. Des données invalides seront insérées dans ma base.

Voici un petit exemple pour bien comprendre.
soit:
- une base définie en AMERICAN_AMERICA.WE8ISO8859P1
- un client SQLPLUS graphique utilisant un code page 1252
- un client SQLPLUS DOS utilisant un code page 850

Sous le client SQLPLUS graphique:
SQL> create table nls_test(c1 varchar2(50));

Table créée.

SQL> insert into nls_test values ('une ligne insérée avec windows graphique');

1 ligne créée.

SQL> commit;

Validation effectuée.

Sous le client SQLPLUS texte:
SQL>  insert into nls_test values ('une ligne insérée avec windows texte');

1 ligne crÚÚe.

SQL> commit;

Validation effectuÚe.

Sous le client SQLPLUS graphique:
SQL> select * from nls_test;

C1
--------------------------------------------------
une ligne ins¿r¿e avec windows texte
une ligne insérée avec windows graphique


Sous le client SQLPLUS texte:
SQL> select * from nls_test;

C1
--------------------------------------------------
une ligne ins┐r┐e avec windows texte
une ligne insÚrÚe avec windows graphique

Comment expliquer ces erreurs d'affichage?
Sous SQLPLUS en mode graphique le système d'encodage utilisée est WE8MSWIN1252 et la couche Oracle NET le sait (car NLS_LANG=FRENCH_FRANCE.WE8MSWIN1252). Il sait aussi que la base est en WE8ISO8859P1 et effectue donc la bonne conversion lors de l'insertion. Le processus inverse est effectué lors du SELECT.

Sous SQLPLUS en mode texte le système d'encodage utilisée est WE8PC850 mais Oracle NET pense que le système utilisée est WE8MSWIN1252 (car NLS_LANG=FRENCH_FRANCE.WE8MSWIN1252) et utilise donc le mauvais code page pour effectuer la conversion. Les données insérées sont donc invalides.

Pour effectuer des insertions correctes il aurait falu que je définisse au niveau de ma session DOS le bon NLS_LANG comme dans l'exemple ci-dessous:

Sous le client SQLPLUS texte:
D:\>set NLS_LANG=FRENCH_FRANCE.WE8PC850

SQL> truncate table nls_test;

Table tronquée.

SQL> insert into nls_test values ('une ligne insérée avec windows texte');

1 ligne créée.

SQL> commit;

Validation effectuée.

SQL> select * from nls_test;

C1
--------------------------------------------------
une ligne insérée avec windows texte


CONCLUSION: La variable NLS_LANG doit toujours refléter le système d'encodage utilisé par le client.

6 commentaires:

  1. merci pour l'explication claire
    une question: quand la base comporte ces caractères mal convertis: comment les afficher correctement? Comment donc résoudre le souci que vous avez bien expliqué ci-dessus? merci Jean

    RépondreSupprimer
  2. Bonjour Jean,
    En fait je viens à peine de me rendre compte que je n'avais pas paramétré mon blog pour recevoir les alertes lorsqu'un commentaire a été posté.
    En fait, pour répondre à votre question, si le mauvais code page a été utilisé lors de l'insertion des données il n'est plus possible de les afficher correctement. Les données en base sont corrompus.

    cdmt,
    Ahmed

    RépondreSupprimer
  3. Bonjour Ahmed,
    et si on veut paramétere des caractères bizarres telque : Ö, Ü, ä.
    Quel est le caractère set oracle prédéfini qu'on doit utiliser?
    Cdmt,
    Walid

    RépondreSupprimer
  4. Les caractères spéciaux que t'as listé ci-dessus sont stockables dans un système d'encodage mono-octect 8bits. C'est le cas des characterset d'europe de l'ouest comme le WE8ISO8859P1.
    Par contre si tu veux stocker des pictogrammes ou des caractères définis dans les langues arabes ou d 'extrême orient (Chine, Japon etc.) alors il te faut un système d'encodage multi-octects comme le AL32UTF8.

    RépondreSupprimer
  5. Salut,

    J'ai modifié le NLS_LANG de mon client et j'ai aussi changé le type de la colonne ne NVARCHAR2 et malgré ça quand j'insère de mon application web les caractères tels que : ęłśćźżń la base les insère ou les affiche : elsczzn !!

    est-ce que la modification du NLS_LANG coté client ne suffit pas ?

    RépondreSupprimer
  6. Bjour Ahmed, Forms 6i ( client ) sur Oracle 11g ( sur serveur ) tu en as essayé ? Apparemment problème de jeu de Character set ( sur la base du serveur AL32UTF8.). Solution d'après les forums changer Character set par WE8MSWIN1252 qui n'est pas évident dans mon cas ( une grande base de données attaquée par plusieurs applications ). Ma question : Changer le NLS_LANG au niveau du client qui contient FORMS ( (HKEY_LOCAL_MACHINE\SOFTWARE\ORACLE et dev6i home ) en AL32UTF8 résoud t-il le problème ? et Merci.

    RépondreSupprimer