Developpez.com - ALM
X

Choisissez d'abord la catégorieensuite la rubrique :

Bases de données relationnelles et normalisation :
de la première à la sixième forme normale

Date de publication : 07/09/2008. Date de mise à jour : 14/07/2012.


3. Forme normale de Boyce-Codd, deuxième et troisième formes normales
3.1. Les états d'âme provoqués par la première forme normale
3.1.1. Une tentative de normalisation en 1NF
3.1.2. Conséquences de la normalisation : des redondances à profusion
3.1.3. Les difficultés de mise à jour (Insert, Delete, Update)
3.1.4. Quelle alternative ?
3.1.5. Approche conceptuelle de la solution
3.2. L'approche analytique, ses outils
3.2.1. Remarque préliminaire
3.2.2. Dépendance fonctionnelle (DF)
3.2.3. Clé candidate
3.2.4. Surclé
3.2.5. Sous-clé
3.2.6. Clé primaire
3.3. Forme normale de Boyce-Codd (BCNF)
3.3.1. Énoncé de la BCNF
3.3.2. Théorème de Heath
3.3.3. Comment normaliser une relvar qui n'est pas en BCNF
3.3.4. Décomposition par projection - jointure préservant l'information
3.3.5. Normalisation et intégrité référentielle
3.3.6. Conséquences de la normalisation par projection - jointure
3.4. Deuxième et troisième formes normales
3.4.1. BCNF versus 2NF et 3NF
3.4.2. Deuxième forme normale (2NF)
3.5. Dépendance transitive, dépendance directe
3.6. Troisième forme normale (3NF)
3.7. Un problème embarrassant de BCNF
3.8. Retour sur la dénormalisation a priori
3.9. Une relvar respectant la BCNF peut-elle violer la 3NF ?


3. Forme normale de Boyce-Codd, deuxième et troisième formes normales


3.1. Les états d'âme provoqués par la première forme normale

 

3.1.1. Une tentative de normalisation en 1NF

Considérons à nouveau la table Membre de la Figure 2.3, qui pourrait représenter une ébauche de la table des membres de DVP. Au sens de Codd, cette table n'est pas normalisée en 1NF et pour la conserver sans la casser en deux, tout en normalisant, on pourrait la structurer ainsi (certains noms d'attributs ont été modifiés, pour des contraintes de représentation graphique) :

?
Figure 3.1 - Table Membre, normalisée en 1NF

On notera au passage qu'il a fallu faire évoluer la composition des contraintes MbrPk, CK2 et CK3. La clé primaire de la table est désormais composée de la paire {MbrId, MessId} (attributs soulignés ci-dessous).

?
Figure 3.2 - Table Membre, normalisée en 1NF (corps de la table)


3.1.2. Conséquences de la normalisation : des redondances à profusion

Au vu du contenu de la table de la Figure 3.2, on peut constater la présence de redondances (en rouge) ne faisant que polluer la table. On apprend ainsi quatre fois — c'est-à-dire autant de fois qu'il a échangé des messages — que le membre 31411 a pour pseudonyme fsmrel, qu'il s'est inscrit le 08/09/2006, qu'il vit dans le Relationland et qu'il a pour adresse fsmrel@xxx.re. Pour sa part, Marc en est à 20 000 messages : la normalisation en 1NF de la table Membre a provoqué une inflation déraisonnable de redondances, faisant que la table prend de l'embonpoint.

Exemple :

Coût du stockage (DB2 for z/OS V8). Par référence aux tables telles qu'elles sont décrites dans les figures 2.4 et 3.1, et sur la base d'un taux de compression d'environ 75% pour la table Membre et de 90% pour les autres tables, le coût du stockage est de l'ordre de celui-ci (en notant que dans le cas du couple de tables {Membre, Message}, l'index primaire de la table Message, de clé {MbrId, MessageId} sert aussi pour la clé étrangère {MbrId}) :

?
Coûts de stockage (DB2)


Contrairement à ce que l'intuition pourrait faire croire, dans le cas de la table unique (figure 3.1), le coût de stockage est supérieur à celui des tables Membre + Message de la figure 2.4.

On peut s'interroger sur le nombre de lectures/écritures en cache ou sur disque nécessaires pour, par exemple, modifier la localisation ou l'adresse de courriel de quelqu'un qui, comme Marc, a rédigé 20 000 messages. Dans le cas de l'adresse de courriel, ne pas oublier que ces lectures/écritures concernent aussi l'index utilisé pour garantir l'unicité des couples {MbrId, AdrCourriel}. Sans oublier non plus les désorganisations qui s'ensuivent, l'encombrement des fichiers journaux, les contentions entre transactions concurrentes, etc.

Tout cela, indépendamment d'une catégorie de problèmes bien connus de mise à jour, inhérents à la non normalisation, très tôt pointés par Codd et repris par tous les auteurs, problèmes évoqués dans le paragraphe 3.1.3 qui suit.

Quant aux performances concernant la lecture, si avec la table unique on évite une jointure, un prototypage en ce sens et des mesures poussées pourraient bien montrer qu'à part quelques requêtes pour lesquelles on ferait des économies (de bouts de chandelle à dire vrai), certains coûts sont cachés et peuvent être élevés (tris déclenchés par l'emploi de la clause SQL DISTINCT, etc.) sans compter que le nombre de certains niveaux d'index augmente. Se reporter au paragraphe 3.8 pour juger de prétendus méfaits dont certains accusent bien à tort la jointure, sans avoir vérifié.

Ce qui se passe réellement dans la soute n'a pas grand-chose à voir avec ce que l'on peut imaginer du haut de la dunette...


3.1.3. Les difficultés de mise à jour (Insert, Delete, Update)

L'organisation de la table Membre (Figure 3.1) pose quelques problèmes concernant les mises à jour.

Insert : On ne peut pas prendre en compte l'inscription des membres tant qu'ils n'ont pas envoyé un premier message (intégrité d'entité oblige, cf. paragraphe 3.2.6 : l'attribut MessId participe à la clé primaire et ne peut être marqué « NULL »).

Delete : Si l'on veut supprimer les messages de fsmrel, on est carrément obligé de supprimer ce dernier (intégrité d'entité oblige encore).

Update : Suite à l'exécution d'une requête mal codée, rien n'empêche de modifier le pseudonyme d'un membre seulement dans certaines lignes qui le concernent et « d'oublier » de le faire dans d'autres lignes. Bien sûr, à coups de contraintes (triggers SQL en général), on peut contrôler qu'un membre n'a qu'un pseudonyme, qu'une date d'inscription, etc. Mais quelle surcharge de travail pour le développeur et le DBA, et quel surcoût en matière de consommation de ressources pour le SGBD chargé de garantir les contraintes...


3.1.4. Quelle alternative ?

Que faire ? Se résigner à mettre en oeuvre la table Membre (Figure 3.1) respectant la 1NF ? Revenir à la situation initiale, en conservant une structure calquée sur celle de la relation de la Figure 2.3 ? Encore faudrait-il que le SGBD accepte les attributs à valeurs relations (RVA). Évidemment, aux difficultés près posées par ce genre d'attributs (cf. paragraphe 2.6), les redondances disparaissent et la table subit une sérieuse et bénéfique cure d'amaigrissement ; désormais (grâce à l'ensemble vide) une inscription peut être effectuée sans qu'il soit besoin d'attendre que le nouvel inscrit (rismo) ait envoyé son premier message ; de même, on peut supprimer tous les messages de Marc (sitôt dit, sitôt fait) sans que celui-ci disparaisse pour autant, etc., suite à quoi la table aurait l'allure suivante (structure et valeur) :

?
Figure 3.3 - Table Membre, avec l'attribut Messages dans une configuration RVA


3.1.5. Approche conceptuelle de la solution

Il y a bien sûr une alternative. Celui qui a l'habitude de pratiquer l'art de la modélisation conceptuelle, qu'il s'agisse de produire des MCD ou des diagrammes de classes, n'hésitera guère et fournira un diagramme qui, par dérivation, donnera lieu à des tables normalisées en 1NF, mais dépourvues d'attributs RVA, ce qui tombe à pic quand le SGBD ne permet pas la mise en oeuvre de tels attributs. Disons que l'on procède en l'occurrence selon une démarche dite synthétique, tandis que dans une étape suivante, on vérifiera la qualité du résultat, selon une démarche dite analytique, mettant en jeu les techniques de normalisation qui permettront de s'assurer que l'architecture de la base de données est valide, une fois éliminées les redondances. En fait, on pourra toujours améliorer cette architecture pour éviter par exemple la présence du bonhomme NULL, ou pour anticiper quant à la performance des applications, mais ceci sort du cadre de la normalisation. Quoi qu'il en soit, le concepteur modélisera quelque chose comme ceci :

?
Figure 3.4 - Membres des forums - vision conceptuelle


Ce qui donnera lors de l'étape de dérivation un diagramme logique tel que celui-ci :

?
Figure 3.5 - Membres des forums - diagramme logique    


Autrement dit, lors de la génération du code SQL correspondant, on retrouvera la structure des tables Membre et Message de la Figure 2.4. Vous me direz : que de détours pour revenir en quelque sorte à la case Départ ! Mais il fallait bien voir qu'il y a plus d'une façon de modéliser avant de décider d'une stratégie. Et si l'on opte pour les structures des tables de la Figure 2.4, il ne restera plus qu'à démontrer qu'elles sont en cinquième forme normale — vaste programme, mais réalisable sans grand effort dans le cas de cet exemple, grâce aux théorèmes de Date et Fagin (cf. paragraphe 5.8) :

     Si une relvar est en 3NF et si chacune de ses clés candidates ne comporte qu'un seul attribut, alors cette relvar est en 5NF.

     Si une relvar est en BCNF et comporte au moins un attribut non-clé, alors cette relvar est en 5NF.


3.2. L'approche analytique, ses outils


3.2.1. Remarque préliminaire

Notre défi est donc de produire une structure de base de données qui soit valide, évolutive, manipulable, c'est-à-dire débarrassée des redondances dénoncées précédemment. La théorie relationnelle nous en donne les moyens. Elle met à notre disposition des outils dont je cite certains : dépendance fonctionnelle, dépendance multivaluée, dépendance de jointure, clé candidate, surclé, théorème de Heath, théorème de Fagin-Date, opérateurs de projection et de jointure (naturelle), axiomes d'Armstrong. Grâce à ces outils on pourra s'assurer que les tables sont normalisées en 2NF, 3NF, etc. Et par une pratique synthético-analytique rappelant l'art du yoyo, on vérifiera la modélisation conceptuelle en conséquence.

Je propose ici de décrire les outils les plus indispensables, en commençant par les plus simples et qui permettent de s'assurer qu'une table est en forme normale de Boyce-Codd (BCNF), à savoir les dépendances fonctionnelles. Par ailleurs, chronologie oblige, il est d'usage de commencer par étudier la 2NF et la 3NF avant de passer à la BCNF : je procèderai néanmoins dans l'ordre inverse, car la 2NF et la 3NF ne présentent plus guère aujourd'hui d'intérêt qu'historique.

A noter que j'utiliserai ici la terminologie du Modèle Relationnel plutôt que celle du Modèle SQL, mais il est facile de traduire, en remplaçant relvar et relation par table, tuple par ligne, etc.


3.2.2. Dépendance fonctionnelle (DF)

Il est un concept capital sans lequel nous ne pourrions pas traiter de la normalisation, car non seulement il est impliqué systématiquement dans les énoncés des formes normales, mais en outre, il représente un puissant outil pour normaliser les relations : il s'agit de la dépendance fonctionnelle. C'est en quelque sorte notre rasoir d'Ockham.

./images/Carre_invisible_No_Indent.jpg (Guillaume d'Ockham (ou d'Occam), moine anglais du XIVe siècle, à qui l'on doit ce sage conseil de parcimonie : Pluralitas non est ponenda sine necessitate, que nous pouvons interpréter ici comme : Ne multipliez pas les redondances au-delà du nécessaire).
Une dépendance fonctionnelle (DF) est une instruction de la forme :

        X ➔ Y

où X et Y sont deux sous-ensembles d'attributs de l'en-tête d'une relvar R, et satisfaisant à la règle : pour une valeur de X, correspond exactement une valeur de Y, c'est-à-dire que si deux tuples ont la même valeur vx pour X, alors ils ont aussi la même valeur vy pour Y.

On dit encore que Y est fonctionnellement dépendant de X. X est appelé le déterminant de la DF et Y le dépendant.

Pour signifier que X ne détermine pas Y, on peut écrire : XY.


Considérons par exemple la relvar FPV, extension de la relvar FP décrite dans la Figure 2.6 et dont l'en-tête est enrichi, juste pour les besoins de la cause, d'un attribut Ville, utilisé pour savoir dans quelle ville est localisé un fournisseur :

?
Figure 3.6 - Relvar des liens fournisseurs - pièces (enrichie de l'attribut Ville)


La relvar FPV vérifie un certain nombre de DF. Ainsi, pour signifier qu'un fournisseur est localisé dans une et une seule ville, on écrit :

    {Four_No} ➔ {Ville}

La paire {Four_No, Piece_No} étant clé candidate (cf. paragraphe 3.2.3), on a aussi les DF suivantes (entre autres) :

    {Four_No, Piece_No} ➔ {Quantite}

    {Four_No, Piece_No} ➔ {Ville}

./images/warning.gif (L'utilisation des accolades est là pour rappeler que le déterminant et le dépendant d'une DF ne sont pas des attributs, mais des ensembles d'attributs, au sens de la théorie des ensembles).
Avant de poursuivre, procédons à une classification des dépendances fonctionnelles :

  • Dépendance fonctionnelle triviale

    Soit R une relvar et X et Y deux sous-ensembles quelconques d'attributs de l'en-tête de R. La dépendance fonctionnelle X ➔ Y est dite triviale si Y est inclus dans X (inclusion au sens large). 

    La relvar FPV comporte donc (entre autres) les dépendances triviales suivantes :

        {Four_No} ➔ {Four_No}

        {Four_No, Piece_No} ➔ {Four_No}

        {Four_No, Piece_No} ➔ {Piece_No}

        {Ville, Quantite} ➔ {Ville}

        Etc.

    Malgré son caractère ... trivial, la DF triviale est importante, car elle fait l'objet du 1er axiome d'Armstrong (cf. paragraphe E en annexe), et elle intervient dans l'énoncé de la BCNF.

  • Dépendance fonctionnelle partielle

    Soit R une relvar, X un sous-ensemble quelconque d'attributs de R et C un attribut quelconque de R. La dépendance fonctionnelle X ➔ {C} est dite partielle si elle n'est pas triviale et s'il existe Y strictement inclus dans X tel que Y ➔ {C}. 

    Ainsi, la relvar FPV comporte la dépendance fonctionnelle non triviale

        {Four_No, Piece_No} ➔ {Ville}

    laquelle est partielle, car il existe par ailleurs la dépendance fonctionnelle

        {Four_No} ➔ {Ville}

    Ce type de DF intervient dans l'étude de la 2NF.

  • Dépendance fonctionnelle irréductible à gauche (ou élémentaire ou totale)

    Soit R une relvar, X un sous-ensemble quelconque d'attributs de R et C un attribut quelconque de R. La dépendance fonctionnelle X ➔ {C} est dite irréductible à gauche (ou encore élémentaire ou totale) si elle n'est ni triviale ni partielle, c'est-à-dire s'il n'existe pas Y strictement inclus dans X tel que Y ➔ {C}.

    Ainsi, la relvar FPV comporte la dépendance fonctionnelle irréductible à gauche

        {Four_No, Piece_No} ➔ {Quantite}

    En effet, une quantité de pièces n'a de sens que pour un fournisseur et un type de pièce donnés : pour un fournisseur on peut avoir différentes quantités de pièces et il n'existe donc pas de DF {Four_No} ➔ {Quantite}. De la même façon, il n'existe pas de DF {Piece_No} ➔ {Quantite}. 

    Autre exemple : au paragraphe E.6.2 en annexe, on montre qu'étant données la relvar R{A, B, C, D} et l'ensemble de DF : {{A} ➔ {B,C}, {B} ➔ {C}, {A} ➔ {B}, {A,B} ➔ {C}, {A,C} ➔ {D}}, la DF {A,C} ➔ {D} est réductible.

    Ce type de DF intervient essentiellement dans l'étude de la 2NF (et dans telle ou telle définition de la BCNF).

./images/info.png L'expression dépendance fonctionnelle totale est celle utilisée par Ted Codd : full functional dependence [Codd 1971]. L'expression dépendance fonctionnelle irréductible à gauche (left-irreducible functional dependence) apparaît chez Date dans la 6e édition de An Introduction to Database Systems. Le qualificatif irréductible à gauche est plus précis que celui de totale (et d'élémentaire) et fait intervenir, à juste titre, la fermeture d'un ensemble de DF (cf. paragraphes E.1 et E.6.2 en annexe).

3.2.3. Clé candidate




(En notant qu'en vertu de la propriété d'unicité, la DF : K ➔ {A} est vérifiée pour chaque attribut A de R.)

Ainsi, le sous-ensemble défini par la paire {Four_No, Piece_No} est une clé candidate de la relvar FPV (clé, pour faire court). Considérons en effet les DF vues précédemment :

    {Four_No, Piece_No} ➔ {Four_No}         (DF triviale)

    {Four_No, Piece_No} ➔{Piece_No}         (DF triviale)

    {Four_No, Piece_No} ➔ {Quantite}         (DF irréductible à gauche)

    {Four_No, Piece_No} ➔ {Ville}               (DF partielle)

Du fait de ces DF, on constate tout d'abord que la paire {Four_No, Piece_No} détermine fonctionnellement chaque attribut de FPV, ce qui répond à la règle d'unicité.

Ensuite, cette paire n'est pas réductible. En effet, les sous-ensembles {Four_No} et {Piece_No} qui la composent ne sont pas assujettis, pris isolément, à respecter la contrainte d'unicité (de fait, le fournisseur S1 apparaît légalement dans 6 tuples de la relvar FPV et la pièce P2 dans 4 tuples) et ne peuvent donc pas faire à eux seuls l'objet de clés candidates.

Puisqu'elle satisfait aux contraintes d'unicité et d'irréductibilité, la paire {Four_No, Piece_No} est clé candidate.

Les triplets {Four_No, Piece_No, Quantite} et {Four_No, Piece_No, Ville} garantissent la règle d'unicité, mais sont réductibles car le sous-ensemble {Four_No, Piece_No} inclus dans chacun d'eux garantit aussi la règle : ces triplets ne peuvent être clés candidates, pas plus que le quadruplet {Four_No, Piece_No, Quantite, Ville} réductible lui aussi.

./images/info.png On observera qu'une relvar peut posséder plus d'une clé candidate. C'est le cas par exemple de la table Membre telle qu'elle est décrite dans la Figure 2.4 et dans sa version obèse (Figure 3.1), où la table est dotée des trois clés suivantes : {MbrId, MessId}, {Pseudo, MessId}, {AdrCourriel, MessId}.

3.2.4. Surclé

Le concept de surclé intervient souvent dans la théorie de la normalisation, il est donc important d'en donner la définition. La surclé est un surtype de la clé candidate :

La paire {Four_No, Piece_No}, les triplets {Four_No, Piece_No, Quantité}, {Four_No, Piece_No, Ville} et le quadruplet {Four_No, Piece_No, Quantité, Ville} constituent autant de surclés pour FPV.

On retiendra donc que :

  • Un sous-ensemble K d'une relvar R est une surclé de R si et seulement si chaque attribut C de R en dépend fonctionnellement (trivialement, partiellement ou totalement).

  • Une clé candidate est une surclé spécialisée, car elle est astreinte à respecter le principe d'irréductibilité outre celui d'unicité.

3.2.5. Sous-clé




Une sous-clé est un sous-ensemble (non strict) d'une clé candidate.



La paire {Four_No, Piece_No} et les singletons {Four_No} et {Piece_No} sont des sous-clés de la relvar FPV. Même chose pour l'ensemble vide {}.

On peut noter qu'une clé candidate est à la fois surclé et sous-clé.


3.2.6. Clé primaire

Dans le contexte du Modèle Relationnel lui-même, le concept de clé primaire est aujourd'hui obsolète (rasoir d'Ockham oblige). Mais du point de vue SQL, il est toujours bien présent : dans ces conditions, si une table T possède plusieurs clés candidates, on en privilégie une pour être primaire, et si T ne possède qu'une seule clé candidate, celle-ci l'est de facto. L'intention est non seulement d'« identifier » chaque tuple de T, mais aussi d'établir des ponts entre T et d'autres tables, garantir l'intégrité référentielle par le jeu des liens entre clés primaires et étrangères [Codd 1970]. Les clés candidates non retenues sont dites alternatives ou alternées, de rechange... (Alternate keys).

Avec SQL donc, même si cela est a priori arbitraire, il faut faire appel à quelques critères aidant à fixer son choix :

./images/Carre_noir(puce)non_indente.jpg Il est impératif qu'une clé primaire soit stable, c'est-à-dire qu'elle ne change jamais de valeur. En effet, du fait des contraintes référentielles impliquant clés primaires et étrangères, le remplacement d'une valeur de clé primaire peut déclencher une cascade de remplacements de valeurs de clés primaires/étrangères dans de nombreuses autres tables et finir par pénaliser très sensiblement l'activité d'une production informatique (accès physiques aux données, phénomènes de blocage, désorganisation des données au niveau physique, etc., et n'oublions pas que pour la majorité des SGBDR, la structure d'un tuple induit celle d'un enregistrement physique, hélas !)

Par exemple, si la table des fournisseurs comporte le numéro de SIREN en tant qu'attribut, il faut éviter de faire participer celui-ci à clé primaire de la table. En effet, ce numéro est attribué par l'INSEE qui, tous les mois, fournit la liste des numéros erronés et à remplacer. Plus généralement, il ne faut pas utiliser des informations dont les valeurs sont fournies par un organisme extérieur à l'entreprise et susceptibles de changer, à l'instar de ces numéros.

De la même façon, il faut éviter d'utiliser pour les clés primaires des données dont les valeurs sont significatives, du genre : les deux premiers caractères représentent telle information, les trois suivants telle autre information, cela relativement aux deux premiers... Sinon, on en arrive à figer un système qui pourtant doit s'adapter aux évolutions propres à l'entreprise ou imposées par un système externe  (l'ISBN codifié par l'ISO, l'EAN13 ou l'EAN8, les codes bancaires présents sur la ligne CMC7 des chèques, etc.)

Ainsi, une clé primaire ne doit pas pouvoir changer de valeur et ne doit donc pas être significative : elle doit être définie en accord avec l'administrateur de la base de données, lequel aura à terme le bébé sur les bras. En général, on en vient à définir un attribut artificiel de type entier, que l'on incrémente ou que l'on hache (tâches confiées généralement au SGBDR), ou encore de type TIMESTAMP, etc.
./images/IndexBleu_19x30_indent.png Pour des raisons de lisibilité et ne pas décourager le lecteur, nous dérogerons à cette règle en ce qui concerne certains exemples qui émaillent cet article, cf. par exemple le paragraphe 3.7, mais dans un contexte de production, on ne déroge pas).
./images/Carre_noir(puce)non_indente.jpg Une clé primaire doit respecter l'intégrité d'entité au sens de Codd, c'est-à-dire qu'aucun de ses composants ne peut être marqué « NULL ».
Il est évident que si aucune clé candidate ne répond aux critères que l'on vient d'énumérer, alors il faudra inventer de toutes pièces une clé primaire qui soit conforme, en définissant à cet effet un attribut parfaitement artificiel. Ceci est à rapprocher de ce qu'a écrit Yves Tabourier au sujet des propriétés des entités-types (ou individus-types) [Tabourier 1986] page 80 :

« ... la fonction d'une propriété est de décrire les objets (et les rencontres), alors que l'identifiant ne décrit rien. Son rôle fondamental est d'être sûr de distinguer deux jumeaux parfaits, malgré des descriptions identiques.
      L'expérience montre d'ailleurs que l'usage des « identifiants significatifs » (ou « codes significatifs ») a pu provoquer des dégâts tellement coûteux que la sagesse est d'éviter avec le plus grand soin de construire des identifiants décrivant les objets ou, pis encore, leurs liens avec d'autres objets...  »
On ne saurait mieux dire.

Observations concernant les clés primaires

Pour leur part, les continuateurs de l'oeuvre de Codd, à savoir Date & Darwen, estiment que le choix d'une clé candidate pour être clé primaire, relève de considérations d'ordre purement psychologiques car, en quelque sorte, « elle serait plus égale que les autres ». Reste son rôle en relation avec l'intégrité référentielle : que ce soit dans le cadre du Modèle Relationnel ou du Modèle SQL, une clé étrangère pouvant être en relation avec n'importe quelle clé candidate servant de référence, on pourrait simplement exiger que la clé référencée vérifie les critères précédemment énoncés (stabilité, absence de signification, marquage « NULL » interdit). Une réflexion attentive montre qu'il faut se rendre aux arguments développés par D & D dans [Date 1995] : les deux concepts de clé primaire et de clé alternative peuvent être évacués sans rien remettre en cause au plan fonctionnel. C'est pourquoi Tutorial D (le langage de D & D) les ignore, au bénéfice du seul concept de clé (sous-entendu candidate). Mais, dans le contexte de la modélisation, il peut être utile de raisonner en termes de clé primaire (comme interprétation de l'identifiant d'une entité-type d'un modèle conceptuel de données).

Les concepts de clé primaire et de clé alternative pourraient donc théoriquement être évacués de SQL, au bénéfice du seul concept de clé (toujours sous-entendu candidate), mais il y a malgré tout la réalité des bases de données en production, et l'on ne peut donc pas supprimer du langage un mot-clé omniprésent, simplement en claquant des doigts.


3.3. Forme normale de Boyce-Codd (BCNF)

On a besoin de techniques permettant d'évacuer les anomalies de mise à jour (cf. paragraphe 3.1.3), en décomposant une relvar R en d'autres relvars R1, R2, ..., Rn, elles-mêmes dépourvues de ces anomalies. Décomposer signifie, réciprocité oblige, savoir recomposer R. Pour cela, on met en oeuvre un procédé de normalisation, s'appuyant fondamentalement sur les relations qu'il peut y avoir entre clés et dépendances fonctionnelles.

Il faut d'abord être en mesure de vérifier si une relvar est à décomposer. L'énoncé fondamental permettant de s'en assurer porte le nom de forme normale de Boyce-Codd (BCNF pour Boyce-Codd Normal Form), cf. [Fagin 1977], [Ullman 1982], [Date 2008].


3.3.1. Énoncé de la BCNF

Énoncé de Boyce et Codd [Codd 1974], où la BCNF est encore nommée 3NF :

./images/Carre_vert_clair_No_Indent.jpg A relation R is in third normal form if it is in first normal form and, for every attribute, collection C of R, if any attribute not in C is functionally dependent on C, then all attributes in R are functionally dependent on C.
Énoncé de Date ([Date 2008]), plus au goût du jour que le précédent, je traduis :

./images/Carre_vert_clair_No_Indent.jpg Une relvar R est en forme normale de Boyce-Codd (BCNF) si et seulement si pour chaque dépendance fonctionnelle non triviale A ➔ B qui doit être vérifiée par R, A est une surclé de R.

(A et B sont des sous-ensembles d'attributs de l'en-tête de la relvar R).
Variante 1, due à Carlo Zaniolo (cf. [Date 2004]) :

./images/Carre_vert_clair_No_Indent.jpg Soit R une relvar, X un sous-ensemble d'attributs de l'en-tête de R et A un attribut de cet en-tête. R est en forme normale de Boyce-Codd (BCNF) si et seulement si, pour chaque dépendance fonctionnelle X ➔ {A} qui doit être vérifiée par R, au moins une des conditions suivantes est satisfaite :
1.   A est un élément de X (cette dépendance fonctionnelle est donc triviale).
2.   X est une surclé de R.
On peut ainsi affirmer que la relvar FPV (cf. Figure 3.6) n'est pas en forme normale de Boyce-Codd, puisqu'il existe la dépendance fonctionnelle non triviale :

     {Four_No} ➔ {Ville}

dont le déterminant {Four_No} ne constitue pas une surclé, car ne respectant pas la contrainte d'unicité (cf. paragraphe 3.2.3). Pour normaliser la relvar FPV, on se servira du théorème de Heath (voir ci-dessous).

./images/warning.gif Petit problème en passant : pour prouver qu'une relvar R est en BCNF il faut d'abord dresser l'inventaire complet des DF non triviales satisfaites par R, et vérifier que chacune d'elles est une surclé. Or, une DF est de la forme A ➔ B, A et B étant des sous-ensembles des attributs de R.  Comme à un ensemble de n éléments correspond 2n sous-ensembles (en tenant compte de l'ensemble vide), la limite supérieure du nombre de DF est égale à 22n. Certains chiffres découragent... 
Heureusement, on dispose d'un algorithme permettant de s'assurer relativement rapidement du respect de la BCNF (cf. paragraphe E.5 en annexe). 
Variante 2 de l'énoncé de la BCNF (basée sur les dépendances fonctionnelles irréductibles à gauche) :

./images/Carre_vert_clair_No_Indent.jpg Soit R une relvar, X un sous-ensemble d'attributs de l'en-tête de R et A un attribut de cet en-tête. R est en forme normale de Boyce-Codd (BCNF) si et seulement si, pour chaque dépendance fonctionnelle X ➔ {A} qui doit être vérifiée par R, une des conditions suivantes est satisfaite :
1.   Cette dépendance fonctionnelle est triviale (A est un élément de X).
2.   Cette dépendance fonctionnelle est irréductible à gauche et X est une clé candidate de R.
Petite consolation concernant la chasse aux DF : La recherche peut être limitée aux DF irréductibles à gauche. Il n'en demeure pas moins que la méthode décrite au paragraphe E.6.2 sera encore d'un grand secours, par exemple pour prouver qu'une DF est irréductible. A noter que cette définition a pour origine une de celles qui sont proposées dans [Date 2004] :

./images/Carre_invisible_No_Indent.jpg « A relvar is in BCNF if and only if every nontrivial, left-irreducible FD has a candidate key as its determinant. »
(Une relvar est en BCNF si et seulement  si le déterminant de chaque DF non triviale , irréductible à gauche est une clé candidate de  la relvar.) 

3.3.2. Théorème de Heath

On doit à Ian Heath un théorème très important (1971), que l'on mettra à profit pour normaliser :

./images/Carre_vert_clair_No_Indent.jpg Soit la relvar R {A, B, C} dans laquelle A, B et C sont des sous-ensembles d'attributs de R. Si R satisfait à la dépendance fonctionnelle A ➔ B, alors R est égale à la jointure de ses projections sur {A, B} et {A, C}.
Autrement dit, si R1 et R2 représentent les relvars résultant de ces projections, alors R = R1 JOIN R2 (préservation du contenu de la base de données). On dispose là d'un moyen très efficace pour décomposer une relvar non normalisée. A user sans modération (sauf si l'on autorise la présence du bonhomme NULL, cf. paragraphe D en annexe), mais tout en ayant à l'esprit la règle de préservation des dépendances :

Préservation des dépendances

On doit à Jorma Rissanen une règle importante, grâce à laquelle on évitera de perdre des DF :

./images/Carre_vert_clair_No_Indent.jpg Soit la relvar R {A, B, C} dans laquelle A, B et C sont des sous-ensembles d'attributs de R. Si R satisfait aux dépendances fonctionnelles A ➔ B et B ➔ C, alors plutôt que de décomposer R en {A, B} et {A, C}, on décomposera R en {A, B} et {B, C}.
./images/warning.gif En effet, en procédant ainsi, R est encore égale à la jointure de ses projections sur {A, B} et {B, C}, et surtout, on ne perd pas en route la DF   B ➔ C, qui est quand même la traduction formalisée d'une règle de gestion des données.
Se reporter aussi au paragraphe E.7.2 où l'on traite plus avant de la préservation des dépendances.

3.3.3. Comment normaliser une relvar qui n'est pas en BCNF

Dans le cas de la relvar FPV (cf. Figure 3.6), pour évacuer le problème posé par la dépendance fonctionnelle {Four_No} ➔ {Ville}, si l'on a présent à l'esprit le théorème de Heath, il suffit de procéder ainsi :

1.   Définir une relvar FV, qui soit la projection de la paire {Four_No, Ville} :

         FV (Four_No, Ville)

      (par application de la définition de la clé candidate (principes d'unicité et d'irréductibilité), la clé de FV est composée du seul attribut Four_No).

2.   Définir une relvar FPQ qui soit la projection de FPV sur l'ensemble des attributs de FPV sauf Ville :

         FPQ (Four_No, Piece_No, Quantite)

En sorte que par jointure naturelle de FV et FPQ on retrouve FPV :

FPV = FV  JOIN  FPQ




3.3.4. Décomposition par projection - jointure préservant l'information

Ainsi, le processus de normalisation met en jeu le mécanisme de projection - jointure (naturelle) :

  1. Par projection d'une DF qui est la cause d'un viol de BCNF, on décompose une relvar R non normalisée en deux relvars R1 et R2 (mieux, sinon totalement) normalisées.

    Et l'on sait que — en vertu du théorème de Heath — par jointure naturelle des relvars R1 et R2, on retrouve la relation initiale, avec préservation du contenu de la base de données.

    Ce dernier point est important, car le but de l'opération est de retrouver la relation initiale dans son intégrité : la normalisation par projection/jointure (nonloss decomposition) est une décomposition préservant l'information.

  2. On recommence le processus tant qu'il y a encore à normaliser.
Application. Résultat de la normalisation de la relvar FPV (FPV = FV  JOIN  FPQ) :

Figure 3.7 - Normalisation par décomposition de la relvar FPV



3.3.5. Normalisation et intégrité référentielle

Il est évident que lorsqu'on a normalisé en ayant tenu compte de la recommandation de Rissanen, l'intégrité référentielle doit à son tour être assurée. Ainsi, existe-t-il une contrainte de ce type entre les relvars FV et FPQ suite à la normalisation de la relvar FPV en BCNF (cf. ci-dessus) :

Version Tutorial D :


     VAR  FPQ  BASE RELATION {
               Four_No     INTEGER
             , Piece_No    INTEGER
             , Quantite     INTEGER }
         KEY {Four_No, Piece_No}
         FOREIGN KEY {Four_No}   REFERENCES  FV {Four_No} ... ;


Version SQL :


     CREATE TABLE FPQ (
               Four_No     INTEGER      NOT NULL
             , Piece_No    INTEGER      NOT NULL
             , Quantite     INTEGER      NOT NULL
         CONSTRAINT  FPQ_PK    PRIMARY KEY (Four_No, Piece_No)
         CONSTRAINT  FPQ_AK1  FOREIGN KEY (Four_No)  REFERENCES  FV (Four_No) ...) ;




3.3.6. Conséquences de la normalisation par projection - jointure

Une fois achevé le travail de normalisation, on observera que :

1.    Du point de vue de la modélisation conceptuelle, tout est plus cohérent.

2.    Les problèmes de mise à jour s'arrangent.

       a.     Ainsi, on peut désormais stocker dans la base de données le fait que le fournisseur S5 réside en Arles même s'il n'a effectué aucun
               envoi (comme dans le cas de la relvar F de la Figure 2.6, dont FV est une restriction/projection).

       b.     On peut aussi supprimer les envois d'un fournisseur, tout en conservant l'information comme quoi il réside dans telle ville.

       c.     On contraint un fournisseur à résider dans une seule ville, comme cela l'a été spécifié au niveau des règles de gestion :
               Nul besoin de définir une contrainte supplémentaire à cet effet.


3.4. Deuxième et troisième formes normales


3.4.1. BCNF versus 2NF et 3NF

Chronologiquement, la BCNF (présentée en 1974) fut précédée de deux autres formes normales, dites deuxième et troisième formes normales (2NF et 3NF), décrites initialement par Ted Codd (voir [Codd 1971] par exemple).

Au fil du temps, ces deux formes normales ont été, hélas ! parfois remaniées, faussées par des mains inexpérimentées. Quoi qu'il en soit, les deuxième et troisième formes normales sont moins efficaces : la BCNF permet d'éliminer des redondances face auxquelles ses deux sœurs aînées sont impuissantes. Et puis, il serait dommage de mémoriser trois définitions quand une seule suffit, celle de la BCNF, dont l'énoncé est conceptuellement plus simple et ne fait pas référence à la panoplie complète des dépendances fonctionnelles. Qui peut le plus peut le moins :

./images/warning.gif Sur le terrain, on gagnera un temps précieux à vérifier directement la BCNF et à ne faire éventuellement appel à ses petites sœurs — moins efficaces — qu'en cas de problème, à savoir violer la BCNF pour éviter la perte d'une règle de gestion (cf. paragraphe 3.7).

3.4.2. Deuxième forme normale (2NF)

Dans son article de 1971, Ted Codd donne la définition suivante de la deuxième forme normale (2NF), dans laquelle nous conservons le terme relation, bien que celui de relvar y soit plus approprié :

./images/Carre_vert_clair_No_Indent.jpg Une relation R est en deuxième forme normale si elle est en première forme normale et si chaque attribut n'appartenant à aucune clé candidate est en dépendance totale de chaque clé candidate de R.

(« A relation R is in second normal form if it is in first normal form and every non-prime attribute of R is fully dependant on each candidate key of R. »)
Pour Codd, un prime attribute, est un attribut qui appartient à au moins une clé candidate de R, et a contrario, un non-prime attribute est un attribut qui n'appartient à aucune clé candidate de R (on peut préférer utiliser l'expression attribut non clé).

Rappelons qu'une dépendance fonctionnelle est totale (ou irréductible à gauche ou élémentaire) si elle n'est ni triviale ni partielle (cf. paragraphe 3.2.2).

On peut ainsi constater que la relvar FPV (Figure 3.6) n'est pas en deuxième forme normale, car l'attribut Ville n'appartient pas à la clé candidate {Four_No, Piece_No}, mais en dépend partiellement puisque {Four_No} ➔ {Ville}. Pour normaliser, on utilise une fois de plus le mécanisme de décomposition sans perte, par projection - jointure, en mettant à nouveau à profit le théorème de Heath.

./images/warning.gif Remarque importante :

Hélas, trois fois hélas ! La définition la plus courante donnée de la 2NF est la suivante :
./images/zekill.gif Une relation R est en deuxième forme normale si elle est en première forme normale et si chaque attribut n'appartenant pas à la clé primaire de R est en dépendance totale de celle-ci.
Cette définition est bien sûr incomplète, car seule la clé primaire est prise en compte, tandis que les clés alternatives passent à la trappe. Il est probable qu'en se recopiant les uns les autres, les rédacteurs se sont inspirés de la définition proposée par Chris Date, mais sans tenir compte de l'avertissement que celui-ci a pourtant pris soin de donner au lecteur :

« Pour des raisons de simplicité, on supposera que chaque relation a exactement une clé candidate, c'est-à-dire une clé primaire et aucune clé alternative. »
D'où la définition donnée par Date, valable seulement dans ce cas particulier :

./images/Carre_vert_clair_No_Indent.jpg A relvar is in 2NF if and only if it is in 1NF and every nonkey attribute is irreducibly dependant on the primary key.
Un attribut est non clé (nonkey) quand il n'appartient pas à la clé primaire de la relvar. Nombre de ceux qui ont recopié cette définition de Date n'ont malheureusement pas lu son avertissement et feraient bien de s'inspirer de [Bouzeghoub 1990], je cite :

./images/Carre_vert_clair_No_Indent.jpg « Si une relation possède plusieurs clés, la définition doit être vérifiée pour chaque clé. »
A défaut, dans le cas général, cette définition de la 2NF qui ne tient compte que de la clé primaire est bien sûr trop faible : il est par exemple courant de mettre en oeuvre un attribut artificiel, utilisé pour définir la clé primaire d'une table (ceci vaut au niveau conceptuel, mutatis mutandis), admettons. C'est ce qu'on a fait pour la relvar FPVX ci-dessous, supposée remplacer la relvar FP (cf. Figure 2.6), étendue de l'attribut Ville. Mais l'ajout de l'attribut PVX_Id, fait que la paire {Four_No, Piece_No} est ravalée au rang de clé alternative (clé primaire soulignée, clé alternative en italiques) :

?
Figure 3.8 - La relvar FPVX, censée respecter 2e forme normale


PVX_Id étant le seul attribut entrant dans la composition de la clé primaire, tous les autres attributs sont en dépendance totale de celle-ci : FPVX respecte la deuxième forme normale incomplète ! Mais la redondance et les problèmes de mise à jour et autres sont toujours bien présents...

=>  Bannissons la définition incomplète et utilisons celle de Codd (ou mieux, restons-en à la BCNF).

Par ailleurs, si la mise en oeuvre d'un attribut tel que PVX_Id s'avère nécessaire, ne l'envisageons que pour des relvars déjà normalisées en BCNF (ou de préférence en 5NF, car certaines erreurs pourraient encore se faufiler).


3.5. Dépendance transitive, dépendance directe

A son tour, la définition de la troisième forme normale fait intervenir un autre type de DF, à savoir la DF transitive.

Soit deux sous-ensembles d'attributs X, Y appartenant à l'en-tête H d'une relvar R, et un attribut A appartenant également à H mais n'appartenant pas à Y. La dépendance fonctionnelle X ➔ {A} est dite transitive si elle vérifie (cf. [Delobel 1982], « Normalité d'une relation ») :

(1)    X ➔ Y

(2)    Y ➔ {A}

(3)    YX   (la dépendance fonctionnelle Y ➔ X n'existe pas)

(4)    Y n'est pas inclus dans X   (la dépendance fonctionnelle X ➔ Y est n'est donc pas triviale)

A contrario si {A} ne dépend pas transitivement de X, la dépendance fonctionnelle X ➔ {A} est dite directe.


3.6. Troisième forme normale (3NF)

Dans son article de 1971, Ted Codd donne cette définition de la troisième forme normale (3NF) :

./images/Carre_vert_clair_No_Indent.jpg Une relation R est en troisième forme normale si elle est en deuxième forme normale et si chaque attribut n'appartenant à aucune clé candidate ne dépend directement que des clés candidates de R.

(« A relation R is in third normal form if it is in second normal form and every non-prime attribute of R is non-transitively dependant on each candidate key of R. »)
Rappel : pour Codd, un prime attribute, est un attribut qui appartient à au moins une clé candidate de R, et a contrario, un non-prime attribute est un attribut qui n'appartient à aucune clé candidate de R.

La traduction littérale de la définition de Codd ne serait pas très satisfaisante, d'où un léger aménagement. Peu importe, retenons ceci : pour que la 3NF soit respectée, un attribut n'appartenant pas à une clé candidate ne doit jamais dépendre directement d'un sous-ensemble d'attributs qui ne constitue pas lui-même une clé candidate.

Une définition plus intéressante est celle de Zaniolo, car elle montre que la BCNF implique la 3NF (il suffit en effet d'y supprimer la 3e condition pour retrouver la définition de la BCNF) :

./images/Carre_vert_clair_No_Indent.jpg Soit R une relvar, X un sous-ensemble d'attributs de l'en-tête de R et A un attribut de cet en-tête. R est en troisième forme normale (3NF) si et seulement si, pour chaque dépendance fonctionnelle X ➔ {A} qui doit être vérifiée par R, au moins une des conditions suivantes est satisfaite :
1.   A est un élément de X (cette dépendance fonctionnelle est donc triviale).
2.   X est une surclé de R.
3.   A appartient à une clé candidate de R.
Bien entendu, Il existe d'autres définitions de la troisième forme normale, dont la plus courante (hélas !) est la suivante :

./images/zekill.gif Une relation R est en troisième forme normale si et seulement si :
1.   Elle est en deuxième forme normale ;
2.   Tout attribut n'appartenant pas à la clé primaire de R n'en dépend pas transitivement.
Comme dans le cas de la 2NF non coddienne, cette définition est incomplète (et même fausse dans le cas général, cf. paragraphe 3.9), puisque cette fois-ci encore elle est muette à propos des clés alternatives. Nombre de ceux qui ont recopié la définition de Chris Date ont une fois de plus oublié de tenir compte de son avertissement :

« Pour des raisons de simplicité, on supposera que chaque relation a exactement une clé candidate, c'est-à-dire une clé primaire et aucune clé alternative. »
A titre d'exercice, on peut montrer que la relvar FPVX de la Figure 3.8 enfreint la 3NF.

En effet, au sens de Codd, la relvar enfreint la 2NF, donc a fortiori la 3NF. Mais acceptons la définition incomplète de la 2NF, selon laquelle FPVX respecterait la 2NF. Toutefois, quelle que soit la définition que l'on utilise de la 3NF, on montre que FPVX enfreint la 3NF. De fait, puisque {PVX_Id} est clé primaire, on peut mettre en évidence les DF suivantes :

      DF1 :  {PVX_Id} ➔ {Four_No}

      DF2 :  {PVX_Id} ➔ {Piece_No}

      DF3 :  {PVX_Id} ➔ {Quantite}

      DF4 :  {PVX_Id} ➔ {Ville}

Pour être complet, comme {Four_No, Piece_No} est clé alternative, on a aussi :

      DF5 :  {Four_No, Piece_No} ➔ {PVX_Id}

      DF6 :  {Four_No, Piece_No} ➔ {Quantite}

      DF7 :  {Four_No, Piece_No} ➔ {Ville}

En utilisant la règle d'union inférée des axiomes d'Armstrong (cf. paragraphe E en annexe), de DF1 et DF2, on produit la DF :

      DF8 :  {PVX_Id} ➔ {Four_No, Piece_No}

Il existe aussi la DF à l'origine de l'infraction :

      DF9 :  {Four_No} ➔ {Ville}

La relvar FPVX n'est pas en 3NF, puisqu'en fait DF4 peut être obtenue à partir de DF1 et DF9 (axiome d'Armstrong de transitivité), c'est-à-dire que l'attribut Ville qui n'appartient pas à la clé primaire {PVX_Id} de la relvar, dépend transitivement de cette clé, via le singleton {Four_No} qui lui-même n'est pas clé.

Conclusion :

  1. N'utiliser que les définitions de Codd de la 2NF et de la 3NF (on peut aussi préférer Zaniolo), ou celles qui lui sont équivalentes (cf. par exemple [Delobel 1982], [Gardarin 1988]), et d'urgence mettre à la poubelle celles qui se limitent aux clés primaires.

  2. Mieux encore : dans le travail de normalisation, au nom du rasoir d'Ockham, prendre l'habitude de laisser tomber la 2NF et la 3NF, au profit de la seule BCNF. On peut aussi arguer de ce choix si l'on est limité par le temps, un peu paresseux et doté d'une mémoire assez volatile pour ne pas retenir toutes les définitions.
Ainsi, dans le cas de l'exercice qui vient d'être proposé, sans perdre de temps à vérifier la 2NF puis la 3NF avant d'en arriver à la BCNF, le diagnostic est rapide et le remède simple et efficace :

Le déterminant {Four_No} de la DF non triviale DF9 n'étant pas une surclé de la relvar FPVX, celle-ci enfreint la BCNF et il faut la décomposer. Pour cela, on applique le théorème de Heath, sans oublier la règle de Rissanen de préservation des dépendances (cf. paragraphe 3.3.2).

Procédons ainsi, en représentant d'abord la relvar FPVX sous la forme suivante :

      FPVX { {Four_No}, {Ville}, {PVX_Id, Piece_No, Quantite} }

Puisque l'on a la DF {Four_No} ➔ {Ville}, la relvar peut être ainsi décomposée (avec préservation des DF) :

      FPVXa {Four_No, Ville}

      FPVXb {Four_No, PVX_Id, Piece_No, Quantite}

FPVXa n'est jamais que la projection sur les attributs Four_No et Ville de la relvar F (Figure 2.6), et comme elle n'en est qu'un sous-ensemble elle peut disparaître.

FPVXb n'est jamais que de la relvar FP (Figure 2.6) ayant subi une extension (ajout de l'attribut PVX_Id). En fait, l'attribut PVX_Id n'apporte strictement rien au plan de la modélisation et des opérations : au nom du rasoir d'Ockham, il doit passer à la trappe, à la suite de quoi on retrouve FP.


3.7. Un problème embarrassant de BCNF

On peut être confronté au dilemme suivant :

Si une relvar n'est pas en BCNF, par application du théorème de Heath, on sait la décomposer sans perte, mais il y a un hic : on pourrait ne pas respecter la règle de préservation des dépendances (cf. paragraphe 3.3.2 et annexe E.7.2), c'est-à-dire qu'en décomposant, on perdrait une DF et par voie de conséquence, la règle de gestion dont cette DF est issue ne serait plus garantie. Le remède peut être pire que le mal. Que faire ?

Illustrons ceci à l'aide d'un exemple proposé par Chris Date (cf. [Date 2004], page 370), en considérant la relvar EMP suivante (francisée) :

?
Figure 3.9 - Relvar EMP


Règles de gestion extraites du corpus des règles consignées dans le dossier de conception qui nous a été transmis :

      (R1)     Pour chaque matière étudiée par un étudiant, celui-ci n'a qu'un professeur.

      (R2)     Chaque professeur n'enseigne qu'une matière.

      (R3)     Chaque matière peut être enseignée par plusieurs professeurs.

      (R4)     Chaque professeur peut enseigner sa matière à plusieurs étudiants.

       ...       Etc.

Traduction sous forme de dépendances fonctionnelles :

      DF1 :  {Etudiant, Matiere} ➔ {Professeur}

      DF2 :  {Professeur} ➔ {Matiere}


La relvar EMP ne respecte pas la BCNF, car le déterminant {Professeur} de DF2 n'est pas une surclé, un professeur pouvant avoir plusieurs étudiants (par exemple, M. Lebrun a au moins deux étudiants, Albert et Nanard).

On normalise en appliquant le théorème de Heath sur la base de DF2, en décomposant donc ainsi la relvar EMP :

      PM {Professeur, Matière}              ayant pour seule clé candidate {Professeur}.

      PE {Professeur, Etudiant}              ayant pour seule clé candidate {Professeur, Etudiant}.

Projection de EMP selon PM et PE
Figure 3.10 - Projection de EMP selon PM et PE


Ces deux relvars sont en BCNF, l'information est préservée, mais on perd DF1, donc la règle de gestion (R1), et si Mlle Martin enseigne le grec à Nanard, rien n'empêcherait désormais que Mme Prévert lui enseignât aussi cette matière...

Alors faut-il violer la BCNF ?

On peut effectivement préférer ne pas décomposer la relvar EMP et se limiter au respect de la 3NF, mais en contrepartie il faudra prévoir une contrainte garantissant le respect de la dépendance fonctionnelle DF2, comme quoi un professeur n'enseigne qu'une matière.

Dans le style de Tutorial D :


     VAR EMP BASE RELATION {Etudiant CHAR, Matiere CHAR, Professeur CHAR}
         KEY {Etudiant, Matiere} ;

     CONSTRAINT EMPDF2
         FORALL x, y    EMP (x.Professeur = y.Professeur x.Matiere = y.Matiere) ;


        (La contrainte se lit ainsi : Quels que soient les tuples x et y de la relvar EMP, si x.Professeur = y.Professeur alors x.Matiere = y.Matiere)

Version SQL :
        (Rappel : En situation réelle, on utilise bien sûr des valeurs non significatives et invariantes pour les attributs composant les clés primaires (cf. paragraphe 3.2.6))

 CREATE TABLE  EMP (
    Etudiant     CHAR(16)  NOT NULL     
  , Matiere      CHAR(16)  NOT NULL           
  , Professeur   CHAR(16)  NOT NULL         
 , PRIMARY KEY (Etudiant, Matiere)) ;     
 
 CREATE ASSERTION EMPDF2 CHECK 
    (NOT EXISTS (
                 SELECT x.Matiere
                 FROM   EMP AS x, EMP AS y
                 WHERE  x.Professeur = y.Professeur
                   AND  x.Matiere <> y.Matiere)
                ) ;
				


A défaut, à la place de l'assertion SQL, on peut utiliser une vue munie d'une clause WITH CHECK OPTION :
						
 CREATE VIEW EMP_BIS (Etudiant, Matiere, Professeur) AS
    SELECT  x.Etudiant, x.Matiere, x.Professeur
    FROM    EMP AS x 
    WHERE NOT EXISTS (
                      SELECT *
                      FROM   EMP AS y 
                      WHERE  x.Professeur = y.Professeur  
                        AND  x.Matiere <> y.Matiere
                    )
    WITH CHECK OPTION ;
				

Mais il faudra interdire l'accès à la table EMP pour les opérations de mise à jour et n'autoriser en l'occurrence que l'utilisation de la vue, système manquant un peu de souplesse. Quoi qu'il en soit, on peut aussi se rabattre sur un trigger SQL pour garantir la dépendance fonctionnelle DF2.

Exemple avec SQL Server 2005 :
				
 CREATE TRIGGER EMPDF2Insert ON EMP INSTEAD OF INSERT AS
    SELECT x.Matiere
    FROM   Inserted x, EMP y
    WHERE  x.Professeur = y.Professeur
      AND  x.Matiere <> y.Matiere ; 	
 If @@Rowcount > 0	
     Begin
         Raiserror ('Viol BCNF !',16,1)
     Return 
 End
 INSERT INTO EMP SELECT DISTINCT Etudiant, Matiere, Professeur 
                 FROM   Inserted ; 
 GO	
				

Et bien entendu, il faudra prévoir un trigger pour contrôler les nouvelles valeurs prises par les attributs Matiere et Professeur lors des opérations d'update portant sur ces attributs.

Concernant les opérations d'INSERT, on peut désormais violer la BCNF, puisque l'on sait faire respecter la DF {Professeur} ➔ {Matiere}.

./images/Boum!.jpg Pour le reste, il y a quand même un hic, car si Nanard n'étudie pas le grec mais le sanscrit, remplacer le tuple <"Nanard", "Grec", "Mlle Martin"> par le tuple <"Nanard", "sanscrit", "M. Antoun"> a pour effet que l'on perd l'information selon laquelle Mlle Martin enseigne le grec si Nanard était son seul élève dans cette matière. Il sera prudent de commencer par le commencement, c'est-à-dire produire un MCD (ou un diagramme de classes), dans lequel figurent les entités-types Professeur, Matiere, Etudiant, une association-type entre Professeur et Matiere, une association-type ternaire, à savoir EMP, entre les trois entités-types et une CIF (contrainte d'intégrité fonctionnelle) entre EMP et Professeur. Une fois cette tâche accomplie, alors seulement on pourra dériver le MCD : il s'agit là du processus normal de conception d'une base de données.
?
Figure 3.11 - Relvar EMP - Vision conceptuelle

En redescendant au niveau relationnel, on devra s'occuper des pièges qui seront immanquablement tendus. Par exemple, si le corps de la relvar EMP contient le tuple <"Nanard", "Grec", "Mlle Martin">, après tout la matière enseignée par Mlle Martin peut très bien être le solfège... On part pour un trigger supplémentaire (ou une « surclé étrangère » inavouable entre EMP et Professeur...) pour s'assurer que la matière enseignée par un professeur aux élèves est bien celle que ce professeur est réputé enseigner.

Alors faut-il respecter la BCNF ?

En réaction à ce qui précède, normaliser et mettre en oeuvre les relvars PM {Professeur, Matière} et PE {Professeur, Etudiant} semble s'imposer, avec toutefois un contrôle à assurer pour la dépendance fonctionnelle DF1  {Etudiant, Matiere} ➔ {Professeur} puisque celle-ci est perdue après projection.

Par contre, sémantiquement parlant, le MCD correspondant ne ressemble pas à grand-chose, et respecter la BCNF n'est finalement peut-être pas ici ce qu'il y a de mieux...

?
Figure 3.12 - Relvar EMP - Vision conceptuelle, variante

On est forcé d'en convenir. En tout cas, le code correspondant est le suivant :

Dans le style de Tutorial D :


     VAR PM BASE RELATION {Professeur CHAR, Matiere CHAR}
         KEY {Professeur} ;

     VAR PE BASE RELATION {Professeur CHAR, Etudiant CHAR}
         KEY {Professeur, Etudiant}
         FOREIGN KEY {Professeur} REFERENCES PM ;

     CONSTRAINT EMPDF1
         FORALL x, y    (PE JOIN PM) (x.Etudiant = y.Etudiant AND x.Matiere = y.Matiere x.Professeur = y.Professeur) ;



Version SQL :
 		       
 CREATE TABLE PM (
     Professeur   CHAR(16)  NOT NULL
   , Matiere      CHAR(16)  NOT NULL
   , PRIMARY KEY (Professeur) 
  ) ;
 CREATE TABLE PE ( 
     Professeur   CHAR(16)  NOT NULL 
   , Etudiant     CHAR(16)  NOT NULL 
  , PRIMARY KEY (Professeur, Etudiant) 
  , FOREIGN KEY (Professeur) REFERENCES PM  
 ) ; 
 GO
 CREATE ASSERTION EMPDF1 CHECK  
      (NOT EXISTS ( 
                   SELECT X.Etudiant, X.Matiere, X.Professeur 
                   FROM  (SELECT PE.Etudiant, PM.Matiere, PM.Professeur 
                          FROM   PE INNER JOIN PM  
                                 ON PE.Professeur = PM.Professeur) AS X  
                         INNER JOIN
                         (SELECT PE.Etudiant, PM.Matiere, PM.Professeur 
                          FROM   PE INNER JOIN PM  
                                 ON PE.Professeur = PM.Professeur) AS Y 
                          ON X.Etudiant = Y.Etudiant AND X.Matiere = Y.Matiere 
                   WHERE   X.Professeur <> Y.Professeur 
                  )); 		
		
Etc.


3.8. Retour sur la dénormalisation a priori

Le thème de la dénormalisation a été abordé au paragraphe 1.6. Voici comment en termes choisis on propage un mythe sans manifestement être allé vérifier comment se passent concrètement les choses. Je cite à nouveau le redoutable [RoMo 1989] qui écrit à la page 198 de l'ouvrage, sous le titre « Optimisation du MLD relationnel » :

« Une optimisation structurelle principale peut être effectuée au niveau logique, il s'agit de la création d'une certaine redondance en vue de diminuer le nombre d'actions élémentaires nécessaires à la satisfaction d'une requête en diminuant notamment le nombre de jointures entre tuples différents... »
Et l'auteur fournit un exemple de MLD dans lequel une table initialement en BCNF (en fait 5NF) viole désormais la 3NF, suite à injection d'une DF transitive (cf. paragraphe 3.5). Un avion (table Avion) est d'un certain type (table TypeAvion, porteuse d'un attribut NombreDePlaces) : la recommandation est de dupliquer l'attribut NombreDePlaces dans la table Avion. Cela part d'une bonne intention, l'auteur évoquant la possibilité « qu'il soit souvent demandé quelle est la capacité d'un avion ». Mais il aurait dû parler des effets secondaires et insister sur les précautions élémentaires à prendre avant de nous laisser nous embarquer dans ce genre de galère : prototyper et quantifier les performances, mettre en œuvre une contrainte sous-traitée au SGBD pour garantir en toute sécurité la cohérence des valeurs prises par l'attribut NombreDePlaces dans les deux tables concernées... Bref, l'imprudent aurait surtout dû conclure que les concepteurs et les développeurs doivent s'en remettre aux DBA, lesquels sont outillés pour juger objectivement de l'(in)efficacité de la dénormalisation : ficher une zoubia pas possible pour se « satisfaire » d'un gain qui sera peut-être de l'ordre de la milliseconde ?

L'enfer est pavé de bonnes intentions. Allons-y d'un contre-exemple.

Dans une banque d'une ville méridionale où il fait bon vivre. On me soumet le problème suivant : Les chèques des clients sont regroupés en remises, lesquelles sont à leur tour regroupées en lots. Tous les chèques datés du tant doivent être supprimés de la table des chèques. Manque de chance, l'attribut de type Date utilisé pour l'opération de restriction ne figure pas dans la table Cheque, mais dans la table « grand-parente », Lot (attribut LotDate).

Ce qui est embêtant dans cette affaire, c'est que le traitement (hebdomadaire) dure près de 9 heures, pour une table de 2 millions de chèques (d'accord, ça se passait en 1993, mais quand même...) A l'unanimité moins une voix, celle de votre serviteur, le verdict tomba : « C'est la faute à la 3NF, il faut dé-nor-ma-lis-er ! »

Structure des tables :

?
Figure 3.13 - Dénormalisation de la table des chèques


Requête de suppression des chèques, avant dénormalisation :

 
DELETE  FROM Cheque WHERE Exists
    (SELECT * 
     FROM Lot  
     WHERE  Cheque.LotId = Lot.LotId
       AND  Lot.LotDate = <d>) ;	
        

Après dénormalisation :
 
DELETE  FROM Cheque WHERE LotDate = <d> ;
        

Dénormaliser est certes bien tentant, mais avant que l'on ne commette le viol (en fait celui de la 2NF dans cet exemple), avant restructuration de la table Cheque, avant que ne soit aménagé le programme de suppression des chèques, etc., j'ai demandé que fut mesuré le coût du seul SELECT : celui-ci était de l'ordre de deux minutes. Stupeur des accusateurs ! La jointure, cette présumée coupable était lavée de tous les soupçons (son surcoût était infime, de l'ordre de quelques secondes). En fait, la table Cheque était lourdement lestée de cinq index que le SGBD (à savoir DB2) devait mettre à jour eux aussi (dont un du reste ne servait strictement à rien). La solution était donc évidente : ne pas dénormaliser, et avant d'exécuter le traitement de suppression des chèques, commencer par « dropper » les index (sauf l'index cluster qui s'avérait peu pénalisant, par contraste avec les autres index qui étaient tous à l'origine de très nombreuses lectures/écritures de pages physiques, sans compter la journalisation), puis une fois l'opération de suppression proprement dite terminée, recréer les index. Pour une fenêtre batch autorisée de 1h30, le temps de traitement, y-compris une réorganisation de la table et la création des index, n'excéda plus une quinzaine de minutes, et l'irréparable ne fut pas commis...

Moralité : avant de dire et (faire) faire des bêtises, et se retrouver au point de départ, en ayant gagné une poignée de secondes sur 9 heures après avoir dénormalisé, on vérifie concrètement et on prouve. Bon d'accord, j'ai pris un exemple édifiant, mais j'en ai bien d'autres.

Mais attention, rien n'est jamais acquis...

Trois ans plus tard, le même problème se pose, mais cette fois-ci dans une banque située en Normandie. Fort de mon expérience méridionale, je prépare sereinement un travail de prototypage, dans les règles de l'art. Avant de faire le ménage dans la table des chèques, allons-y pour « dropper » les index qui peuvent l'être. Ceci fait, mesurons la durée de l'opération de suppression. Surprise ! Vérité en deçà des Alpilles, erreur au delà... Il se trouve que de la table des chèques dépendent d'autres tables, et que le pourcentage de chèques à supprimer est cette fois-ci beaucoup plus important, ce qui fait que l'ensemble des pages des table spaces et index spaces (fichiers DB2) est à mettre à jour, et dans ces conditions la performance attendue n'est pas au rendez-vous. Mais si on échoue par la face Nord, reste la face Sud : plutôt que supprimer, on crée. En l'occurrence, cela consista à décharger seulement les lignes à ne pas supprimer puis à les recharger dans la table en cause et dans ses tables « filles ». Ouf... Quoi qu'il en soit, une fois de plus, la normalisation était bien sûr totalement étrangère au problème de performance, mais elle aurait bien trouvé des accusateurs, ainsi qu'un folliculaire pour entretenir le mythe dans la presse du cœur informatique.


3.9. Une relvar respectant la BCNF peut-elle violer la 3NF ?

Une relvar en BCNF est nécessairement en 3NF, mais si on se sert de la définition non coddienne figurant au paragraphe 3.6, alors on peut arriver à une contradiction, à savoir qu'une relvar respectant la BCNF peut violer la 3NF. Rappelons cette définition incomplète et peccamineuse :

./images/zekill.gif Une relation R est en troisième forme normale si et seulement si :
1.   Elle est en deuxième forme normale ;
2.   Tout attribut n'appartenant pas à la clé primaire de R n'en dépend pas transitivement. 
La contradiction peut être montrée à partir d'exemples très simples, tels que celui-ci :

Soit une relvar R {A, B, C} où A, B et C sont des attributs, et les dépendances fonctionnelles :

      DF1 :    {A} ➔ {B}

      DF2 :    {A} ➔ {C}

      DF3 :    {B} ➔ {A}

      DF4 :    {B} ➔ {C}

Ce sont les seules DF non triviales et leurs déterminants {A} et {B} sont clés candidates : R respecte la BCNF. Mais si l'on choisit par exemple {A} pour clé primaire, puisque {A} détermine {C} transitivement, la 3NF incomplète est violée. En effet :

       {A} ➔ {B} et {B} ➔ {C}  =>  {A} ➔ {C}

Parce qu'incomplète, la définition de la 3NF est fausse. Mais pour varier les plaisirs et l'occasion faisant le larron, profitons d'un exercice récapitulatif pour mettre à nouveau la contradiction en évidence. Intéressons-nous pour cela aux courses hippiques (courses de plat, galop). Ainsi, le prix de l'Arc de Triomphe, édition 2009, doté de 4 000 000 d'euros a eu lieu le 4 octobre 2009 et a vu la victoire de Sea The Stars, monté par Michael Kinane. L'édition 2010, dotée à nouveau de 4 000 000 d'euros, a vu la victoire de Workforce, monté par Ryan-Lee Moore, etc.

Définissons une table des courses et une table des partants :

?
Figure 3.14 - Extrait de la table des courses hippiques

?
Figure 3.15 - Extrait de la table des partants

Dans la course cs01, Le cheval ch01, porteur du numéro 18, était monté par le jockey j01, il a fini à la 1re place, il portait 56 kilos et était entraîné par l'entraîneur En01, etc. A noter que dans la course cs02, le cheval ch02 a terminé non classé, alors que l'année d'avant, dans le même Prix (Arc de Triomphe), il avait fini 2e.

Intéressons nous aux dépendances fonctionnelles de la relvar PARTANT. Dans une couse donnée, un cheval ne porte qu'un numéro, il n'est monté que par un seul jockey, il termine exactement à une place, il porte exactement un poids et n'a qu'un entraîneur. On peut donc inférer la DF :

      DF01 :     {CourseId, ChevalId} ➔ {Numero, JockeyId, Place, Poids, EntraineurId}

En appliquant à cette DF l'axiome d'augmentation (cf. Annexe E.2), on infère la DF :

      DF02 :     {CourseId, ChevalId} ➔ {CourseId, ChevalId, Numero, JockeyId, Place, Poids, EntraineurId}

En conséquence quoi le déterminant {CourseId, ChevalId} de cette DF est clé candidate de la relvar.

De la même façon, dans une course donnée, un jockey ne monte qu'un cheval :

      DF03 :     {CourseId, JockeyId} ➔ {ChevalId}

En appliquant à son tour à cette DF l'axiome d'augmentation, on infère la DF :

      DF04 :     {CourseId, JockeyId} ➔ {CourseId, ChevalId}

En ce qui concerne le numéro porté par le cheval :

      DF05 :     {CourseId, Numero} ➔ {ChevalId}

En appliquant là encore à cette DF l'axiome d'augmentation, on infère la DF :

      DF06 :     {CourseId, Numero} ➔ {CourseId, ChevalId}

Et en appliquant l'axiome de transitivité, on infère les DF :

      DF07 :     {CourseId, JockeyId} ➔ {CourseId, ChevalId, Numero, JockeyId, Place, Poids, EntraineurId}

      DF08 :     {CourseId, Numero} ➔ {CourseId, ChevalId, Numero, JockeyId, Place, Poids, EntraineurId}

Ainsi, les paires {CourseId, JockeyId} et {CourseId, Numero} sont aussi clés candidates. Par ailleurs, il n'existe pas d'autres DF non triviales que celles que l'on a énumérées (deux ou plusieurs chevaux peuvent partager la même place dans la même course : ex-æquo, non classé, non partant ; un entraîneur peut avoir deux ou plusieurs chevaux engagés dans la même course, etc.)

=>    Le déterminant de chaque DF non triviale est clé candidate de la relvar : celle-ci respecte la BCNF.

Maintenant, quelle que soit la clé candidate que l'on retient pour être clé primaire, par exemple la paire {CourseId, ChevalId}, en l'occurrence on vérifie :

      DF09 :     {CourseId, ChevalId} ➔ {CourseId, JockeyId}

Mais aussi :

      DF10 :     {CourseId, JockeyId} ➔ {Place, Poids, EntraineurId}

Donc par transitivité :

      DF11 :     {CourseId, ChevalId} ➔ {Place, Poids, EntraineurId}

Autrement dit, alors que la relvar respecte la BCNF, selon la définition incomplète et peccamineuse de la 3NF, celle-ci est violée, en contradiction avec la règle selon laquelle toute relvar respectant la BCNF respecte nécessairement la 3NF :


./images/zekill.gif La définition incomplète et peccamineuse est donc fausse et bonne pour la poubelle.

S'il fallait la suivre, par application du théorème de Heath, la relvar PARTANT donnerait lieu à un ensemble de relvars tel que celui-ci :

      PARTANT01 {CourseId, ChevalId, Place, Poids, EntraineurId}

      PARTANT02 {CourseId, ChevalId, Numero}

      PARTANT03 {CourseId, ChevalId, JockeyId}

Ces structures désormais « 3NF » peuvent être intéressantes, mais elles relèvent plutôt de l'optimisation de la relvar initiale PARTANT (cf. paragraphe 1.7).


Pour conclure et marquer un temps de repos bien mérité, citons Pierre Dac (1893-1975) :

« Gloire à ceux qui ont forgé silencieusement mais efficacement le fier levain qui, demain ou après-demain au plus tard, fera germer le grain fécond du ciment victorieux, au sein duquel, enfin, sera ficelée, entre les deux mamelles de l'harmonie universelle, la prestigieuse clef de voûte qui ouvrira à deux battants la porte cochère d'un avenir meilleur sur le péristyle d'un monde nouveau ! »
Fermez le ban.

N.B. J'avoue ne pas savoir si la prestigieuse clef de voûte dont parle notre cher Dac est candidate, primaire, alternative, étrangère ou autre, libre à chacun de se faire son opinion. En tout cas, gloire à Codd, Date, Darwen, et tous ceux qui ont forgé le fier levain relationnel !

 

Valid XHTML 1.0 TransitionalValid CSS!

Copyright © 2008 - François de Sainte Marie. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Contacter le responsable de la rubrique ALM