mercredi 29 septembre 2010

PostgreSQL: passer du type money au type numeric

Le problème qui se pose.
Intéressons-nous à la table entrh déjà considérée précédemment.
Elle contient les colonnes suivantes:

 colonne     | type          |             
-------------+---------------+
 matricule   | integer       |
 nom         | character(50) |
 adresse     | character(50) |
 salaire     | money         |
 code_postal | character(5)  |
 cp_seq      | smallint      | 

Nous aimerions changer le type de la colonne salaire de money à numeric(11,2).
Pourquoi changer?
Parce que le type money est obsolète depuis longtemps.
Faux: le type money est dé-obsolétisé (undeprecated) depuis la version 8.3 de PostgreSQL.
Parce que après connexion d'openoffice à notre base de donnée en utilisant JDBC, une erreur se produit à la lecture de la table entrh:
Mais il nous est loisible d'utiliser un autre pilote que JDBC, le pilote SDBC intégré à openoffice (sous forme d'une extension), ce qui permet une connexion sans couche externe supplémentaire.
Parce que on VEUT utiliser JDBC (cette fois, c'est imparable).

Le problème qui se pose est qu'il n'est pas possible de convertir le type salaire en numéric:

toto=> select salaire::numeric
toto-> from entrh
toto-> limit 1;
ERREUR:  ne peut pas convertir le type money en numeric
LIGNE 1 : select salaire::numeric
                        ^

Premières investigations
Par contre  le convertir en type texte ne pose aucun problème:

toto=> select salaire::text
toto-> from entrh
toto-> limit 1;
  salaire  
-----------
 €2.458,53
(1 ligne)

En suite, on peut passer du type texte au numérique. Pas quel que soit le contenu bien sûr, et pas pour  '€2.458,53' :
toto=> select salaire::text::numeric(11,2)
toto-> from entrh
toto-> limit 1;
ERREUR:  syntaxe en entrée invalide pour le type numeric : « €2.458,53 »

Voici un exemple de contenu pour lequel ça fonctionne:

toto=> select '2458.53'::numeric(11,2);
 numeric
---------
 2458.53
(1 ligne)


La solution
Nous allons donc d'abord transformer la colonne "salaire" en colonne de type texte:

toto=> alter table entrh
toto-> alter salaire type text;
ALTER TABLE

Ensuite il s'agit de mettre la chaîne "salaire" au format qui convient pour que la conversion vers le type numeric(11,2) soit acceptée:

toto=> update entrh
toto-> set salaire = regexp_replace(salaire, '[€.]', '', 'g');

Nous rencontrons de nouveau une fonction qui utilise les expressions régulières, la précédente étant regexp_matches. Le premier argument de cette nouvelle fonction regexp_replace est la chaîne qui sera sera traitée. Le deuxième argument est l'expression régulière (en bleu) servant à sélectionner une ou plusieurs parties de la chaîne fournie en input. Ensuite vient la chaîne de remplacement qui se substituera aux différentes sélections et finalement les options. Ici, la chaîne de remplacement est vide: chacun des éléments de la classe [€.] s'il est rencontré, est remplacé par rien, donc supprimé. L'option 'g' demande que tous les remplacements possibles soient effectués. Sans cette option, seul le € initial disparaîtrait.
Vérifions le résultat:

toto=> select salaire
toto-> from entrh
toto-> limit 1;
 salaire
---------
 2458,53
(1 ligne)

Il reste maintenant à remplacer la virgule par un point. On peut procéder comme ceci (pour varier les plaisirs):

toto=> update entrh
toto-> set salaire = overlay(salaire placing '.' from char_length(salaire) - 2);

ou comme cela:

toto=> update entrh
toto-> set salaire = regexp_replace(salaire, ',', '.')
toto-> ;

Vérifions:

toto=> select salaire
toto-> from entrh
toto-> limit 1;
 salaire
---------
 2458.53
(1 ligne)

Et pour terminer: 
                                                                                                                                  
toto=> alter table entrh
toto-> alter salaire type numeric(11,2);
ERREUR:  la colonne « salaire » ne peut pas être convertie vers le type numeric
Grrrr!

En fait, il faut procéder comme ceci

toto=> alter table entrh
toto-> alter salaire type numeric(11,2)
toto-> using salaire::numeric(11,2)
toto-> ;
ALTER TABLE

Je veux une vue!
C'est merveilleux: nous pouvons maintenant afficher la table entrh dans notre outil graphique connecté à PostgeSQL via JDBC puisque qu'elle ne comprend plus de colonne de type money.

Mais maintenant dans un tableau tel que celui-ci (obtenu depuis un terminal psql):
ou dans un tableau openoffice, l'affichage du salaire n'est plus vraiment satisfaisant: on a le point comme séparateur décimal et pas de séparateur de millier. Pour retrouver l'ancien affichage, il nous est loisible de créer une vue  ne contenant pas la colonne salaire mais une colonne salaire_d définie comme dans l'instruction suivante:

toto=> select salaire,
toto-> regexp_replace(salaire::text, E'\\.', ',')::money as salaire_d
toto-> from entrh
toto-> limit 1;
 salaire | salaire_d
---------+-----------
 2458.53 | €2.458,53
(1 ligne)

La syntaxe qui apparaît dans la fonction regexp_replace mérite une explication:
Le premier argument doit être une chaîne de caractère, mais le salaire dans entrh est maintenant un nombre: c'est pourquoi il est converti en texte. Dans le deuxième argument, celui qui fournit l'expression régulière utilisée, le E indique que la chaîne qui suit contient des caractères d'échappement à traiter comme tel.
Mais pourquoi doubler le backslash (\)? 
Le test suivant donnera l'explication:

toto=> select E'\\.'as test;
 test
------
 \.
(1 ligne)

Le premier backslash (en rouge) est l'opérateur d'échappement qui agit sur le deuxième backslash et le transforme en caractère ordinaire. Le résultat est une suite de deux caractères ordinaires. En effet dans une chaîne SQL, le point est un caractère ordinaire. Cette suite constitue notre expression régulière, deuxième argument de la fonction regexp_replace. Dans cette expression régulière, le backslash survivant est de nouveau un opérateur d'échappement qui agit sur le méta-caractère point pour le transformer en caractère ordinaire. Celui-ci est ensuite remplacé par une virgule.

Donc nous créons la vue ventrh à partir du fichier Cventrh dont voici le contenu.

create view ventrh
as
select matricule, nom, adresse,
regexp_replace(salaire::text, E'\\.', ',')::money as salaire_d,
code_postal, cp_seq
from entrh;


C'est bon, ça fonctionne, mais PAS dans notre outil graphique utilisé avec JDBC: nous avons exactement la même erreur que précédemment.
La solution pour avoir un affichage convenable des montants monétaires, sans utilisation du CAST text::money, est d'utiliser la fonction to_char:

toto=> select salaire, to_char(salaire, '9G999G999D99') as salaire_t,
toto-> regexp_matches(to_char(salaire, '9G999G999D99'), E' \\d') as salaire_m,
toto-> regexp_replace(to_char(salaire, '9G999G999D99'), E' \\d', E'€\\&') as salaire_d
toto-> from entrhm
toto-> limit 2;
 salaire  |   salaire_t   | salaire_m |   salaire_d    
----------+---------------+-----------+----------------
  2458.53 |      2.458,53 | {" 2"}    |     € 2.458,53
 13587.00 |     13.587,00 | {" 1"}    |    € 13.587,00
(2 lignes)

La colonne salaire_t, t pour texte, nous montre le résultat de la fonction to_char.
La colonne salaire_m, m pour matché (!), nous donne la sélection du salaire-texte correspondant à l'expression régulière ' \d', un espace suivi d'un chiffre: \d est une notation abrégée pour la classe [0-9].
La colonne salaire_d, d pour displayable (!!), nous donne le résultat final. Dans la chaîne de remplacement '€\&', \& représente la totalité de la sélection, ce qui explique le blanc entre le symbole monétaire € et le montant proprement dit. Ce n'est pas plus mal. Pour éliminer ce blanc, il suffit d'utiliser les parenthèses mémorisantes et de mettre \1 au lieu de \& dans la chaîne de remplacement. En effet, \1 représente dans la chaine de remplacement, non pas toute la sélection, mais la partie de celle-ci qui est en rapport avec la partie de l'expression régulière encadrée par les parenthèses mémorisantes:

toto=> select salaire,
toto-> regexp_replace(to_char(salaire, '9G999G999D99'), E' (\\d)', E'€\\1') as salaire_d
toto-> from entrhm
toto-> limit 2;
 salaire  |   salaire_d   
----------+---------------
  2458.53 |     €2.458,53
 13587.00 |    €13.587,00 
(2 lignes)

Modifions notre fichier Cventrh pour qu'il contienne ceci:

drop view ventrh;
create view ventrh
as
select matricule, nom, adresse,
regexp_replace(to_char(salaire, '9G999G999D99'), E' (\\d)', E'€\\1') as salaire_d,
code_postal, cp_seq
from entrh;

Procédons et vérifions:


Voilà qui est parfait.

Et dans les formulaires? 
Dans les formulaires aussi, l'abandon du type money nous fait perdre le bel affichage des montants monétaires:

Et une vue ne pouvant pas être directement mise à jour, mieux vaut ne pas en utiliser.
Cependant le problème peut-être réglé relativement facilement:
Cliquer sur la zone de saisie du montant, la touche CTRL étant maintenue enfoncée.
Dans la barre d'outils de gauche, choisir 'Contrôle':
Puis, onglet 'Général', Formatage appuyer sur les '...' adjacents et dans la fenêtre qui s'ouvre, choisir le formatage qui convient:


Et voilà le résultat:




mardi 28 septembre 2010

Connecter openoffice à PostgreSQL

Connexion utilisant le pilote SDBC
Démarrant openoffice base, nous tombons sur cet écran:


Après avoir choisi 'Se connecter à une base de données existante', nous examinons les différentes possibilités:


postgresql est-il dans la liste?
Parfait ce sera notre premier choix, celui qui fait appel au pilote SDBC
Pas de postgresql?
Et bien , on ferme openoffice base!
Mais pas de soucis: il suffit d'ajouter une extension à openoffice.
Vérifions si un paquet fournissant l'extension existe pour la distribution utilisée.
Chez Ubuntu, c'est le paquet openoffice.org-sdbc-postgresql. 
A défaut, Il faut télécharger ici le pilote qui convient.
Attention, avec Ubuntu, ce pilote téléchargé ne fonctionnera pas
Il existe deux façons de l'installer:
Méthode 1:
Démarrer openoffice writer
Choisir Outils/Gestionnaire d'extension
Cliquer sur le bouton Ajouter et sélectionner le fichier téléchargé (qui ne doit pas avoir été décompressé!).
Fermer writer et relancer openoffice base: postgresql devrait maintenant figurer dans la liste.
Méthode 2:
Dans un terminal (openoffice doit être fermé)

[toto@rigel ~]$ cd $(dirname $(which soffice));cd $(ls -l soffice | sed -r 's/^.*>.(.*)soffice$/\1/')
[toto@rigel program]$ unopkg add /home/toto/downloads/postgresql-sdbc-0.7.6a.zip
(/home/toto/downloads/ est un exemple d'endroit où le fichier téléchargé pourrait se trouver)

La première des deux commandes semble assez effrayante, mais il s'agit simplement de trouver où est installé openoffice et de s'y rendre. Son emploi est facultatif.

Une installation pour tous les utilisateurs nécessite que l'on agisse en tant que root:
[root@rigel ~]# cd $(dirname $(which soffice));cd $(ls -l soffice | sed -r 's/^.*>.(.*)soffice$/\1/')
[root@rigel program]# unopkg add --shared /home/toto/downloads/postgresql-sdbc-0.7.6a.zip 

Bon, nous pouvons maintenant sélectionner postgresql dans la liste déroulante et puis cliquer sur 'Suivant'. Dans la fenêtre qui surgit, il reste à indiquer le nom de la base de données:


Et puis C'EST TOUT. On peut déjà cliquer sur Terminer sauf si on désire se connecter sous un autre nom que celui de l'utilisateur qui à lancé openoffice, ou tester la connexion, auquel cas on cliquera une nouvelle fois sur Suivant avant de cliquer sur Terminer:


Connexion avec JDBC
L'utilisation de ce type de connexion requiert l'installation du paquet postgresql-jdbc (libpg-java chez Ubuntu).
Openoffice doit tourner en utilisant un environnement d'exécution java (voir Outils/Options):

Cliquant sur 'Chemin de classe' puis 'Ajouter une archive', on a la possibilité d'ajouter un jar. Comment le choisir? Il semblerait que postgresql-jdbc (ou libpg-java) ait installé plusieurs jar.
Mais la commande suivante:
[toto@rigel ~]$ rpm -qlv postgresql-jdbc | sed -rn 's/^.* (\/.*jar)$/\1/p'
/usr/share/java/postgresql-jdbc-8.4.701.jar
/usr/share/java/postgresql-jdbc.jar -> postgresql-jdbc-8.4.701.jar
/usr/share/java/postgresql-jdbc2.jar -> postgresql-jdbc.jar
/usr/share/java/postgresql-jdbc2ee.jar -> postgresql-jdbc.jar
/usr/share/java/postgresql-jdbc3.jar -> postgresql-jdbc.jar
ou celle-ci (suivante qu'on utilise une distribution rpm ou deb):
toto@rigel:~$ dpkg -L libpg-java | xargs ls -ld | sed -rn 's/^.* (\/.*jar)$/\1/p'
/usr/share/java/postgresql.jar -> postgresql-jdbc3-8.4.jar
/usr/share/java/postgresql-jdbc3-8.4.jar
/usr/share/java/postgresql-jdbc3.jar -> postgresql-jdbc3-8.4.jar
montre qu'en fait il n'y a qu'un seul jar (les autres sont des pointeurs).
On va ajouter  /usr/share/java/postgresql-jdbc.jar:




Après avoir redémarré openoffice, nous sommes maintenant prêt à nous connecter via JDBC.
Paramétrer la connexion est quand même moins simple que dans le cas précédent. Après avoir saisi ce qui convient, nous testons tout de suite la classe: en cas d'échec inutile de poursuivre.


Poursuivons par le test de la connexion, comme précédemment:
Aie:
En fait, on passe maintenant par la boucle locale. Et dans ce cas, ce qui es prévu dans pg_hba.conf est une authentification par mot de passe. 
Mais attention: il s'agit d'un mot de passe PostgreSQL et non pas d'un mot de passe système d'exploitation. Donc en l'absence d'un tel mot de passe, il faut l'initialiser:

Ensuite, on coche 'Mot de passe requis' et on re-teste après avoir saisi un nom d'utilisateur:


Connexion depuis un client installé ailleurs que sur le serveur
Tout d'abord il faut configurer le serveur pour qu'il accepte des connexions qui viennent d'autres machines: le fichier pg_hba.conf par défaut ne permet pas de telles connexions. Voici le fichier tel que modifié en conséquence (en rouge):
# TYPE  DATABASE    USER        CIDR-ADDRESS          METHOD

# "local" is for Unix domain socket connections only
local   all         @group01                          ident map=map01
#
local   all         all                               ident
# IPv4 local connections:
host    all         all         127.0.0.1/32          md5
# IPv6 local connections:
host    all         all         ::1/128               md5
# IPv4
host    all         all         192.168.1.0/24        md5
( L'adresse indiquée dépend évidemment du réseau).


De plus, il faut agir au niveau du fichier postgresql.conf:

# - Connection Settings -
listen_addresses = '*'                  # what IP address(es) to listen on;
                                        # comma-separated list of addresses;
                                        # defaults to 'localhost', '*' = all
                                        # (change requires restart)
port = 5432                             # (change requires restart)


Reste quelques petits aménagements  au niveau du pare-feu.
Sur la machine client, il suffit d'installer le paquet client postgresql.
Pour ce connecter depuis ce client, il faut indiquer le nom de la machine serveur:
Pilote SDBC:

Pilote JDBC


Et pour se connecter dans un simple terminal (c'est hors-sujet, mais bon...):


dimanche 19 septembre 2010

Le pager most

Je voudrais présenter les avantages liés à l'utilisation du pager most.

Des "man" en couleur


Bon, c'est plutôt cosmétique, mais ce n'est pas tout.
Des possibilités de recherche:

 


Et last but not least:

Gestion de la largeur

Utile quand la largeur de l'output dépasse la largeur de l'écran.
Voici le résultat dans un terminal psql de la requête
select * from pg_settings;
Avec le pager par défaut:


Totalement inexploitable.
Par contre avec most:

Après déplacement vers la droite:

most s'active même s'il y a seulement dépassement de largeur (sans dépassement de hauteur):
Sans most:
Le pager ne s'active pas.
Avec most:
Après déplacement vers la droite:


Installation

Le paquet à installer s'appelle most dans la plupart des distributions.
En fait je n'ai jamais rencontré une distribution où il s'appelle autrement, mais peut-être que ça existe.

Ensuite, il reste à remplacer le pager utilisé par most, comme ceci:

# echo 'export PAGER=most' >> /etc/bashrc

Le nom du fichier dans lequel on doit écrire peut varier légèrement suivant la distribution. Par exemple chez Ubuntu, la commande serait:

# echo 'export PAGER=most' >> /etc/bash.bashrc

On peut aussi procéder en tant qu'utilisateur lambda dans son propre bashrc:

$ echo 'export PAGER=most' >> ~/.bashrc

vendredi 17 septembre 2010

PostgreSQL (suite)

Dans le billet précédent nous avons créé une base de données PostgreSQL. Nous y avons ajouté et chargé les tables
frregions: table des régions
frdepartem: table des départements
frcp: table des code postaux et des localités correspondantes
Ces trois tables sont liées par des relations d'intégrités différentielles qui garantissent que chaque localité est dans un département et chaque département dans une région.
Après connexion à openoffice, on peut facilement sur base des tables frregions et frdepartem établir un rapport tel que celui-ci:
Openoffice permet notamment aussi la création de formulaires pour la visualisation où la saisie de données. Nous montrerons bientôt comment connecter openoffice à PostgreSQL. Mais auparavant, nous allons encore travailler dans un terminal interactif psql, afin notamment de créer une nouvelle table pour tester la saisie des données. En effet les 3 tables existantes sont destinées à ne pas être modifiées. Aussi voudrions-nous les rendre read only, même pour toto.
Le problème est que à l'origine (voir billet précédent), nous avons créé un toto tout puissant, quasiment l'égal de postgres, l'administrateur désigné de PostgeSQL. Nous devons d'abord lui enlever la qualité de superuser. Mais en outre, étant propriétaire-créateur, toto a tous les droits sur ces tables. On va donc procéder aux opérations suivantes:


Les commandes sont suffisamment explicites pour qu'on n'ait pas besoin d'en dire davantage.
Notons simplement toto a pu se retirer lui-même la qualité de superuser, mais il ne pourra pas faire machine arrière!

La table des ressources humaines.
Comme indiqué un peu plus haut, nous avons encore besoin d'une table supplémentaire.
Nous allons par exemple créer la table entrh (ressources humaines de l'entreprise ent) à partir du fichier Centrh:

CREATE TABLE entrh
(matricule SERIAL PRIMARY KEY,
 nom CHAR(50),
 adresse char(50),
 salaire money,
 code_postal char(5),
 cp_seq smallint,
 FOREIGN KEY (code_postal,cp_seq) REFERENCES frcp)
;

Un matricule sera créé automatiquement pour chaque personne figurant dans la table de manière à former une clef primaire et l'ensemble code_postal, cp_seq devra pointer obligatoirement vers une rangée de la table frcp (à moins d'avoir la valeur NULL).
Bien sûr, il s'agit d'une table toute simple qui bafoue d'ailleurs allègrement le principe consistant à ne pas mélanger les données signalétiques aux autres. Elle est uniquement  destinée à illustrer notre propos (au départ ce devait être seulement une table des adresses). Nous ne développons pas ici un véritable projet de gestion des ressources humaines (ce qui nécessiterait plusieurs dizaines de tables).
C'est parti:

Nous voudrions aussi que la série de matricules ne commence pas par 1:


Le salaire est une donnée confidentielle: attribuons le privilège select de manière telle que cette donnée ne soit pas accessible::

Le groupe des gestionnaires du personnel.
Il reste à créer le groupe des personnes qui va s'occuper de la gestion du personnel et à lui attribuer les droits qui conviennent sur la table entrh en tenant compte de ce que le matricule ne peut pas être mis à jour. Par contre, il faut un update sur la séquence entrh_matricule_seq afin de permettre la création des matricules:

Pour l'instant le groupe de gestion (gestionrh) ne comprend qu'une personne, titine qui a juste été créée pour l'occasion. Par la suite, on pourra facilement en ajouter d'autres (ou en retirer) avec la commande alter group:
ALTER GROUP groupname ADD USER username [, ... ]
ALTER GROUP groupname DROP USER username [, ... ]
Notons qu'il ne faut pas confondre l'instruction CREATE USER qui a créé titine, avec la commande createuser utilisée précédemment pour créer toto (voir billet précédent).

Tentative de connexion en tant que gestionnaire.
Afin de vérifier si les personnes du groupe gestionrh ont les droits suffisants, tentons de  connecter titine à la base de données toto avec la commande psql. La syntaxe de cette commande est:
psql [option...] [dbname [username]]
dbname et username sont par défaut égal au nom de l'utilisateur qui exécute la commande.
Donc si je ne suis pas titine, je dois préciser les deux paramètres:


Ça ne fonctionne pas. Essayons en activant le terminal interactif psql en tant que toto: on peut y modifier sa connexion avec la méta-commande \connect:


Nouvel échec. Comment procéder pour que toto puisse se connecter facilement en tant que titine? La solution se trouve dans le fichier de configuration pg_hba.conf.

Les fichiers pg_hba.conf et pg_ident.conf 
Le chemin vers les fichiers de configuration  de PostgreSQL, et notamment pg_hba.conf, dépend de la distribution.
Exemples de tels chemins:
/var/lib/pgsql/data/
/etc/postgresql/8.4/main/
Sur notre machine le fichier de configuration pg_hba.conf par défaut est le suivant:

# TYPE  DATABASE  USER        CIDR-ADDRESS          METHOD

# "local" is for Unix domain socket connections only
local   all       all                               ident
# IPv4 local connections:
host    all       all         127.0.0.1/32          md5
# IPv6 local connections:
host    all       all         ::1/128               md5

Nous y constatons la présence de deux méthodes d'authentification md5 (par mot de passe crypté) et ident. D'autres méthodes sont possibles.
La méthode d'authentification Ident fonctionne en prenant le nom de l'utilisateur du système d'exploitation et en s'en servant comme nom d'utilisateur PostgreSQL. L'authentification échouera si l'utilisateur PostgreSQL obtenu par Ident n'existe pas, ou encore s'il est différent du nom que l'on transmet en paramètre dans la commande de connexion. C'est le cas ici. Alors comment procéder pour résoudre notre problème? Nous pourrions modifier le fichier pg_hba.conf comme ceci:

# TYPE  DATABASE  USER        CIDR-ADDRESS          METHOD

# "local" is for Unix domain socket connections only
local   all       titine                            md5
local   all       all                               ident
# IPv4 local connections:
host    all       all         127.0.0.1/32          md5
# IPv6 local connections:
host    all       all         ::1/128               md5

Pour se connecter en tant que titine, il suffit de fournir son mot de passe. Il n'y a aucun lien établi avec l'utilisateur du système d'exploitation qui demande la connexion. titine peut même ne pas exister au niveau du système d'exploitation. Et le mot de passe alors? Attention, il s'agit d'un mot de passe défini dans PostgreSQL. Mais le but de la manoeuvre étant que toto puisse se connecter facilement en tant que titine, ce n'est pas vraiment la bonne solution.
Non, la solution est de passer par l'intermédiaire du fichier pg_ident.conf afin de définir un mappage entre les utilisateurs PostgreSQL et les utilisateurs du système d'exploitation. Modifions ce fichier pour qu'il contienne ceci:

# MAPNAME     SYSTEM-USERNAME    PG-USERNAME
map01         toto               titine
map01         titine             titine

La première ligne permet à toto de se connecter en tant que titine. Il faut aussi que titine puisse se connecter, d'où l'existence de la deuxième ligne.
Il s'agit maintenant d'établir un lien entre pg_hba.conf et ce fichier pg-ident.conf, comme ceci:
# TYPE  DATABASE  USER        CIDR-ADDRESS          METHOD

# "local" is for Unix domain socket connections only
local   all       titine                            ident map=map01
local   all       all                               ident
# IPv4 local connections:
host    all       all         127.0.0.1/32          md5
# IPv6 local connections:
host    all       all         ::1/128               md5


On pourrait dans la ligne ajoutée remplacer titine par @group01, où group01 est un fichier qui se trouve dans le même dossier que pg_hba.conf et contenant le mot titine. Attention si on ajoute à group01 d'autres utilisateurs sans modifier pg_ident.conf en conséquence, ils ne pourront plus se connecter.
D'autres méthodes d'authentification sont possibles:
trust: n'importe qui peut se connecter en utilisant n'importe quel nom d'utilisateur PostgreSQL.
reject: éventuellement utile pour rejeter certains postes clients.
et beaucoup d'autres encore....

Pour que les changements introduits soient pris en compte, il faut recharger le serveur:

# service postgresql reload

Insertion dans la table des ressources humaines
Nous avons préparé une instruction SQL d'insertion d'une rangée dans la table entrh. Celle-ci se trouve dans le fichier insert01:

insert into entrh
values (default, 'Gédéon Théodore','Avenue des oiseaux rares, 28','2456,3'::money,'02200',3)
;

Cette instruction doit être exécutée par titine puisque nous voulons vérifier que les membres du groupe gestionrh ont les droits qui conviennent pour pouvoir travailler. Le problème de connexion toto -> titine étant résolu, tout devrait bien se passer:

?? Pour la connexion c'est OK, mais pour le reste...En fait, toto, propriétaire de la table frcp droit récupérer son droit d'update sur cette table. Cela permet au système de tenir un lock sur la table frcp pendant la durée de la transaction d'insertion afin de garantir l'intégrité référentielle des données. Il ne faudrait pas que quelqu'un supprime (ou modifie) la rangée de frcp vers laquelle va pointer la nouvelle rangée insérée dans entrh. Il est donc illusoire de penser que l'on peut rendre la table frcp read only pour toto. Par contre pour titine, elle le reste bel et bien (heureusement). 
Procédons:
Vérifions le résultat:
Tiens, on n'avait pas dit que les matricules commençaient à 1005?
Oui, mais lors du premier essai, au moment de l'erreur sur frcp, la séquence qui sert pour les matricules avait déjà été mise à jour.
Or il n'y a jamais de retour en arrière (rollback) pour les séquences.

Bon, le contenu de ce billet est déjà bien assez consistant.
Nous avions annoncé que nous allions montrer comment se connecter avec openoffice.
Et si on en reparlait dans un prochain billet?

mercredi 1 septembre 2010

PostgreSQL (initiation)

PostgreSQL est une base de données moderne et puissante. Nous allons dans ce billet expliquer comment démarrer avec PostgreSQL et comment l'utiliser et ce de la manière la plus simple possible.

Installation et démarrage
En principe,  il suffit d'installer le paquet serveur (postgresql-server), le paquet client (postgresql) et les autres paquets indispensables  s'installeront alors automatiquement par le jeu des dépendances. Le nom de ces paquets peut évidemment varier suivant la distribution. Par exemple chez Ubuntu, le paquet serveur est postgresql et le paquet client postgresql-client. Une fois l'installation terminée, la première opération consiste à initialiser la base de donnée. Ensuite seulement le service peut être démarré.


Il se peut que le nom du service ne soit pas exactement postgresql. Dans ce cas, taper service post + tabulation afin de trouver le bon nom.
Chez Ubuntu, l'option initdb n'est pas reconnue. On passe alors directement à la deuxième commande.

Création du premier utilisateur PostgreSQL (autre que l'administrateur)
L'installation du paquet postgresql-server a ajouté au système d'exploitation un nouvel utilisateur: postgres, administrateur PostgreSQL. C'est le seul utilisateur connu de notre système de bases de données. Il faut donc lui ajouter d'autres utilisateurs. On est obligé de devenir postgres avant de procéder. Pour ce faire, il faut être dans un terminal avec les droits du root (obtenu avec sudo -s chez Ubuntu), ou sinon le sytème demande le mot de passe de postgres.


On a ajouté à PostgreSQL un nouvel utilisateur: toto. celui-ci pourra créer des bases de données (option -d) et ajouter des utilisateurs tout en étant super-utilisateur (option -a). toto doit être un utilisateur du système d'exploitation (ce n'est pas absolument obligatoire, mais c'est une question de facilité). Le mieux est même que toto soit notre nom d'utilisateur.
Il suffit alors d'entrer deux fois la commande exit, et voilà, on est redevenu toto.

Création de la première base de données
Reste à créer la base de données et à s'y connecter:


A des fins d'organisation, un dosssier SQL a d'abord été créé puis on s'y est rendu. La commande createdb exécutée sans arguments a créé la base de données toto. On s'y connecte avec la commande psql lancée aussi sans arguments. Quoi de plus simple. Bien sûr, il est possible faire plus compliqué.

Une première table: les régions de France
Imaginons que nous disposions par exemple d'un fichier FRregions (placé dans le dossier SQL) des différentes régions de  France:
01    Alsace
02    Aquitaine
03    Auvergne
04    Basse-Normandie
05    Bourgogne
06    Bretagne
07    Centre
08    Champagne-Ardenne
09    Corse
10    Franche-Comté
 ..................

que nous voudrions  importer dans la base de données toto.
Il nous faut en premier  créer une table où placer les données que l'on va importer. Inscrivons les instructions sql de création de la table:
CREATE TABLE frregions
(region_id CHAR(2) PRIMARY KEY,
 region_nom CHAR(30));

dans petit fichier texte appelé Cfrregions que nous mettons dans le dossier SQL (comme tous les autres fichiers dont il sera question dans la suite).
Bien sûr on pourrait taper ces commandes directement dans  le terminal psql, mais on les a mis dans un fichier. Alors, comment procéder? Il faut savoir que dans un terminal psql, on peut aussi taper des méta-commandes comme par exemple:
toto=# \i FICHIER          
qui exécute les commande du fichier.
Et bien, procédons:
Nous sommes avertis qu'un index a été automatiquement créé. Dans la foulée, nous avons importé le fichier FRregions avec la méta-commande \copy. Pour que cette commande fonctionne sans aucun autre argument, les champs du fichier ne doivent pas être séparés par des espaces mais par un caractère de tabulation qui est le séparateur par défaut.
Vérifions que tout c'est bien passé:
Et voilà le résultat:
(On s'est limité aux régions métropolitaines.)

Pour tester l'intégrité référentielle: la table des départements
Maintenant nous allons créer de la même façon une table des départements.
Le fichier des données:
01    Ain    22
02    Aisne    19
03    Allier    03
04    Alpes-de-Haute-Provence    21
05    Hautes-Alpes    21
06    Alpes-Maritimes    21
07    Ardèche    22
08    Ardennes    08
........
De nouveau, il n'y a pas de blancs mais des caractères de tabulation. Les numéros en fin de lignes se rapportent à la région.
Les instructions SQL de création de la table:
CREATE TABLE frdepartem
(departem_id CHAR(2) PRIMARY KEY,
 departem_nom CHAR(30),
 code_region CHAR(2) REFERENCES frregions);

La dernière ligne de ces instructions implique que les données du champ code_region devront absolument faire référence à une région existante. La contrainte imposée dans cette dernière ligne est appelée une contrainte d'intégrité référentielle. En clair, un département doit se trouver dans une région.
Après en avoir terminé avec la création de cette nouvelle table, essayons de muter le département des Ardennes vers la région 23:
Et si maintenant, j'essaye de détruire une rangée de la table frregions:
PostgreSQL garantit l'intégité référentielle de nos données.

Affichage html des données
Considérons la requête suivante:
select departem_id as "Numéro", departem_nom as "Département" , region_nom as "Région"
from frdepartem, frregions
where code_region = region_id
;
contenue dans le fichier sdepreg. Le terminal psql permet l'envoi du résultat de cette requête dans un fichier html:
\H bascule le format de sortie vers l'html.
Comme cela n'a pas tellement se sens d'afficher de l'html dans le terminal psql, on redirige la sortie vers le fichier depreg.html avec la commande
\ o depreg.html
Et voici ce que cela donne:

Ensuite:
\o supprime la redirection
\H revient au format de sortie précédent

Et pourquoi pas une table des codes postaux?
Notre base de données ne serait pas complète sans une table avec les codes postaux. Pour charger cette future table, nous disposons d'un fichier FRcp0 dont voici un extrait:


.......
25720   Aveney  25
25720   Beure   25
25720   Arguel  25
25720   Larnod  25
25720   Avanne-Aveney   25
25720   Pugey   25
25750   Aibre   25
25750   Le Vernoy       25
25750   Semondans       25
25750   Désandans       25
25750   Arcey   25
.......

On aimerait associer au code postal un numéro séquentiel de manière à pouvoir différencier les différentes entrées relatives à un même code postal.
Nous allons utiliser awk après avoir trié le fichier:

[toto@rigel SQL]$ sort FRcp0 | awk 'BEGIN {FS="\t"}; x==$1 {i++}; !(x==$1) {i=1}; {x=$1};{print $1"\t"i"\t"$2"\t"$3}' > FRcp1
[toto@rigel SQL]$

Dans la clause BEGIN, nous demandons que le séparateur de champ soit un caractère de tabulation. Ensuite si x est égal au champ 1 (le code postal), nous incrémentons i de 1 (évidemment pour la première ligne ce n'est pas le cas). Si x n'est pas égal au champ 1, nous donnons à i la valeur 1, puis seulement nous mettons le champ 1 dans x.
On obtient ceci:

.....
25720   1       Arguel  25
25720   2       Avanne-Aveney   25
25720   3       Aveney  25
25720   4       Beure   25
25720   5       Larnod  25
25720   6       Pugey   25
25750   1       Aibre   25
25750   2       Arcey   25
25750   3       Désandans       25
25750   4       Le Vernoy       25
25750   5       Semondans       25
.....

Et voici le fichier Cfrcp avec les instructions SQL de création de la table:

CREATE TABLE frcp
(code_postal CHAR(5),
 cp_seq smallint,
 localite_nom CHAR(50),
 code_departem CHAR(2) REFERENCES frdepartem,
 PRIMARY KEY (code_postal,cp_seq));

L'instruction PRIMARY KEY permettra la création d'une relation d'intégrité référentielle entre une future table d'adresses et la table des codes postaux. Cette table des adresses ne contiendra pas le nom de la localité, mais contiendra pour chaque rangée un code postal associé à un numéro de séquence qui devra désigner impérativement une rangée de la table des codes postaux.
Allons-y pour la création de cette table des codes postaux:


Et oui! Tout ne se passe pas toujours comme prévu. De nouveau une contrainte d'intégrité référentielle qui est violée.
L'erreur est clairement expliquée: le fichier FRcp1 comprend des lignes avec un code département qui est un blanc. Or il n'existe pas dans la table frdepartem de département sans code.
Le résultat de l'instruction select * from frcp montre qu'aucune rangée n'a été importée (en tout cas elles ne sont pas accessibles). Un comportement envisageable aurait été que seules les rangées en erreur n'aient pas été importées. Il reste à identifier les lignes fautives:

[toto@rigel SQL]$ awk '/[^0-9AB]$/{print NR}' Frcp1
5491
5493
5494
5495
9452
13868

à corriger le fichier et à recommencer l'import.
L'identification se base sur le fait que les lignes fautives ne se terminent pas par un chiffre, ni par un A, ni par un B (pour les deux départements corses).

PostgreSQL et les expressions régulières
PostgreSQL supporte l'emploi d'expressions régulières dans les instructions sql. Pourquoi ne pas essayer avec cette nouvelle table des codes postaux:

Ici nous avons recherché les noms de localité avec "bar" en sixième position. La première colonne reprend la sélection correspondant à l'expression régulière. Ensuite nous avons demandé l'affichage du nom complet de la localité ainsi que de son code postal.
Ce query est tellement extraordinaire que nous avons donné instruction de l'écrire dans le fichier sregexp. On pourra donc le ré-exécuter quand on veut avec la méta-commande: 
toto=# \i sregexp.

Pour conclure
Il existe des solutions graphiques pour accéder à une base de données PostgreSQL.
Dans un prochain billet nous verrons notamment comment procéder avec openoffice.