Les règles d'intégrité des
Il semble que l'utilisation des règles d'intégrité dans les
Tous les SGBD (même
Si on utilise un SGBD, c'est probablement parce les données qu'on
lui confie sont importantes. On ne souhaite donc pas qu'elles soient
modifiées librement, elles doivent, à tout moment, respecter un
certain nombre de
Il y a plusieurs endroits pour mettre de telles règles. Si un seul
programme accède à la base en écriture, on peut mettre ces règles dans
le programme (qu'il soit en
Ce développement du
Naturellement, aucun filet de sécurité n'est parfait. Toutes les
règles ne peuvent pas être exprimées dans la base (le SQL standard n'est pas un
Comment programme t-on des règles d'intégrité ? Il existe de
nombreuses méthodes. Commençons par les plus standard, qui font partie
de SQL. Tous les exemples sont faits avec
Si une donnée doit être présente une fois et une seule dans une
table, le mot-clé
CREATE TABLE Domaines (nom TEXT UNIQUE);
NOTICE: CREATE TABLE / UNIQUE will create implicit index "domaines_nom_key" for table "domaines"
CREATE TABLE
essais=> INSERT INTO Domaines VALUES ('foobar.example');
INSERT 372690 1
essais=> INSERT INTO Domaines VALUES ('autre.example');
INSERT 372691 1
essais=> INSERT INTO Domaines VALUES ('foobar.example');
ERROR: duplicate key violates unique constraint "domaines_nom_key"
]]>
Si une donnée doit appartenir à un ensemble bien défini de valeurs,
le système de typage de SQL peut aider. Ainsi, on peut préciser qu'une
donnée doit être
une date (la syntaxe d'entrée est
CREATE TABLE Nouvelles (pub DATE);
CREATE TABLE
essais=> INSERT INTO Nouvelles VALUES ('2007-1-16');
INSERT 372695 1
essais=> INSERT INTO Nouvelles VALUES ('1879-8-8');
INSERT 372696 1
essais=> INSERT INTO Nouvelles VALUES ('2000-13-32');
ERROR: date/time field value out of range: "2000-13-32"
]]>
On est ainsi protégé contre les erreurs de saisie ou de calcul.
PostgreSQL permet de créer
ses propres types, ce qui permet d'obtenir ces contraintes de
types (SQL dit de
SQL permet également d'imposer qu'une
CREATE TABLE Catalogue (ref VARCHAR(6) UNIQUE, description TEXT);
NOTICE: CREATE TABLE / UNIQUE will create implicit index "catalogue_ref_key" for table "catalogue"
CREATE TABLE
essais=> INSERT INTO Catalogue VALUES ('456223', 'Lampe de chevet');
INSERT 372714 1
essais=> INSERT INTO Catalogue VALUES ('6778', 'Lampe de poche');
INSERT 372715 1
essais=> INSERT INTO Catalogue VALUES ('99115', 'Allumettes');
INSERT 372716 1
essais=> CREATE TABLE Commandes (quand DATE, quoi VARCHAR(6) REFERENCES Catalogue(ref));
CREATE TABLE
essais=> INSERT INTO Commandes VALUES ('2007-1-17', '6778');
INSERT 372723 1
essais=> INSERT INTO Commandes VALUES ('2007-1-17', '1');
ERROR: insert or update on table "commandes" violates foreign key constraint "$1"
DETAIL: Key (quoi)=(1) is not present in table "catalogue".
]]>
On note que ces contraintes ne s'appliquent pas qu'à la création
(elles suivent l'esprit SQL, ce
sont des déclarations, des assertions sur la base, pas des
instructions à exécuter). Elles empêchent également de « scier la
branche sur laquelle on est assis », par exemple en détruisant des
données qu'on référence encore :
DELETE FROM Catalogue WHERE ref = '6778';
ERROR: update or delete on "catalogue" violates foreign key constraint "$1" on "commandes"
DETAIL: Key (ref)=(6778) is still referenced from table "commandes".
]]>
Si les contrôles à effectuer sont un peu plus compliquées, et qu'on
ne souhaite pas forcément créer ses propres types,
CREATE TABLE Employes (salaire NUMERIC CHECK (salaire > 1280));
-- Le SMIC
CREATE TABLE
essais=> INSERT INTO Employes VALUES (3150);
INSERT 372735 1
essais=> INSERT INTO Employes VALUES (1291);
INSERT 372736 1
essais=> INSERT INTO Employes VALUES (940);
ERROR: new row for relation "employes" violates check constraint "employes_salaire"
]]>
Malgré les efforts de
Les tests faits avec
CREATE TABLE Contacts (adresse TEXT NOT NULL
essais(> CONSTRAINT Adresse_valide CHECK (adresse ~ '^.+@.+$'));
CREATE TABLE
essais=> INSERT INTO Contacts VALUES ('stephane+blog@bortzmeyer.org');
INSERT 372743 1
essais=> INSERT INTO Contacts VALUES ('postmaster@hotmail.com');
INSERT 372744 1
essais=> INSERT INTO Contacts VALUES ('ano nymous');
ERROR: new row for relation "contacts" violates check constraint "adresse_valide"
]]>
On peut tester de manière analogue, toujours avec des
CREATE TABLE Contacts (telephone TEXT NOT NULL
essais(> CONSTRAINT Numero_telephone CHECK (telephone ~ '^\\+[0-9]+[ 0-9]+$'));
CREATE TABLE
essais=> INSERT INTO Contacts VALUES ('+33 1 39 30 83 46');
INSERT 372751 1
essais=> INSERT INTO Contacts VALUES ('+1 123 456');
INSERT 372752 1
essais=> INSERT INTO Contacts VALUES ('22');
ERROR: new row for relation "contacts" violates check constraint "numero_telephone"
]]>
Cette syntaxe (à la norme
Autre utilisation de
CREATE TABLE Personne (statut TEXT
essais(> CHECK (statut IN ('prospect', 'fournisseur', 'client', 'presse')));
CREATE TABLE
essais=> INSERT INTO Personne VALUES ('fournisseur');
INSERT 372759 1
essais=> INSERT INTO Personne VALUES ('client');
INSERT 372760 1
essais=> INSERT INTO Personne VALUES ('autre');
ERROR: new row for relation "personne" violates check constraint "personne_statut"
]]>
Il existe des cas où la syntaxe simple de
Voici un exemple de fonction pour tester la syntaxe que le registre
de noms de domaine accepte (elle est souvent plus restrictive que ce
que le
Pour que cette fonction soit appelée avant la création du nom de
domaine, on utilise les
Ainsi, toute tentative d'insérer (ou de modifier) un nom de domaine va
appeler la function
CREATE TABLE Domains (name TEXT UNIQUE NOT NULL);
NOTICE: CREATE TABLE / UNIQUE will create implicit index "domains_name_key" for table "domains"
CREATE TABLE
essais=> \i mychecks.sql
CREATE FUNCTION
CREATE FUNCTION
CREATE TRIGGER
essais=> INSERT INTO Domains VALUES ('foobar.example');
INSERT 372800 1
essais=> INSERT INTO Domains VALUES ('foobar..example');
ERROR: Illegal syntax for a domain name
essais=> INSERT INTO Domains VALUES ('tyuyty ghg');
ERROR: Illegal syntax for a domain name
]]>
Pour le cas où on veut utiliser des données qui se trouvent dans la
base, on définit fonction et déclencheur :
= 10000;
END;'
LANGUAGE PLPGSQL;
CREATE OR REPLACE FUNCTION check_money() RETURNS TRIGGER
AS 'BEGIN
IF NOT enough_money(NEW.value) THEN
RAISE EXCEPTION ''Not enough money for this order'';
END IF;
RETURN NEW;
END;'
LANGUAGE PLPGSQL;
DROP TRIGGER check_money ON Orders;
CREATE TRIGGER check_money
BEFORE INSERT ON Orders
FOR EACH ROW
EXECUTE PROCEDURE check_money();
]]>
et on peut les utiliser :
CREATE TABLE Cash (amount INTEGER);
CREATE TABLE
essais=> CREATE TABLE Orders (value INTEGER);
CREATE TABLE
essais=> \i mychecks.sql
CREATE FUNCTION
CREATE FUNCTION
DROP TRIGGER
CREATE TRIGGER
essais=> SELECT amount FROM Cash;
amount
--------
30000
(1 row)
essais=> INSERT INTO Orders VALUES (30000);
ERROR: Not enough money for this order
essais=> INSERT INTO Orders VALUES (20000);
INSERT 372829 1
]]>
Tout ne peut pas s'exprimer avec ces règles, notamment si on a des
règles « métier » très complexes. On doit alors passer aux langages de
programmation classiques. Une autre limite, avec PostgreSQL, est que
les déclencheurs sont par instruction SQL, pas par
transaction (on ne peut pas définir de déclencheur
Une autre limite des règles d'intégrité dans la base est qu'on
souhaite parfois mettre ces mêmes règles à d'autres endroits, par
exemple dans un code
Pour conclure, disons que les règles d'intégrité mises dans base
elle-même sont un excellent outil (même avec leurs limites) et que
leur utilisation épargnerait bien des ennuis à beaucoup de
Parmi les articles sur ce sujet, je recommande chaudement