Re: Espaces à droite dans des colonnes de type CHAR

From: Jean-Paul Argudo <jean-paul(at)postgresqlfr(dot)org>
To: philippe(dot)beaudoin(at)bull(dot)net
Cc: pgsql-fr-generale(at)postgresql(dot)org
Subject: Re: Espaces à droite dans des colonnes de type CHAR
Date: 2009-01-13 13:26:12
Message-ID: 496C9674.1030304@postgresqlfr.org
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-fr-generale

Bonjour Philippe,

> Je rencontre un autre problème, cette fois avec l'utilisation de colonnes
> de type CHARACTER.
> La documentation (
> http://www.postgresql.org/docs/8.3/static/datatype-character.html) indique
> que les colonnes de type CHARACTER stockent des caractères (on s'en doute)
> sur une longueur fixe, avec éventuellement padding de blancs à droite.

Il me semble qu'en fait une série de blancs (espaces) à droite est tout
simplement remplacée par \0. Il suffit de chercher dans les sources de
PostgreSQL, on y trouve 'Remove trailing blanks'.

Pour preuve, j'ai inséré dans une table de test à une seule colonne
CHAR(5) les valeurs successives suivantes: "1", "12", "123", "1234",
"12345", "123 " (123 suivi de 2 blancs), "123 5" (le 4 est remplacé par
un blanc.

Voici ce que cela donne, avec une requête pour voir le contenu de la table:

jpargudo=# select a, length(a), '*'||substr(a,5,1)||'*' from montest;
a | length | ?column?
-------+--------+----------
1 | 1 | **
12 | 2 | **
123 | 3 | **
1234 | 4 | **
12345 | 5 | *5*
123 | 3 | **
123 5 | 5 | *5*
(7 lignes)

On voit bien que la donnée "123 " est remplacée par 123\0 dans la base...

> "If the string to be stored is shorter than the declared length, values of
> type character will be space-padded"
> Jusque là, tout va bien.

Sauf qu'à mon avis ce passage de la doc est pour le moins imprécis.

Si la chaîne à stocker a une taille inférieure à la valeur x dans
char(x), alors on colle le \0..

> Mais l'exemple en bas de page me semble contradictoire avec cette
> définition :
> CREATE TABLE test1 (a character(4));
> INSERT INTO test1 VALUES ('ok');
> SELECT a, char_length(a) FROM test1; -- (1)
> a | char_length
> ------+-------------
> ok | 2
> Puisque la colonne "a" fait 4 caractères, je m'attends à ce que la longueur
> de ce qui est stocké, et surtout restitué, soit égale à 4 et non 2.

C'est une question d'implémentation de la norme SQL, que Daniel a bien
couvert dans sa réponse (voir mail précédent).

> A ce moment, je me dis que la suppression des blancs à droite peut être
> très intéressant pour gagner de la place sur disque.

Pas la peine, PostgreSQL le fait pour nous déjà, en mettant le \0 s'il
n'y a que des blancs "à droite".

> Mais dans certains cas, des requêtes existantes (utilisées avec un autre
> SGBD) ne fonctionnent plus, [...]

Requêtes provenant d'un autre SGBD qui implémente (au moins)
différemment la norme SQL que PostgreSQL...

> Un exemple concret : prenons une colonne contenant une chaîne de caractères
> représentant des indicateurs sous forme de caractères pouvant prendre
> chacun la valeur espace ou autre chose. Pour obtenir le 50ème indicateur,
> je peux utiliser l'expression SUBSTR(colonne,50,1). Pour tester si ce 50ème
> indicateur est à espace ou non, je dois écrire la condition
> SUBSTR(colonne,50,1) = ' ' ... ou bien SUBSTR(colonne,50,1) = '' (mais pas
> les deux !).

Une solution que je vous propose c'est de contourner la chose grâce à
rpad().

Si on reprend ma requête plus haut et qu'au lieu d'utiliser a tout seul
on utilise rpad(a,5) alors on obtient une simulation de ce que les
autres SGBD font, tout en continuant à économiser de l'espace disque:

jpargudo=# select rpad(a,5), length(rpad(a,5)),
'*'||substr(rpad(a,5),5,1)||'*' from montest;
rpad | length | ?column?
-------+--------+----------
1 | 5 | * *
12 | 5 | * *
123 | 5 | * *
1234 | 5 | * *
12345 | 5 | *5*
123 | 5 | * *
123 5 | 5 | *5*
(7 lignes)

Et on remarque que le substr(colonne,...) comme vous vouliez fonctionne:
on affiche bien un blanc.

À noter qu'on peut jouer avec les vues si on veut s'affranchir des
rpad() en permanence:

create view v_montest as select rpad(a,5) as a from montest;

jpargudo=# select '*'||substr(a,5,1)||'*' from v_montest ;
?column?
----------
* *
* *
* *
* *
*5*
* *
*5*
(7 lignes)

Au passage, on aura aussi remarqué le dernier cas de mon usecase: j'ai
ajouté "123 5" afin que la dernière position du CHAR(5) ne soit pas un
blanc: du coup, PostgreSQL est *obligé* de tout stocker... Ça peut être
aussi une solution (pas belle, j'en conviens!!), si vous voulez
absolument tout migrer sans vous soucier de cet effet de la migration:
faire un +1 sur tous vos CHAR(x) -> CHAR(x+1) et ajouter un caractère
autre que "blanc" à la fin, genre "X" pour "terminer la chaîne"
autrement qu'avec le \0 que PostgreSQL vient lui coller d'office...

Puis qu'on est sur le sujet, sachez que l'insertion d'une valeur
dépassant la limite autorisée *est possible* si les caractères dépassant
cette dernière ne sont *que des blancs*:

jpargudo=# insert into montest (a) values ('123 5 ');
INSERT 0 1

!!!!

Comme indiqué dans les sources où ce cas est géré, c'est dû à la norme
SQL qui est comme ça:

* If the input string is too long, raise an error, unless the extra
* characters are spaces, in which case they're truncated. (per SQL)

En espérant avoir été clair,

--
Jean-Paul Argudo
www.PostgreSQLFr.org
www.Dalibo.com

In response to

Responses

Browse pgsql-fr-generale by date

  From Date Subject
Next Message philippe.beaudoin 2009-01-13 14:44:46 Réf. : Re: Espaces à droite dans des colonnes de type CHAR
Previous Message Daniel Verite 2009-01-13 11:36:19 Re: Espaces à droite dans des colonnes de type CHAR