IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Perl et les bases de données (DBI)

MySQL, Oracle…

Ce tutoriel vous permettra d'apprendre les bases d'utilisation du module DBI afin de se connecter à une base de données et de lire ou insérer des données.
La notion des placeholders pour protéger et optimiser les requêtes sera abordée. 11 commentaires Donner une note à l´article (5)

Article lu   fois.

L'auteur

Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

Le but de ce tutoriel est d'apprendre à interagir avec les bases de données en Perl.
Nous commencerons par définir la notion de base de données. Il vous sera ensuite expliqué et listé ce dont vous avez besoin pour travailler.

I-A. Les bases de données

Une base de données (communément appelée « BD »" ou « database » en anglais) est une entité dans laquelle on stocke des informations de façon structurée. Il est possible de localiser facilement ces données et de les mettre rapidement à jour.
Un autre avantage de la BD est qu'elle peut être interrogée par plusieurs personnes, dans un réseau sécurisé, par l'intermédiaire d'un système de gestion de base de données (communément abrégé « SGBD »).

Le terme entité peut vous paraître abstrait. Pour mieux comprendre, retenez qu'une base de données n'est en fait qu'un ensemble de tables de données, un peu à la manière dont vous pourriez inscrire des données dans une feuille de tableur (Excel ou autre).

Le but de ce tutoriel n'est pas de vous apprendre les notions de BD et SGBD, reportez-vous aux cours du site de developpez.com pour avoir de plus amples informations sur ce sujet.

Voici quelques liens :

I-B. Interaction avec les bases de données

Il existe différentes façons d'interagir avec une base de données :

  • en ligne de commande via une console Dos, Unix, Linux… ;
  • à partir d'une interface web (avec un logiciel comme phpMyAdmin) ;
  • via un langage de programmation comme Perl.

Notez que Perl n'est pas le seul langage qui permet d'interroger une base de données. La plupart des langages de programmation le permettent (ex : PHP, Java, C#…).

II. Perl et les bases de données

Cette section du tutoriel permet de rentrer dans le vif du sujet ! Il faut dans un premier temps installer les outils nécessaires pour commencer à travailler sur les bases de données avec Perl.

II-A. Prérequis pour travailler

Pour pouvoir créer ou interroger une base de données, vous devez dans un premier temps installer un système de gestion de base de données (MySQLMySQL, PostgreSQLPostgreSQL, ou autres). Il en existe beaucoup. Si vous avez installé EasyPHPEasyPHP, vous disposez d'un SGBD.

II-B. Modules Perl nécessaires

Perl a besoin du module DBI pour interagir avec les bases de données et d'un module driver pour les SGBD. Dans cet article, nous utiliserons MySQL comme SGBD. Nous aurons besoin du module driver adéquat : DBD::mysql.

Sachez qu'il existe des drivers pour toutes les SGBD que vous souhaiterez utiliser. Ils sont disponibles sur le site du CPAN et sont généralement sous la forme DBD::*.

Exemple de drivers

Si vous rencontrez des difficultés dans l'installation des modules, lisez le tutoriel sur l'installation des modules PerlInstallation des modules Perl CPAN écrit par nos soins ! Sinon, le forum Perl est toujours à votre disposition !

II-C. Débuter par l'exemple

Nous sommes maintenant prêts à coder ! La meilleure façon d'apprendre est de faire un exemple. Voici donc l'énoncé d'un exercice qui nous permettra de comprendre les commandes de base.

II-C-1. Enoncé de l'exercice

Afin de jouer avec le module DBI, nous allons créer un programme dont le but sera d'effectuer les tâches suivantes :

  • lister tous les départements d'une région ;
  • afficher le nom du département, de la région et le nombre de communes en fonction d'un numéro de département ;
  • donner le nom du département et de la région d'une commune.

Pour résoudre cet exercice, il faut que l'on puisse accéder à ces informations. Pour cela, nous téléchargeons trois fichiers contenant toutes les communes, départements et régions de France connus au 1er janvier 2013 sur le site de l'INSEEINSEE. Grâce à ces fichiers, on crée une base de données et trois tables SQL.

Voici un affichage de quelques lignes de ces fichiers :

Exemple du fichier des régions de France

REGION

CHEFLIEU

TNCC

NCC

NCCENR

1

971053

3

GUADELOUPE

Guadeloupe

11

75056

1

ILE-DE-FRANCE

Île-de-France

Exemple du fichier des communes de France

CDC

CHEFLIEU

REG

DEP

COM

AR

CT

TNCC

ARTMAJ

NCC

ARTMIN

NCCENR

1

4

11

75

56

1

99

0

 

PARIS

 

Paris

0

0

82

1

42

2

27

0

 

BEY

 

Bey

0

0

24

41

023

1

13

0

 

BOURRE

 

Bourré

0

0

82

26

142

1

06

0

 

GLANDAGE

 

Glandage

0

0

74

87

003

1

28

1

 

ARNAC-LA-POSTE

 

Arnac-la-Poste

Exemple du fichier des départements de France

REGION

DEP

CHEFLIEU

TNCC

NCC

NCCENR

82

1

1053

5

AIN

Ain

22

2

2408

5

AISNE

Aisne

II-C-2. Création de la base de données, des tables SQL et insertion des données

Créons une base de données que nous nommerons France2013 via une console (ou une interface Web).

Commande SQL
Sélectionnez
1.
2.
/*    Création de la base de données France2013 */
CREATE DATABASE `France2013`;
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
C:\>mysql -u root -p
Enter password: *****
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 25
Server version: 5.1.31-community MySQL Community Server (GPL)

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

mysql> CREATE DATABASE `France2013`;
Query OK, 1 row affected (0.04 sec)

La suite sera totalement intégrée dans un script Perl.
Créons un script CreationTableSQLFrance.pl qui se charge de créer les tables SQL et de les remplir avec les données du fichier.

Voici notre programme :

CreationTableSQLFrance.pl
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
#!/usr/bin/perl
#========================================================================================
# Auteur : djibril
# But     : Création des tables SQL et insertion de données
#
# http://djibril.developpez.com/tutoriels/perl/perl-dbi/
#========================================================================================
use warnings;
use strict;
use Encode;
use utf8;
use DBI;            # Charger le module DBI
use vars qw/ $VERSION /;    # Version du script
$VERSION = '1.0';

ActiverAccents();

# Paramètres de connection à la base de données
my $bd        = 'France2013';
my $serveur    = 'localhost'; # Il est possible de mettre une adresse IP
my $identifiant = 'root';      # identifiant 
my $motdepasse    = '';
my $port    = '';        #Si vous ne savez pas, ne rien mettre

# Connection à la base de données mysql
print "Connexion à la base de données $bd\n";
my $dbh = DBI->connect( "DBI:mysql:database=$bd;host=$serveur;port=$port", 
    $identifiant, $motdepasse, { 
    RaiseError => 1,
    }  
) or die "Connection impossible à la base de données $bd !\n $! \n $@\n$DBI::errstr";

# Création des tables
print "Création de la table Regions\n";
my $sql_creation_table_regions = <<"SQL";
CREATE TABLE Regions (
  id_region INT         NOT NULL ,
  cheflieu  VARCHAR( 6 )    NOT NULL ,
  tncc        INT         NOT NULL ,
  ncc        VARCHAR( 100 )  NOT NULL ,
  nccenr    VARCHAR( 100 )  NOT NULL ,
  PRIMARY KEY ( id_region )
)  COMMENT = 'LES REGIONS 2013 EN FRANCE';
SQL

$dbh->do('DROP TABLE IF EXISTS Regions;') or die "Impossible de supprimer la table Regions\n\n";
$dbh->do($sql_creation_table_regions) or die "Impossible de créer la table Regions\n\n";

print "Création de la table Communes\n";
my $sql_creation_table_communes = <<"SQL";
CREATE TABLE Communes (
  id_communes      INT          NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'Elle sera générée automatiquement.',
  cdc          INT          NOT NULL ,
  cheflieu      VARCHAR( 6 )      NOT NULL ,
  id_region      INT          NOT NULL ,
  id_departement  VARCHAR( 10 )   NOT NULL ,
  com          INT          NOT NULL ,
  ar          INT          NOT NULL ,
  ct          INT          NOT NULL ,
  tncc          INT          NOT NULL ,
  artmaj      VARCHAR( 20 )   ,
  ncc          VARCHAR( 100 )  NOT NULL ,
  artmin      VARCHAR( 20 )   ,
  nccenr      VARCHAR( 100 )  NOT NULL
) COMMENT = 'LES COMMUNES 2013 EN FRANCE';
SQL

$dbh->do('DROP TABLE IF EXISTS Communes;') or die "Impossible de supprimer la table Communes\n\n";
$dbh->do($sql_creation_table_communes) or die "Impossible de créer la table Communes\n\n";

print "Création de la table Departements\n";
my $sql_creation_table_departements = <<"SQL";
CREATE TABLE Departements  (
  id_departement  VARCHAR( 10 )   NOT NULL COMMENT 'les valeurs sont uniques',
  id_region      INT          NOT NULL ,
  cheflieu      VARCHAR( 6 )      NOT NULL ,
  tncc          INT          NOT NULL ,
  ncc          VARCHAR( 100 )  NOT NULL ,
  nccenr      VARCHAR( 100 )  NOT NULL ,
  PRIMARY KEY ( id_departement )
) COMMENT = 'LES DEPARTEMENTS 2013 EN FRANCE';
SQL

$dbh->do('DROP TABLE IF EXISTS Departements;') or die "Impossible de supprimer la table Departements\n\n";
$dbh->do($sql_creation_table_departements) or die "Impossible de créer la table Departements\n\n";

# Lecture des fichiers et insertion des données
my $fichier_regions     = 'reg2013.txt';
my $fichier_communes     = 'comsimp2013.txt';
my $fichier_departements = 'depts2013.txt';

# Fichier Region
print "Insertion des données dans la table regions\n";
open my $fh_regions, '<:encoding(UTF-8)', $fichier_regions or die "Impossible de lire le fichier $fichier_regions\n";
my $entete_fichier_region = <$fh_regions>;

# Insertion des données
my $requete_sql_region = <<"SQL";
  INSERT INTO regions ( id_region, cheflieu, tncc, ncc, nccenr )
  VALUES ( ?, ?, ?, ?, ? );
SQL

my $sth_regions = $dbh->prepare($requete_sql_region);

while ( my $ligne = <$fh_regions> ) {
  chomp $ligne;
  my ( $REGION, $CHEFLIEU, $TNCC, $NCC, $NCCENR ) = split /\t/, $ligne;
  $sth_regions->execute( $REGION, $CHEFLIEU, $TNCC, encode('latin1',$NCC), encode('latin1',$NCCENR) )
    or die "Echec Requ&#234;te $requete_sql_region : $DBI::errstr";
}
close $fh_regions;

# Fichier Communes
print "Insertion des données dans la table Communes\n";
open my $fh_communes, '<:encoding(UTF-8)', $fichier_communes or die "Impossible de lire le fichier $fichier_communes\n";
my $entete_fichier_communes = <$fh_communes>;

# Insertion des données
my $requete_sql_communes = <<"SQL";
  INSERT INTO communes ( 
    id_communes, cdc,         cheflieu, 
    id_region,     id_departement, com, 
    ar,      ct,         tncc, 
    artmaj,     ncc,         artmin,
    nccenr )
  VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? );
SQL

my $sth_communes = $dbh->prepare($requete_sql_communes);
while ( my $ligne = <$fh_communes> ) {
  chomp $ligne;
  my ( $CDC, $CHEFLIEU, $REG, $DEP, $COM, $AR, $CT, $TNCC, $ARTMAJ, $NCC, $ARTMIN, $NCCENR ) = split /\t/,
    $ligne;

  $sth_communes->execute( undef, $CDC, $CHEFLIEU, $REG, $DEP, $COM, $AR, $CT, $TNCC, $ARTMAJ, $NCC, encode('latin1',$ARTMIN),
    encode('latin1',$NCCENR) )
    or die "Echec Requ&#234;te $requete_sql_communes : $DBI::errstr";
}
close $fh_communes;

# Fichier Departements
print "Insertion des données dans la table Departements\n";
open my $fh_departements, '<:encoding(UTF-8)', $fichier_departements
  or die "Impossible de lire le fichier $fichier_departements\n";
my $entete_fichier_departements = <$fh_departements>;

# Insertion des données
my $requete_sql_departement = <<"SQL";
  INSERT INTO Departements ( 
    id_departement, id_region, cheflieu, 
    tncc,        ncc,       nccenr )
  VALUES ( ?, ?, ?, ?, ?, ? );
SQL

my $sth_departements = $dbh->prepare($requete_sql_departement);
while ( my $ligne = <$fh_departements> ) {
  chomp $ligne;
  my ( $REGION, $DEP, $CHEFLIEU, $TNCC, $NCC, $NCCENR ) = split /\t/, $ligne;
  $sth_departements->execute( $DEP, $REGION, $CHEFLIEU, $TNCC, $NCC, encode('latin1',$NCCENR) )
    or die "Echec Requ&#234;te $requete_sql_departement : $DBI::errstr";
}
close $fh_departements;

# Déconnection de la base de données
$dbh->disconnect();

#==============================================================
# Pour avoir les accents sur la console DOS ou non
# https://perl.developpez.com/faq/perl/?page=Terminal#Accents
#==============================================================
sub ActiverAccents {
    my $encodage;
    # Windows
      if ( lc($^O ) eq 'mswin32') {
    eval {
        my ($codepage) = ( `chcp` =~ m/:\s+(\d+)/ );
        $encodage = "cp$codepage";
        foreach my $h ( \*STDOUT, \*STDERR, \*STDIN, ) {
        binmode $h, ":encoding($encodage)";
        }
    };
    }
    else {
    $encodage = `locale charmap`;
    eval {
        foreach my $h ( \*STDOUT, \*STDERR, \*STDIN, ) {
        binmode $h, ":encoding($encodage)";
        }
    };
    }
  return $encodage;
}
  • Explication :

Nous utilisons le module Encode pour l'encodage des caractères avant insertion des données dans la bases. Notre programme est encodé en utf-8 d'où le chargement du module « utf8 ».
Puis nous utilisons le module DBI.

Le code

 
Sélectionnez
1.
ActiverAccents();

permet d'avoir un affichage correct des accents sous sa console. Pour en savoir plus, la FAQ Perl à ce sujetComment afficher des accents sur une console Windows (DOS) ou un terminal Linux/Unix ?.

Ensuite, on se connecte à la base de données via la méthode connect. Si nous avions utilisé un SGBD tel PostgreSQL, nous aurions écrit ceci :

Connexion à la base de données PostgreSQL
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
# Connection à la base de données  PostgreSQL
print "Connexion à la base de données $bd\n";
my $dbh = DBI->connect( "DBI:Pg:database=$bd;host=$serveur;port=$port", 
    $identifiant, $motdepasse, { 
    RaiseError => 1,
    }  
) or die "Connection impossible à la base de données $bd !\n $! \n $@\n$DBI::errstr";

C'est donc à ce niveau que le module DBI distingue le driver à utiliser et effectue la connexion à notre base. Créons maintenant les tables SQL.

Les tables SQL sont créées (et au préalable supprimées si elles existaient). Nous avons utilisé la méthode do qui peut être utilisée pour toutes les commandes SQL non répétées (c'est-à-dire requêtes exécutées une seule fois) qui ne sont pas de type SELECT.

Ligne 92 - à la fin : insertion des données dans les tables SQL.

Nous déclarons les variables contenant les noms des fichiers à analyser pour remplir nos tables SQL. Il faut garder à l'esprit que les fichiers sont encodés en UTF-8 d'où la lecture sous cette encodage des fichiers.

Prenons pour exemple le fichier Region.
En ligne 93, nous lisons l'entête du fichier (ligne 1 du fichier) et en lignes 98-101, nous construisons notre requête d'insertion. Vous remarquez à ce niveau que dans notre requête, nous avons des points d'interrogation. En fait, nous allons utiliser la notion de placeholders.

  • Qu'est-ce que les placeholders ?

Il existe ce qu'on appelle des placeholders dans le module DBI qui permettent d'exécuter les requêtes SQL lancées par Perl plus rapidement et de les protéger. Ils protègent efficacement contre l'injection SQL (fléau des sites web dynamiques), comme DBI est souvent utilisé dans un contexte web.

Ils sont supportés par la plupart des drivers. Je suis incapable de vous dire les drivers qui ne les supportent pas car ce n'est pas précisé dans la documentation.

Si un sympathique lecteur en a la liste, qu'il me fasse signe, je suis preneur !! Ayant utilisé DBD::Mysql et DBD::Pg, je n'ai jamais eu de souci.

  • Revenons à notre exemple

Nous allons lire notre fichier ligne à ligne et exécuter plusieurs fois le même type de requête. Seules les données changent, mais l'insertion se fera toujours dans les mêmes champs. Nous avons donc préparé notre requête (grâce à la méthode prepare) en dehors de la boucle while (ligne 103). Cela évite à DBI de devoir créer la requête à chaque ligne du fichier (perte de performance).

Une fois la requête conçue, nous lisons notre fichier ligne à ligne, récupérons les données (tabulées) et exécutons la requête grâce à la méthode execute (lignes 108,109). C'est le même principe pour les trois fichiers.

N.B. En ligne 135, nous avons mis le terme undef. En fait, pour le champ id_communes, nous avons mis un attribut AUTO_INCREMENT qui permet de générer un identifiant unique pour toutes les nouvelles lignes.

 
Sélectionnez
1.
id_communes  INT   NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'Elle sera générée automatiquement.',

Notre requête SQL ressemble à :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
  INSERT INTO communes ( 
    id_communes, cdc,         cheflieu, 
    id_region,     id_departement, com, 
    ar,      ct,         tncc, 
    artmaj,     ncc,         artmin,
    nccenr )
  VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? );

Nous avons exécuté cette requête de la sorte :

 
Sélectionnez
1.
2.
3.
  $sth_communes->execute( undef, $CDC, $CHEFLIEU, $REG, $DEP, $COM, $AR, $CT, $TNCC, $ARTMAJ, $NCC,
        encode('latin1',$ARTMIN), encode('latin1',$NCCENR) )
    or die "Echec Requ&#234;te $requete_sql_communes : $DBI::errstr";

Mettre undef permet à Perl de dire à SQL qu'il s'agit de la valeur NULL. Comme ce champ contient l'attribut AUTO_INCREMENT, SQL incrémentera tout seul la valeur du champ.

Notre base de données est encodée en latin, donc nous encodant certains champs où il est susceptible d'avoir des accents afin d'avoir des caractères correctement affichés dans la base.

 
Sélectionnez
1.
encode('latin1',$ARTMIN)

Petit Résumé :

Les méthodes prepare et execute sont généralement utilisées pour toutes les requêtes différentes du type SELECT (exemple : INSERT, UPDATE…).
Vous me direz, mais do aussi ! Alors laquelle choisir ?

Si vous avez une requête à exécuter une seule fois, comme une création de table (comme dans notre script), la mise à jour d'une seule ligne dans votre table SQL, la méthode do est recommandée. Par contre, si vous avez plusieurs requêtes à lancer, comme c'est le cas pour l'insertion des données, il est conseillé d'utiliser les méthodes prepare et execute car il sera plus simple de les optimiser via l'utilisation des placeholders.

Voilà, notre premier script nous permet de créer nos tables et d'y insérer les données.

II-C-2-a. Exemple sous Oracle

dmganges a repris l'exemple précédent pour proposer un exemple sous Oracle comme nous allons le voir. Il y a quelques légères modifications :

  • requêtes de création sous forme de fichier.sql pour centraliser les créations, çà c'est une préférence perso ;
  • modification table COMMUNES car Oracle ne gère pas les colonnes AUTO_INCREMENT, remplacement par une SEQUENCE et un TRIGGER
OracleCreationTableFrance.pl
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
#!/usr/bin/perl
#========================================================================================
# Auteur : djibril
# But     : Création des tables SQL et insertion de données
#
# http://djibril.developpez.com/tutoriels/perl/perl-dbi/
#
# Quelques modifs mineures par dmganges pour Oracle :
# - Modification table COMMUNES car Oracle ne gère pas les colonnes AUTO_INCREMENT
#         Remplacement par une SEQUENCE et un TRIGGER
#========================================================================================
use warnings;
use strict;
use utf8;
use DBI;                      # Charge le module DBI

use vars qw/ $VERSION /;              # Version du script
$VERSION = '1.0';

ActiverAccents();

# Paramètres de connexion à la base de données
my $host = "localhost";
my $port = "1521";
my $bdd  = "XE";
my $user = "michel";
my $pass = "michel";

my $dbh = DBI->connect("dbi:Oracle:host=$host;port=$port;sid=$bdd", $user, $pass, {
    PrintError => 0,
    AutoCommit => 0
}) or die "\nERR=Connexion impossible à la base de données $bdd\n $! \n $@\n$DBI::errstr";

my ($Sysdate) = $dbh->selectrow_array("SELECT sysdate FROM dual")
    or die "\nERR=Sysdate\n $! \n $@\n$DBI::errstr";
print "Base OK Date = $Sysdate\n\n";

# Création des tables
foreach my $Script ( 'CreTabRegions.sql', 'CreTabCommunes.sql', 'CreTabDepartements.sql') {
    print "Exécution de $Script\n";
    open( SQL, $Script) or die "\nERR=open $Script\n $!";
    my @Sqls = split(/\n\//,join('',<SQL>));
    for my $Sql( @Sqls ){
    # print "SEL=$Sql=\n";
    if ( $Sql ) {
       $dbh->do($Sql); # or die "\nERR=dbh->do\n $! \n $@\n$DBI::errstr";
    }
    }
    close SQL;
}

# NB Suppression contrôle $DBI::errstr car dans CreTabCommunes.sql
# La création du trigger génère un WARNIG qui déclenche le or die
# Alors que le trigger est créé correctement !

# Lecture des fichiers et insertion des données
my $fichier_regions     = 'reg2013.txt';
my $fichier_communes     = 'comsimp2013.txt';
my $fichier_departements = 'depts2013.txt';

# Fichier Region
print "Insertion des données dans la table regions\n";
open my $fh_regions, '<:encoding(UTF-8)', $fichier_regions or die "\nERR=open $fichier_regions\n $!";
my $entete_fichier_region = <$fh_regions>;

# Insertion des données
my $requete_sql_region = <<"SQL";
INSERT INTO regions ( id_region, cheflieu, tncc, ncc, nccenr )
    VALUES ( ?, ?, ?, ?, ? )
SQL

my $sth_regions = $dbh->prepare($requete_sql_region) or die "\nERR=dbh->prepare\n $! \n $@\n$DBI::errstr";

while ( my $ligne = <$fh_regions> ) {
    chomp $ligne;
    my ( $REGION, $CHEFLIEU, $TNCC, $NCC, $NCCENR ) = split /\t/, $ligne;
    $sth_regions->execute( $REGION, $CHEFLIEU, $TNCC, $NCC, $NCCENR )
    or die "\nERR=sth_regions->execute\n $! \n $@\n$DBI::errstr";
}
close $fh_regions;

# Fichier Communes
print "Insertion des données dans la table Communes\n";
open my $fh_communes, '<:encoding(UTF-8)', $fichier_communes or die "\nERR=open $fichier_communes\n $!";
my $entete_fichier_communes = <$fh_communes>;

# Insertion des données
my $requete_sql_communes = <<"SQL";
INSERT INTO communes (
    id_communes, cdc,         cheflieu,
    id_region,     id_departement, com,
    ar,      ct,         tncc,
    artmaj,     ncc,         artmin,
    nccenr )
    VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )
SQL

my $sth_communes = $dbh->prepare($requete_sql_communes)
    or die "\nERR=dbh->prepare\n $! \n $@\n$DBI::errstr";
while ( my $ligne = <$fh_communes> ) {
    chomp $ligne;
    my ( $CDC, $CHEFLIEU, $REG, $DEP, $COM, $AR, $CT, $TNCC, $ARTMAJ, $NCC, $ARTMIN, $NCCENR ) = split /\t/, $ligne;
    $sth_communes->execute( undef, $CDC, $CHEFLIEU, $REG, $DEP, $COM, $AR, $CT, $TNCC, $ARTMAJ, $NCC, $ARTMIN, $NCCENR )
    or die "\nERR=sth_communes->execute\n $! \n $@\n$DBI::errstr";
}
close $fh_communes;

# Fichier Departements
print "Insertion des données dans la table Departements\n";
open my $fh_departements, '<:encoding(UTF-8)', $fichier_departements or die "\nERR=open $fichier_departements\n $!";
my $entete_fichier_departements = <$fh_departements>;

# Insertion des données 
my $requete_sql_departement = <<"SQL";
INSERT INTO Departements (
    id_departement, id_region, cheflieu,
    tncc,        ncc,       nccenr )
    VALUES ( ?, ?, ?, ?, ?, ? )
SQL

my $sth_departements = $dbh->prepare($requete_sql_departement)
    or die "\nERR=dbh->prepare\n $! \n $@\n$DBI::errstr";
while ( my $ligne = <$fh_departements> ) {
    chomp $ligne;
    my ( $REGION, $DEP, $CHEFLIEU, $TNCC, $NCC, $NCCENR ) = split /\t/, $ligne;
    $sth_departements->execute( $DEP, $REGION, $CHEFLIEU, $TNCC, $NCC, $NCCENR )
    or die "\nERR=sth_departements->execute\n $! \n $@\n$DBI::errstr";
}
close $fh_departements;
print "Fin insertion\n";
$dbh->disconnect() or die "\nERR=disconnect\n $! \n $@\n$DBI::errstr";

#==============================================================
# Pour avoir les accents sur la console DOS ou non
# https://perl.developpez.com/faq/perl/?page=Terminal#Accents
#==============================================================
sub ActiverAccents {
    my $encodage;
    # Windows
      if ( lc($^O ) eq 'mswin32') {
    eval {
        my ($codepage) = ( `chcp` =~ m/:\s+(\d+)/ );
        $encodage = "cp$codepage";
        foreach my $h ( \*STDOUT, \*STDERR, \*STDIN, ) {
        binmode $h, ":encoding($encodage)";
        }
    };    
    }
    else {
    $encodage = `locale charmap`;
    eval {
        foreach my $h ( \*STDOUT, \*STDERR, \*STDIN, ) {
        binmode $h, ":encoding($encodage)";
        }
    };    
    }
  return $encodage;
}

#The following conventions are used in this document:
#
#  $dbh    Database handle object
#  $sth    Statement handle object
#  $drh    Driver handle object (rarely seen or used in applications)
#  $h       Any of the handle types above ($dbh, $sth, or $drh)
#  $rc       General Return Code    (boolean: true=ok, false=error)
#  $rv       General Return Value (typically an integer)
#  @ary    List of values returned from the database, typically a row of data
#  $rows   Number of rows processed (if available, else -1)
#  $fh       A filehandle
#  undef   NULL values are represented by undefined values in Perl
#  \%attr  Reference to a hash of attribute values passed to methods

Voici le code des fichiers SQL qui seront de toutes façon disponibles en téléchargement.

Region
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
DROP TABLE Regions
/

CREATE TABLE Regions ( 
    id_region INT         NOT NULL ,
    cheflieu  VARCHAR( 6 )   NOT NULL ,
    tncc      INT         NOT NULL ,
    ncc       VARCHAR( 50 )  NOT NULL ,
    nccenr    VARCHAR( 50 )  NOT NULL ,
    PRIMARY KEY ( id_region )
) tablespace USERS
/

COMMENT ON TABLE Regions is 'LES REGION DE FRANCE EN 2013'
/
Communes
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
DROP TABLE Communes
/

CREATE TABLE Communes (
    id_communes     INT         NOT NULL ,
    cdc         INT         NOT NULL ,
    cheflieu        VARCHAR( 6 )    NOT NULL ,
    id_region        INT         NOT NULL ,
    id_departement  VARCHAR( 10 )   NOT NULL ,
    com         INT         NOT NULL ,
    ar            INT         NOT NULL ,
    ct            INT,
    tncc        INT         NOT NULL ,
    artmaj        VARCHAR( 10 ),
    ncc         VARCHAR( 50 )   NOT NULL ,
    artmin        VARCHAR( 10 ),
    nccenr        VARCHAR( 50 )   NOT NULL ,
    PRIMARY KEY ( id_communes ) 
) tablespace USERS
/

COMMENT ON TABLE Communes is 'LES COMMUNES DE FRANCE EN 2013'
/

DROP SEQUENCE id_communes_seq
/

CREATE SEQUENCE id_communes_seq start with 1 increment by 1
/

create or replace trigger communes_insert
before insert on communes
for each row
begin
    select id_communes_seq.nextval into :new.id_communes from dual;
end communes_insert;
/
Département
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
DROP TABLE Departements
/

CREATE TABLE Departements  (
    id_departement  VARCHAR( 3 )    NOT NULL ,
    id_region        INT         NOT NULL ,
    cheflieu        VARCHAR( 6 )    NOT NULL ,
    tncc        INT         NOT NULL ,
    ncc         VARCHAR( 50 )   NOT NULL ,
    nccenr        VARCHAR( 50 )   NOT NULL ,
    PRIMARY KEY ( id_departement )
) tablespace USERS
/

COMMENT ON TABLE Departements is 'LES DEPARTEMENTS DE FRANCE EN 2013'
/

Le programme Perl est bien expliqué, je ne vais donc pas revenir dessus dans le détail.

Le driver utilisé est « dbi:OracleModule DBD::Oracle », ce qui est logique et les fichiers SQL sont analysés afin que le programme crée la requête SQL. Si vous avez des questions, n'hésitez pas à les poser dans la discussion de cet article sur le forum : 11 commentaires Donner une note à l´article (5).

PrintError => 0 n'est là que pour éviter d'avoir les erreurs affichées (printées) deux fois la première par Oracle la deuxième par « or die ».

AutoCommit => 1 On laisse Oracle faire ses « commit » comme il l'entend, mais en cas de problème un « rollback » explicite n'est pas pris en compte !

AutoCommit => 0 un « rollback » est possible mais doit être explicite, sinon en fin de traitement sans erreur un « commit » implicite est réalisé.

AutoCommit => 0 est préférable, surtout si on a l'habitude de faire un copier/coller de la connexion…

II-C-3. Interrogation de la base de données

Maintenant que notre base de données et les tables sont prêtes, créons notre deuxième script "GestionRegionFrance.pl" permettant de résoudre notre exercice.

Son but est de :

  • lister tous les départements d'une région choisie par l'utilisateur ;
  • afficher le nom du département, de la région et le nombre de communes en fonction d'un numéro de département voulu ;
  • afficher le nom du département et de la région d'une commune voulue.

Veuillez installer les modules Term::UITerm::UI et Term::ReadLineTerm::ReadLine qui nous permettrons d'interagir avec la console DOS, Unix, Linux…
Les méthodes fetchrow_array, fetchrow_hashref, selectrow_hashref, selectall_hashref et selectrow_array sont utilisées dans le script. Elles vous seront expliquées ci-dessous.

Examinons progressivement le script.

Dans un premier temps, nous avons besoin de charger les modules nécessaires, puis de nous connecter à notre base de données.

GestionRegionFrance.pl
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
#!/usr/bin/perl
#==========================================================================
# Auteur : djibril
# But     : Interrogation de ma base de données
#==========================================================================
use warnings;
use strict;

use DBI;    
use Term::UI;
use Term::ReadLine;
use Encode;
use utf8;

use vars qw/ $VERSION /;    # Version du script
$VERSION = '1.0';

my $encodage = ActiverAccents();

# Paramètres de connection à la base de données
my $bd        = 'France2013';
my $serveur    = 'localhost';      # Il est possible de mettre une adresse IP
my $identifiant = 'root';      # identifiant 
my $motdepasse    = '';
my $port    = '';

# Connection à la base de données mysql
print "Connexion à la base de données $bd\n";
my $dbh = DBI->connect( "DBI:mysql:database=$bd;host=$serveur;port=$port", 
    $identifiant, $motdepasse, { 
    RaiseError => 1,
    }  
) or die "Connection impossible à la base de données $bd !\n $! \n $@\n$DBI::errstr";

Le code ci-dessus est semblable au code du premier script. Il nous permet de nous connecter à la base de données. Notez que nous avons chargé les modules Term::UI et Term::ReadLine qui nous permettront de créer une interaction entre le programme et nous à travers la console.

II-C-3-a. Lister tous les départements d'une région

Nous souhaitons afficher tous les départements d'une région de notre choix. Afin de rendre notre programme plus interactif, ce dernier nous proposera une liste de différentes régions. On pourra ainsi effectuer notre choix.

Dans un premier temps, créons une procédure afin de récupérer dans notre base de données toutes les régions de France.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
# Procédure pour obtenir toutes les régions de France - méthode fetchrow_array
sub obtenir_region_france {
  my $dbh = shift;

  my @les_regions_france;
  my $prep = $dbh->prepare('SELECT ncc FROM Regions ORDER BY ncc') or die $dbh->errstr;
  $prep->execute() or die "Echec requ&#234;te\n";
  while ( my ($region) = $prep->fetchrow_array ) {
    push( @les_regions_france, $region );
  }
  $prep->finish();
  print "\n";

  return @les_regions_france;
}

Cette procédure récupère en argument l'objet DBI et utilise les méthodes classiques pour faire un SELECT. Il s'agit des méthodes prepare, execute, fetchXXX_YYY et finish.
Notez qu'il est possible d'utiliser d'autres méthodes qui permettent d'écrire moins de code, nous en parlerons plus tard.

  • la méthode prepare permet à DBI de préparer la requête SQL pour une exécution ultérieure et nous retourne un descripteur d'instruction que nous utiliserons par la suite (ligne 6) ;
  • la méthode execute effectue le traitement nécessaire pour exécuter l'instruction préparée ;
  • la méthode fetchrow_array récupère une ligne de données et retourne une liste contenant les valeurs de champ (ligne 8).

Pense-bête de fetchrow_array :

  • fetch => récupérer ;
  • row => ligne ;
  • array => tableau.

Dans notre cas, nous nous attendons à plusieurs lignes de résultats (plusieurs régions), d'où l'utilisation de la boucle while. Tant qu'il y aura une ligne de résultat, Perl restera dans le while.
Notre requête SQL ne sélectionne qu'un seul champ (ncc), donc notre tableau n'aura qu'une seule case.

Ligne 8
Sélectionnez
8.
while ( my ($region) = $prep->fetchrow_array ) {

Les régions sont stockées dans la liste @les_regions_france, ensuite, nous fermons la requête grâce à la méthode finish.

Maintenant que nous avons la liste des régions de France, créons une interaction entre le programme et nous par l'intermédiaire de l'utilisation du module Term::UI. Nous aurions pu utiliser <STDIN> mais le module nous facilite la vie. Voici le code :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
#===========================================
# Lister tous les départements d'une région
#===========================================
# 1 - Récupération de toutes les régions de France
my @regions_france = obtenir_region_france($dbh);

# 2 - Choix de l'utilisateur
my $term           = Term::ReadLine->new('brand');
my $nom_region_choisie = $term->get_reply(
  print_me => "1- Listons tous les départements d'une région",
  prompt   => encode($encodage,'Choisissez une région de France : '),
  choices  => \@regions_france,
  default  => 'PICARDIE',
);

# 3 - Récupération de tous les départements - méthode fetchrow_hashref
afficher_departement_dune_region( $dbh, $nom_region_choisie );

La méthode get_reply du module Term::ReadLine nous permet de poser une question dans la console en proposant une liste de choix (nos régions de France) grâce à l'option choices et en donnant une sélection par défaut (default).
Nous obtenons ceci :

Tous les départements d'une région
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
1- Listons tous les départements d'une région
  1> ALSACE
  2> AQUITAINE
  3> AUVERGNE
  4> BASSE-NORMANDIE
  5> BOURGOGNE
  6> BRETAGNE
  7> CENTRE
  8> CHAMPAGNE-ARDENNE
  9> CORSE
 10> FRANCHE-COMTE
 11> GUADELOUPE
 12> GUYANE
 13> HAUTE-NORMANDIE
 14> ILE-DE-FRANCE
 15> LA REUNION
 16> LANGUEDOC-ROUSSILLON
 17> LIMOUSIN
 18> LORRAINE
 19> MARTINIQUE
 20> MAYOTTE
 21> MIDI-PYRENEES
 22> NORD-PAS-DE-CALAIS
 23> PAYS DE LA LOIRE
 24> PICARDIE
 25> POITOU-CHARENTES
 26> PROVENCE-ALPES-COTE D'AZUR
 27> RHONE-ALPES

Choisissez une région de France :  [24]:

Une fois notre choix effectué, nous utiliserons cette région pour lister les départements à partir de la procédure afficher_departement_dune_region suivante :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
# Procédure pour afficher les départements d'une région
sub afficher_departement_dune_region {
  my ( $dbh, $nom_region_choisie ) = @_;

  my $requete_departement_cheflieu = <<"SQL";
    SELECT Departements.nccenr as NomDep, Departements.cheflieu as cheflieu
    FROM Departements
    INNER JOIN Regions ON Regions.id_region = Departements.id_region
    WHERE Regions.ncc = "$nom_region_choisie"
SQL
  my $prep = $dbh->prepare($requete_departement_cheflieu) or die $dbh->errstr;
  $prep->execute() or die "Echec requ&#234;te : $requete_departement_cheflieu\n";

  print "La région $nom_region_choisie possède ", $prep->rows, " département(s)\n";
  while ( my $ref_donnees = $prep->fetchrow_hashref ) {
    print "\t- $ref_donnees->{NomDep}, chef-lieu $ref_donnees->{cheflieu}\n";
  }
  $prep->finish();
  print "\n";
  return;
}

Cette procédure nous permet d'utiliser fetchrow_hashref à la place de fetchrow_array

Pense-bête :

  • fetch => récupérer ;
  • row => ligne ;
  • hashref => référence de hash.
Ligne 15
Sélectionnez
15.
while ( my $ref_donnees = $prep->fetchrow_hashref ) {

Chaque ligne de résultat est retournée sous forme de référence de hash au lieu de liste comme précédemment.
Pour accéder à la valeur de chaque champ, il suffit de passer à notre référence la clé qui correspondant au nom de chaque champ spécifié dans notre requête SQL (NomDep et cheflieu) :

 
Sélectionnez
1.
2.
3.
4.
SELECT Departements.nccenr as NomDep, Departements.cheflieu as cheflieu 
FROM Departements 
INNER JOIN Regions ON Regions.id_region = Departements.id_region 
WHERE Regions.ncc = "$nom_region_choisie"

D'où en ligne 16 : $ref_donnees->{NomDep} et $ref_donnees->{cheflieu}.

Si vous avez des difficultés à comprendre la notion de référence, lisez la FAQ , section :Les référencesLes références.

En choisissant la région ILE-DE-FRANCE (14), nous obtenons :

Départements d'ile de France
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
Choisissez une région de France :    [23]: 14
La région ILE-DE-FRANCE possède 8 département(s)
    - Paris, chef-lieu 75056
    - Seine-et-Marne, chef-lieu 77288
    - Yvelines, chef-lieu 78646
    - Essonne, chef-lieu 91228
    - Hauts-de-Seine, chef-lieu 92050
    - Seine-Saint-Denis, chef-lieu 93008
    - Val-de-Marne, chef-lieu 94028
    - Val-d'Oise, chef-lieu 95500

Ouf, la première question de notre exercice est terminée ! J'espère que vous êtes encore en forme pour continuer Image non disponible !

II-C-3-b. Afficher le nom du département, de la région et le nombre de communes en fonction d'un numéro de département

Maintenant, nous n'utiliserons plus les méthodes fetchXXX_YYY, mais les méthodes selectXXX_YYY. Pour ce faire, procédons comme ci-dessus en créant une interaction et une procédure pour cet affichage (afficher_departement_dune_region).

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
# 4- Afficher le nom du département, de la région et le nombre de
# communes en fonction d'un numéro de département - méthode selectrow_array,
my $numero_departement = $term->get_reply(
  print_me =>
    "2- Trouvons le nom d'un département, de la région et le nombre de communes à partir d'un numéro de département",
  prompt  => encode($encodage,'Donnez un numéro de département : '),
  default => 75,
);

afficher_informations_dun_departement( $dbh, $numero_departement );
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
# Procédure pour afficher toutes les informations d'un numéro de département
sub afficher_informations_dun_departement {
  my ( $dbh, $numero_departement ) = @_;

  my $requete_dep_cheflieu_region = <<"SQL";
  SELECT 
       Departements.nccenr   AS NomDep, 
       Departements.cheflieu AS cheflieu,
       Regions.ncc         AS NomRegion
  FROM Departements
  INNER JOIN Regions ON Regions.id_region = Departements.id_region
  WHERE Departements.id_departement = '$numero_departement'
SQL
  my $requete_nombre_commune = <<"SQL";
    SELECT COUNT( id_communes ) as NbrCommunes
    FROM communes
    WHERE id_departement = "$numero_departement"
SQL
  my ( $nom_departement, $nom_cheflieu, $nom_region ) = $dbh->selectrow_array($requete_dep_cheflieu_region);

  if ( defined $nom_cheflieu ) {
    print "\t - Numéro du département : $numero_departement\n";
    print "\t - Nom du département : $nom_departement\n";
    print "\t - Nom du chef-lieu : $nom_cheflieu\n";
    print "\t - Nom de la région : $nom_region\n";

    # Calculer le nombre de communes
    my ($ref_commune) = $dbh->selectrow_hashref($requete_nombre_commune);
    print "\t - Nombre de communes : ", $ref_commune->{NbrCommunes}, "\n\n";
  }
  else {
    print "Numéro de département inconnu\n";
  }

  return;
}

Dans le code ci-dessus, nous utilisons les méthodes selectrow_array et selectrow_hashref pour récupérer le nom de la région, du département et le nombre de communes pour le numéro de département saisi par l'utilisateur.
Ah !! Je vais donc vous expliquer comment écrire moins de code Image non disponible !

Pense-bête :

  • select => sélectionne ;
  • row => ligne ;
  • hashref => référence de hash ;
  • array => tableau.

En fait, les méthodes de type selectXXX_YYY combinent les méthodes prepare, execute et fetchXXX_YYY.

Comparaison entre fetch et select

select

fetch

selectrow_array

combine prepare, execute et fetchrow_array

selectrow_arrayref

combine prepare, execute et fetchrow_arrayref

selectrow_hashref

combine prepare, execute et fetchrow_hashref

  • Avantage :



La combinaison de trois méthodes permet de densifier le code. Par exemple :

ligne 19
Sélectionnez
19.
20.
my ( $nom_departement, $nom_cheflieu, $nom_region ) =
          $dbh->selectrow_array($requete_dep_cheflieu_region);

Ici, nous récupérons un tableau contenant trois cases en une seule ligne, via la méthode selectrow_array. Alors qu'ici :

ligne 28
Sélectionnez
28.
my ($ref_commune) = $dbh->selectrow_hashref($requete_nombre_commune);

nous récupérons une référence de hash grâce à la méthode selectrow_hashref.

Je vous conseille d'utiliser les méthodes selectrow_YYY lorsque vous souhaitez récupérer une seule ligne de résultat.

Exemple de résultat :
Sélectionnez
1.
2.
3.
4.
5.
6.
Donnez un numéro de département :  [75]: 48
     - Numéro du département : 48
     - Nom du département : Lozère
     - Nom du chef-lieu : 48095
     - Nom de la région : LANGUEDOC-ROUSSILLON
     - Nombre de communes : 185
II-C-3-c. Trouver le nom du département et de la région d'une commune donnée

Maintenant, nous utiliserons la méthode selectall_YYY. Pour ce faire, procédons comme ci-dessus en créant une interaction et une procédure pour cet affichage (afficher_informations_dune_commune).

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
# 5- Trouver le nom du département et de la région d'une commune donnée
my $commune = $term->get_reply(
  print_me => "3- Trouvons le nom du département et de la région d'une commune",
  prompt   => "Nom d'une commune : ",
  default  => 'glandage',
);

# Les noms des communes (ncc) sont en majuscules dans la base
$commune = uc $commune;
# Les espaces dans les noms des communes sont remplacés par un tiret dans la base
$commune =~ s{\s}{-}g;
afficher_informations_dune_commune( $dbh, $commune );

# Déconnexion de la base de données
$dbh->disconnect();
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
# Procédure pour afficher toutes les informations d'une commune
sub afficher_informations_dune_commune {
  my ( $dbh, $commune ) = @_;

  my $requete_commune = <<"SQL";
    SELECT 
     Departements.id_departement    AS id_departement, 
     Departements.nccenr        AS NomDep, 
     Departements.cheflieu        AS cheflieu,
     Regions.ncc            AS NomRegion,
     Communes.ncc            AS commune
    FROM Communes
    INNER JOIN Regions ON Regions.id_region = Communes.id_region
    INNER JOIN Departements ON Departements.id_departement = Communes.id_departement
    WHERE Communes.ncc LIKE "%$commune%"
SQL

  my $hash_ref_donnee = $dbh->selectall_hashref($requete_commune, 'id_departement');
  foreach my $id_dep ( sort { $a <=> $b } keys %{$hash_ref_donnee} ) {
    print "\t - Nom de la commune : $hash_ref_donnee->{$id_dep}{commune}\n";
    print "\t - Nom du département : $hash_ref_donnee->{$id_dep}{NomDep}\n";
    print "\t - Nom du chef-lieu : $hash_ref_donnee->{$id_dep}{cheflieu}\n";
    print "\t - Nom de la région : $hash_ref_donnee->{$id_dep}{NomRegion}\n\n\n";
  }

  return;
}

Dans le code ci-dessus, nous utilisons la méthode selectall_hashref pour récupérer le nom du département, de la région, le chef-lieu pour une commune saisie par l'utilisateur.

Pense-bête :

  • select => sélectionne ;
  • all => toutes les lignes ;
  • hashref => référence de hash ;

En fait, les méthodes de type selectall_YYY combinent les méthodes prepare, execute et fetchall_YYY.

Comparaison entre fetch et select

select

fetch

selectall_arrayref

combine prepare, execute et fetchall_arrayref

selectall_hashref

combine prepare, execute et fetchall_hashref

select_all_hashref renvoie une référence de hash (hashref) qui contient toutes les lignes de résultats. Chaque ligne est identifiable par une clé correspondant à la valeur du champ que nous avons spécifié (généralement un champ dont les valeurs sont uniques) et en valeur une référence de hash avec pour clé le nom du champ et en valeur la donnée.

 
Sélectionnez
1.
my $hash_ref_donnee = $dbh->selectall_hashref($requete_commune, 'id_departement');

Ici, nous précisons que la clé qui identifiera chaque ligne de résultat correspondra à la valeur de l'identifiant de département (nous savons qu'elle est unique à chaque ligne de résultat dans notre table). Le choix de la clé est important sous peine d'écraser les informations dans le hash et avoir une incohérence de résultat. Nous choisissons également id_departement car nous l'avons sélectionné dans notre requête SQL. Donc faites attention !!

Ensuite, il faut parcourir ce hash pour afficher les informations.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
  my $hash_ref_donnee = $dbh->selectall_hashref($requete_commune, 'id_departement');
  foreach my $id_dep ( sort { $a <=> $b } keys %{$hash_ref_donnee} ) {
    print "\t - Nom de la commune : $hash_ref_donnee->{$id_dep}{commune}\n";
    print "\t - Nom du département : $hash_ref_donnee->{$id_dep}{NomDep}\n";
    print "\t - Nom du chef-lieu : $hash_ref_donnee->{$id_dep}{cheflieu}\n";
    print "\t - Nom de la région : $hash_ref_donnee->{$id_dep}{NomRegion}\n\n\n";
  }

Avantage :

  • c'est donc une façon élégante de récupérer les résultats d'une requête ;
  • en terme de lisibilité et de maintenance, on y gagne grandement ;
  • il n'est pas nécessaire d'utiliser la méthode finish, DBI le gérera pour nous.

Inconvénient :

  • il ne faut pas les utiliser si notre requête génère beaucoup de données, car tout sera en mémoire dans le hash ou array ;
  • selectall_hashref permet d'avoir un champ à utiliser comme clé. Il faut donc bien vérifier que les valeurs de ce champ sont uniques pour éviter de mauvaises surprises ;
  • il faut bien maîtriser l'utilisation des références pour s'y retrouver. En soi, ce n'est pas vraiment un inconvénient car c'est plutôt utile en Perl !!
Exemple de résultat :
Sélectionnez
1.
2.
3.
4.
5.
Nom d'une commune :  [glandage]:
     - Nom de la commune : GLANDAGE
     - Nom du département : Drôme
     - Nom du chef-lieu : 26362
     - Nom de la région : RHONE-ALPES

II-C-4. Exemple sous Oracle

Pour que le programme ci-dessus fonctionne sous Oracle, il faut quelques modifications :

  • Oracle renvoie les noms des colonnes en MAJUSCULES ce qui implique les selectrow_hashref.
    Par exemple : $ref_commune->{'NBRCOMMUNES'} au lieu de $ref_commune->{NbrCommunes} ainsi que dans les clauses

     
    Sélectionnez
    1.
    AS SELECT COUNT( id_communes ) as NBRCOMMUNES ;
    
  • Les clauses LIKE avec point d'interrogation « ? » on étaient remplacées par l'utilisation de la méthode bind_columns ($sth->bind_columns). Ce n'est pas obligatoire mais cela montre une autre façon d'écrire.

  • Les clauses JOIN dans les requêtes ont été supprimées pour une autre écriture. C'est le choix de dmganges par habitude Image non disponible !

Voici le programme qui sera téléchargeable ci-dessous :

OracleGestionRegionFrance.pl
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
224.
225.
226.
227.
228.
229.
230.
231.
232.
233.
234.
235.
236.
237.
238.
239.
240.
241.
242.
243.
244.
245.
246.
247.
248.
249.
250.
251.
252.
253.
254.
255.
256.
257.
258.
259.
260.
261.
262.
263.
264.
265.
266.
267.
268.
269.
270.
271.
272.
273.
#!/usr/bin/perl
#==========================================================================
# Auteur : djibril
# But     : Création des tables SQL et insertion de données
#
# http://djibril.developpez.com/tutoriels/perl/perl-dbi/
#
# Quelques modifs mineures par dmganges pour Oracle :
# - Connexion à BDD
# - Oracle renvoie les noms des colonnes en MAJUSCULES ce qui implique
#     dans les selectrow_hashref par ex :
#         $ref_commune->{'NBRCOMMUNES'} au lieu de $ref_commune->{NbrCommunes}
#   Ainsi que dand les clause AS
#         SELECT COUNT( id_communes ) as NBRCOMMUNES
#    - Modif des clauses LIKE avec ? pour méthode $sth->bind_columns
#==========================================================================
use warnings;
use strict;
use utf8;
use DBI;                      # Charge le module DBI
use DBI qw(:sql_types);               # Pour utiliser les types du genre SQL_VARCHAR
use Term::UI;                      # Pour interaction avec la console
use Term::ReadLine;                  # Pour interaction avec la console

use vars qw/ $VERSION /;              # Version du script
$VERSION = '1.0';

ActiverAccents();

# Paramètres de connexion à la base de données
my $host = "localhost";
my $port = "1521";
my $bdd  = "XE";
my $user = "michel";
my $pass = "michel";

my $dbh = DBI->connect("dbi:Oracle:host=$host;port=$port;sid=$bdd", $user, $pass, {
    PrintError => 0,
    AutoCommit => 0
}) or die "\nERR=Connexion impossible à la base de données $bdd\n $! \n $@\n$DBI::errstr";

my ($Sysdate) = $dbh->selectrow_array("SELECT sysdate FROM dual")
    or die "\nERR=Sysdate\n $! \n $@\n$DBI::errstr";
print "Base OK Date = $Sysdate\n\n";

# 1 - Récupération de toutes les régions de France
my @Regions = &obtenir_region_france($dbh);
foreach my $Region ( @Regions ) {
    print "$Region\n";
}

#===========================================
# Lister tous les départements d'une région
#===========================================
# 2 - Choix de l'utilisateur
my $term           = Term::ReadLine->new('brand');
my $nom_region_choisie = $term->get_reply(
  print_me => "1- Listons tous les départements d'une région",
  prompt   => "Choisissez une r\x82gion de France : ",
  choices  => \@Regions,
  default  => 'PICARDIE',
);

# 3 - Récupération de tous les départements - méthode $sth->bind_columns
afficher_departement_dune_region( $dbh, $nom_region_choisie );

# 4- Afficher le nom du département, de la région et le nombre de
# communes en fonction d'un numéro de département - méthode selectrow_array
# nombre de communes - méthode selectrow_hashref
my $numero_departement = $term->get_reply(
  print_me =>
    "\n2- Trouvons le nom d'un département, de la région et le nombre de communes à partir d'un numéro de département",
  prompt  => "Donnez un num\x82ro de d\x82partement  :",
  default => 75,
);

afficher_informations_dun_departement( $dbh, $numero_departement );

# 5- Trouver le nom du département et de la région d'une commune donnée
my $commune = $term->get_reply(
  print_me => "\n3- Trouvons le nom du département et de la région d'une commune",
  prompt   => "Nom d'une commune : ",
  default  => 'GANGE',
);

# Les noms des communes (ncc) sont en majuscules dans la base
$commune = uc $commune;
# Les espaces dans les noms des communes sont remplacés par un tiret dans la base
$commune =~ s{\s}{-}g;
afficher_informations_dune_commune( $dbh, $commune );

print "\n\nFin consultation\n";
$dbh->disconnect() or die "\nERR=disconnect\n $! \n $@\n$DBI::errstr";

##############################################################################
# Procédure pour obtenir toutes les régions de France - méthode fetchrow_array
sub obtenir_region_france {
    my $dbh = shift;
    my @les_regions_france;
    my $sth = $dbh->prepare('SELECT ncc FROM Regions ORDER BY ncc')
    or die "\nERR=dbh->prepare\n $! \n $@\n$DBI::errstr";
    $sth->execute() or die "\nERR=execute\n $! \n $@\n$DBI::errstr";
    while ( my ($region) = $sth->fetchrow_array ) {
    push( @les_regions_france, $region );
    }
    $sth->finish() or die "\nERR=sth->finish\n $! \n $@\n$DBI::errstr";
    print "\n";
    return @les_regions_france;
} 

# Procédure pour afficher les départements d'une région
# Modifiée :
# - use DBI qw(:sql_types) Pour utiliser les types du genre SQL_VARCHAR
# - autre fa&#231;on de construire la requ&#234;te
# - suppression des clauses AS
# - passage d'un paramètre avec $sth->bind_param
# - récupération des colonnes dans l'ordre avec $sth->bind_columns directement dans my( $NomDep, $ChefLieu )

sub afficher_departement_dune_region {
    my ( $dbh, $nom_region_choisie ) = @_;
    my $Sql = qq{
    SELECT departements.nccenr, departements.cheflieu
    FROM departements, regions
    WHERE regions.id_region = departements.id_region
    AND regions.ncc LIKE ?
    };

    my $sth = $dbh->prepare($Sql) or die "\nERR=dbh->prepare\n $! \n $@\n$DBI::errstr";
    $sth->bind_param( 1, $nom_region_choisie, SQL_VARCHAR );
    my $rv = $sth->execute() or die "\nERR=execute\n $! \n $@\n$DBI::errstr";

    my( $NomDep, $ChefLieu );
  $sth->bind_columns( \$NomDep, \$ChefLieu ) or die "\nERR=sth->bind_columns\n $! \n $@\n$DBI::errstr";
    print "\n";
    while( $sth->fetch() ) {
    print (sprintf ( "%20s %8s \n", $NomDep, $ChefLieu ));
    }
    print "\nFIN\n";
    $sth->finish() or die "\nERR=sth->finish\n $! \n $@\n$DBI::errstr";
    return;
} 

# Procédure pour afficher toutes les informations d'un numéro de département 
sub afficher_informations_dun_departement { 
    my ( $dbh, $numero_departement ) = @_; 
    my $requete_dep_cheflieu_region = <<"SQL"; 
    SELECT  
    Departements.nccenr   AS NomDep,  
    Departements.cheflieu AS cheflieu, 
    Regions.ncc       AS NomRegion 
    FROM Departements 
    INNER JOIN Regions ON Regions.id_region = Departements.id_region 
    WHERE Departements.id_departement = '$numero_departement' 
SQL

    my $requete_nombre_commune = <<"SQL";
    SELECT COUNT(id_communes) as NbrCommunes
    FROM communes
    WHERE id_departement = '$numero_departement'
SQL

    my ( $nom_departement, $nom_cheflieu, $nom_region ) = $dbh->selectrow_array($requete_dep_cheflieu_region);
    print "\n";
    if ( defined $nom_cheflieu ) {
    print "\t - Numéro du département : $numero_departement\n";
    print "\t - Nom du département      : $nom_departement\n";
    print "\t - Nom du chef-lieu      : $nom_cheflieu\n";
    print "\t - Nom de la région      : $nom_region\n";

    # Calculer le nombre de communes
    # Nb Oracle renvoie les noms des colonnes en MAJUSCULES
    my ($ref_commune) = $dbh->selectrow_hashref($requete_nombre_commune) or die $dbh->errstr;
    print "\n\t - Nombre de communes    : ", $ref_commune->{'NBRCOMMUNES'}, "\n\n";

    # Ou, si on ne fait pas as NbrCommunes :
    # print "\t - Nombre de communes : ", $ref_commune->{'COUNT(ID_COMMUNES)'}, "\n\n";

    # Ou avec selectrow_array
    # my ($ref_commune) = $dbh->selectrow_array($requete_nombre_commune);
    # print "\n\t - Nombre de communes    : $ref_commune\n\n";
    } else {
    print "\nNuméro de département inconnu\n";
    }
    print "\nFIN\n";
    return;
}

# Procédure pour afficher toutes les informations d'une commune
sub afficher_informations_dune_commune {
  my ( $dbh, $commune ) = @_;
=pod
  my $requete_commune = <<"SQL";
    SELECT 
     Departements.id_departement    AS id_departement, 
     Departements.nccenr        AS NomDep, 
     Departements.cheflieu        AS cheflieu,
     Regions.ncc            AS NomRegion,
     Communes.ncc            AS commune
    FROM Communes
    INNER JOIN Regions ON Regions.id_region = Communes.id_region
    INNER JOIN Departements ON Departements.id_departement = Communes.id_departement
    WHERE Communes.ncc LIKE '%$commune%'
SQL
=cut

  my $requete_commune = <<"SQL";
  SELECT 
    Departements.id_departement    AS id_departement,
    a.ncc               AS commune, 
    Departements.nccenr        AS NomDep, 
    Departements.cheflieu       AS cheflieu,
    b.ncc               AS NomVille,
    Regions.ncc            AS NomRegion
    FROM Departements, Regions, Communes a, Communes b
    WHERE a.id_departement = b.id_departement
    AND b.com = TO_NUMBER(SUBSTR(Departements.cheflieu,3))
    AND Regions.id_region = a.id_region
    AND Departements.id_departement = a.id_departement
    AND a.ncc LIKE '%$commune%'
SQL

    # NB ID_DEPARTEMENT en MAJUSCULES, ainsi que tous les $hash_ref_donnee->{$id_dep}{}
  my $hash_ref_donnee = $dbh->selectall_hashref($requete_commune, 'ID_DEPARTEMENT')
      or die "\nERR=dbh->selectall_hashref\n $! \n $@\n$DBI::errstr";
  foreach my $id_dep ( sort { $a <=> $b } keys %{$hash_ref_donnee} ) {
    print "\t - Nom de la commune           : $hash_ref_donnee->{$id_dep}{COMMUNE}\n";
    print "\t - Nom du département           : $hash_ref_donnee->{$id_dep}{NOMDEP}\n";
    print "\t - Ville du chef-lieu dept & code : $hash_ref_donnee->{$id_dep}{NOMVILLE} \t $hash_ref_donnee->{$id_dep}{CHEFLIEU} \n";
    print "\t - Nom de la région           : $hash_ref_donnee->{$id_dep}{NOMREGION}\n\n\n";
  }
  return;
}

#==============================================================
# Pour avoir les accents sur la console DOS ou non
# http://perl.developpez.com/faq/perl/?page=Terminal#Accents
#==============================================================
sub ActiverAccents {
    my $encodage;
    # Windows
      if ( lc($^O ) eq 'mswin32') {
    eval {
        my ($codepage) = ( `chcp` =~ m/:\s+(\d+)/ );
        $encodage = "cp$codepage";
        foreach my $h ( \*STDOUT, \*STDERR, \*STDIN, ) {
        binmode $h, ":encoding($encodage)";
        }
    };    
    }
    else {
    $encodage = `locale charmap`;
    eval {
        foreach my $h ( \*STDOUT, \*STDERR, \*STDIN, ) {
        binmode $h, ":encoding($encodage)";
        }
    };    
    }
  return $encodage;
}

#The following conventions are used in this document:
#
#  $dbh    Database handle object
#  $sth    Statement handle object
#  $drh    Driver handle object (rarely seen or used in applications)
#  $h       Any of the handle types above ($dbh, $sth, or $drh)
#  $rc       General Return Code    (boolean: true=ok, false=error)
#  $rv       General Return Value (typically an integer)
#  @ary    List of values returned from the database, typically a row of data
#  $rows   Number of rows processed (if available, else -1)
#  $fh       A filehandle
#  undef   NULL values are represented by undefined values in Perl
#  \%attr  Reference to a hash of attribute values passed to methods

PrintError => 0 n'est là que pour éviter d'avoir les erreurs affichées (printées) deux fois la première par Oracle la deuxième par « or die ».

AutoCommit => 1 On laisse Oracle faire ses « commit » comme il l'entend, mais en cas de problème un « rollback » explicite n'est pas pris en compte !

AutoCommit => 0 un « rollback » est possible mais doit être explicite, sinon en fin de traitement sans erreur un « commit » implicite est réalisé.

AutoCommit => 0 est préférable, surtout si on a l'habitude de faire un copier/coller de la connexion…

II-C-5. Téléchargement des programmes

Vous trouverez ici quatre programmes, trois fichiers de l'INSEE et les trois fichiers SQL utiles.pour les programmes Oracle pour résoudre l'exercice.
Je tiens à préciser que je n'ai pas testé les programmes pour la partie Oracle Si vous constatez une anomalie ou amélioration, n'hésitez pas à nous en faire part. 11 commentaires Donner une note à l´article (5)

III. Liens

IV. Conclusion

Avant toute chose, pensez à écrire correctement vos requêtes SQL ! Testez-les en console ou via les logiciels comme phpMyAdmin ou phpPgAdmin. Choisissez correctement vos méthodes DBI. Lisez la documentation CPAN de DBIDBI, vous y trouverez d'autres méthodes intéressantes comme COMMIT. Cette dernière permet de protéger vos transferts de données en cas d'arrêt brutal du serveur pour une raison quelconque.
Profitez des cours SQL et Perl qui sont sur ce site et n'oubliez pas qu'il y a aussi les forums pour vous aider !

N.B. : Ces scripts ont été testés sous MySQL et sous PostgreSQL.
En ce qui concerne Oracle, je ne sais pas s'il y a des modifications à prévoir dans les codes Perl en dehors de la connexion à la base de données. Faites-moi un retour si nécessaire.

J'espère que cet article vous a aidé à comprendre l'utilisation du module DBI pour interagir avec les bases de données en Perl.

N'hésitez pas à faire des remarques, corrections ou appréciations. 11 commentaires Donner une note à l´article (5)

V. Remerciements

Je remercie dmganges pour les remarques et sa contribution pour les exemples de programmes Oracle.
Je remercie également ClaudeLELOUP et stoyak pour la relecture de ce tutoriel.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2013 djibril. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.