Philippe Durif
9 juillet 2010
Chapitre 1
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.
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 !
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.
4
alter table Etudiant
add column mon_diplome integer ;
– 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 ;
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.
6
Chapitre 2
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.
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
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 ;
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 ;
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;
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
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 ;
Les types définis par la norme ne sont malheureusement pas toujours respectés.
13
– Numeric (p) nombre entier, qui signifie Numeric (p, 0)
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
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.
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.
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.
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’))
(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).
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).
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.
-- 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.
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".
Cela posera un problème car la valeur de Serial de id finira par atteindre la valeur 666.
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.
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) ;
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
24
Chapitre 4
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
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.
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.
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 ;
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
Ce modèle est lié à la théorie des ensembles (unicité des éléments, sous-ensemble, produit cartésien,
. . .)
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 !
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.
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) ;
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).
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.
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 ?
Π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 .
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)
) ;
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
Fig. 6.1 – Un exemple de valeur de table avec deux clefs étrangères etu et mat dans la table Note.
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.
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é.
R ⊲⊳P S = σP (R × S)
É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.
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é :
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.
update Etudiant
set sexe = ’F’
where ide = 3 ;
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.
42
Attention : ne pas interpréter ce null comme le pointeur null des langages de programmation ni
comme le zéro des entiers !
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.
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.
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
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.
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.
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
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).
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 :
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) :
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.
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.
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;
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
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)
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) ;
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.
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).
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
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.
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
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
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.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)
) ;
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
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.
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) ;
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 ?
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
62
1 | toto | 13.3333333333333333
63
Chapitre 8
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.
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.
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.
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
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.
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}}
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
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} ?
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.
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).
70