Vous êtes sur la page 1sur 70

Support de cours BD en PostgreSQL

Philippe Durif

9 juillet 2010
Chapitre 1

Qu’est-ce qu’une base de données

1.1 Qu’est-ce qu’une base de données (BD)


Une base de données peut être vue comme le besoin de mémoriser de façon durable des données et
de pouvoir exprimer le plus précisément possible les relations qu’entretiennent ces données.

Une fois cette représentation faite il est nécessaire d’associer des fonctionnalités (programmes et des
requêtes) à cette base de données afin de pouvoir l’exploiter le plus facilement possible.

Toutes les personnes exploitant la même base de données n’ont pas la même fonction et n’ont donc
pas forcément besoin de voir les mêmes informations ou d’appliquer les mêmes actions à la base de
données. Les systèmes des privilèges, des vues et des programmes stockés permettent de délimiter ri-
goureusement ces différentes visions d’une même base de données (chaque vision est nommée schéma
externe).

Enfin, plusieurs utilisateurs peuvent appliquer simultanément des modifications à la même base de
données, il est alors nécessaire d’utiliser des techniques d’isolation et de synchronisation afin de
garantir la cohérence de ces modifications.

1.2 Qu’est-ce qu’un système de gestion de base de données


(SGBD)
Un SGBD est la structure d’accueil d’une ou plusieurs bases de données : il offre les outils nécessaires
à la mise en place d’une base de données. On pourrait comparer le SGBD au système d’exploitation
et la base de données à un programme d’application utilisant les services du système.

Voici quelques-unes des caractéristiques d’un SGBD :


– Capacité de gérer des données persistantes et structurées.
– Capacité à gérer, autant que possible, la sémantique des données et à garantir des propriétés (les
contraintes, assertions, domaines des attributs, triggers et procédures stockées)
– Pouvoir manipuler facilement et efficacement de très grand volumes de données.
– Permettre l’exécution de transactions concurrentes par un ou plusieurs utilisateurs tout en conser-
vant les propriétés de la BD.
– Assurer la sécurité des données :
– contrôler les accès en fonction de droits accordés aux différents utilisateurs.
– tolérer les pannes logicielles ou matérielles grâce à des procédures de reprise.
– Procurer l’indépendance physique : le SGBD permet de manipuler les données indépendemment
de leurs implantations matérielles.

2
– Procurer l’indépendance logique : chaque utilisateur ne voit de la base que les données qui lui
sont nécessaires (schéma externe).
– Le cœur d’un SGBD est le modèle de données qu’il supporte, c’est à dire la manière d’organiser
les données qu’il offre. Le modèle actuellement le plus utilisé est le relationnel inventé dans les
années 1970 dont une belle qualité est probablement la symétrie naturelle qu’il offre dans les
associations inter-données. Il existe d’autres modèle de données : hiérarchique, réseau et objet, qui
eux ne sont pas franchement symétriques.
– Fournir un langage de haut niveau adapté au modèle : SQL pour le modèle relationnel, CODASYL
pour le modèle réseau, OQL pour le modèle objet.
– Exemples de SGBD relationnels : Oracle, PostgreSQL, MySQL, Access et plein d’autres !

1.3 Les modèles de données


Un modèle de données est un formalisme permettant de :
– décrire les données (organisation, typage, ...)
– manipuler ces données.
Les deux principaux modèles :
Modèles à accès purement associatif Ce sont :
Relationnel années 1970, SQL1 1987, SQL2 1992
Déductif année 1980-1990, calcul des prédicats logiques du premier ordre, par exemple DA-
TALOG (à la Prolog)
La manipulation des données est déclarative : le programmeur n’a pas à se soucier du comment
mais seulement du quoi, par exemple : je veux la liste des clients dont les soldes sont positifs,
je n’ai pas à dire comment faire pour obtenir cette liste, c’est le SGBD qui s’en charge.
Modèles à accès Navigationnel Ce sont :
Fichiers avec chaı̂nage programme APOLLO 1965,
Hiérarchique fin des années 1960, utilisation de pointeurs permettant la navigation
Réseaux fin des années 1960, COSET
Orienté Objet années 1980-1990 (O2)
La manipulation des données est procédurale : en plus du quoi, le programmeur doit se
préoccuper du comment, par exemple : tant qu’il reste au moins un client, mettre le prochain
client dans la liste si son solde est positif.
Modèles hybrides On trouve des modèles hybrides qui disposent d’accès associatif et navigation-
nel : le relationnel-objet (SQL3 1999, Oracle, PostgreSQL).

1.4 Les niveaux d’abstraction


Pour assurer l’indépendance logique et l’indépendance physique, le groupe ANSI/X3/SPARC a défini
en 1975 trois niveaux de description d’une base de données :
– le schéma conceptuel, à ce niveau on définit la structuration et le typage des données. C’est le
domaine du concepteur de la base.
– Des schémas externes donnent différentes vues d’un même schéma conceptuel, chacun étant
approprié à un type d’utilisateur (SQL introduit la notion de vue et de privilège).
– le schéma interne qui définit les paramètres de stockage, les index favorisant certains accès. C’est
le domaine de l’administrateur/optimiseur.
Ce niveau est le dernier avant la représentation physique des données sur disque et en mémoire
centrale et qui est à la charge du SGBD.

3
1.5 Schéma et instances
Dans une BD, il y a un schéma qui décrit la structure des données et des données fournies y sont
mémorisées.
Le schéma d’une BD est le résultat de la conception (par exemple le MCD de Merise) qui décrit
l’organisation des données. Un schéma n’est pas destiné à être modifié (ou bien rarement).
Une instance d’un schéma correspond aux données stockées dans la base à un moment donné. Les
données d’une instance respectent évidemment l’organisation imposée par le schéma. Le contenu
d’une BD est éminemment variable : chaque modification de la BD produit une nouvelle instance du
schéma correspondant.
Exemple :
1. soit le schéma relationnel : Personne (idp, Nom, Prénom), et une instances possible de ce
schéma :
idp Nom Prénom
1 DURAND Gaston
2 DUPOND Jules
3 LAGAFFE Gaston
Chaque ligne correspond à une personne, le fait que idp est souligné indique que idp est la clef
primaire (primary key) devant avoir une valeur unique pour chaque ligne de la table.
2. le même schéma qui crée une table vide en SQL :
create table Personne (
idp Serial,
Nom Varchar (20),
Prenom Varchar (20),
constraint Personne_PK primary key (idp)
) ;
Serial est un générateur d’entiers (integer) qui commence à 1, idp est la clef primaire de la
table : deux lignes différentes de Personne ne pourront avoir la même valeur en idp.

1.6 Les différents langages côté serveur


1.6.1 DDL : Data Definition Language
Pour définir/modifier les schémas externes et le schéma conceptuel
– par exemple, pour le modèle relationnel, SQL propose la création de table :
create table Diplome (
id Serial,
mention Varchar (20),
constraint Diplome_PK primary key (id)
) ;

create table Etudiant (


id Serial,
nom Varchar (20),
prenom Varchar (20),
constraint Etudiant_PK primary key (id)
) ;
– il est possible de modifier le schéma qui ajoute une colonne et une contrainte à la table Etudiant :

4
alter table Etudiant
add column mon_diplome integer ;

alter table Etudiant


add constraint Etudiant_Diplome_FK
foreign key (mon_diplome) references Diplome (id) ;
Une foreign key est une référence à une ligne dans une autre table ou la même table (les colonnes
de la table référencée doivent être la clef primaire (primary key) de cette table). La clef primaire
de la table référencée doit exister (sinon erreur et aucune modification de la table référençante).
Si on tente de créer (avec insert) un étudiants avec un id de diplôme qui n’existe pas, il y aura
une erreur et l’étudiant ne sera pas créé.

– création d’une vue qui donne le nombre d’étudiants portant le même nom :
create view Nb_Homonymes (Nom, Nombre_D_Etudiants_Portant_Ce_Nom) as
select e.nom, count (*)
from Etudiant e
group by e.nom ;
Le group by crée un groupe de ligne des étudiants ayant le même nom, le count (*) compte le
nombre d’étudiants de chaque groupe.
– création d’une vue qui donne le nombre d’étudiants inscrits dans chaque diplôme :
create view Effectifs (id, mention, nb_etudiants) as
select d.id, d.mention, count (e.id)
from Diplome d
left outer join Etudiant e on e.mon_diplome = d.id
group by d.id, d.mention ;

1.6.2 DML : Data Manipulation Language


Permet de modifier le contenu de la base (insertion, mises à jour, suppression de données) et d’inter-
roger la base (langage de requête).
– par exemple, pour le modèle relationnel, SQL propose les instructions insert, update, delete et
la requête select.
– par exemple, pour le modèle objet, la norme ODMG propose OQL (Object Query Language) et
OML (Object Manipulation Language).

1.6.3 DCL : Data Control Language


Pour gérer les utilisateurs et leurs privilèges.
Par exemple en SQL Oracle :
CREATE USER <nom-utilisateur> ... (crée un utilisateur avec des options données)
CREATE ROLE <nom-role> ... (crée un r^
ole avec des options données)
DROP USER ... (supprime un utilisateur)
GRANT { { SELECT | INSERT | UPDATE | DELETE | REFERENCES | TRIGGER }
[,...] | ALL [ PRIVILEGES ] }
ON [ TABLE ] tablename [, ...]
TO { [ GROUP ] rolename | PUBLIC } [, ...] [ WITH GRANT OPTION ]
Un rôle peut concerner un ou plusieurs utilisateurs.

5
1.7 L’Architecture Client/Serveur
Très souvent le SGBD tourne sur une machine serveur plus ou moins dédiée, par contre les applicatifs
client tournent sur d’autres machines et doivent se connecter au SGBD via le réseau.

Il faut donc distinguer clairement entre ce qui doit tourner sur le serveur et ce qui doit tourner sur
le client.

1.7.1 Le code exécuté par le SGBD (le serveur)


Les ordres SQL select, insert, update, delete,
Les triggers réflexes déclenchés lors d’une modification des données, pour vérifier des contraintes
complexes, ou pour rendre la base de données plus autonome. (langage : PL/SQL d’Oracle,
ou PLPGSQL de PostgreSQL qui ressemblent tous deux fortement à Ada).
Les procédures stockées pour écrire des traitements complexes n’ayant de sens que s’ils sont
menés jusqu’à leur terme, par exemple une opération de virement d’un compte à un autre qui
nécessite deux opérations de mise à jour successives (2 update) (langage : PL/SQL d’Oracle,
ou PLPGSQL de Postgres qui ressemblent tous deux fortement à Ada).
Les SGBD proposent souvent leur propre langage de programmation : PL/SQL pour Oracle, plpgsql
pour PostgreSQL et le langage de MySQL.

1.8 Le code applicatif exécuté côté serveur et/ou client


Ce code est en général écrit dans un langage hôte : ce sont des langages classiques (Cobol, C, Ada,
Java, . . .) qui permettront d’écrire une application cliente complète, ou du code destiné à être exécuté
par le serveur.

Il y a deux possibilités pour utiliser le SGBD à partir d’un langage hôte :


API La première possibilité est de fournir une API plus ou moins spécifique au SGBD (ODBC, JDBC
pour Java, libpq pour C de Postgres, OCI pour Oracle, . . .), il suffit d’utiliser les primitives de
l’API dans un programme traditionnel.
SQL embarqué La seconde, de loin la plus agréable, repose sur une extension du langage hôte
permettant d’écrire et d’exploiter très naturellement des ordres du SGBD (des ordres SQL par
exemple, et on parle alors de SQL embarqué ou embedded SQL). Le programme obtenu doit être
traité par un préprocesseur, en général fourni par l’éditeur du SGBD, qui, entre autres choses,
remplace les ordres embarqués par des appels à une API spécifique. Le nouveau programme
obtenu est écrit dans le langage hôte d’origine et contient des appels à une API, on est alors
ramené à la première possibilité.
Exemples de préprocesseurs :
– Oracle : Pro*C/C++, Pro*COBOL, SQLJ,
– Postgres : ecpg pour PostgreSQL écrit en langage C,
– le projet GNADE : SQL embarqué dans du Ada 95, avec des API ODBC, PostgreSQL et
MySQL
Avec le développement de l’accès à des bases de données via le réseau Internet, de nombreux
environnements normalisés ou non existent. Par exemple l’environnement Hibernate qui tend à
rendre transparent au programmeur la persistance des objets stockés dans une base de données
(http://www.hibernate.org/).

6
Chapitre 2

Introduction à la conception d’une base


de données (MCD)

2.1 Un MCD d’hôpital


Dans un MCD un rectangle est une entité et une éclipse ou un cercle est une association entre deux
ou plusieurs entités.

Une entité peut avoir plusieurs attributs qui décrivent l’entitée.

Une association est paramétrée par un nombre d’associations possible, une association peut posséder
des attributs qui lui sont spécifiques.

Chambre 1,n
1 lieu 1
idc
1,n appartient nb_lits
numlit Sejour
Service Patient
idj 1 0,n
ids n 1 accueilli idp
soignant date_entree
nom nom
date_sortie

Fig. 2.1 – Voici un exemple de MCD décrivant la structure d’un hôpital avec ses chambres et ses
patients accueillis.

Q.1 Y a-t-il de la redondance d’information dans le MCD de la figure 2.1 page 7


Une association qui a un 1 et un n sera implantée par une clef étrangère dans l’entité du côté 1.
Voici une implémentation du MCD de l’hôpital :
create table Patient (
idp Serial,
nom varchar (30),
constraint Patient_PK primary key (idp)
) ; -- sequence "patient_idp_seq"
create table Service (
ids Serial,
nom varchar (30),
constraint Service_PK primary key (ids)

7
) ; -- sequence "service_ids_seq"
create table Chambre (
idc Serial,
nb_lits Int4,
service integer,
constraint Chambre_PK primary key (idc),
constraint Chambre_Service_FK foreign key (service) references Service (ids)
) ; -- sequence "chambre_idc_seq"
create table Sejour (
idj Serial,
date_entree Date,
date_sortie Date,
patient integer,
service integer,
chambre integer,
numlit int2,
constraint Sejour_PK primary key (idj),
constraint Sejour_Patient_FK foreign key (patient) references Patient (idp),
constraint Sejour_Service_FK foreign key (service) references Service (ids),
constraint Sejour_Chambre_FK foreign key (chambre) references Chambre (idc)
) ; -- sequence "sejour_idj_seq"
Remarquez que les tables référencées par une clef étrangère (foreign key) doivent exister avant que
les tables référençantes soient créées.
De même si une clef étrangère est insérée dans une table référençante la ligne référencée doit exister
(à moins que la clef étrangère soit indéfinie (is null). Un exemple où le patient est donné mais ni le
service ni la chambre ne sont donnés pour le séjour :
insert into Patient (nom) values (’durif’) ; -- idp = 1

select * from patient ;


idp | nom
-----+-------
1 | durif
(1 row)

insert into Sejour (date_entree, date_sortie, patient)


values (to_date(’23/10/2010’, ’DD/MM/YYYY’),
to_date(’03/11/2010’, ’DD/MM/YYYY’), 1) ;

select * from Sejour ;


idj | date_entree | date_sortie | patient | service | chambre
-----+-------------+-------------+---------+---------+---------
1 | 2010-10-23 | 2010-11-03 | 1 | |
(1 row)
Le service et la chambre sont indéfinis, le prédicat is null permet de le savoir :
select * from Sejour
where service is null and chambre is null;
idj | date_entree | date_sortie | patient | service | chambre
-----+-------------+-------------+---------+---------+---------
1 | 2010-10-23 | 2010-11-03 | 1 | |
(1 row)

8
Q.2 Écrire une requête qui donne le nombre de patient à une date donnée.
Pour supprimer la base de données, il faut d’abord détruire les tables référençantes :
drop table Sejour ;
drop table Chambre ;
drop table Service ;
drop table Patient ;

2.2 Un MCD des diplômes de l’université

Matiere UE Parcours Diplome


idm 1,n 1,n idue 1,n 1,n idp 1 1 idd
Composition Constitution Delivre
libelle semestre intitule nom
ects titre valide

Fig. 2.2 – Voici un exemple de MCD décrivant la structure des diplômes d’une université.

En revanche une association qui a un n et un n des deux côtés sera implantée par une table dont
chaque tuple aura deux clefs étrangères. Voici une implantation des diplômes :
create table Diplome (
idd Serial,
nom Varchar (20),
constraint Diplome_PK primary key (idd)
) ;
create table Parcours (
idp Serial,
intitule Varchar (20),
valide Boolean default FALSE not null,
idd Integer,
constraint Parcours_PK primary key (idp),
constraint Parcours_Diplome_FK foreign key (idd) references Diplome (idd),
constraint Parcours_Valide check (valide = ’n’ or valide = ’v’)
) ;
create table Matiere (
idm Serial,
libelle Varchar (20),
ects integer,
constraint Matiere_PK primary key (idm)
) ;
create table UE (
idue Serial,
semestre int4,
titre Varchar (20),
constraint UE_PK primary key (idue),
constraint UE_semestre check (1 <= semestre and semestre <= 10)
) ;
Le check garantit qu’un semestre est compris entre 1 et 10. Et par défaut un parcours est invalide.

9
Les associations en 1,n et 1,n :
create table Composition (
idue integer,
idm integer,
constraint Composition_PK primary key (idue, idm),
constraint Composition_UE_FK foreign key (idue) references UE (idue),
constraint Composition_Matiere_FK foreign key (idm) references Matiere (idm)
) ;
create table Constitution (
parcours integer,
idue integer,
constraint Constitution_PK primary key (parcours, idue),
constraint Constitution_Parcours_FK foreign key (parcours)
references Parcours (idp),
constraint Constitution_UE_FK foreign key (idue) references UE (idue)
) ;
Remarquez que les tables référencées par une clef étrangère (foreign key) doivent déjà exister.
Il faut d’abord détruire les tables référençantes (contenant des foreign key) :
drop table Constitution ;
drop table Composition ;
drop table UE ;
drop table Matiere ;
drop table Parcours ;
drop table Diplome ;
Q.3 Écrire une requête qui permet de voir que certaines UE ont plus de 5 ECTS.
Exercice 1 Sur les courses
Voici les trois tables :
create table Coureur (
id_coureur Serial constraint Coureur_PK primary key,
nom Varchar (20),
age Int2 default 0 check (age >= 0)
) ;
create table Course (
id_course Serial constraint Course_PK primary key,
date_course Date,
lieu Varchar (20),
age_minimum Int2 default 0 check (age_minimum > 0),
nbMaxCoureurs Int4 -- nombre maximum de coureurs
) ;
create table Inscription (
id_course Integer,
id_coureur Integer,
constraint Inscription_PK primary key (id_course, id_coureur),
constraint Inscription_Course_FK foreign key (id_course)
references Course (id_course),
constraint Inscription_Coureur_FK foreign key (id_coureur)
references Coureur (id_coureur)
) ;
Voici le garnissage des deux premières tables :

10
insert into Coureur (nom, age) values (’toto’, 20); --id_coureur=1
insert into Coureur (nom, age) values (’titi’, 20); --id_coureur=2
insert into Coureur (nom, age) values (’tete’, 13); --id_coureur=3
insert into Coureur (nom, age) values (’tata’, 20); --id_coureur=4
insert into Coureur (nom, age) values (’tutu’, 20); --id_coureur=4
select * from Coureur ;

\set DateStyle ’DMY’


insert into Course (date_course, lieu, age_minimum, nbMaxCoureurs)
values (CAST(’03/09/2010’ as Date), ’Lille’, 15, 3) ; -- id_course=1
insert into Course (date_course, lieu, age_minimum, nbMaxCoureurs)
values (CAST(’03/10/2010’ as Date), ’Paris’, 15, 3) ; -- id_course=2
select * from Course ;
Q.1 Donner le MCD correspondant aux tables précédentes
Q.2 Dans quel ordre peut-on détruire les trois tables ?
Une solution pour éviter qu’une personne ne puisse s’inscrire à une course si elle est trop jeune est
de mettre en place un trigger qui détectera cette erreur en empêchera l’inscription :

CREATE or replace
FUNCTION agerespecte()
RETURNS TRIGGER AS $agerespecte$
DECLARE
age_min Int2 ; age_courant Int2 ;
BEGIN
select age_minimum into age_min from Course where id_course = NEW.id_course ;
select age into age_courant from Coureur where id_coureur = NEW.id_coureur ;
if age_courant < age_min then
RAISE EXCEPTION ’Trop jeune : age coureur % < age min %’, age_courant, age_min;
end if;
return NEW;
END ;
$agerespecte$ LANGUAGE plpgsql;

CREATE TRIGGER agerespecte AFTER INSERT OR UPDATE ON Inscription


FOR EACH ROW EXECUTE PROCEDURE agerespecte();

insert into Inscription values (1, 1); -- OK


insert into Inscription values (1, 2); -- OK
insert into Inscription values (1, 3); -- ERREUR
ERROR: Trop jeune : age coureur 13 < age min 15

select * from Inscription ;


id_course | id_coureur
-----------+------------
1 | 1
1 | 2

On voit que le coureur numéro 3 n’est pas inscrit car il est trop jeune.
Le trigger traitera toute ligne ajoutée (insert) ou modifiée (update).

11
Chapitre 3

Création d’une table et ses contraintes


d’intégrité en SQL

DDL = Data Definition Language


Dès la déclaration d’une table on peut fixer un certain nombre de propriétés sur les valeurs que
peuvent prendre les attributs.

3.1 Création des tables


create table <nom-table>
( <liste-des-colonnes-et-contraintes-de-table> ) ;
create table Client (
idc Serial,
nom Varchar (20) constraint Client_Nom_Defini not null,
prenom Varchar (20),
solde Numeric (6, 2) default 0.0,
constraint Client_PK primary key (idc)
) ;

insert into Client (solde) values (100.55) ;


ERROR: null value in column "nom" violates not-null constraint
-- le idc Serial a été incrémenté

insert into Client (nom, solde) values (’Toto’, 100.55) ;


insert into Client (nom) values (’Titi’) ;

select * from client ;


idc | nom | prenom | solde
-----+------+--------+--------
2 | Toto | | 100.55
3 | Titi | | 0.00
La clause default n’est pas une contrainte, elle provoque simplement l’introduction de la valeur par
défaut lors d’un insert ne précisant pas de valeur explicite.

3.2 Les commentaires


Un commentaire est introduit par -- et se termine en fin de la ligne.

12
3.3 Les types de données
3.3.1 Le type booléen
PostgreSQL dispose du type BOOLEAN avec les valeurs TRUE et FALSE notée dans l’affichage
d’une requête par t ou f :
select idc, nom, solde = 100.55 as a_ce_solde
from Client ;

idc | nom | a_ce_solde


-----+------+------------
2 | Toto | t
3 | Titi | f
En PostgreSQL c’est l’absence de valeur is null qui dit que la colonne n’a pas de valeur, on a aussi
is not null qui est vrai si la colonne a une valeur.

Les types définis par la norme ne sont malheureusement pas toujours respectés.

3.3.2 Types numériques


Numeric : possiblement des entiers ou des réels, et Real
Real est un réel de 4 octets.
Numeric (precision, scale) le nombre de chiffres décimaux est donné par precision, scale cor-
respond au nombre maximum de chiffre après la virgule :
– si scale est positif on a scale chiffres décimaux après la virgule, donc le nombre de chiffres avant
la virgule est de precision - scale. chiffres décimaux.
– si scale vaut 0 ou est absent on a un entier de precision chiffres décimaux.
create table Bof (prix Numeric (5, 2)) ; -- un réel avec 2 chiffre après le point
insert into Bof values (111.55) ;
select * from Bof ;
prix
--------
111.55
create table BofBof (prix Numeric (5, 0)) ; -- un entier
insert into BofBof values (11155) ;
select * from BofBof ;
prix
-------
11155
create table BofBofBof (prix Numeric (5)) ; -- un entier
insert into BofBofBof values (11155) ;
select * from BofBofBof ;
prix
-------
11155
insert into BofBofBof values (111555) ; -- trop de chiffres
ERROR: numeric field overflow
DETAIL: The absolute value is greater than or equal to 10^5 for field
with precision 5, scale 0.

13
– Numeric (p) nombre entier, qui signifie Numeric (p, 0)

Int8, Int4, Int2, SmallInt, Integer, Bigint


Il s’agit d’entiers :
– SmallInt et Int2 utilisent 2 octets.
– Integer Int4 utilisent 4 octets.
– Bigint Int8 utilisent 8 octets.

Serial
Une colonne de table ayant le type Serial se voit générer un compteur qui sera incrémenté à partir
de 1 lors de chaque insertion (insert).

Serial peut être bien pratique pour la génération de clef primaire : le type Serial correspond à un
compteur qui sera incrémenté à chaque insertion, sa première valeur sera 1 :
create table Jouet (
idj Serial,
nom Varchar (20),
constraint Jouet_PK primary key (idj)
) ;
insert into Jouet (nom) values (’Cheval’) ;
insert into Jouet (nom) values (’Cartes’) ;
select * from Jouet ;
idj | nom
-----+--------
1 | Cheval
2 | Cartes

3.3.3 Types chaı̂nes de caractères


Pour PostgreSQL
Varchar (n) chaı̂nes de taille variable et de longueur inférieure ou égale à n.
Character (n) de taille forcément égale à n ou remplie d’espace si elle est trop courte.
Text une chaı̂ne de longueur quelconque.
Char Length (<chaı̂ne>) une fonction qui donne la longueur de la chaı̂ne.

3.3.4 Types temporels


Pour PostgreSQL
– Date = année-mois-jour
On dispose des fonctions :
– la fonction To date(text, text), par exemple to_date(’05 12 2010’, ’DD MM YYYY’) :
select to_date(’05 12 2010’, ’DD MM YYYY’)
from client
where idc = 2;

to_date
------------
2010-12-05

14
– La fonction Current date donne la date courante du système.
– La fonction Current time donne l’heure courante du système avec le type time.

3.4 Les contraintes


Déclarées à la création de la table, puis vérifiées automatiquement par le SGBD :
– programmation allégée
– sécurité plus forte
PostgreSQL vérifie les contraintes lors de l’exécution d’une instruction DML (de modification)
complètement terminée, si elle n’est pas respectée la modification est annulée.
Si une contrainte n’est pas vérifiée en fin d’instruction DML, il y a annulation de la mise à jour
avec message d’erreur. Plus précisément, la table est remise dans l’état dans lequel elle était avant
le début de l’instruction DML (fonctionnement en tout ou rien).

3.4.1 Baptisez vos contraintes !


Chaque contrainte peut être baptisée (et on a toujours intérêt à le faire), elle pourra ensuite être
manipulée facilement par certaines commandes simplement en donnant sont nom.
Le nom d’une contrainte est donné après le mot-clef constraint :
constraint <nom-de-la-contrainte> <définition-de-la-contrainte>
La contrainte sera alors vérifiée en fin de l’instruction modifiant la table (insert, update ou delete).

3.4.2 Aspects syntaxiques


SQL distingue deux syntaxes pour décrire les contraintes : les contraintes de colonnes et les contraintes
de table.

Syntaxe contrainte de colonne Syntaxe contrainte de table


Une contrainte de table peut porter sur plusieurs
colonnes, elle est indiquée comme un élément de la
liste des colonnes de la table :

Une contrainte de colonne porte sur exactement create table Commande (


une colonne (par exemple la contrainte not produit Numeric (5),
null) est indiquée au moment de la déclaration client Numeric (5),
de la colonne et on peut en mettre plusieurs : quantite Numeric (5) default 0,
constraint Commande_PK
create table Produit ( primary key (produit, client),
id Serial constraint Commande_Produit_FK
constraint Produit_PK primary key, foreign key(produit) references Produit(id),
nom Varchar (10), constraint Commande_Client_FK
stock Numeric (5) default 0 foreign key(client) references Client(idc),
constraint Produit_stock_defini constraint Quantite_Positive
not null, check (quantite >= 0)) ;
constraint Stock_Positif
check (stock >= 0)) ; Le default introduit une valeur d’initialisation
des colonnes stock de Produit et quantite de
Deux contraintes portent sur la colonne stock. Commande quand on ne leur donne pas de valeur.
Fig. 3.1 – Les deux manières de déclarer des contraintes

15
La seule contrainte qui ne peut être décrite qu’en tant que contrainte de colonne est not null car
elle qualifie toujours une seule colonne.

Une autre contrainte exprimable dans les deux syntaxes est primary key pouvant s’appliquer à
plusieurs colonnes. Chaque valeur de la clef primaire doit être unique et définie dans la table contenant
ces lignes.
Lors de la création (insert) d’une commande il faut que le produit référencé par la clef étrangère
produit et le client référencé par la clef étrangère client existent déjà, sinon il y aura une erreur et
l’insertion ne sera pas faite. Lors d’un update on aura le même comportement si produit ou client
n’existe pas dans leur table.

3.4.3 Liste des contraintes


not null l’attribut doit toujours avoir une valeur définie, c’est la seule contrainte qui ne peut
s’écrire qu’en contrainte de colonne.
default <expression> : donne la valeur de son expression à la colonne si la valeur donnée est
indéfinie (is null).
primary key Aucune des colonnes de la clef primaire ne peut être indéfinie et chaque ligne
doit avoir des valeurs différentes (PostgreSQL crée un index unique pour cette contrainte).
unique Sur une colonne ou un groupe de colonnes dont la valeur, quand elle est définie, doit être
unique dans la table (PostgreSQL crée un index unique pour cette contrainte). Des colonnes de
unique peuvent être indéfinies. Par exemple si on pose la contrainte unique (formation, rang)
les deux couples (1, 23) et (1, 24) sont bien distincts, en revanche (1, null) et (1, null)
seront considérés par PostgreSQL comme des valeurs différentes. L’unicité, par exemple (null, null)
et (null, null) sont considérés comme différents.
foreign key et une référence sur un élément d’une autre table, les colonnes référencées doivent
être soit une clef primaire soit unique. Si lors d’un insert (ajout d’une ligne) ou d’un update
(mise à jour d’une ligne existante la clef n’existe pas dans la table référencée, la modification
échouera).
check vérifie que la condition donnée à droite est vraie, sinon on a un échec de l’insert ou du
update.

La contrainte unique
Cette contrainte admet que des colonnes uniques puissent être indéfinies (is null). Si les colonnes
sont définies elles doivent être uniques dans la table, sinon une erreur sera provoquée.

La syntaxe est unique(nom_col_1{, nom_col_autre}).

create table X (
L’exemple de droite garantit
a Numeric (5), b Numeric (5), c Numeric (5),
qu’on ne pourra pas avoir deux
constraint X_PK primary key (a),
fois les mêmes valeurs de b et c
constraint Unicite Unique (b, c)
à cause de la contrainte unique.
) ;
En revanche si b et c sont indéfinis sur plusieurs lignes, ces lignes seront acceptées.
Les autres contraintes peuvent être décrites indifféremment en tant que contrainte de colonne ou
contrainte de table ce sont unique, primary key, foreign key, check et not null.

– PostgreSQL respecte la norme SQL, c’est à dire qu’il considère (1, null) et (1, null) comme
distincts.

16
create table T (
formation int4,
rang int4,
constraint Unicite unique (formation, rang)
) ;
insert into T values (1, null) ;
insert into T values (1, null) ; -- accepté
insert into T values (null, null) ;
insert into T values (null, null) ; -- accepté
insert into T values (null, null) ; -- accepté ...

select * from T ;
formation | rang
-----------+------
1 |
1 |
|
|
|
On a bien 5 lignes en postgreSQL.
– check prédicat portant sur les colonnes d’un même nuplet
check (qte >= 0)
check (date_debut <= date_fin)
check (couleur IN (’BLANC’, ’VERT’, ’ROUGE’))

create table Couleur (


c varchar (20),
constraint autorise check (c in (’BLANC’, ’VERT’, ’ROUGE’))
) ;
insert into Couleur values (’VERT’) ;
insert into Couleur values (’vert’) ; -- erreur, pas d’insertion
insert into Couleur values (null) ; -- OK car peut ^
etre indéfini

select * from Couleur ;


c
------
VERT

(2 rows)
Restrictions PostgreSQL : le prédicat doit porter uniquement sur la valeur de la ligne courante, on
ne peut pas y mettre une sous-requête.
Si la condition de check est vraie ou is null (présomption d’innocence) la propriété est considérée
comme respectée et la mise à jour est acceptée.
Q.3 À votre avis, le delete provoque-t-il la vérification des contraintes not null et check ?
Q.4 Ce même delete a-t-il des vérifications à faire quand il y a des contraintes primary key et
unique et si la table est référencée par une clef étrangère (foreign key). Lesquelles ?
Si la condition d’un check s’évalue à UNKNOWN alors la contrainte est considérée comme satisfaite.

Par exemple :

17
check (salaire > 0 or (salaire = 0 and commission > 0))

Q.5 Montrer que si salaire is null la mise à jour est acceptée quel que soit l’état de commission.

L’idée est qu’on ne peut pas empêcher la création d’un nuplet en l’absence d’information (présomption
d’innocence).
Q.6 Si commission n’est pas définie, le salaire peut-il être négatif ?
Q.7 Corriger la contrainte pour garantir que le salaire et la commission ne sont jamais négatifs
(une idée consiste à utiliser l’opérateur is null, une autre idée à mettre plusieurs check).

Définition de nouveaux domaines en PostgreSQL


En SQL2 et PostgreSQL oui, mais pas en Oracle :
create domain Quantite Integer default 0 check (value >= 0) ;

create table Q (qte_produit Quantite) ;


insert into Q values (5) ;
insert into Q values (-1) ; --erreur : non insertion
ERROR: value for domain quantite violates check constraint "quantite_check"
select * from Q ;
qte_produit
-------------
5
Un exemple de domaine en PostgreSQL :
create domain Couleurs_Additives
as Text default ’bleu’
constraint Couleurs_Additives_CHK
check (upper (value) in (’ROUGE’, ’VERT’, ’BLEU’)) ;

-- liste des domaines avec la commande suivante : \dD


create table T (id int primary key, c Couleurs_Additives) ;
select * from T ;
insert into T (id) values (1) ;
insert into T (id, c) values (2, ’vert’) ;
insert into T (id, c) values (3, ’ROUGE’) ;
insert into T (id, c) values (4, ’verte’) ; -- echec
select * from T ;
id | c
----+-------
1 | bleu
2 | vert
3 | ROUGE

3.4.4 Contraintes d’intégrité d’entité : clef primaire


Il s’agit des clefs primaires
create table Contient (
commande Numeric (4),
produit Numeric (4),
quantite Numeric (4),

18
constraint Contient_PK primary key (commande, produit),
constraint Quantite_Positive check (quantite > 0)
) ;
insert into contient values (1, 2, 0);
ERROR: new row for relation "contient" violates check constraint "quantite_positive"

Les colonnes de la clef primaire doivent être définies et les clefs primaires forment un ensemble (unicité
de chaque valeur de clef primaire qui peut-être constituée de plusieurs colonnes).
Sous PostgreSQL (et d’autres), un index unique est automatiquement créé sur la clef primaire, il
prend le nom de la contrainte (Produit_PK dans l’exemple).

Table sans clef primaire


En théorie, une table sans clef primaire peut contenir la même valeur sur plusieurs lignes :
En PostgreSQL il est possible de définir une table sans clef :
create table Sans_Clef (num Int4) ;
insert into Sans_Clef values (1) ;
insert into Sans_Clef values (1) ;
select * from Sans_Clef ;
num
-----
1
1
(2 rows)
et on pourra y insérer plusieurs nuplets de même valeur.

3.4.5 Contraintes d’intégrité référentielle : clef étrangère


create table Etudiant (
id Serial,
nom Varchar (20),
constraint Etudiant_PK primary key (id)
) ;
create table Note (
note Numeric (5, 2) not null,
etudiant Integer,
constraint Note_Etud_FK foreign key (etudiant) references Etudiant (id)
) ;
Le fait que la colonne Note.etudiant est une clef étrangère implique que la table Note dépend de la
table Etudiant. Autrement dit la table Note ne peut être créée que quand la table Etudiant existe.
Ensuite la table Etudiant ne pourra être détruite avec drop table car la table Note en dépend.
Considérons une ligne de la table Note :
– si sa colonne etudiant est définie, il doit exister exactement une ligne de Etudiant dont le id doit
exister dans une ligne de Etudiant.
L’unicité de Etudiant.id est garantie puisque c’est justement la clef primaire.
– si sa colonne etudiant est indéfinie (is null), c’est qu’elle ne référence aucune ligne de Etudiant
et l’insert ou l’update sera accepté.
La colonne Note.etudiant est alors appelée une clef étrangère, on peut aussi la comprendre comme
un pointeur associatif qui n’est pas une adresse mémoire mais une valeur permettant de retrouver la
ligne désignée de la table Etudiant. Par exemple la table Etudiant étant vide :

19
insert into note values (12.5, 1);
ERROR: insert or update on table "note" violates foreign key constraint "note_etud_fk"
DETAIL: Key (etudiant)=(1) is not present in table "etudiant".

Une conséquence du exactement une ligne de la table Etudiant est que la colonne id doit garantir
l’unicité des lignes de Etudiant : id doit soit être une clef primaire soit supporter une contrainte
d’unicité (unique).

Une clef étrangère peut-être constituée de plusieurs colonnes : ces colonnes ne référencent une ligne
que si elles toutes définies.

Une table peut se référencer elle-même :


create table Employe (
id Numeric (4),
nom Varchar (20) constraint nom_not_null not null,
superieur Numeric (4),
constraint Employe_PK primary key (id),
constraint Employe_Superieur_FK
foreign key (superieur) references Employe (id)
) ;
insert into Employe values (1, ’Toto’, null);
insert into Employe values (2, ’Titi’, 1) ;
select * from Employe ;

-- Les supérieurs
select chef.*
from Employe e
inner join Employe chef on chef.id = e.superieur ;
id | nom | superieur
----+------+-----------
1 | Toto | -- Toto n’a pas de supérieur
Remarquez que dans select on a écrit chef.* ce qui ne donne que les informations sur le supérieur.

Voici ce qu’on obtiendrait si on avait noté * :


select *
from Employe e
inner join Employe chef on chef.id = e.superieur ;
id | nom | superieur | id | nom | superieur
----+------+-----------+----+------+-----------
2 | Titi | 1 | 1 | Toto |
Une clef étrangère doit référencer une clef primaire.
Suivent quelques manipulations dont certaines sont erronées.

On peut noter un étudiant non défini !


insert into Note (etudiant) values (13) ;
ERROR: null value in column "note" violates not-null constraint

On ne peut pas noter un étudiant qui n’existe pas


insert into Note (note, etudiant) values (13, 111) ;

20
ERROR: insert or update on table "note" violates foreign key constraint "note_etud_fk"
DETAIL: Key (etudiant)=(111) is not present in table "etudiant".

On ne peut pas modifier la clef cible d’un étudiant noté


insert into Etudiant (nom) values (’Dupont’) ; -- id=1
select * from Etudiant ;
insert into Note values (12.5, 1) ;
select * from Note ;
update Etudiant set id = 666 where nom = ’Dupont’ ;
ERROR: update or delete on "etudiant" violates
foreign key constraint "note_etud_fk" on "note"
DETAIL: Key (id)=(1) is still referenced from table "note".
insert into Etudiant values (666, ’Grand’) ;
select * from Etudiant ;
id | nom
-----+--------
1 | Dupont
666 | Grand

Cela posera un problème car la valeur de Serial de id finira par atteindre la valeur 666.

Modification de contrainte pour propager la mise à jour


Possible en PostgreSQL.

On ne peut pas supprimer un étudiant noté


delete from etudiant where id = 666 ;
-- OK car 666 n’a pas de note
select * from Etudiant ;

delete from etudiant where id = 1 ;


ERROR: update or delete on "etudiant" violates foreign key constraint "note_etudiant_fk"
DETAIL: Key (id)=(1) is still referenced from table "note".

3.4.6 Clef étrangère et modifications de la table maı̂tre


SQL permet de maintenir automatiquement la cohérence des clefs étrangères lorsqu’on modifie la
table référencée (ou table maı̂tre).
Pour cela il propose un certain nombre de comportements, qui ne sont pas tous implémentés par
Oracle :

SQL Commentaire Oracle (10.2) PostgreSQL


on delete|update no action Modification interdite (échec de par défaut par défaut
(par défaut) l’instruction).
on delete cascade Suppression propagée : les nuplets oui oui
référençant sont supprimés
on update cascade Modification propagée. non oui
on delete|update set null La référence devient indéfinie. oui oui
on delete|update set default La référence est remise à sa valeur non oui
par défaut.

21
Un tel comportement est indiqué lors de la déclaration d’une clef étrangère, ainsi on peut avoir des
clefs étrangères ayant la même cible et n’ayant pas le même comportement. Ces comportements sont
des compléments optionnels à ajouter à la définition d’une clef étrangère.

Redéfinition de contrainte pour propager la suppression on delete cascade figure 3.1


alter table Note drop constraint Commande_Produit_FK ;
alter table Note add (constraint Commande_Produit_FK foreign key (produit)
references Produit (id) on delete cascade) ;

select n.note, Coalesce (e.nom, ’anonyme’) as nom


from Note n
left outer join Etudiant e on n.etudiant = e.id ;
NOTE NOM
13 Dupont
10 Dupont
13 anonyme
delete from etudiant where e.nom is null ; -- OK
select n.note, Coalesce (e.nom, ’inconnu’) as nom
from Note n
left outer join Etudiant e on n.etudiant = e.id ;
NOTE NOM
13 Dupont
10 Dupont

3.5 Modification du schéma : alter table


alter table permet :
ajouter/supprimer/modifier la définition d’une colonne
ajouter/supprimer des contraintes

3.5.1 Ajouter, Modifier ou Supprimer une colonne ou une contrainte :


alter table
alter table <nom> add <colonne-ou-contrainte> {, <colonne-ou-contrainte>} ;
alter table <nom> modify <colonne> {, <colonne>} ;
alter table <nom> drop <colonne-ou-contrainte> ;

Ajouter une ou plusieurs colonnes et contraintes : add (...)


create table Client (id Numeric (5)) ;

alter table Client add nom Varchar (20) constraint nom_defini not null ;
insert into client (id) values (1) ;
ERROR: null value in column "nom" violates not-null constraint
insert into client values (1, ’Toto’) ;
select * from Client ;
id | nom
1 | Toto

22
alter table Client add tel Varchar (10) constraint tel_unique unique ;
alter table Client add habitation Varchar (15) default ’Lille’ ;
alter table Client add solde Numeric (10, 2) ;
alter table Client add constraint Client_PK primary key (id) ;

insert into Client (id, nom) values (1, ’Toto’) ;


insert into Client (id, nom) values (1, ’Titi’) ; -- pas d’insertion :
ERROR: duplicate key violates unique constraint "client_pk"

select * from Client ;


id | nom | tel | habitation | solde
1 | Toto | | Lille |

Suppression de colonne et/ou contraintes : drop


Suppression d’une contrainte nommée Suppression d’une colonne
select * from client;
alter table Client drop constraint tel_unique ; id | nom | habitation | solde
alter table Client drop column tel ; ----+------+------------+-------
1 | Toto | Lille |

3.5.2 Suppression d’une Table


Le problème des dépendances dues aux clef étrangères :
create table Maitre (
idm Numeric (3) constraint Maitre_PK primary key) ;
create table Esclave (
ide Numeric (3) constraint Esclave_PK primary key,
maitre Numeric (3),
constraint Esclave_Maitre_FK foreign key (maitre) references Maitre (idm)
) ;
La suppression
drop table Maitre ; -- échec de destruction !
NOTICE: constraint esclave_maitre_fk on table esclave depends on table maitre
ERROR: cannot drop table maitre because other objects depend on it
HINT: Use DROP ... CASCADE to drop the dependent objects too.
La contrainte de clef étrangère Esclave_Maitre_FK empêche la destruction de la table Maitre.
La destruction de la table Maitre échoue si la table est référencée par des clefs étrangères (même si
elle est vide).
Voici la syntaxe : drop table <nom-de-la-table>
Effets :
– enlève la définition de la table du dictionnaire,
– tous les index et triggers associés sont détruits,
– les vues qui utilisent la table détruites doivent être détruites avant la destruction de la table.
– la place occupée par la table est restituée.
– Le drop table échoue si la table est référencée par une clef étrangère d’une autre table et son
contenu est inchangé.

3.5.3 drop table ... cascade


Remarquez que la suppression avec cascade détruit

23
drop table Maitre Cascade ;
NOTICE: drop cascades to constraint Esclave_Maitre_FK on table esclave
-- La table Maitre a disparue ainsi que la contrainte Esclave_Maitre_FK

Un exemple de création de table avec erreur d’insertion


En PostgreSQL :
create table Ville (
idv int4,
nom Varchar (50),
departement int4,
population int4 default 0,
constraint Ville_PK primary key (idv),
constraint Ville_Dpt_Intervalle check (departement between 1 and 100),
constraint Ville_Pop_Val check (0 <= population)
) ;
insert into ville (idv, nom, departement) values (1, ’Paris’, 75);
insert into ville (idv, nom, departement) values (1, ’Lyon’, 69);
ERROR: duplicate key violates unique constraint "ville_pk"
insert into ville (idv, nom, departement) values (2, ’Limoges’, 169);
ERROR: new row for relation "ville" violates check constraint "ville_dpt_intervalle"
select * from Ville ;
idv | nom | departement | population
1 | Paris | 75 | 0
Cet ordre create crée la table Ville dont le schéma, décrit entre les parenthèses, est composé de
quatre attributs et comporte aussi des contraintes permettant de garantir les propriétés :
– constraint Ville_PK primary key (idv) garantit que deux lignes de Ville auront toujours
une valeur définie et différente pour la colonne idv. De façon plus consise on dit que idv est la
clef primaire de Ville. La tentative d’ajouter dans la table Ville une ville dont idv existe déjà
dans une ligne de Ville échouera et la valeur de Ville sera inchangée.
– constraint Ville_Dpt_Intervalle check (departement between 1 and 100) garantit que la
colonne departement aura une valeur comprise entre 1 et 100 si elle est définie. La tentative d’ajou-
ter dans la table Ville une ville dont departement vaut 169 échouera et la valeur de Ville sera
inchangée.
– default 0 donne par défaut la valeur 0 à population.
– constraint Ville_Pop_Val check (0 <= population) garantit que que la colonne population
aura une valeur positive ou nulle quand elle est définie : la tentative d’ajouter dans la table Ville
une ville à population négative échouera et la valeur de Ville sera inchangée.
Une table SQL ressemble à une variable relationnelle mais avec quelques différences dont la première
est importante :
– la valeur d’une variable relationnelle ne peut pas comporter plusieurs fois le même n-uplet alors
qu’une table — sauf si on pose explicitement une contrainte de clef primaire — peut comporter
plusieurs lignes identiques, sauf les clefs primaires !
– un élément d’une relation s’appelle un n-uplet, alors qu’un élement d’une table s’appelle une ligne
(ou row en anglais).
– il est possible en SQL qu’une colonne n’ait pas de valeur, on dit qu’elle est indéfinie et cela se teste
avec l’opérateur booléen is null. En revanche cela n’aurait pas de sens pour une relation car cela
correspondrait à un n-uplet auquel il manque un attribut, ce qui n’aurait pas de sens en théorie.

24
Chapitre 4

Implantation d’un MCD

4.1 Passage du modèle Entité Association au modèle rela-


tionnel
Chaque entité est traduite par une table ayant les attributs et la clef de l’entité.
Pour les associations, on a plusieurs cas :
1-1 Soit on fusionne les 2 entités en une seule table, soit on conserve deux tables en recopiant la
clef d’une des deux dans l’autre en tant que clef étrangère ou de façon plus symétrique chaque
table reçoit la clef de l’autre ; les attributs de l’association sont mis dans une des deux tables.
1-n On copie dans la table fille (celle qui participe à au plus une association) la clef de la table
parente en tant que clef étrangère ainsi que les attributs de l’association.
La table parente peut être référencée par plus d’une fille.
n-n L’association est traduite par une nouvelle table dont la clef est formée des clefs des différentes
entités liées par cette association. Cette table contient aussi les attributs éventuels de l’asso-
ciation.
En résumé, le MCD :

Ville Client Commande Produit


idv 1,n 1 idc 0,n 1 idcom 1,n Pour 0,n idp
Habite Passe quantité
nom nom la_date nom
departement prix
population

sera traduit dans les relations :


Table Ville (idv, nom, departement, population)
Table Client (idc, nom, ville →Ville.idv)
Table Produit (idp, nom,

prix)
Table Commande (idcom,
 client →Client.idc,la date)
Association Pour (commande →Commande.idcom, produit →Produit.idp, quantite)
 

Où :
– c1,
 ...,ck est une clef primaire  
– ville; , client , commande  et produit sont des clefs étrangères désignant un nuplet d’une
  
autre table, par exemple la colonne client  d’une commande doit soit être égale à la colonne
id d’au plus un des clients soit être indéfinie. On peut comprendre une clef étrangère comme un
pointeur associatif. Une clef étrangère peut bien entendu être constituée de plusieurs colonnes, dans
ce cas elle référence autant de colonnes de la table référencée. Puisque pas plus d’une ligne de la

25
table référencée ne doit être désignée par une clef étrangère, il faut que les colonnes référencées par
une clef étrangères garantissent l’unicité des lignes : elles doivent soit être la clef primaire de la table
référencée soit être l’objet de la contrainte unique. Ces règles sont bien entendu appliquées par
Oracle et PostgreSQL et certainement beaucoup d’autres SGBD. Depuis sûrement assez longtemps
MySQL accepte la syntaxe de déclaration de clef étrangère, il n’en assure la sémantique que depuis
sa version 6 et uniquement dans InnoDB.
Ces deux notations se traduisent en SQL par des contraintes exprimées lors de la création des tables
correspondant aux relations, par exemple :
create table Ville (
idv Serial,
nom Varchar (20),
departement Numeric (3),
population Numeric (10),
constraint Ville_PK primary key (idv)
) ;
create table Client (
idc Serial,
nom Varchar (20),
ville Numeric (3),
constraint Client_PK primary key (idc),
constraint Client_Ville_FK foreign key (ville) references Ville (idv)
) ;
create table Commande (
idcom Serial,
client Integer,
la_date Date,
constraint Commande_PK primary key (idcom),
constraint Commande_Client_FK foreign key (client) references Client (idc)
) ;
create table Produit (
idp Serial,
nom Varchar (20),
prix Numeric (10, 2), -- 2 indique 2 chiffres après la virgule
constraint Produit_PK primary key (idp)
) ;
create table Pour (
commande Integer,
produit Integer,
quantite Numeric (5),
constraint Pour_PK primary key (commande, produit),
constraint Pour_Commande_FK foreign key (commande) references Commande (idcom),
constraint Pour_Produit_FK foreign key (produit) references Produit (idp)
) ;
L’ordre de création des tables n’est pas quelconque : une table référençante ne peut être créée que si
les tables qu’elle référence l’ont déjà été.
Q.8 Combien aura-t-on de chiffres avant la virgule pour le prix d’un Produit.
Q.9 Trouver un autre ordre de création en SQL des cinq tables ?
Q.10 Écrire une requête qui donne le nombre de commandes par client.

26
Q.11 Écrire l’implantation en tables du MCD suivant :

0,n 0,1
Localite Camion

Produit cdm
cdl 0,n 0,1 Envoi 0,n
cdp
0,n quantité 0,n Client
ville libellé
0,n cdc
dpt nom
0,1

Q.12 Voici le MCD du parc de voitures empruntables dans une entreprise, donnez-en une implan-
tation :

Voiture Réservation
idr
idv 0,n 1 1
De jourDebut Reserve
marque jourFin 0,n Employe
nbPlaces Service
0,1 1 1,n
ide Dans ids
PrévuPar 0,n
nom libelle
1
Emprunt Emprunte
1
jourDebut
jourFin

27
Chapitre 5

SQL/DML les ordres de modification du


contenu des tables : insert update delete

SQL signifie Structured Query Language


SQL = {DDL, DML, DCL}
DML = Data Manipulation Language

5.1 insert : ajout de nouvelles lignes


Pour ajouter de nouvelles lignes.
insert into <nomTable> [(col1, ..., coln)] values (val1, ..., valn) ;
ou
insert into <nomTable> [(col1, ..., coln)] <requete> ;
[(col1, ..., coln)] est optionnel à cause des crochets [].

Les colonnes non mentionnées dans (col1, ..., coln) sont indéfinies ou ont leur valeur par défaut
ou sont indéfinies (motclefis null).

On peut donner l’ordre des colonnes de (col1, ..., coln) dans n’importe quel ordre mais à condi-
tion de respecter cet ordre dans (val1, ..., valn).

Exemple :
– Insertion d’une ligne en explicitant la valeur de toutes les colonnes dans l’ordre de leurs déclarations :
insert into Client values (4, ’Durif’, ’Philippe’, 300) ;

On peut explicitement indiquer qu’une colonne n’est pas définie (is null) en mettant null pour
signifier l’absence de valeur.
– Insertion d’une ligne en explicitant les valeurs d’un sous-ensemble des colonnes de la table :
insert into Client (num_client, nom, prenom) values (5, ’Durif’, ’Pablo’) ;

Les colonnes non mentionnées seront indéfinies ou bien auront leur valeur par défaut éventuellement
indiquée lors de la création de la table (default).
– Insertion dans Client du résultat d’une requête donnant des employés ayant un salaire > 1000 :
insert into Client (num_client, nom, prenom)
select ref, nom, prenom
from Employe
where salaire > 1000 ;

28
Le mot clef default peut être utilisé en tant que valeur d’une colonne et indique que la colonne doit
prendre sa valeur par défaut si on ne lui donne pas de valeur (voir create table section 3.1 page 12)
ou être indéfinie si elle n’a pas de valeur par défaut.

5.2 update : la mise à jour de lignes existantes


Pour modifier des lignes existantes.
update <nomTable>
set affectation {, affectation}
[where condition] ;

affectation ::= colonne = expression


| (col1, ..., colp) = (sous-requ^
ete-1-ligne-p-colonnes)

Attention : la sous-requête éventuelle ne doit pas porter sur la table en cours de modification sinon
on aura une erreur de table mutante.
Dans l’expression à droite de =, solde a l’ancienne valeur du client.
Exemple, augmentation du solde des clients ayant un numéro inférieur à 4 :
update Client set solde = solde + 100 where num_client < 4 ;

L’ancien solde des clients dont le num_client est inférieur à 4 est utilisé dans l’expression solde + 100.

Exemple avec une liste de colonnes :


create table Departement (
deptno Numeric (5) primary key,
prefecture Varchar (10) not null unique
) ;
create table Employe (
id Numeric (5) primary key,
deptno Numeric (5),
salaire Numeric (10, 2),
commission Numeric (10, 2),
constraint Employe_Departement_FK foreign key (deptno)
references Departement (deptno) -- clef étrangère
) ;

On veut déplacer sur Paris les employés des départements de Lille et Lyon en doublant leurs salaires
et en leur accordant une commission de 500 euros.

Voici un exemple :
insert into Departement values (59, ’Lille’) ;
insert into Departement values (69, ’Lyon’) ;
insert into Departement values (75, ’Paris’) ;
select * from Departement ;
insert into Employe values (1, 59, 2000, 0) ;
insert into Employe values (2, 69, 3000, 0) ;

29
On regarde le contenu complet de Employe :
select * from Employe ;
id | deptno | salaire | commission
----+--------+---------+------------
1 | 59 | 2000.00 | 0.00
2 | 69 | 3000.00 | 0.00

update Employe
set salaire = 2 * Employe.salaire,
commission = 500.0,
deptno = (select d.deptno from Departement d where d.prefecture = ’Paris’)
where deptno in(select deptno from Departement
where prefecture in(’Lille’,’Lyon’));
select * from Employe ;
id | deptno | salaire | commission
----+--------+---------+------------
1 | 75 | 4000.00 | 500.00
2 | 75 | 6000.00 | 500.00
drop table Employe ;
drop table Departement ;

5.3 delete : suppression de lignes existantes


delete from <nomTable> [where condition] ;

Supprime les lignes pour lesquelles le condition est vraie.

Exemple suppression des clients ayant un numéro num_client égal à 2 ou 5 :


delete from Client where num_client in (2, 5) ;

Suppression de tous les clients :


delete from Client ; -- vide la table

Si un client est référencé par une clef étrangère, la suppression du client échouera et tous les clients
continueront d’exister.

30
Chapitre 6

Le modèle relationnel et SQL

Inventé par E.F. Codd en 1970, chez IBM.

Ce modèle est lié à la théorie des ensembles (unicité des éléments, sous-ensemble, produit cartésien,
. . .)

Une de ses réalisations pratiques : SQL (Structured Query Language).

Historique
– 1970, Codd invente l’algèbre relationnelle,
– 1972 à 1975 IBM invente SEQUEL puis SEQUEL/2 en 1977 pour le prototype SYSTEM-R de
SGBD relationnel
– SEQUEL donne naissance à SQL
– Parallèlement, Ingres développe le langage QUEL en 1976
– Dès 1979, Oracle utilise SQL
– 1981, IBM sort SQL/DS
– 1983, IBM sort DB2 (héritier de SYSTEM-R) qui fournit SQL.
– 1982, l’ANSI (organisme de normalisation américain) commence la normalisation de SQL qui
aboutit en 1986 et donne la norme ISO en 1987
– en 1986 PostgreSQL commence son développement.
– Une nouvelle norme SQL-89
– Puis la norme SQL-92 (ou SQL2) qui est la plus utilisée,
– Puis la normalisation SQL-99 (ou SQL3) avec, entre-autres, les extensions relationnel-objet, qui
n’est pas encore terminée !

6.1 Qu’est-ce qu’un ensemble


Un ensemble est une collection d’éléments de même nature. Par exemple l’ensemble des entiers
négatifs, ensemble des caractères, des voyelles, des mots de la langue françaises.
Définition d’un ensemble :
– par extension (ou énumération) : on explicite chaque élément, par exemple l’ensemble des voyelles :
{a, e, i, o, u, y}.
L’ordre des éléments n’a aucune importance : {a, e, i} = {i, a, e}.
Unicité de chaque élément apparaissant dans un ensemble, contre-exemple : {a, e, i, a} n’est pas
un ensemble.
L’ensemble vide : {} = ∅
– par intention (ou caractérisation) : on définit la ou les propriétés vérifiées par chaque élément de
l’ensemble et seulement les éléments de l’ensemble. Par exemple l’ensemble des entiers naturels

31
pairs :{x|x = 2p, p ∈ N}
En SQL on parle plutôt de domaine que d’ensemble, par exemple Varchar (20) est l’ensemble de
toutes les chaı̂nes de caractères de longueurs inférieures ou égales à 20 et, en Oracle, Number (5, 2)
est l’ensemble des nombres positifs ou négatifs pouvant s’exprimer avec 5 chiffres décimaux dont 2
après la virgule.

6.2 Notion centrale : schéma et valeur d’une relation


Le schéma d’une relation exprime comment est constituée une relation : le nombre d’attributs par
n-uplet, un nom différent pour chaque attribut et, pour chaque attribut, le domaine dans lequel il
prend ses valeurs. Par exemple :
schéma : Etudiant (NumCarte : Entier ; Nom : Chaine ; Note : Entier)
Le nombre d’attributs du schéma s’appelle son arité, le schéma Etudiant a une arité de 3.
La valeur d’une relation est un sous-ensemble du produit cartésien des domaines de son schéma (un
domaine est un ensemble de valeurs, par exemple l’ensemble des chaı̂nes de caractères, l’ensemble
des couleurs primaires, l’ensemble des notes de 0 à 20, l’ensemble des mentions de diplômes délivrés
par l’USTL, . . .).
Voici un exemple de valeur d’une relation :
NumCarte Nom Note
(122678555, ’Toto’, 12)
(123678555, ’Truc’, 10)
(213678555, ’Bidule’, 15)
qui est bien un sous-ensemble du produit cartésien : Entier × Chaı̂ne × Entier.
Chaque ligne de la relation est un n-uplet1 dont l’ordre des attributs est fixé par le schéma. Dans
l’exemple, la première valeur de chaque n-uplet est le numéro de carte d’un étudiant, la deuxième
son nom, la troisième sa note. Chaque n-uplet représente un étudiant.

6.2.1 Schéma ou intention d’une relation


Par exemple voici la relation Ville :
schéma : Ville (Id : Entier, Nom : Chaine, Departement : 1..100, Population : Naturel)

6.2.2 Contenu ou instance ou extension d’une relation


L’extension d’une relation est un sous-ensemble du produit cartésien D1 × D2 × . . . × Dk .
Les membres (ou éléments) d’une relation sont appelés nuplets (k-uplets).

SQL
Plusieurs façons d’ajouter une ville dans la table Ville en PostgreSQL :
– insert into Ville values (1, ’Lille’, 59, 222400) ;
insert into Ville values (7, ’Dunkerque’, 59, 175000) ;
Dans cette forme on doit donner une valeur à chaque colonne dans l’ordre dans lequel sont déclarées
les colonnes.
– insert into Ville (id, Departement, Nom , Population)
values (3, 75, ’Paris’, 2200000) ;
Ici on voit qu’en explicitant les noms des colonnes on peut utiliser un autre ordre.
1
Ici on a affaire à des 3-uplet.

32
– insert into Ville (Nom, id) values (’Paris-Texas’, 4) ;
Enfin, en explicitant les colonnes à initialiser on peut n’en donner qu’un sous-ensemble, les colonnes
non mentionnées seront indéfinies (is null) sauf celles qui ont une valeur par défaut (default) pour
la colonne population.
select * from Ville ; insert into Ville values (7, ’Dunkerque’, 59, 175000) ;

6.2.3 Schéma et extension


Souvent on représente par un seul tableau à la fois le schéma et une instance possible de la relation :
Id Nom Departement Population
1 Lille 59 222400
2 Dunkerque 59 175000
3 Paris 75 2200000
4 Paris-Texas 0
5 Marseille 13 880000
6 Lyon 69 420000
On voit que le département de Paris-Texas n’est pas défini (le test est is null). Les colonnes blanches
ou vides de Paris-Texas correspondent à des colonnes indéfinies.
Q.13 Combien d’éléments ou lignes contient le produit cartésien du tableau précédent avec lui-même ?

6.3 Clef d’une relation


Une clef candidate C d’une relation R est un sous-ensemble minimal d’attributs de R qui déterminent
les autres attributs de R, c’est à dire que pour une valeur donnée de C, les autres attributs ont exac-
tement une valeur.

Par exemple le numéro de carte d’étudiant détermine le nom de l’étudiant et certainement d’autres
informations.

Autrement, dit une valeur de C apparaı̂t au plus une fois dans toute extension de R.

Une relation peut posséder plusieurs colonnes servant de clef primaire (primary key).

Par exemple : Etudiant (num_carte, num_insee, nom, prénom, datenaiss) pourrait posséder
deux clefs candidates : (num_carte) qui doit être différent pour chaque étudiant et (num_insee) qui
identifie la naissance d’une personne et est censée être unique pour chaque personne née en France.
On peut choisir (num_carte) comme clef primaire.
Q.14 Quel problème se poserait si on choisissait (nom, prénom) comme clef primaire d’un étudiant ?

En SQL, la clef primaire fait l’objet d’une contrainte primary key, les autres clefs candidates
peuvent faire l’objet d’une contrainte d’unicité (unique).
En Oracle ainsi qu’en PostgreSQL, aucune des colonnes d’une clef primaire ne peut être indéfinie (is
null).

6.4 Clef étrangère


Une clef étrangère est constituée d’une ou plusieurs colonnes et permet de désigner au plus une ligne
d’une autre table ou de la même table.

33
Une clef étrangère peut être interprétée comme un pointeur associatif vers une ligne d’une autre
table ou de la même table. Les colonnes de l’autre table correspondant à celles de la clef étrangère
doivent être la clef primaire complète de cette table ou constituer complètement les colonnes d’une
contrainte d’unicité.
Associatif signifie que pour retrouver la ligne référencée on recherche dans l’autre table la ligne
dont les colonnes de la clef primaire ou de la contrainte d’unicité sont égales à celles de la ligne
référençante (cela peut heureusement se faire efficacement grâce aux index associées aux clefs pri-
maires et contraintes d’unicité voir le chapitre 8 page 64).

Par exemple une fête référence la ville dans laquelle elle se passe en mentionnant en tant que clef
étrangère le numéro de département et le nom de la ville dans ce département (deux villes de deux
départements différents pouvant porter le même nom) :
create table Ville ( create table Fete (
departement Int4, <- departement Int4,
nom Varchar (20), <- nom Varchar (20),
constraint Ville_PK id Int4,
primary key (departement, nom) jour Date,
) constraint Fete_PK
primary key (id),
constraint Fete_Ville_FK
foreign key (departement, nom)
-- | |
-- V V
references Ville (departement, nom)
)
Donner un nom à chaque contrainte avec le mot clef constraint permet de rendre un message d’er-
reur plus clair car le nom de la contrainte apparaı̂t dans le message d’erreur.

L’ordre des colonnes est bien entendu important dans la déclaration de la contrainte foreign key.

Une clef étrangère comportant une colonne indéfinie ne désigne aucune ligne, sinon le SGBD (Oracle,
PostgreSQL et MySQL avec InnoDB) garantit que la ligne désignée existe, sinon l’ordre échoue.

Par défaut, une ligne référencée par une clef étrangère ne peut pas être détruite, d’autres comporte-
ments peuvent être spécifiés grâce à des options de déclaration de clef étrangère, par exemple si une
ligne référencée est détruite on peut demander que les lignes référençantes soient aussi détruites ou
modifiées avec les options de clef étrangère :
– on delete cascade qui indique que la ligne référençante sera elle aussi détruite.
– set null rend les colonnes clefs étrangères indéfinies.
– on update cascade met à jour les colonnes clefs étrangères.

6.5 L’algèbre relationnelle et le langage de requêtes SQL


Une requête permet de voir l’état partiel ou complet de une ou plusieurs tables (avec des inner join
ou left outer join).

6.5.1 Préliminaire : l’identité


En notation relationnelle, il suffit de mentionner le nom de la relation, par exemple R, et on a alors
accès implicitement à sa valeur (son extension), exactement comme lorsqu’on mentionne la variable

34
x dans une expression arithmétique.

En SQL il faut par contre écrire la requête suivante pour voir le contenu complet d’une table :
select * from Ville ;
L’étoile * indique d’afficher toutes les colonnes de la table Ville. Tous les nuplets de la table Ville
sont alors affichés.
Ou, si on veut garantir l’unicité de chaque nuplet affiché :
select distinct * from Ville ;
Le mot clef distinct garantie l’unicité des lignes affichées.
Q.15 Si Ville possède une clef primaire, le distinct est-il utile dans la requête précédente ?

6.5.2 Les opérateurs de base


La projection : SELECT
Pour ne conserver que certaines colonnes.

ΠAp1 ,...,Apk (R) = {(xp1 , . . . , xpk ) | ∃(y1 , . . . , yn ) ∈ R, xpi = ypi ∀i ∈ [1, k]}

Par exemple l’opérateur ΠDpt,P opulation correspond à une projection sur les deux colonnes Dpt et
Population :
select v.Departement as departement, v.Population as population
from Ville v ;
On peut donner un nom aux colonnes affichées avec as. Ce qui donne :

departement population
59 222400
59 175000
59 222400
75 2.200000

13 880000
69 420000
En SQL, c’est la clause select de la requête qui exprime la projection. Le qualificatif distinct permet
d’obtenir l’unicité des lignes du résultat (distinct porte sur toutes les colonnes de la projection) :
select distinct v.Departement, v.Population from Ville v ;
Ce qui donne :

departement population
59 222400
59 175000
75 2.200000

13 880000
69 420000

Si on ne met pas distinct, les doublons éventuels sont conservés (voir au dessus).

35
La restriction : WHERE qui ne conserve que les lignes vérifiant sa condition
Pour ne conserver que les nuplets vérifiant le prédicat P .

σP (R) = {(x1 , . . . , xk ) | (x1 , . . . , xk ) ∈ R ∧ P (x1 , . . . , xk )}


Par exemple, on veut les villes du nord :
select *
from Ville v
where v.Departement = 59 ;
On obtient :
Id Nom Departement Population
1 Lille 59 222400
7 Dunkerque 59 175000
En SQL, c’est la clause where de la requête qui exprime la sélection des lignes (si sa condition est
vraie).
Si en revanche on veut toutes les villes qui ne sont pas dans le département du nord :
select *
from Ville v
where v.Departement != 59 ; -- prédicat de la restriction (ou <> en PostgreSQL)
on ne voit pas les villes de département inconnu (ou non renseigné ou is null) car le prédicat n’ayant
pas de valeur dans ce cas, le nuplet est rejeté par la requête.

L’union : UNION
R et S sont deux relations de même schéma.
R ∪ S = {(x1 , . . . , xk ) | (x1 , . . . , xk ) ∈ R ∨ (x1 , . . . , xk ) ∈ S}
Une requête select peut être utilisée comme une table, on peut donc avoir des emboı̂tements de
requêtes.
1. La requête ensembliste (sans doublons) union :
select nom, ’Etudiant’ as categorie from Etudiant
Union
select nom, ’Enseignant’ as categorie from Enseignant ;
2. La requête ensembliste qui conserve les doublons union all :
select nom, ’Etudiant’ as categorie from Etudiant
Union All
select nom, ’Enseignant’ as categorie from Enseignant ;
Lors d’une instruction insert il est possible d’ajouter 0, 1 ou plusieurs lignes d’un coup à condition
de remplacer la clause values par une requête, par exemple :
create table Ville_Du_Nord (
id Serial,
nom Varchar (50),
departement Int4,
constraint Ville_Du_Nord_PK primary key (id)
) ;

insert into Ville_Du_Nord (departement, nom)


select v.dpt, v.nom from Ville v where v.dpt = 59 ;

36
Le type Serial de id est en fait un compteur entier incrémenté à chaque insertion, ce qui donne des
valeurs différentes pour la clef primaire.

La différence : EXCEPT
Except donne les lignes de la requête gauche qui n’apparaissent pas dans la requête droite. Les lignes
sont uniques à moins de mettre l’opérateur Except all
R et S sont deux relations de même schéma.
R − S = {(x1 , . . . , xk ) | (x1 , . . . , xk ) ∈ R ∧ (x1 , . . . , xk ) 6∈ S}
Les villes dont le département est connu :
select * from Ville EXCEPT select * from Ville where Departement is null ;
Ou autrement sans Except :
select * from Ville
where Departement is not null ;

L’intersection : INTERSECT

R et S sont deux relations de même schéma.


R∩S = {(x1 , . . . , xk ) | (x1 , . . . , xk ) ∈ R∧(x1 , . . . , xk ) ∈ S}
Oracle ne propose pas d’opérateur d’intersec-
tion, mais on peut la réaliser grâce à l’égalité : R ∩ S = R − (R − S)
Calcule l’intersection entre deux requête :
select nom, departement from ville
INTERSECT
select nom, departement from ville where departement = 59 ;
nom | departement
-----------+-----
Dunkerque | 59
Lille | 59
La clause All est possible pour conserver les lignes identiques :
select nom, dpt from ville
INTERSECT all
select nom, dpt from ville where dpt = 59 ;

Nouveau jeu de données (figure 6.1)

Fig. 6.1 – Un exemple de valeur de table avec deux clefs étrangères etu et mat dans la table Note.

Table Etudiant Table Note


    Table Matiere
nom ide Etudiant.ide←etu  →Matiere.idm
note mat 
idm nom coeff
Alfred 1 1 12 1
1 BD 3
Marc 2 1 14 2
2 CL 5
Julie 3 3 15 2

Dans la table Note etu et mat servent de clef primaire.


Q.16 Écrire le MCD correspondant aux trois tables précedantes.
Q.17 Donner la déclaration de ces trois tables et remplissez les tables avec les données ci-dessus.

37
Le produit cartésien : CROSS JOIN
Le produit cartésien cross join est une fonction binaire dont les deux opérandes sont des ensembles
quelconques et la valeur est l’ensemble des couples formés d’un élément du premier opérande et d’un
élément du second opérande. Exemple : {b, f } × {e, i, o} = {(b, e), (b, i), (b, o), (f, e), (f, i), (f, o)}.
Dans un couple (ou 2-uplet) l’ordre des éléments est important : (b, e) 6= (e, b).
Autre exemple : le produit cartésien de l’ensemble des étudiants de licence GMI avec l’ensemble des
UE de licence GMI.

Etudiant×Matiere = {(e1 , . . . , eke , m1 , . . . , mkm ) | (e1 , . . . , eke ) ∈ Etudiant∧(m1 , . . . , mkm ) ∈ Matiere}

Tous les couples étudiant, matière (Oracle10, PostgreSQL, SQL92) :


select *
from Etudiant etu
cross Join Matiere mat ;
ide | nom | idm | nom | coeff
-----+--------+-----+-----+-------
1 | Alfred | 1 | BD | 3
1 | Alfred | 2 | CL | 5
2 | Marc | 1 | BD | 3
2 | Marc | 2 | CL | 5
3 | Julie | 1 | BD | 3
3 | Julie | 2 | CL | 5
on obtient 3 × 2 lignes.
En Oracle 10 et en PostgreSQL on peut écrire (et en général en SQL on peut écrire) le produit
cartésien comme ceci :
-- Oracle10, PostgreSQL, SQL92
select *
from Etudiant, Matiere ;
On obtient le même résultat que précédemment.
Si on ne veut afficher que la partie Etudiant de chaque élément du produit cartésien, on peut préfixer
* avec le nom de la table ou son alias :
select etu.*
from Etudiant etu
cross Join Matiere mat ;
etu est l’alias de la table Etudiant, mat est l’alias de la table Matiere.
Q.18 Sous quelle condition les deux requêtes suivantes ont-elle la même valeur, sous quelle condition
ont-elle des valeurs différentes ?

select * from Etudiant ;

select distinct etu.*


from Etudiant etu cross join Matiere mat ;
ide | nom
-----+--------
1 | Alfred
2 | Marc
3 | Julie
(3 rows)

38
6.5.3 Quelques opérateurs supplémentaires
Ils peuvent s’exprimer grâce aux opérateurs de base vus précédemment et ne sont donc théoriquement
pas insdispensables, mais ils sont tellement pratiques qu’à la fois le relationnel et SQL leur attribuent
une identité.

La jointure, produit cartésien et restriction : ... INNER JOIN ... ON <condition>


Elle permet de ne conserver que les éléments pertinents d’un produit cartésien.

R ⊲⊳P S = σP (R × S)

où P exprime la condition de conservation d’un élément du produit cartésien.


Par exemple les couples (étudiant, matière) si l’étudiant a une note dans cette matière en se basant
sur les contenu des tables de la figure 6.1 page 37 :
select e.nom as etudiant, m.nom as matiere, n.note as note
from Etudiant e
cross join Note n
cross join Matiere m
where e.ide = n.etu
and n.mat = m.idm ;
SQL2, PostgreSQL et Oracle 10 (et d’autres bien entendu) disposent d’un opérateur de jointure
spécifique <table> inner join <table> on <condition>. La requête précédente peut alors être
réécrite plus clairement en :
select e.nom as etudiant, m.nom as matiere, n.note as note
from Etudiant e
inner join Note n on n.etu = e.ide
inner join Matiere m on m.idm = n.mat ;

etudiant | matiere | note


----------+---------+------
Alfred | BD | 12
Alfred | CL | 14
Julie | CL | 15
(3 rows)
Q.19 Pourquoi Marc n’apparaı̂t-il pas dans le résultat ?
Le mot clef inner permet de distinguer cette jointure de la jointure dite externe (voir la section 6.12
page 54) qui, elle, utilise le mot clef outer plutôt que inner.
L’opérateur != signifie différent et peut aussi se noter <> en PostgreSQL.

On distingue plusieurs cas particuliers de jointures

Équi-jointure Égalité entre colonnes : c’est probablement la plus courante, très souvent on teste
l’égalité entre la clef étrangère d’une table et la clef primaire d’une autre table. L’exemple précédent
9 est une équi-jointure.

Jointure naturelle : attention danger Équi-jointure de R et S sur les colonnes de mêmes noms.
En SQL92 et PostgreSQL on ajoute le mot clef natural.
La jointure naturelle est particulièrement dangereuse : supposons une application qui utilise la join-
ture naturelle entre deux tables T1 et T2 . Si, plus tard, on ajoute à T1 et à T2 une colonne homonyme

39
et de même type alors ces deux colonnes participeront automatiquement à cette jointure naturelle,
ce qui n’est pas forcément ce que souhaite celui qui ajoute ces colonnes.

create table Note_Naturelle (


ide Int4,
idm Int4,
note Int2,
constraint Note_Naturelle_PK primary key (ide, idm),
constraint Note_Naturelle_Etudiant_FK foreign key (ide)
references Etudiant (ide),
constraint Note_Naturelle_Matiere_FK foreign key (idm)
references Matiere (idm)
) ;
insert into Etudiant values (1, ’Alfred’) ;
insert into Etudiant values (2, ’Marc’) ;
insert into Etudiant values (3, ’Julie’) ;
select * from Etudiant ;
insert into Matiere values (1, ’BD’, 3) ;
insert into Matiere values (2, ’CL’, 5) ;
select * from Matiere ;
insert into Note_Naturelle values (1, 1, 12) ;
insert into Note_Naturelle values (1, 2, 14) ;
insert into Note_Naturelle values (3, 2, 15) ;
select * from Note_Naturelle ;

select e.nom as etudiant, n.note as note


from Etudiant e
natural join Note_Naturelle n ;

etudiant | note
----------+------
Alfred | 12
Alfred | 14
Julie | 15

Auto-jointure Jointure d’une relation avec elle-même. Par exemple, les employés qui sont chef
d’au moins un autre employé :

create table Employe (


id Serial,
nom Varchar (20),
mon_chef Int4,
contraint Employe_PK primary key (id),
contraint Employe_Chef_FK foreign key (mon_chef)
reference Employe (id)
) ;

select distinct chef.*


from Employe emp
inner join Employe chef on chef.id = emp.mon_chef ; -- équi-jointure

40
Non équi-jointure Le prédicat de la clause on d’une jointure n’est pas forcément une égalité :
toute condition peut convenir.

Grâce à l’ordre alter, on ajoute l’attribut sexe aux étudiants :


alter table Etudiant
add sexe Varchar (1)
default ’M’ -- valeur par défaut (discutable !)
check (sexe in (’M’, ’F’)) -- les 2 valeurs possibles
not null ; -- ne peut ^
etre indéfini

update Etudiant
set sexe = ’F’
where ide = 3 ;

select * from etudiant;


id | nom | sexe
----+--------+------
1 | Alfred | M
2 | Marc | M
3 | Julie | F
Q.20 Écrire la requête qui donne tous les binômes mixtes d’étudiant et sans redondance : si on
obtient le binôme (Alfred, Julie) on ne doit pas obtenir aussi le binôme (Marc, Julie) car Marc
n’a pas de note.
Q.21 Utiliser count() pour compter le nombre de matières de chaque étudiant.
Q.22 Pour la BD suivante, donner la requête fournissant les étudiants inscrits à toutes les matières
(clause having qui sélectionne un groupe).
select e.ide, e.nom, e.sexe
from Etudiant e
inner join Note n on n.etu = e.ide
group by e.ide, e.nom, e.sexe
having count (*) = (select count(*) from Matiere) ;

ide | nom | sexe


-----+--------+------
1 | Alfred | M
La clause having permet de conserver un groupe si sa condition vaut vraie.
En particulier la contrainte primary key garantit que ses colonnes sont définies et donc les colonnes
clefs étrangères de Note sont forcément définies.
La clause having représente une condition de conservation d’un groupe. Ici un groupe correspond
au fait qu’un étudiant a une note dans toutes les matières. Cette condition porte sur chaque étudiant
séparément, ainsi l’expression count (*) représente le nombre de matières d’un même étudiant.
Pour résumer : la condition du having porte sur le nombre de lignes d’un groupe (un étudiant
identifé par sa clef primaire) produite par la clause from et la condition du having porte sur chaque
groupe construit par le group by.

6.6 Le cas des valeurs indéfinies : le test est is [not] null


Soit la table Client :

41
create table Client (
id Int4,
nom Varchar (20),
tel Varchar (30),
constraint Client_PK primary key (id)
) ;
Dans la pratique il est souhaitable de pouvoir mémoriser une nouvelle ligne dans une table, même si
certaines colonnes ne peuvent être renseignées du fait qu’on n’a pas forcément toute l’information.

Par exemple je veux quand même pouvoir enregistrer un nouveau client même si je ne connais pas son
numéro de téléphone. Par exemple voici deux ordres équivalents qui ne renseignent pas le téléphone
d’un nouveau client :
Insert into Client (id, nom, tel) values (13, ’Tartampion’, null) ;
Insert into Client (id, nom) values (14, ’Gidon’) ;
Insert into Client (id, nom, tel) values (15, ’Guy’, ’03/20/18/18/18’) ;
select * from client;
id | nom | tel
----+------------+----------------
13 | Tartampion |
14 | Gidon |
15 | Guy | 03/20/18/18/18
Et une manière d’enregistrer le fait qu’on ne connaı̂t plus le nouveau numéro du client 15 :
update Client set tel = null where id = 15 ;
select * from client;
id | nom | tel
----+------------+-----
13 | Tartampion |
14 | Gidon |
15 | Guy |
La colonne téléphone sera alors dite indéfinie : elle n’a pas de valeur. On pourra tester si une colonne
(etplus généralement une expression) est définie ou non avec le prédicat booléen is [not] null :
– <expr> is null vrai ssi <expr> est indéfinie, faux ssi <expr> est définie.

    
– <expr> is not null est équivalent à not (<expr> is null)
   
Par exemple, les villes dont on ne connaı̂t pas le département :
select v.nom
from Ville v
where v.departement is null ;

Paris-Texas
Q.23 Quelle ambiguı̈té y a-t-il dans la question : les villes du nord du tableau. (page 33)
Q.24 Lister les villes qui ne sont pas dans le département du Nord ou dont le département n’est pas
renseigné.
SQL permet qu’une colonne soit is null à condition qu’elle ne soit assujettie ni à la contrainte not
null ni à primary key.

6.6.1 Noter l’absence de valeur


Un opérande n’ayant pas de valeur peut se noter explicitement avec le mot clef null en PostgreSQL
et Oracle, par exemple null + 5 donnera une valeur indéfinie.

42
Attention : ne pas interpréter ce null comme le pointeur null des langages de programmation ni
comme le zéro des entiers !

6.6.2 Comportement des opérateurs et des fonctions à valeur non booléenne


La plupart des opérateurs et des fonctions à valeur autre que booléenne sont indéfinis si un de leurs
opérandes est indéfini. Par exemple :
a b a+b
1 2 3
is null 2 is null = true
0 0 0
0 is null is null = true
is null is null is null = true
Par
 exemple :  
(1
 + n.note) is null ⇔ n.note is null 

6.6.3 Comportement des opérateurs relationnels


Les opérateurs relationnels (=, <, <=, >, >= et x between a and b) sont à valeur booléenne.
Quand un de leurs opérandes est indéfini, il ont vraiment une valeur qui peut être testée avec is null
en PostgreSQL ou vide en PostgreSQL.

a b a = b, a != b, a <= b, ...
is not null is not null vrai (t) ou faux (f)
Au moins un des deux is null is null = true
Par exemple, quel que soit l’état de la colonne nom, les expressions null=null et nom!=null valent
une valeur indéfinie (is null) en PostgreSQL.

6.6.4 Comportement des opérateurs logiques


Les opérateurs logiques (not, or et and) travaillent donc en logique tri-valuée, c’est à dire que leurs
opérandes ont des valeurs prises dans un ensemble de trois valeurs : {vrai, faux, is null}.

Quand aucun des opérandes n’est is null on a affaire à la logique binaire habituelle. Précisons ce qui
se passe quand un des opérandes est is null :
not vaut évidemment unknown ou vide.
and vaut faux si l’autre opérande vaut faux, sinon unknown ou vide.
or vaut vrai si l’autre opérande vaut vrai, sinon unknown ou vide.
a b not b a and b a or b
is null is null is null ou vide is null ou vide is null ou vide
is null faux vrai faux unknown ou vide
is null vrai faux is null ou vide vrai

Q.25 Que donnerait le ou exclusif xor qui n’existe pas en Oracle et en PostgreSQL ?
Q.26 Donner une définition du prédicat x between a and b en utilisant uniquement les opérateurs
<= et and.
Q.27 Que donnerait l’opérateur a between b and c si un de ses opérandes est indéfini ?

43
6.6.5 L’expression conditionnelle : case {when <condition> then} else
case
when <predicat1> then valeur1
[when <predicat2> then valeur2
...
when <predicatN> then valeurN]
[else valeurParDéfaut ]
end
Le premier prédicat qui vaut vrai donne sa valeur au case, si aucun prédicat ne vaut vrai c’est la
valeur par défaut du else qui est donné, s’il n’y a pas de else et que tous les prédicats sont faux la
valeur est indéfinie (is null).
Par exemple :
select v.nom as nom,
case
when v.population >= 100000 then ’Grande ville’
when v.population < 100000 then ’Petite ville’
else ’Je ne sais pas : la population est indéfinie’
end as categorie
from Ville v ;

nom | categorie
-------------+-------------------------------------------------
Lille | Grande Ville
Paris | Grande Ville
Paris-Texas | Petite Ville
Paris | Petite Ville
Dunkerque | Petite Ville
Montpellier | Je ne sais pas : la population est indéfinie !
Q.28 Donner une autre formulation équivalente au case précédant qui utilise is null.

6.6.6 Présomption d’innocence de la clause where


La clause where peut apparaı̂tre dans une requête (select) mais aussi dans une mise à jour de lignes
(update) ou une suppression de lignes (delete).
Si la condition d’une clause where s’évalue à false ou unknown alors le nuplet correspondant n’est
pas traité.
Par exemple, pour le delete, l’idée est qu’on ne veut pas détruire un nuplet si on ne sait pas s’il
vérifie la condition de suppression (présomption d’innocence).
Q.29 La requête suivante, censée lister les clients dont le nom n’est pas défini.
select * from Client c where c.nom is null ;

6.7 Quelques opérateurs et fonctions scalaires de SQL/Oracle


et PostgreSQL
6.7.1 between a and b
Les expressions a et b peuvent être des nombres, des chaı̂nes, des dates, tout type disposant d’un
ordre.

44
select * from ville where departement between 59 and 75;
id | nom | departement | population
----+-----------+-------------+------------
2 | Lille | 59 | 222400
3 | Paris | 75 | 2200000
4 | Dunkerque | 59 | 175000

6.7.2 La fonction coalesce de PostgreSQL


La fonction coalesce, à au moins un paramètre ou plus de deux et vaut la première valeur définie
en partant de la gauche et est indéfinie si tous ses paramètres le sont2 .
select ’Bonjour ’||v.nom||’ ’||
coalesce(upper(v.departement),’pas de departement’) as coucou
from Ville v ;

coucou
----------------------------------------
Bonjour Lille 59
Bonjour Paris 75
Bonjour Paris-Texas pas de departement
Bonjour Paris 75
Bonjour Dunkerque 59
Bonjour Montpellier pas de departement
Q.30 Écrire l’équivalent de coalesce (a, b, c) en utilisant l’opérateur case.

6.7.3 Manipuler les chaı̂nes


Les fonctions de chaı̂ne (upper, lower)
select upper (nom) as majuscule from Etudiant;
majuscule
-----------
ALFRED
MARC
JULIE
select lower (nom) as minuscule from Etudiant;
minuscule
-----------
alfred
marc
julie

Concaténation : || et reconnaissance de modèle : like


select ’Bonjour ’ || e.nom as bonjour
from Etudiant e ;
bonjour
----------------
Bonjour Alfred
2
PostgreSQL propose aussi la fonction coalesce avec la même signification.

45
Bonjour Marc
Bonjour Julie
Je ne dis bonjour qu’aux étudiants dont le nom contient un r qui n’est pas la dernière lettre : Dans
le modèle de like :
– % correspond à un nombre quelconque (éventuellement nul) de n’importe quel caractère.
– _ correspond à exactement un caractère quelconque.
Par exemple ’Alfred’ like ’%r_%’ est vrai et ’mer’ like ’%r_%’ est faux.
select *
from Etudiant
where nom like ’%r_%’;
id | nom | sexe
----+--------+------
1 | Alfred | M
2 | Marc | M
Q.31 Écrire le modèle qui reconnaı̂t toute chaı̂ne contenant le caractère x qui n’est ni le premier, ni
le dernier de la chaı̂ne.
Q.32 Écrire le modèle qui reconnaı̂t toute chaı̂ne contenant deux caractères x séparés par au moins
deux caractères.
Q.33 Comment reconnaı̂tre les chaı̂nes qui ont le caractère x en première et/ou en dernière position ?

La chaı̂ne vide en PostgreSQL n’est pas indéfinie. PostgreSQL est parfaitement cohérent sur la notion
de chaı̂ne vide qui est bien entendu parfaitement définie.

6.8 Les fonctions d’agrégation count, sum, avg, min, max


Ces fonctions effectuent un calcul synthétique sur l’ensemble des nuplets fournis à la projection
(requête select).

Par exemple sum calcule la somme des valeurs définies que prend son expression pour chacun des
nuplets et min en calcule la plus petite.

Une requête dont la clause select comportant de telles fonctions dans ses expressions de projection
fournit exactement une ligne (sauf si la requête est munie d’une clause group by, voir la section 6.11).

sum, avg, min et max donnent un résultat indéfini si l’expression argument n’est jamais définie,
c’est en particulier le cas quand aucun nuplet n’est sélectionné.
En revanche count, qui compte le nombre de fois que son expression a une valeur définie, a toujours
une valeur définie (éventuellement la valeur zéro si aucune ligne n’est traitée ou que toutes les valeurs
sont indéfinies).
Par exemple count (e.id) donne le nombre de fois que l’attribut e.id est défini. Formes spéciales :
– count (*) renvoie le nombre total de nuplets fournis.
– count (distinct <expression>) nombre de valeurs différentes et définies que prend l’expression.
Q.34 Donner d’autres formes de count (*) qui soient équivalentes.
Enfin, on ne peut pas demander à la clause select de fournir à la fois une information synthétique
(exactement un nuplet) et une information individuelle (0, 1 ou plusieurs nuplets). Donc, dès qu’une
fonction d’agrégation apparaı̂t dans la clause select, un nom de colonne ne peut apparaı̂tre que dans
une expression argument d’une fonction d’agrégation.
La requête suivante fournira toujours exactement une ligne :

46
select count (distinct n.mat) as nb_matieres,
avg (n.note) as moyenne,
sum (n.note) / count (n.note) as autre_moyenne,
max (n.note) as meilleure_note
from Note n ;
nb_matieres | moyenne | autre_moyenne | meilleure_note
-------------+---------------------+---------------+----------------
2 | 13.6666666666666667 | 13 | 15
Et voici un exemple incorrect car il mélange information individuelle et information synthétique :
select e.nom as nom, -- incorrect à cause de count (*)
count (*) as nb_etudiants
from Etudiant e ;
ERROR: syntax error at or near "count"
LINE 2: count (*) as nb_etudiants,
^
Le tableau suivant résume les différentes fonctions d’agrégation count, sum, avg, min, max

fonction valeur si expr est toujours


indéfinie ou que
aucune ligne ne
lui est fournie
sum (expr) somme des valeurs définies de expr is null
avg (expr) moyenne des valeurs définies de expr is null
min (expr) min des valeurs définies de expr is null
max (expr) max des valeurs définies de expr is null
count (expr) nombre de valeurs définies de expr 0
count (distinct expr) nombre de valeurs définies et différentes de expr 0
count (*) nombre de lignes 0 si aucune ligne
count (1+2) nombre de lignes 0 si aucune ligne
count (’abc’) nombre de lignes 0 si aucune ligne
count (e.nom) nombre de e.nom is not null 0 si e.nom
est toujours indéfini

Q.35 Parmi les expressions de la figure 6.2 page 47, regrouper celles qui ont exactement le même
comportement (vous devriez obtenir 6 groupes).

count (*) count (e.nom) count (55 + 2*3.14)


sum (1) count (’coucou’) sum (e.note) / count (*)
count (e.id) count (upper (e.nom)) sum (case when e.nom is null then 0 else 1 end)
avg (e.note) count (e.nom is null) sum (e.note) / count (e.note)

Fig. 6.2 – Expressions à classer.

6.8.1 Évaluation d’une requête synthétique (fonctions d’agrégation)


Une requête synthétique produit toujours exactement une ligne (même si le from where ne produit
aucune ligne) en utilisant les fonctions d’agrégation dans sa clause select.

47
Une requête synthétique produit toujours exactement une ligne (même si le from where ne produit
aucune ligne) en utilisant les fonctions d’agrégation dans sa clause Même si aucun produit n’a un
prix supérieur à 100 on aura quand même une ligne égale à 0.

Si la table Produit est vide ou qu’aucun prix n’est supérieur à 100, on obtient :

select count (*) as nb_trop_cher


from Produit p
where p.prix > 100 ;

nb_trop_cher
--------------
0

On veut calculer la moyenne pondérée par les coefficients de matière de l’étudiant Alfred. Voici la
requête et, conceptuellement, comment elle va être évaluée (il est très probable qu’un vrai moteur
SQL ne fera pas l’évaluation de cette manière) :

select Sum (n.note*m.coeff) /


Sum (case when n.note is null then 0 else m.coeff end) as moy_alfred
from Etudiant e
inner join Note n on e.ide = n.etu
inner join Matiere m on n.mat = m.idm
where e.ide = 1 ;

-- 1) résultat de la jointure et de la restriction where :


NOTE| COEFF
--------|------
12| 3
14| 5

-- 2) calcul des expressions en argument des fonctions d’agrégation :


N.NOTE*M.COEFF| CASE ...
--------------|------
36| 3
70| 5
--
-- 3) Calcul les sommes de chacune des deux colonnes :
SUM(N.NOTE*M.COEFF)|SUM(CASE WHEN NOTE IS NULL THEN 0 ELSE M.COEFFEND)
-------------------|----------------------------------------
106| 8

-- 4) Enfin calcul de la moyenne d’Alfred (la division) imprécis car entier:


moy_alfred
------------
13

Les expressions arguments des fonctions d’agrégation sont donc évaluées séparément pour chaque
nuplet et les expressions externes aux fonctions d’agrégation sont calculées en dernier.

Pour avoir cette même moyenne pour chaque étudiant, on pourra utiliser la clause group by, voir
la section 6.11.

48
6.9 Les sous-requêtes
6.9.1 sous-requête dans la clause from
Dans la clause from on peut écrire un select entre parenthèses à la place du nom d’une table. Par
exemple : les villes dont la population est supérieure ou égale à la moyenne des populations :
select v.nom as ville
from Ville v
cross join (select AVG (v.population) as moyenne
from Ville v
where v.population is not null) pop
where v.population >= pop.moyenne ;

ville
-------
Paris
Ou encore, les villes dont la population est supérieure ou égale à la population moyenne par ville de
leur département :
select v.nom as ville
from Ville v
inner join (select AVG (vv.population) as moyenne,
vv.departement as departement
from Ville vv
group by vv.dpt) popParDpt
on popParDpt.departement = v.departement
where v.population >= popParDpt.moyenne ;

ville
-------
Paris
Lille
Remarquer que la sous-requête calculant la moyenne de population par département est close (auto-
nome) : elle ne dépend en rien de la requête englobante.

Une clause on ne peut mentionner que des alias de tables déjà déclarés.

Une sous-requête dans la clause from ne peut pas mentionner des colonnes appartenant aux tables
de la clause englobante : elle doit être close ou autonome (idem en PostgreSQL).
Autrement dit : une sous-requête dans une clause from ne peut pas être corrélée (ou dépendante)
avec une table ou une autre sous-requête de la même clause from.
L’exemple suivant est refusé par Oracle et PostgreSQL car la sous-requête n’est pas close :
select v.nom as ville
from Ville v
inner join (select AVG (vv.population) as moyenne,
max (vv.departement) as departement
from Ville vv
where vv.departement = v.departement) pop_par_dpt
on v.departement = pop_par_dpt.departement
where v.population >= pop_par_dpt.moyenne ;

49
ERROR: invalid reference to FROM-clause entry for table "v"
HINT: There is an entry for table "v", but it cannot be referenced from this
part of the query.

6.9.2 sous-requêtes dans les clauses where et select


En général un opérande dans une expression peut être une sous-requête entre parenthèses.
Si cette sous-requête produit :
– exactement une ligne d’une colonne, elle peut être employée avec un opérateur scalaire correspon-
dant au type de la colonne de la ligne courante.
– un nombre quelconque de nuplets, elle devra être utilisée avec un opérateur ensembliste approprié
(exists (sous-requête) si une ligne existe dans le résultat de la requête, expression in (sous-
requête) si la valeur de gauche apparaı̂t dans le requête de droite, expression not in (sous-
requête) vrai si l’expression n’a pas sa valeur dans la sous-requête). Les requêtes sont entre
parenthèses.
Dans where et select une sous-requête peut être corrélée si elle mentionne des colonnes appartenant
à des tables de la clause from de la requête englobante.

sous-requête close, autonome ou non corrélée


C’est une sous-requête qui ne dépend pas du nuplet courant de la requête englobante, une sous-
requête non corrélée donnera donc toujours le même résultat, l’optimiseur peut s’en rendre compte
et ne l’évaluer qu’une seule fois.
Par exemple : les villes dont la population est supérieure ou égale à la moyenne :
select v.nom from Ville v
where v.population >= (select AVG (vv.population) from Ville vv
where vv.population is not null) ;
nom
-------
Paris

sous-requête corrélée
Le résultat d’une sous-requête corrélée dépend du nuplet courant de la requête principale car elle
mentionne des colonnes de ce nuplet.

Une corrélation ne peut se faire que dans la clause select ou where, mais pas dans la clause from.

Par exemple les villes dont la population est supérieure ou égale à la moyenne de leur département :
select v.nom from Ville v
where v.population >= (select AVG (vl.population) from Ville vl
where vl.departement = v.departement) ;
Q.36 Lister les couples matière, nom d’un étudiant ayant la meilleure note dans cette matière avec
les deux techniques : sous-requête dans la clause from et sous-requête dans la condition. On a trois
tables : Etudiant, Note et Matiere.

6.10 Ordonner le listing des nuplets : order by


Cette clause order by expressions [ASC — DESC] permet d’indiquer dans quel ordre croissant
(par défaut ou avec ASC) ou décroissant avec DESC on souhaite obtenir les nuplets produits par

50
la clause select.

Obtenir les nuplets dans un certain ordre n’est utile que pour un lecteur humain (par exemple : lors
d’un jury on aime bien avoir la liste des étudiants par moyenne décroissante) ou pour un programme
dont l’algorithme a besoin de récupérer les nuplets dans un ordre bien précis (par exemple si on
veut vérifier par programme que les numéros d’étudiants sont uniques et contigüs le plus simple est
d’ouvrir un curseur sur les numéros croissants).
Cette clause d’ordre n’est donc utilisable que pour le select.

Pour trier les villes par départements croissants, puis populations décroissantes, puis noms croissants :
select * from Ville v
order by v.Departement asc, v.Population desc, v.Nom;

dpt | nom | population


-----+-----------+------------
59 | Lille | 20000
59 | Dunkerque | 10000
75 | Paris | 222000
Par défaut l’ordre est asc (i.e. croissant), desc demande un ordre décroissant.
On n’est évidemment pas obligé d’ordonner sur toutes les colonnes et on peut trier sur le résultat
d’une expression :
select * from Ville v
order by upper (v.Nom) ;

-- On peut aussi ordonner sur une colonne de la projection du select :


select upper (v.Nom) as nom_MAJ from Ville v
order by nom_MAJ ;
La clause order by est toujours la dernière d’une requête.

6.11 La formation de groupes : group by


L’ensemble des nuplets produits par les clauses from et where peut être partitionné en sous-
ensembles ou groupes non vides et disjoints. La manière de partitionner est indiquée par les colonnes
données après la clause group by qu’on appellera clef de groupe : les nuplets ayant la même valeur
pour la clef de groupe font partie du même groupe. Seules les expressions du group by peuvent
figurer en direct dans la projection du select, toute autre expression ou nom de colonne ne peut
figurer qu’en paramètre d’une fonction d’agrégation : cette fonction s’appliquera donc aux nuplets
de chaque groupes traités séparément.
Par exemple pour calculer la moyenne de chaque étudiant on utilise la clef de groupe e.id, e.nom :
select e.ide, e.nom, avg (n.note) as moyenne
from Etudiant e
inner join Note n on e.ide = n.etu
group by e.ide, e.nom ;

id | nom | moyenne
----+--------+----------
1 | Alfred | 13
3 | Julie | 15

51
Une telle requête peut constituer un nombre quelconque de groupes (éventuellement aucun groupe si
aucun nuplet n’est retenu par le where qui doit être écit avant le group by) et elle produira autant
de nuplets qu’il y a de groupes.
Une manière de visualiser ce regroupement est de remplacer la clause group by par une clause order
by dont la clef de tri est la clef de groupe :
select e.ide as id, e.nom as nom, n.note as note
from Etudiant e
inner join Note n on e.ide = n.etu
order by e.ide, e.nom ;
id | nom | note
----+--------+------
1 | Alfred | 12
1 | Alfred | 14
3 | Julie | 15
Remarquer que dans ce cas on ne peut pas appliquer la fonction avg() sur les notes.

Le regroupement devient intéressant dès qu’on veut obtenir une information synthétique sur chaque
groupe grâce aux fonctions d’agrégation (sinon on peut se contenter du qualificatif distinct de la
clause select).
Par exemple on souhaite connaı̂tre la moyenne de chaque étudiant :
select e.ide, e.nom, n.note as note
from Etudiant e
inner join Note n on e.ide = n.etu ;
id | nom | note
----+--------+------
1 | Alfred | 12
1 | Alfred | 14
3 | Julie | 15

select e.ide, e.nom, avg (n.note) as moyenne, count (*) as nb_notes


from Etudiant e
inner join Note n on e.ide = n.etu
group by e.ide, e.nom ;
id | nom | moyenne | nb_notes
----+--------+---------------------
1 | Alfred | 13 | 2
3 | Julie | 15 | 1
Et encore une manière de lister, pour chaque matière, les étudiants qui ont la meilleure note. On
remplace, dans la clause from, la table Matiere par la table (virtuelle) des notes maxi de chaque
matière :
select m_max.nom as matiere, e.nom as nom
from Etudiant e
inner join Note n on e.ide = n.etu
inner join (select m.idm as idm, m.nom as nom, Max (n.note) as note_max
-- meilleure note de chaque matière
from Matiere m
inner join Note n on m.idm = n.mat
group by m.idm, m.nom) m_max on n.mat = m_max.idm
where n.note = m_max.note_max ;
matiere | nom

52
---------+--------
BD | Alfred
CL | Julie
Q.37 En supposant que chaque matière soit dotée d’un coefficient coeff, calculer la moyenne
pondérée de chaque étudiant. On supposera que toutes les notes et coefficients sont renseignés (is
not null).
Q.38 Que se passe-t-il si le coefficient d’une matière est indéfini ?
Q.39 Comment calculer une moyenne correcte pour l’étudiant si certaines notes ne sont pas ren-
seignées ? (si une note n’est pas renseignée, il faut ne pas la prendre en compte)

6.11.1 Sélectionner des groupes : la clause having


La sous-clause having sélectionne un groupe de group by est l’équivalent pour un groupe de la
clause where pour une ligne. Elle permet de ne traiter que les groupes qui vérifient sa condition.
Elle peut donc mentionner que des expressions de la clef du group by ou des fonctions d’agrégation
sur les autres colonnes.

Par exemple la moyenne des étudiants ayant au moins deux notes :


select e.ide, e.nom, AVG (n.note) as moyenne, count (*) as nb_notes
from Etudiant e
inner join Note n on e.ide = n.etu
group by e.ide, e.nom
having count (*) >= 2 ;

id | nom | moyenne | nb_notes


----+--------+---------------------
1 | Alfred | 13 | 2
Le having ne conserve que les groupes ayant au moins 2 lignes.
Q.40 Moyenne pondérée des étudiants ayant une note renseignée dans chaque matière.
Q.41 Pour chaque étudiant, nombre de matières pour lesquelles il a une note définie.
Q.42 Quelle sera la valeur systématique d’une requête ayant un group by muni de la clause having
count (*) >= 1 ?
Q.43 Donnez une nouvelle version de la requête listant les étudiants inscrits à toutes les UE.

6.11.2 group by et informations indéfinies


Lors d’un group by sur une seule expression E, Oracle 10 considère que toutes les lignes pour les-
quelles E est indéfinie (is null) font partie du même groupe (ce qui n’est pas plus évident que de
considérer qu’elle forment autant de groupes différents). PostgreSQL a la même attitude.

Le mieux serait cependant d’expliciter la valeur indéfnie :


select coalesce (<expression>, ’inconnu’), ...
...
group by coalesce (<expression>, ’inconnu’), ...
Attention : ’inconnu’ doit être du même type que <expression>.
create table X (N Int4) ;
insert into X values (2) ;

53
insert into X values (null) ;
insert into X values (null) ;
insert into X values (null) ;
insert into X values (2) ;
insert into X values (3) ;

select coalesce (N, -1) as valeur, count (*) as cardinal


from X
group by coalesce (N, -1) ;

valeur | cardinal
--------+---------
-1 | 3
3 | 1
2 | 2
Q.44 Mettre en place une expérience pour savoir comment se comporte votre SGBD favori dans ce
cas.

6.12 Les jointures externes : outer join


Dans l’exercice précédent, le problème est qu’on ne voit pas Marc car n’ayant pas de notes il ne fait
pas partie de la jointure (figure 6.1 page 37).
On peut résoudre ce problème grâce à une jointure externe sur la table Etudiant (left outer join)
: un étudiant n’ayant aucune note fera alors partie de la jointure mais toutes les colonnes relatives à
la partie Note seront indéfinies (Oracle10, PostgreSQL92) :
select e.ide, e.nom, n.note
from Etudiant e
left outer join Note n on e.ide = n.etu ;

id | nom | note
----+--------+------
1 | Alfred | 12
1 | Alfred | 14
2 | Marc | <--nuplet supplémentaire d^ u au left outer join (note indéfinie)
3 | Julie | 15
Si un nuplet Etudiant n’a pas de note, le left outer join le concatène quand même avec un nuplet
Note dont toutes les colonnes sont indéfinies (is null).
Cette jointure externe est signalée par left outer join. Le left désigne la table dont on veut conserver
tous les nuplets : celle de gauche, on peut utiliser right pour conserver les lignes de la table de droite
ou full pour conserver les lignes des deux tables.
Q.45 Dans la requête précédente, qu’obtiendrait-on avec une jointure externe conservant les lignes
de la table de droite : right outer join qui conserve les lignes de la table de droite ?
La jointure externe n’est pas une primitive car on peut l’exprimer grâce aux opérateurs précédents,
voici l’équivalent de la requête précédente :
select e.ide as id, e.nom as nom, n.note as note
from Etudiant e inner join Note n on e.ide = n.etu
union
select e.ide as id, e.nom as nom, null as note
from Etudiant e

54
where e.ide not in (select distinct n.etu from Note n) ;

id | nom | note
----+--------+------
1 | Alfred | 12
1 | Alfred | 14
2 | Marc |
3 | Julie | 15
Il suffit de rajouter le group by pour obtenir des informations synthétiques par étudiant (Oracle10,
PostgreSQL, SQL92) :
select e.ide, e.nom, count (n.etu) as nb_notes
from Etudiant e
left outer join Note n on e.ide = n.etu
group by e.ide, e.nom ;

id | nom | nb_notes
----+--------+----------
1 | Alfred | 2
2 | Marc | <-- car n.etu est indéfini pour Marc
3 | Julie | 1
La fonction count (expression) compte le nombre de fois que expression est définie. n.etu étant
indéfini pour Marc, son nombre de matières vaut zéro.

La fonction AVG n’est définie que si son expression est définie au moins une fois pour les différents
nuplets du groupe. Elle est donc indéfinie pour le groupe (2, Marc).

Les jointures sont (Oracle10, PostgreSQL, SQL92) :


inner join : jointure classique (interne)
left outer join : jointure externe conservant les lignes de la table de gauche qui ne s’apparient avec
aucune ligne de la table de droite,
right outer join : comme ci-dessus mais ce sont les lignes de la table de droite qui sont conservées,
full outer join : pour une jointure externe complète (conservation des lignes des tables de gauche
et de droite)
Et il est possible de préfixer chacune de ces possibilités par natural pour indiquer une équi-jointure,
à condition que les clefs étrangères aient le même nom que les clefs primaires qu’elles référencent, la
clause on étant alors interdite l’égalité sera faite implicitement.

Exemple : liste des couples étudiant, matière, même pour les étudiants n’ayant aucune note et matière
est alors indéfinie :
select e.nom, coalesce (m.nom, ’aucune matière’)
from Etudiant e
left outer join Note n on e.ide = n.etu
left outer join Matiere m on n.mat = m.idm ;

nom | coalesce
--------+-----------------
Alfred | BD
Alfred | CL

55
Julie | CL
Marc | aucune matière

6.13 Contraintes sur l’usage des fonctions d’agrégation


Une clause on ne peut mentionner aucune fonction d’agrégation, elle s’applique à la construction
d’une concaténation de lignes.

Une clause where ne peut mentionner aucune fonction d’agrégation car elle s’applique à exactement
une ligne de la clause from. Cependant elle peut contenir une sous-requête utilisant des fonctions
d’agrégation car une sous-requête est un nouveau monde et n’a donc pas d’impact sur la clause
where, par exemple pour avoir les notes des étudiants supérieures à leurs moyennes :
select e.ide as id, e.nom as nom, n.note as note
from Etudiant e
inner join Note n on n.etu = e.ide
where n.note > (select Avg (n.note) as moyenne
from Note n
where n.etu = e.ide) ;
id | nom | note
----+--------+------
1 | Alfred | 14
Une clause group by ne peut mentionner aucune fonction d’agrégation.

Une clause having peut mentionner des fonctions d’agrégation. Les colonnes clef du group by
peuvent apparaı̂tre en dehors ou à l’intérieur de fonctions d’agrégation, les autres colonnes doivent
absolument apparaı̂tre à l’intérieur de fonctions d’agrégation.

La clause select d’une requête R peut :


1. si R n’a pas de clause group by :
– si R n’est pas une requête synthétique aucune fonction d’agrégation n’apparaı̂t,
– si R est une requête synthétique, toute colonne provenant de sa clause from doit apparaı̂tre
dans une fonction d’agrégation dont la profondeur est exactement de 1.
En revanche des constantes ou des colonnes provenant d’une requête englobante peuvent
apparaı̂tre en dehors des fonctions d’agrégation, ou à l’intérieur, car elles ont une valeur
constante pour l’évaluation de R.
2. si R a une clause group by :
– si R n’est pas une requête synthétique alors toute colonne ne faisant pas partie de la clef de
groupe doit apparaı̂tre dans une fonction d’agrégation avec une profondeur de 1. Les colonnes
clef de groupe peuvent apparaı̂tre à l’extérieur ou à l’intérieur des fonctions d’agrégation.
– si R est une requête synthétique alors toute colonne ne faisant pas partie de la clef de
groupe doit apparaı̂tre dans un double emboı̂tement de fonctions d’agrégation (profondeur
de 2) Attention : PostgreSQL ne permet pas d’emboı̂ter deux fonctions d’agrégation (en
revanche Oracle le permet). Les colonnes clef de groupe doivent apparaı̂tre à une profondeur
1 ou 2 dans les fonctions d’agrégation.
En revanche des constantes ou des colonnes provenant d’une requête englobante peuvent
apparaı̂tre en dehors des fonctions d’agrégation, ou à l’intérieur, car elles ont une valeur
constante pour l’évaluation de R. Par exemple :
select Sum (n.note*m.coeff) / Sum (m.coeff) as moyenne_promo
from Note n

56
inner join Matiere m on m.idm = n.mat
where n.mat = 2 and n.note is not null
group by n.etu ; -- Sum porte sur toutes les notes d’un m^
eme étudiant
moyenne_promo
---------------
15
13

6.14 Emplacement des fonctions d’agrégation


Une fonction d’agrégation ne peut être utilisée ni dans une clause on de jointure ni dans la clause
where.

Il est possible d’emboı̂ter des fonctions d’agrégation dans le select d’une requête munie d’une clause
group by, mais sans dépasser une profondeur d’emboı̂tement de deux. Dans ce cas la requête donne
une information synthétique des informations obtenues pour chaque groupe, par exemple la moyenne
des moyennes des étudiants :
select Sum (n.note*m.coeff) / Sum (m.coeff) as moyenne_promo
from Etudiant e
inner join Note n on e.ide = n.etu
inner join Matiere m on m.idm = n.mat
group by e.ide, e.nom ;
moyenne_promo
---------------
13
15
Cette requête calcule la moyenne de chaque étudiant, puis la moyenne de ces moyennes.

Il est aussi possible d’utiliser des fonctions d’agrégation dans l’expression du having mais avec une
profondeur d’emboı̂tement de un : donc on ne peut y emboı̂ter deux fonctions d’agrégation. Par
exemple si on veut la moyenne des moyennes supérieures ou égales à 10 :
select Avg (n.note) as moyenne_promo
from Etudiant e
inner join Note n on e.ide = n.etu
group by e.ide, e.nom
having AVG (n.note) >= 10 ;
moyenne_promo
---------------
13
15

6.15 Pour conclure


En conclusion, l’exécution d’une requête se fait conceptuellement dans cet ordre :
1. from produit les nuplets (ou lignes) du produit cartésien (éventuellement la jointure pour ANSI
SQL et Oracle 9, PostgreSQL),
2. where applique une condition de sélection aux lignes obtenues depuis la clause from, et
conserve la ligne si sa condition est vraie.

57
3. group by construit des groupes avec sa clause optionnelle.
4. having applique sa condition au groupe et conserve le groupe si sa condition est vraie.
5. select produit la projection de chaque groupe de nuplets provenant du group by ou de chaque
nuplet du where s’il n’y a pas de group by,
6. order by ordonne les nuplets provenant du select.
On peut remarquer que l’ordre syntaxique et l’ordre conceptuel n’ont pas grand chose à voir l’un
avec l’autre ! En particulier, la clause select est la dernière à être exécutée.

58
Chapitre 7

Les vues

7.1 Syntaxe PostgreSQL de création d’une vue


CREATE [ OR REPLACE ] [ TEMP | TEMPORARY ] VIEW name [ ( column_name [, ...] ) ]
AS query ;
– OR REPLACE permet de changer la requête (query). de la vue, il faut que les colonnes produites
par la nouvelle requête aient le même sens que dans la requête remplacée.
– name est le nom de la vue.
– [ ( column_name \[, ...\] )\] est optionnel et si on le met donne un nom à chaque colonne
produite par la requête (query). Si on ne met pas cette liste de colonnes les colonnes ont les noms
(as) de la requête.
– Une vue qualifiée par TEMP ou TEMPORARY disparaı̂t lorsque l’utilisateur se déconnecte de
la base de données.
Une vue PostgreSQL n’est pas modifiable.

7.2 Syntaxe PostgreSQL de destruction d’une vue


DROP VIEW <nom-de-la-vue> ;

7.3 Un exemple
En première approche, une vue est un objet qui associe un nom à une requête. Une fois créée, on
pourra consulter cette vue comme si c’était une table :
create table Client (
idc Serial,
nom Varchar (20),
solde Numeric (10, 2),
constraint Client_PK primary key (idc)
) ;

insert into Client (nom, solde) values (’Toto’, 55.2) ;


insert into Client (nom, solde) values (’TresGold’, 5555.2) ;
insert into Client (nom, solde) values (’Pasgold’, 4000.2) ;

create view Bon_Client (idc, nom, solde) as


select idc, nom, solde
from Client

59
where solde > 1000 ;
Dans une requête une vue est prise comme une table
select *
from Bon_Client ;
idc | nom | solde
-----+----------+---------
2 | TresGold | 5555.20
3 | Pasgold | 4000.20

select *
from Bon_Client
where lower (nom) like ’%gold%’ ;
idc | nom | solde
-----+----------+---------
2 | TresGold | 5555.20
3 | Pasgold | 4000.20

select Max (solde) as LeMax, Sum (solde) as LaSomme


from Bon_Client ;
lemax | lasomme
---------+---------
5555.20 | 9555.40

select bc.idc as id_meilleur_client, bc.nom as nom_meilleur_client


from Bon_Client bc
where bc.solde = (select Max (solde) from Bon_Client) ;
id_meilleur_client | nom_meilleur_client
--------------------+---------------------
2 | TresGold
En général, une utilisation particulière d’une base de données ne nécessite pas de voir toutes les
données de la base de données, ceci pour des raisons de confidentialité mais aussi tout simplement
pour ne pas polluer l’utilisateur avec des informations qui ne le concernent pas.

Par exemple les étudiants qui conçoivent l’annuaire des anciens GMI ne peuvent pas voir le sa-
laire individuel que certains anciens renseignent, mais il peuvent en obtenir une moyenne. Ainsi ces
étudiants n’auront aucun droit sur la table Ancien mais disposeront d’une vue correspondant à la
table Ancien amputée de la colonne salaire et d’une vue calculant le salaire moyen.

Pour mettre en place une vision limitée et appropriée à la mission de l’utilisateur de la base de
données, les vues sont un des outils majeurs (le système de privilèges intervient lui aussi).

Les vues constituant le cadre juste nécessaire à une utilisation particulière de la base de données sont
un des outils permettant de réaliser un schéma externe.

Quelques usages des vues :


– Pour obtenir simplement une information synthétique.
– Pour éviter de divulguer certaines informations (nominative par exemple) : une vue peut restreindre
le nombre de colonnes consultables, l’utilisateur concerné pourra consulter la vue mais pas la ou
les tables d’où elle tire sa valeur.
– Pour assurer l’indépendance du schéma externe vis à vis du schéma interne : on peut espérer qu’une

60
modification des tables qui implantent la base de données permettra de modifier les requêtes des
vues sans changer le sens des informations qu’elles fournissent.
Une vue est évaluée à chaque consultation.
Oracle en définit un grand nombre pour faciliter la consultation de son dictionnaire, par exemple :
tab, user_objects, . . .
Si le schéma externe d’une utilisation n’est constitué que de vues, on aurait tendance à penser que
cette utilisation est incapable de modifier la base, ce qui serait parfois très embêtant !
En fait, comme on le verra, Oracle et PostgreSQL disposent de moyens permettant de modifier la
base de données via les vues d’un schéma externe.

7.3.1 Un exemple
Soit la base de données :
create table Client (
id Number (5) primary key,
nom Varchar2 (20),
solde Number (6, 2) default 0.0) ;

create table Commande (


client references Client (id),
montant Number (6, 2) default 0.0) ;
La vue qui donne la liste des clients avec le montant moyen des commandes qu’il a effectuées
create view Client_Moyenne (id, nom, montant_moyen) as
select Cl.id as id, Cl.Nom as nom, Avg (Co.montant)
from Client Cl
inner join Commande Co on Co.client = Cl.id
group by Cl.nom
with read only ;
Comme une table, une vue peut être mentionnée dans la clause from d’une requête.

Si une des tables utilisées par la vue est détruite, cette dernière devient inutilisable.
Q.46 Les vues Bon Client et Client Moyenne sont-elles modifiables ?

7.4 Vue modifiable : elle est recalculée à chaque usage


Sans l’option with check option toute insertion est possible, mais ne sera pas forcément visible via
la vue :
create view Mauvaise_Vue (id, nom, solde) as
select id, nom, solde
from Client
where solde > 1000 ;

insert into Mauvaise_Vue values (45, ’dupont’, 500) ; -- OK

select * from Mauvaise_Vue ; -- on ne voit pas ’dupont’

update Mauvaise_Vue
set solde = 300
where id = 45 ; -- aucune ligne mise à jour

61
delete from Mauvaise_Vue
where id = 45 ; -- aucune ligne supprimée

7.5 Deux mots à propos de PostgreSQL


En PostgreSQL, on peut modifier les tables sous-jacentes aux vues en créant une règle (create
rule). Une règle permet d’exécuter des commandes supplémentaires lorsqu’une commande donnée
est exécutée sur une table ou une vue donnée (also) ou à la place de la commande (instead).
create table Etudiant (
ide Serial,
nom Varchar (20),
constraint Etudiant_PK primary key (ide)
) ;

create table Matiere (


idm Serial,
nomMat Varchar (20),
constraint Matiere_PK primary key (idm)
) ;

create table Note (


etudiant Int4,
matiere Int4,
note Numeric (5, 2),
constraint Note_PK primary key (etudiant, matiere),
constraint Note_Etudiant_FK foreign key (etudiant)
references Etudiant (ide),
constraint Note_Matiere_FK foreign key (matiere)
references Matiere (idm)
) ;

insert into Etudiant (nom) values (’toto’) ;


insert into Matiere (nomMat) values (’BD’) ;
insert into Matiere (nomMat) values (’CL’) ;
insert into Matiere (nomMat) values (’Maths’) ;
insert into Note values (1, 1, 12) ;
insert into Note values (1, 2, 14) ;
insert into Note values (1, 3, 14) ;

create view Moyenne (ide, nom, moyenne) as


select e.ide, e.nom, coalesce (avg (n.note), -1)
from Etudiant e left outer join Note n on e.ide = n.etudiant
group by e.ide, e.nom ;

select * from Moyenne ;


ide | nom | moyenne
-----+------+---------------------

62
1 | toto | 13.3333333333333333

create rule Creer_Etudiant as


on insert to Moyenne
do instead Insert into Etudiant (nom) values (new.nom) ;

create rule Modifier_Etudiant as


on update to Moyenne
do instead update Etudiant set nom = new.nom where ide = ide ;

create rule Supprimer_Etudiant as


on delete to Moyenne
do instead (delete from Note where etudiant = old.ide ;
delete from Etudiant where ide = old.ide ) ;

update Moyenne set nom = ’titi’ where ide = 1;

select * from Moyenne ;


ide | nom | moyenne
-----+------+---------------------
1 | titi | 13.3333333333333333

insert into Moyenne (nom) values (’Durand’) ;

select * from Etudiant ;


ide | nom
-----+--------
1 | titi
2 | Durand

delete from Moyenne where ide = 1 ;

select * from Etudiant ;


ide | nom
-----+--------
2 | Durand

select * from Note ;


etudiant | matiere | note
----------+---------+------
(0 rows)

select * from Moyenne ;


ide | nom | moyenne
-----+--------+---------
2 | Durand | -1

63
Chapitre 8

Optimisations (Clef primaire et unique)

8.1 Organisation physique d’un SGBD


La durabilité d’une base de données est assurée par son enregistrement sur un disque magnétique
(c’est probablement actuellement la technique la plus utilisée).

L’unité atomique de lecture/écriture sur un disque est le secteur ou le bloc (plusieurs secteurs conti-
gus). La taille d’un secteur peut être de 512 ou 1024 octets voire 4096.

Écrire ou lire un secteur prend un temps énorme par rapport à la même opération en mémoire
centrale. Cela est dû principalement à l’aspect mécanique de l’accès au secteur :
1. le bras supportant la tête de lecture/écriture doit d’abord être déplacé radialement sur la piste
du secteur
2. il faut ensuite attendre que le secteur se présente sous le bras grâce à la rotation du disque,
3. enfin il faut lire ou écrire le secteur, la durée de cette opération dépend elle aussi de la vitesse
de rotation du disque.

Contenu
Index
Debut de la
d’acces :
de la recherche B+−arbre Table

Fig. 8.1 – Le principe d’utilisation d’un index permettant d’obtenir rapidement une lignre de la
table.

PostgreSQL organise ses accès au disque de la façon suivante :


– le bloc est la plus petite unité de l’écriture/écriture dont la taille est fixée par la constante
BLOCK_SIZE, dont la valeur par défaut en PostgreSQL est de 8 kilo-octets, mais on peut la changer
dans le fichier postgresql.conf.
– l’extent est l’unité suivante. Un extent est constitué d’un certain nombre de blocs contigus, ce qui
garantit un accès physique efficace.

64
– le segment est une collection d’extents qui constitue en général un seul objet de la base, par exemple
le segment de donnée d’une table ou le segment d’un index.

8.1.1 Un principe d’organisation d’un nœud du B+-arbre


Un nœud interne (ou aiguillage) ne contient que des clefs et des adresses d’autres nœuds du B+-arbre.
Une adresse est en fait le numéro de page du nœud ou de la feuille pointée. Chaque nœud interne
constitue un aiguillage permettant de trouver le chemin menant à la feuille contenant la clef cherchée
et sa valeur, voir la figure 8.2. Ce sont les primary key et unique qui provoquent la création d’un
B+-arbre.

C1 C2 C3 ........ Cn espace libre

B1 B2 B3 Bn Bn+1

Fig. 8.2 – Nœud interne (page disque) constituant un aiguillage : on a C1 < C2 < . . . < Cn ,
l’élément de clef C telle que Ci−1 < C ≤ Ci ne peut se trouver que dans le sous-arbre Bi . Si C ≤ C1 ,
C doit se trouver dans B1 . Si Cn < C, C doit se trouver dans Bn+1 . On remarque que ce nœud
interne n’est pas saturé et qu’il pourrait donc accueillir d’autres clefs et sous-arbres.

8.1.2 Un exemple de B+-arbre avec une clef atomique


Voici un exemple de B+-arbre associé à une table ayant des lignes du genre (34, "nom") où 34 est
la clef.

B+−ARBRE (ou INDEX) 30 44

4 10 30 31 44 55 66

TABLE
(31, rr) (30, toto) (10, oo) (4, bof) (55, ii) (66, ii) (44, oo)

Fig. 8.3 – Chaque rectangle correspond à un bloc du disque qui peut être de taille variable.

Un autre algorithme plus efficace consiste à ne faire exploser un nœud que quand c’est indispensable :
dans ce cas les explosions se font en remontant le chemin vers la racine : la pile des nœuds pères
saturés est alors nécessaire dont le fond est le dernier nœud père non saturé rencontré s’il en existe
un. Si tous les nœuds de la pile sont saturés alors le nœud en fond de pile est forcément la racine,
c’est le cas où le B+-arbre verra sa profondeur augmenter de 1 à la racine : cela justifie le fait que
les feuilles sont toujours à la même profondeur.
Q.47 Comment retrouver la feuille contenant la clef 44 ? étiqueter les blocs lus avec une *
Q.48 Comment retrouver les feuilles contenant les clefs de ∈ [25, 44] ? étiqueter les blocs lus avec +

Q.49 Dessiner le nouvel état après insertion dans la table de (45, ”truc”) puis (7, ”truc”).

65
Chapitre 9

Dépendances fonctionnelles et
normalisation

L’intérêt de la normalisation est d’éviter les redondances d’information.


Une relation universelle est l’unique relation formée de tous les attributs pertinents d’un problème.
A, B, C, D désignent des attributs.
R, T, X, Y, Z désignent des ensembles d’attributs (éventuellement vides).
F un ensemble de dépendances fonctionnelles (DF)
On notera indifféremment X ∪ Y ou XY .

9.1 Dépendances fonctionnelles


Une DF est notée X → Y et exprime que dans toute extension de X ∪ Y les valeurs des attributs de
X déterminent de façon unique celles des attributs de Y . Autrement dit : si on connaı̂t une valeur
de X alors on connaı̂t la valeur de Y lui correspondant.
Q.50 Soit la table (numéro-de-carte-étudiant, nom), que peut-on faire de numéro-de-carte-étudiant ?

X → Y est élémentaire si X = {C1 C2 · · · Ck } et que pour tout 1 ≤ i ≤ k on n’a pas X − {Ci} → Y .


X → Y est triviale ssi Y ⊆ X, y compris pour Y vide.
Exemple de {numéro-insee} → {sexe, date-naissance} est élémentaire,
dépendances {numéro-insee, sexe} → {sexe, date-naissance} n’est pas élémentaire,
fonctionnelles : {date-naissance, sexe} → {sexe} est triviale.
Soit la relation universelle LDF qui décrit une ligne d’une facture : LDF = {num_facture, la_date,
qté_vendue, num_client, nom_client, num_produit, produit, prix_produit, qté_produit}.
num_facture identifie la facture avec sa la_date, num_client identifie le nom_client et num_produit
identifie le produit, prix_produit et qté_produit. Une facture peut avoir plusieurs produits (i.e.
plusieurs lignes). Un num_produit apparaı̂t dans une ligne d’une facture. num_facture, num_client
et num_produit identifient qté_vendue.Un client a au plus une facture par jour.
Q.51 Donner l’ensemble des DF élémentaires de LDF.
Q.52 Donner quelques DF triviales et quelques DF non triviales et non élémentaires de LDF.
Q.53 Combien y a-t-il de dépendances triviales dont le déterminant (partie gauche de X → Y ) est
LDF ?

9.2 La nécessité de décomposer une relation en sous-relations


Motivation : éviter la répétition (redondance) d’information et l’impossibilité de représenter certaines
informations tout en essayant de conserver les dépendances fonctionnelles.

66
Q.54 Donnez le MCD de LDF qui évite des redondances d’information.
Q.55 Que doit-on faire pour modifier le prix d’un produit.
On a donc souvent besoin de décomposer (normaliser) une relation en plusieurs sous-relations afin
d’éviter ces anomalies.
Q.56 Proposer une telle décomposition de la relation Ligne-de-Facture et conserver les dépendances
fonctionnelles qui sont conservées par les sous-relations.

9.3 Axiomes de Armstrong


Ils permettent de déduire de nou- Axiomes de Armstrong
velles dépendances fonctionnelles à par- (1) trivialité Y ⊆X ⇒ X→Y
tir d’un ensemble F de dépendances (2) augmentation X → Y ⇒ XZ → Y Z
fonctionnelles. (3) transitivité X →Y ∧Y →Z ⇒ X →Z
Q.57 De R = {A, B, C, D, E, F } muni de F = {AB → CD, B → F }, déduire {DE →
E, AB → C, ABD → ADF }. (l’axiome d’augmentation est précieux, ainsi que le fait que
X ∪ X = X)
F + est la clôture de l’ensemble de DF F obtenue par application des axiomes de Armstrong.
Q.58 Calculer la clôture de F = {A → B} sur R = {A, B}.
L’intérêt d’une telle clôture est qu’elle permet de définir l’équivalence entre deux ensembles de DF
F1 et F2 portant sur la même relation universelle : F1 est équivalent à F2 ssi F1 + = F2 +.
Q.59 Sans passer par la clôture, on veut montrer que sur R = {A, B, C}, F1 = {A → B, B → C}
est équivalente à F2 = {A → BC, B → C}. Comment peut-on montrer l’équivalence de F1 et F2 ?

3 corollaires bien pratiques des axiomes de Armstrong


(4) union / décomposition X →Y ∧X →Z ⇔ X →YZ
(5) pseudo-transitivité X →Y ∧YZ →T ⇒ XZ → T
(6) augmentation bis X →Y ∧Z →T ⇒ XZ → Y T

Q.60 Prouver ces corollaires à l’aide des axiomes et des corollaires déjà prouvés.
Soit R = {A, B, C, D, E, F } munie de : F = {{A, B} → C; {C, D} → {E, F }; {E} → {F, D}}

Q.61 Montrer que si on supprime la DF {E} → {F } on perd une information.


Q.62 En revanche si on supprime la DF {C, D} → {F } montrer qu’on ne perd rien.

9.4 Calculer les clés candidates d’une relation


Une clé candidate d’une relation R vis à vis d’un ensemble de dépendances fonctionnelles F , est
un sous-ensemble minimal d’attributs de R qui détermine tous les attributs de R.
Q.63 Quelles sont les clés candidates de R munie de F = {} ?
Définition : tout ensemble d’attributs incluant strictement ceux d’une clé candidate est peut-être une
super-clé.
Cet algorithme détermine l’ensemble des clés candidates d’une relation R munie d’un ensemble de
DF :
1. On construit le graphe des dépendances, y compris les attributs n’apparaissant dans aucune
dépendance et sont donc des sommets isolés dans le graphe.

67
2. Les sommets non cibles d’une flèche appartiennent à toutes les clés, on les marque.
3. Tant qu’il existe un sommet S déterminé par des sommets, marquer S comme non clef.
4. Effacer tous les sommets marqués et les flèches qui en partent.
5. Tant qu’il existe un sommet S non source d’une flèche, effacer S qui n’appartient à aucune clé.
6. Les sommets restant sont forcément dans des cycles, considérer séparément chacun d’eux comme
appartenant à une des clés, le marquer puis recommencer en 3
7. S’il ne reste pas de sommet, supprimer toutes les clés non minimales qui peuvent
exister et c’est fini.
Voici le graphe de C = {Ville, Rue, Zip, D} muni de
F = {{Ville, Rue } → Zip, Zip → Ville}.
D Ville
Une clé non minimale est : {Ville, Rue, Zip, D}. Zip
Les 2 clés candidates sont : {{Ville, Rue, D}, {Rue, Zip, D}} Rue

Q.64 Dessiner le graphe des dépendances de Ligne-de-Facture (voir question Q.51).


Q.65 Marquer les nœuds du graphe LDF déterminés directement ou indirectement par (num-facture,
num-client, num-produit) puis montrer qu’on obtient le même résultat en utilisant les DF et les
axiomes et corollaires de Armstrong.
Q.66 Donner les clés candidates de Ligne-de-Facture.
la relation munie des dépendances fonctionnelles
R = {A, B, C, D, E, F, G, H, I} {A → BC, C → D, BDE → A, F → AG, G → H}
Q.67 Donner
R = {A, B, C, D, E, F, G} {AC → B, B → C, C → DE, D → F, E → F, F → G}
les clés de :
R = {A, B, C, D, E} {A → DE, BC → A, E → B, D → C}
R = {A, B, C, D, E} {A → DE, B → AC → A, E → B, D → C}

Définitions des formes normales : BCNF ⇒ 3NF ⇒ 2NF ⇒ 1NF


Une forme normale permet de mesurer la qualité d’une relation munie de dépendances fonctionnelles.
Par exemple 2NF nous garantit que toutes les clés complètes sont nécessaires pour déterminer les
attributs n’appartenant à aucune clé : cela permettra d’éviter des redondances.
Par exemple Magasin = {P roduit, Date, P rix, P roducteur} muni de Regle = {{P roduit, Date} →
P rix, {P roduit} → P roducteur} a comme clés C = {{P roduit, Date}}. Elle n’est donc pas 2NF.
Q.68 Pourquoi Magasin n’est pas 2NF ? Donner un exemple de redondance sur Magasin.

1NF Si tout attribut a une valeur atomique.


2NF Une relation est en 2NF si elle est 1NF et que tout attribut n’appartenant à aucune clé candi-
date est en dépendance élémentaire ou (irréductible) avec chacune des clés. (contre-exemple :
{A, B, C}, {B → C})
3NF Une relation est en 3NF si tout attribut A n’appartenant à aucune clé X dépend de chacune
des clés par une dépendance fonctionnelle élémentaire ou directe. Une relation 3NF est aussi
2NF. (contre-exemple : {A, B, C, D}, {AB → C, C → D}, 2NF ? Décomposez-la en 3NF)
BCNF : Boyce Codd Normal Form Une relation R est BCNF vis à vis d’un ensemble de DF
F , si toute DF non triviale de F + a comme déterminant une clé primaire ou candidate de R .
Un sous-ensemble d’une clef ne doit pas dépendre d’attributs non clefs. Chaque attribut de R
ne faisant pas partie d’une clef doit dépendre directement d’une clef. Lors d’une décomposition
on conserve les attributs constituant les clefs dans le reste des attributs qu’on veut décomposer
en BCNF. Décomposer {A, B, C, D}, {AB → C, C → D} en BCNF.
Q.69 Par exemple R = {cru, pays,région, qualité} munie de {{cru, pays} → {région, qualité},
{région} → {pays}} n’est pas BCNF car {région} n’est pas une clé. Est-elle 2NF ? 3NF ?

68
Q.70 Normalité de LDF (voir Q.51) ?
Q.71 Normalité de R = {A, B, C, D} munie de F = {AB → CD, BC → D, CD → A} ?
Q.72 Normalité de R = {A, B, C, D} munie de F = {A → BC, B → C, C → B} ?

9.5 Décomposer une relation sans perte d’information


Quand une relation ne satisfait pas la normalité souhaitée, on la décompose en deux sous-relations.
Si cette décomposition ne satisfait toujours pas la normalité souhaitée on pourra à nouveau les
décomposer : le processus de décomposition est itératif.
Cette technique presque mécanique de décomposition risque de donner un résultat similaire à celui
obtenu par une approche plus intuitive comme par exemple la conception du MCD de Merise.

Soient la relation R munie de F et R1 , R2 une décomposition de R (i.e. R1 ∪ R2 = R et R1 ∩ R2 n’est


pas vide). Cette décomposition est sans perte d’information vis à vis de F si toute extension r
de R vérifiant F est égale à ΠR1 (r) ⊲⊳ ΠR2 (r) = r, cette jointure naturelle se fait par égalité sur
les colonnes de R1 ∩ R2 .

Soit R = {A, B, C} munie de F = {A → C}. Pour l’exemple d’extension donné à A B C


droite, montrer que les décompositions suivantes de R : a1 b1 c1
Q.73 R1 = {A, C}, R2 = {A, B} ne perd pas d’information. a1 b2 c1
Q.74 R1 = {B, C}, R2 = {A, C} perd de l’information. a2 b2 c1
Le principe de non perte d’information est évidemment incontournable lors d’une décomposition !
D’où l’importance du théorème suivant.

Théorème de décomposition sans perte d’information Soient R = {A1 , A2 , . . . , An } un schéma


relationnel, F un ensemble de dépendances fonctionnelles et X, Y, Z une partition de R telle que
X → Y ∈ F + et X → Z ∈ F +. Alors R1 = X ∪ Y, R2 = X ∪ Z est une décomposition de R sans
perte d’information1 .

X, Y, Z est une partition de R ⇔ (X ∪ Y ∪ Z = R) ∧ (X ∩ Y = ⊘) ∧ (X ∩ Z = ⊘) ∧ (Y ∩ Z = ⊘)

Démonstration : Soit r une valeur quelconque de R et r1 = ΠR1 (r), r2 = ΠR2 (r). On montre d’abord
que r1 ⊲⊳ r2 ⊆ r, pour cela on peut montrer que r1 ⊲⊳ r2 6⊆ r est une absurdité : supposons que
(xi , yi ) ∈ r1 et (xi , zi ) ∈ r2 et que (xi , yi, zi ) 6∈ r, puisque (xi , yi ) ∈ r1 et (xi , zi ) ∈ r2 ont été obtenus
par projection de r, c’est qu’il existe deux nuplets (xi , yi , zi′ ), (xi , yi′ , zi ) appartenant à r, or X → Y
on a donc yi = yi′ et donc (xi , yi , zi ) ∈ r. De la même manière on montre que r ⊆ r1 ⊲⊳ r2 .

Q.75 Montrer que la condition du théorème est aussi nécessaire, c’est à dire que si une décomposition
est sans perte alors elle vérifie nécessairement la condition du théorème. Suggestion : montrer que si
on n’a ni R1 ∩ R2 → R1 ni R1 ∩ R2 → R2 alors la décomposition est avec perte, un exemple suffit.
Q.76 En SQL, à quelles contraintes serait soumis X dans les tables R1 et R2 ?
L’ensemble des DF de Ri est la projection ΠRi (F +) = {X → Y ∈ F + |X ∪ Y ⊆ Ri }.
Une décomposition sans perte d’information ne préserve pas toujours les dépendances fonctionnelles.

R = {A, B, C, D} munie de {AB → C, C → D}


Exemple : X = {A, B} Y = {C} Z = {D}
R1 = {A, B, C} munie de {AB → C} R2 = {A, B, D} munie de {AB→ D}
1
Autrement dit : R1 , R2 est sans perte d’information ssi R = R1 ∪ R2 et (R1 ∩ R2 → R1 ou R1 ∩ R2 → R2 ).

69
mais la dépendance {C → D} est perdue. On perd donc une contrainte d’intégrité facilement expri-
mable par une contrainte d’unicité ou de clé primaire. Il faudra programmer pour garantir que cette
dépendance est préservée lors des modifications de table.
Une bien meilleure solution ui conserve toutes les DF :
R = {A, B, C, D} munie de {AB → C, C → D}
X = {A, B} Y = {C} Z = {D}
R1 = {A, B, C} munie de {AB → C} R2 = {C, D} munie de {C → D}
Q.77 On décompose la relation R de la question Q.71 en R1 = {A, B, C}, R2 = {A, B, D}. Cette
décomposition est-elle sans perte ? Quelles sont les DF conservées par cette décomposition ?
Q.78 Décomposer LDF (voir Q.51) en sous-relations qui sont toutes BCNF, cette décomposition
conserve-t-elle toutes les DF ?
Remarque : pour un même problème R muni de F il peut y avoir plusieurs décompositions différentes
permettant d’obtenir des sous-relations vérifiant une forme normale.
Attention : une décomposition BCNF sans perte d’information peut perdre des dépendances fonc-
tionnelles (ce n’est pas le cas de 3NF).

Application (emprunté au poly de Mireille Clerbout)


Soit la relation D = {dépôt, journal, titre, catégorie, tx com, prix, adr dépôt, jour, quantité} munie
des dépendances F :
{dépôt} → {adr dépôt} {catégorie} → {tx com} {titre} → {journal}
{dépôt, journal, jour} → {quantité} {journal} → {titre, prix, catégorie, tx com}
Utilisez des diminutifs pour faire les questions, par exemple D pour dépôt, Jl pour journal, Jr pour
jour . . ..
Q.79 Déterminer les clés de D munie de F et montrer qu’elle n’est pas BCNF (section 9.4).
Q.80 Décomposer D par étapes successives en sous-relations qui sont BCNF et qui conservent,
globalement, toutes les DF de F (section 9.5).
Q.81 Dessiner le MCD de la décomposition obtenue.
Q.82 Écrire les ordres SQL de création des tables BCNF et leurs garnissages à partir d’une table D
déjà peuplée.

70