Copyright © 2005-2007 Yves MARCOUX; dernière modification de cette page: 2007-01-07.
Yves MARCOUX - EBSI - Université de Montréal
1. Introduction: modèles et modèles de données
1.3 Modèles de données: pour les SGBD
1.4 Le modèle relationnel: pour les SGBD relationnels
1.5 Le modèle textuel: pour les SGBD textuels
1.6 Scénario type d'utilisation d'un SGBD
2. La struture élémentaire: la table de données
4. Occurrences multiples, oui ou non?
5. Contenu des occurrences, type de données "text"
6. Notation pour les chaînes de caractères
8. Type de données "integer", transformations d'affichage
9. Données manquantes: zéro occurrence ou valeur NULL
10. Restrictions sur le contenu des champs
11. Interdiction d'inscrire la valeur NULL
12. Limites sur la longueur des chaînes
14. Conventions de notation pour la valeur NULL et la chaîne vide
15. Transformations additionnelles: les masques de saisie
17. Interactions entre les restrictions
18. Nombre de tables dans une base de données
20. Opérations sur les bases de données relationnelles
21. SELECT restreints: la syntaxe
23. SELECT restreints: la sémantique
24. Constantes de types autres que "text"
25. Comparaisons avec la valeur NULL
28. Conditions de validité globales
Le but de cette section est d'expliquer l'utilité des modèles de données en général et des modèles textuel et relationnel en particulier.
Un modèle est une représentation simplifiée d'un objet ou d'un système, qui permet d'en mettre en évidence certains aspects, tout en en laissant de côté certains autres. L'utilisation d'un modèle facilite l'étude ou la compréhension de certaines caractéristiques de l'objet ou du système: celles qui sont mises en évidence par le modèle. Par contre, elle ne convient pas du tout à l'étude des caractéristiques qui ne sont pas représentées dans le modèle.
Un exemple très simple de modèle est une maquette, à l'échelle, d'une cathédrale. Si l'on veut étudier la forme des toits de la cathédrale, alors la maquette est beaucoup mieux que la cathédrale elle-même: en effet, il est beaucoup plus facile de regarder les toits sur la maquette que sur la cathédrale. Par contre, si l'on veut étudier la composition des matériaux de construction, la maquette n'est d'aucune utilité, étant faite de matériaux différents. Dans ce cas-ci, la forme des toits est un des aspects de la cathédrale qui est mis en évidence par le modèle, alors que la composition des matériaux de construction est complètement exclue.
D'après vous, lequel des deux aspects suivants d'une cathédrale est plus facile à étudier avec une maquette qu'avec la cathédrale elle-même: l'état des fondations ou le nombre de portes donnant sur l'extérieur? Le nombre de portes donnant sur l'extérieur, évidemment. En effet, la maquette ne dit rien sur les fondations de la cathédrale, alors qu'on peut facilement y repérer toutes les portes donnant sur l'extérieur, sans se déplacer.
Il est possible d'étudier certains aspects d'un logiciel à l'aide d'un modèle; c'est même chose courante. Évidemment, au lieu d'utiliser un modèle concret, comme une maquette pour une cathédrale, on utilise alors un modèle abstrait.
Prenons l'exemple d'un logiciel statistique. Pour utiliser un tel logiciel, il est très utile, sinon essentiel, de connaître les concepts mathématiques de variable aléatoire, de moyenne, d'écart-type, etc. Cet ensemble de concepts constitue un modèle abstrait grâce auquel on comprend les calculs effectués par le logiciel. Notez que ce modèle n'explique que les calculs faits par le logiciel; les autres aspects du logiciel, comme par exemple la façon dont il faut demander ces calculs au logiciel, ne sont pas du tout décrits.
Certains logiciels ont comme fonctionnalité première le stockage et le repérage d'information. Ces logiciels permettent la création, la gestion et l'exploitation d'ensembles structurés de données appelés bases de données. On les appelle donc SGBD, pour systèmes de gestion de bases de données.
Pour décrire la structure des bases données qu'un SGBD peut créer, gérer et exploiter, de même que les différentes opérations qu'on peut effectuer sur ces bases, on utilise un modèle de données. Un modèle de données est donc un modèle abstrait représentant les deux aspects suivants d'un SGBD: la structure des bases données qu'il peut créer, gérer et exploiter, et les opérations de stockage et de repérage qu'on peut effectuer sur ces bases.
Il existe un très grand nombre de SGBD sur le marché. Cependant, en ce qui a trait aux possibilités de structuration des données et aux opérations de stockage et de repérage offertes, plusieurs se ressemblent. Grâce à cette similarité, on peut regrouper les SGBD en classes, et utiliser un seul modèle de données pour étudier toute une classe, plutôt qu'un pour chaque SGBD spécifique.
Deux des plus importants modèles de données sont le modèle textuel et le modèle relationnel. Le premier est ainsi appelé parce qu'il est particulièrement bien adapté aux données textuelles, c'est-à-dire composées surtout des phrases ou des mots. Le second est ainsi appelé parce qu'il est basé sur le concept mathématique de relation. Comme on peut s'y attendre, un SGBD permettant de créer, gérer et exploiter des bases de données selon le modèle textuel s'appelle un SGBD textuel; un SGBD permettant la création, la gestion et l'exploitation de bases de données selon le modèle relationnel est dit relationnel. Le modèle textuel est parfois aussi appelé « fichier plat ».
Le présent texte se concentre sur la présentation du modèle relationnel, correspondant donc aux SGBD relationnels. MySQL, Oracle, Access et SQL Server sont des exemples de SGBD relationnels. Le modèle relationnel est un modèle abstrait et, à ce titre, il n'est parfaitement conforme à aucun SGBD relationnel spécifique. Cependant, ses caractéristiques générales se retrouvent dans tous les SGBD relationnels, et il peut donc servir de point de référence pour l'étude ou l'apprentissage de SGBD relationnels réels.
Tel que mentionné précédemment, un modèle de données ne décrit que la structure des données sur lesquelles un SGBD peut opérer, de même que les opérations qu'il peut effectuer sur ces données. Les autres aspects du SGBD, comme par exemple son interface avec l'usager, ou la façon précise de déclencher telle ou telle opération, ne font pas partie de son modèle de données. De même, les caractéristiques reliées à l'utilisation concrète du SGBD, comme le type de matériel requis, ou la quantité de mémoire requise, sont entièrement exclues du modèle de données: ce dernier ne dit rien sur ces caractéristiques. En conséquence, les caractéristiques des SGBD relationnels qui sont modélisées par le modèle relationnel se résument à la structure des informations traitées, et aux opérations de recherche que l'on peut effectuer sur les informations stockées. Elles n'incluent pas les autres fonctionnalités, comme par exemple la production de rapports imprimés.
Tel que mentionné précédemment, le modèle textuel est utile pour étudier les SGBD textuels. DB/TextWorks, TEXTO, Gesbib, Inmagic Plus, CDS/ISIS et EDIBASE sont des exemples de SGBD textuels. Ces logiciels permettent de créer soi-même, habituellement sur micro-ordinateur, des bases de données textuelles. À plus grande échelle, la plupart des bases de données commerciales accessibles en ligne sur des serveurs sont aussi gérées par des SGBD textuels.
L'approche utilisée ici pour présenter le modèle relationnel est de le comparer avec le modèle textuel, que nous supposons donc connu, du moins dans ses grandes lignes. Le lecteur pourra consulter avec intérêt le didacticiel « Le fichier plat » pour se familiariser avec ce modèle.
Le scénario type d'utilisation d'un SGBD est le suivant: On crée d'abord une base de données en donnant au SGBD une description de la structure désirée pour celle-ci. Cette description s'appelle la définition de la base dans le SGBD. Une fois la création faite, on utilise le SGBD pour alimenter la base, c'est-à-dire y placer de l'information. Une fois cela fait, on soumet au SGBD des requêtes de recherche, dont le but est d'extraire certaines parties de l'information contenue dans la base. La possibilité de faire de telles recherches est la finalité même d'une base de données.
La création d'une base de données n'a lieu en général qu'une seule fois. Autrement dit, on ne donne qu'une fois la définition de la base au SGBD (bien que, dans plusieurs SGBD réels, on puisse modifier la définition d'une base même après sa création). Typiquement, la création d'une base de données est effectuée par un professionnel responsable de la base, alors que son alimentation et la recherche sont faites par des usagers de différentes catégories. L'alimentation ne se fait pas nécessairement en une seule opération, mais peut être effectuée sur une longue période, et dans le cas des bases qu'il faut constamment maintenir à jour, elle n'arrête jamais.
Avant de procéder à la création de la base de données, le professionnel responsable doit la concevoir, c'est-à-dire en établir la définition en fonction des besoins auxquels la base doit répondre. Cette étape comporte, entre autres, la modélisation des données qui seront gérées. La modélisation consiste à représenter le mieux possible des objets (concrets ou abstraits) du monde réel par des objets numériques stockés dans une base de données.
Ce texte ne porte pas sur la modélisation, qui est un sujet vaste. Nous nous contenterons de dire que le processus est analogue à la reproduction d'un objet physique donné (par exemple, une chaise) à l'aide d'un jeu de construction (par exemple, des blocs Lego). Il requerra souvent des compromis, à cause des capacités limitées du modèle de données par rapport à la complexité des objets réels (comme la reproduction d'une chaise en blocs Lego exigera d'adapter aux possibilités d'emboîtement des blocs les angles entre les différentes parties de la chaise). Cela dit, il est clair qu'un modèle de données est parfois mieux adapté qu'un autre à la modélisation d'une certaine situation. Le travail du professionnel responsable de la base inclut donc, avant même la modélisation, le choix du modèle de données le mieux adapté à la situation. Pour faire un choix éclairé, le professionnel doit connaître très bien les différents modèles de données disponibles sur le marché, en particulier les modèles textuel et relationnel (un autre modèle important étant celui des documents structurés).
Les deux modèles (textuel et relationnel) partagent une même structure élémentaire de données: la table de données. Une table de données est une structure rectangulaire à deux dimensions, constituée de lignes et de colonnes. Les lignes de la table sont appelées les fiches de la table; les colonnes sont appelées ses champs.
Une table doit avoir au moins un champ (c'est-à-dire au moins une colonne); chaque champ doit avoir un nom distinct. Le nombre de champs d'une table et leur nom sont fixés à la création de la table et ne varient pas au cours de sa vie. Le nombre de fiches est zéro à la création de la table, mais change au cours de la vie de la table, selon les opérations de mise-à-jour effectuées sur la table. Il n'y a aucune limite supérieure a priori sur le nombre de lignes ou sur le nombre de colonnes d'une table (étant bien entendu que ces nombres sont toujours finis). La table elle-même doit aussi avoir un nom.
On peut visualiser une table de données comme un tableau. On place les noms de champ en en-têtes des colonnes et le nom de la table au-dessus de la ligne d'en-têtes. Voici une table de données sur des personnes-contacts présentée sous cette forme:
Contacts | |||
---|---|---|---|
Numéro | Prénom | Nom | Téléphone |
1 | Jeanne | Tremblay | 555-1212 |
2 | Roger | Drapeau | 555-1213 |
3 | Bernard | Larue | 555-1214 |
Dans cet exemple, la table s'appelle "Contacts" et elle possède quatre champs ("Numéro", "Prénom", "Nom" et "Téléphone") et trois fiches.
À l'intersection d'une ligne et d'une colonne se trouve une cellule. Une fiche est donc constituée de plusieurs cellules, une pour chacun des champs de la table. On appellera d'ailleurs souvent, par un léger abus de langage, ces différentes cellules les champs de la fiche.
Intuitivement, un champ d'une fiche est un certain élément d'information concernant l'« objet » auquel se rapporte la fiche. Par exemple, le champ "AU" d'une fiche de livre pourrait correspondre à l'élément d'information « auteur » pour ce livre. Le champ "Téléphone" d'une fiche de personne pourrait correspondre au numéro de téléphone de la personne.
Concrètement, les opérations de mise-à-jour sur les tables de données (ajout, retrait, modification de fiches) s'effectueront via une interface dépendante du SGBD spécifique utilisé. Cette interface pourrait par exemple utiliser une présentation graphique de la table, comme dans l'exemple ci-dessus, et permettre la saisie et la manipulation d'information directement dans celle-ci. Dans la présente discussion du modèle, cependant, ces détails nous importent peu. Nous supposons simplement qu'il est possible, après la création de la table, d'effectuer toutes les opérations requises pour que le contenu de la table devienne (ou demeure) conforme à la réalité qu'elle représente (par exemple, une collection de livres ou une liste de contacts).
Également, nous verrons plus loin que le concepteur d'une base de données peut appliquer des restrictions sur les contenus admissibles d'une table de données. Nous adopterons des notations nous permettant d'indiquer ces restrictions dans la représentation visuelle de nos exemples, mais la façon concrète de spécifier ces restrictions dans l'interface de définition d'une table dépendra du SGBD spécifique utilisé et nous importe peu; l'important est qu'il y aura toujours une façon de le faire.
Concernant ces restrictions, nous tenons aussi pour acquis qu'un SGBD refusera d'enregistrer une fiche dont le contenu contreviendrait aux restrictions, peu importe comment ce refus se manifeste concrètement dans l'interface-utilisateur. L'important est que le système refuse d'enregistrer des données non conformes aux restrictions applicables. Nous tenons donc pour acquis qu'il est impossible de nous retrouver dans une situation où le contenu enregistré dans une table de données ne respecte pas les restrictions applicables. (En réalité, certains SGBD—notamment Access—acceptent de modifier les restrictions applicables à une table de données même après que l'on ait commencé à saisir des données. Dans ce cas, il serait possible de se retrouver avec des données non conformes aux restrictions. Nous ne tenons pas compte ici de ces cas pathologiques.)
Il sera aussi parfois question de transformations appliquées automatiquement à certaines données d'une table au moment où elles sont saisies. Nous tenons pour acquis que ces transformations sont « incontournables », c'est-à-dire qu'il est impossible que des données se retrouvent dans une table sans avoir préalablement subi ces transformations.
Ici, il y a un point sur lequel les deux modèles diffèrent. Dans le modèle relationnel, les champs d'une fiche contiennent directement les données, mais pas dans le modèle textuel.
En effet, dans le modèle textuel, il y a un intermédiaire entre la cellule et les données, il s'agit de l'occurrence. Une occurrence est une espèce de « case » incluse dans la cellule et qui contient les données. Pourquoi cet intermédiaire? Parce qu'il n'y a pas nécessairement exactement une occurrence par cellule. En effet, il est possible a priori—bien que ce ne soit pas toujours le cas—qu'une cellule contienne plusieurs occurrences.
Graphiquement, on représente les occurrences multiples en subdivisant la cellule correspondant à un champ contenant plusieurs occurrences. Par exemple, si nos personnes-contacts peuvent avoir plus d'un numéro de téléphone, on le représente ainsi:
Contacts1 | |||
---|---|---|---|
Numéro | Prénom | Nom | Téléphone |
1 | Jeanne | Tremblay | 555-1212 |
666-1212 | |||
777-1212 | |||
2 | Roger | Drapeau | 555-1213 |
3 | Bernard | Larue | 555-1214 |
777-1214 |
Intuitivement, un champ contenant plusieurs occurrences correspond à un élément d'information qui a plusieurs valeurs pour le même « objet »; par exemple, une personne peut avoir plusieurs numéros de téléphone, et un livre plusieurs auteurs. Ici, Jeanne Tremblay a trois numéros de téléphones et Bernard Larue deux.
Même si le concept d'occurrence n'existe réellement que dans le modèle textuel, nous l'utiliserons quand même pour le modèle relationnel, par souci d'uniformité. Nous dirons donc que, dans le modèle relationnel, tous les champs de toutes les fiches contiennent toujours exactement une occurrence. Toutes les données contenues dans une table de données sont donc au premier niveau contenues dans une occurrence, laquelle se trouve à l'intersection d'un champ et d'une fiche (éventuellement, mais non nécessairement, en compagnie d'autres occurrences situées à cette même intersection, si le champ a des occurrences multiples).
Dans le modèle textuel, il n'y a a priori aucune limite sur le nombre d'occurrences que peut contenir un champ, mais nous verrons sous peu qu'il existe des façons de le limiter.
Toute occurrence contient nécessairement et obligatoirement quelque chose; elle ne peut sous aucune considération être « vide ». Il s'agit d'une règle absolue pour les deux modèles (textuel et relationnel). Ce quelque chose s'appelle la valeur, ou la donnée—ou les données—contenue(s) dans l'occurrence.
Un des types de valeurs que peut contenir une occurrence est la chaîne de caractères. Bien que nous ne l'ayons pas dit explicitement, toutes les occurrences des exemples montrés jusqu'ici contenaient une chaîne de caractères. Par exemple, dans la table "Contacts", la fiche de Bernard Larue contient la chaîne "3" dans son occurrence du champ "Numéro", la chaîne "Bernard" dans son occurrence du champ "Prénom" et la chaîne "555-1214" dans son occurrence du champ "Téléphone". D'autres types sont possibles, par exemple les valeurs numériques; nous y reviendrons plus tard.
Le type de valeurs admissibles dans les différentes occurrences d'un champ s'appelle le type de données de ce champ. Lorsqu'on crée une table de données, on spécifie pour chaque champ (en plus de son nom) un type de données, et toutes les occurrences de ce champ dans toutes les fiches devront obligatoirement contenir une (et une seule!) valeur de ce type. L'attribution d'un type de données à chaque champ est obligatoire; on ne peut pas avoir un champ qui accepterait n'importe quelle valeur, sans égard à un type de données spécifique.
Notons que les types de données sont attribués par champ, et non par occurrence ou par fiche. Toutes les occurrences d'un champ, peu importe la fiche dans laquelle elles se trouvent, doivent donc contenir une valeur du type associé au champ.
Le type de données d'un champ restreint en fait les valeurs admissibles dans les occurrences de ce champ à des valeurs de ce type bien précis. Mais il fait plus: comme nous verrons sous peu, il conditionne aussi la façon dont les données saisies dans ce champ sont interprétées, et même, parfois, transformées. Il s'agit donc d'une information primordiale, qui doit apparaître dans la représentation graphique d'une table. Nous adoptons à cet effet la convention suivante: le type de données de chaque champ sera indiqué dans une ligne spéciale, appelée la ligne des restrictions, située juste en-dessous de la ligne des noms de champ (et à laquelle, incidemment, nous appliquerons un léger grisé, pour bien la démarquer des autres lignes). Pour le moment, cette ligne ne contiendra que le type de données de chaque champ; plus tard, nous verrons qu'elle pourra aussi indiquer d'autres restrictions de contenu que le type de données.
Formellement, le type de données « chaîne de caractères » porte le nom de "text". C'est donc ce mot qui figurera dans la ligne des restrictions des champs dont les occurrences contiennent des chaînes de caractères. Par exemple, la table "Contacts" ci-dessus serait représentée ainsi:
Contacts2 | |||
---|---|---|---|
Numéro | Prénom | Nom | Téléphone |
text | text | text | text |
1 | Jeanne | Tremblay | 555-1212 |
666-1212 | |||
777-1212 | |||
2 | Roger | Drapeau | 555-1213 |
3 | Bernard | Larue | 555-1214 |
777-1214 |
Dans les champs de type "text", il n'y a a priori aucune limite sur la longueur des chaînes de caractères contenues dans une occurrence; nous verrons cependant sous peu qu'il existe des façons de la restreindre.
Le lecteur aguerri se demandera quels caractères exactement peuvent survenir dans une chaîne. La réponse est qu'au moment de créer une table de données, on précise le jeu de caractères à partir duquel les chaînes seront formées. Le répertoire des caractères que l'on peut retrouver dans une chaîne est déterminé par ce jeu. En général, le jeu de caractères est implicite et déterminé par le SGBD utilisé. Dans nos exemples, nous supposons l'ISO-Latin-1. Pour en savoir plus sur les jeux de caractères, consultez le texte « Les jeux de caractères ».
A priori, une chaîne de caractères (c'est-à-dire une valeur de type "Text") peut être absolument quelconque. En particulier, elle peut être vide (ne contenir aucun caractère), ou encore débuter et/ou se terminer par des espaces, ou même ne contenir que des espaces. Pour traiter ces cas sans ambiguïté, nous avons besoin de quelques conventions de notation.
Si une chaîne n'a aucune particularité, nous l'inscrivons telle quelle dans l'occurrence (sans l'entourer de guillemets). C'est le cas de toutes les chaînes rencontrées dans nos exemples jusqu'ici. Cependant, si une chaîne commence et/ou se termine par des espaces, alors on l'incluera entre guillemets doubles. La chaîne vide sera notée "" (deux guillemets doubles consécutifs). Pour rendre explicites les espaces contenues dans une chaîne, on pourra utiliser le caractère de soulignement _. Ainsi, la chaîne constituée de la lettre "A" précédée et suivie d'une espace serait notée "_A_".
Dans nos exemples, nous n'utiliserons jamais le guillemet double ni le soulignement comme caractères à l'intérieur d'une chaîne; nous n'avons donc pas besoin de conventions spéciales pour ces situations. Pour traiter ces cas, il suffirait d'adopter la convention de doubler le caractère « offensant »; par exemple la chaîne « C=A"+B » pourrait être notée "C=A""+B".
Nous avons mentionné ci-dessus que le type de données d'un champ influençait la façon dont les données stockées dans ce champ étaient interprétées, et même parfois transformées. En particulier, chaque type de données a une transformation de saisie associée à lui, laquelle est appliquée aux données saisies dans les champs de ce type avant qu'elles ne soient stockées dans une occurrence.
Par exemple, dans le cas du type "text", la transformation de saisie est l'élimination des espaces finales que l'on aurait pu inscrire. Ainsi, si l'on saisit "Alfred___", c'est la chaîne "Alfred" qui est inscrite dans la table de données. Si l'on saisit "_____", c'est la chaîne vide ("") qui est enregistrée.
Le concept de transformation à la saisie est intéressant en soi, mais somme toute assez banal dans le cas du type "text". La raison pour laquelle nous en avons parlé si tôt est qu'il nous permettra d'introduire plus facilement les types de données autres que "text", qui se comportent un peu bizarrement à première vue.
Le type "integer", qui signifie en anglais « nombre entier », restreint les occurrences d'un champ à des valeurs numériques en représentation interne d'ordinateur (binaire) sur 32 bits. Dans un champ de ce type, la valeur 23 (par exemple) n'est pas représentée par la chaîne de deux caractères "23", mais bien par une suite de 32 zéros et/ou uns.
Évidemment, il n'est pas possible de saisir des données binaires directement au clavier: on ne peut que taper des caractères. C'est ici que la notion de transformation de saisie entre en jeu: on tapera les nombres « normalement » au clavier (avec des chiffres décimaux) et une transformation de saisie, associée au type "integer", les convertira en représentation binaire sur 32 bits, pour les stocker sous cette forme.
Évidemment, la représentation binaire n'est pas plus adaptée à l'affichage des données qu'à leur saisie. Le type "integer" doit donc faire intervenir une deuxième transformation, qui n'était pas présente dans le type "text": la transformation d'affichage. La transformation d'affichage prend les données dans leur représentation interne (telles que stockées dans la table) et les rend propres à la « consommation humaine ». Dans le cas du type "integer", cette transformation convertit un nombre binaire sur 32 bits vers une représentation décimale, qui peut être affichée à l'écran ou imprimée sur papier (c'est, intuitivement, l'inverse de la transformation de saisie).
Par exemple, pour saisir le nombre 23 dans une occurrence d'un champ de type "integer", on tape en fait la chaîne de caractères "23". La transformation de saisie transforme cette suite de caractères en un nombre binaire sur 32 bits, lequel est stocké dans l'occurrence. Lorsque le système nous présente cette valeur, il applique la transformation d'affichage, qui donne de nouveau la chaîne de caractères "23", laquelle peut être affichée ou imprimée.
Évidemment, la transformation de saisie ne peut pas accepter n'importe quoi en entrée; elle ne peut accepter qu'une suite de caractères correspondant à un nombre (décimal) qu'on peut stocker en binaire sur 32 bits, soit précisément les nombres entiers entre -2147483648 et 2147483647. Si on tape quoi que ce soit d'autre—par exemple "abc"—, la transformation de saisie donnera une erreur et n'enregistrera pas l'occurrence. C'est de cette façon que la restriction associée au type de données se concrétise.
Dans la table "Contacts2" ci-dessus, on voit que le champ "Numéro" ne contient que des données numériques. Mais il est de type "text" et rien n'empêcherait donc d'y saisir n'importe quelle chaîne de caractères. On aurait avantage à en faire un champ de type "integer", puisque alors, seules les valeurs numériques seraient acceptées. Selon notre convention de notation, voici ce que cela donnerait:
Contacts3 | |||
---|---|---|---|
Numéro | Prénom | Nom | Téléphone |
integer | text | text | text |
1 | Jeanne | Tremblay | 555-1212 |
666-1212 | |||
777-1212 | |||
2 | Roger | Drapeau | 555-1213 |
3 | Bernard | Larue | 555-1214 |
777-1214 |
Comme nous avons fait remarquer plus haut, la transformation d'affichage n'existe pas pour le type "text". D'autres types sont aussi dans le même cas. Par souci d'uniformité, au lieu de dire que ces types ne comportent pas de transformation d'affichage, nous dirons plutôt qu'ils comportent une transformation d'affichage « bidon », qui ne fait rien; techniquement, on appelle une telle transformation la « transformation identité ».
Les transformations de saisie et d'affichage sont la plupart du temps complètement transparentes, mais on peut en constater l'effet à l'occasion. Par exemple, si l'on saisit un nombre avec des zéros inutiles (par exemple, 0023 au lieu de 23) dans un champ de type "integer", alors ces zéros non significatifs ne seront pas reproduits à l'affichage.
Par convention, dans la représentation visuelle d'une table, les données sont toujours présentées telles que transformées par la transformation de saisie puis par la transformation d'affichage. Nous ne pourrions donc jamais, par exemple, retrouver l'inscription 010 sous un champ de type "integer", ou "Joseph__" sous un champ de type "text". En effet, le jeu de transformations de saisie et d'affichage aurait transformé ces données en (respectivement) 10 et "Joseph".
Il est à noter que le type "integer" que nous venons de décrire n'est pas la même chose que le type "number" que l'on retrouve fréquemment dans les SGBD textuels (notamment DB/TextWorks). En effet, la transformation de saisie du type "number" n'effectue aucune transformation des données; elle vérifie simplement qu'un nombre valide est saisi, déclarant une erreur si ce n'est pas le cas. En d'autres termes, les nombres ne sont pas stockés en binaire, mais bien comme des chaînes de caractères (en conséquence de quoi la transformation d'affichage du type "number" est bien sûr la transformation identité).
Les restrictions imposées par la transformation de saisie d'un type ont évidemment un impact direct sur l'utilisateur final d'une table de données: elles l'empêcheront de saisir n'importe quoi dans un champ de ce type. Ce n'est pas la même chose pour la représentation interne associée à un type. Étant donné la présence des transformations de saisie et d'affichage, l'utilisateur final verra le plus souvent ses données reproduites exactement telles que saisies, peu importe leur représentation interne. Cependant, la représentation interne associée au type d'un champ détermine l'espace disque occupé physiquement par les données stockées dans ce champ. Elle a aussi un impact sur la façon dont sont exécutées les expressions de recherche dans lesquelles ce champ intervient. Pour ces raisons, elle doit être comprise par le concepteur d'une base de données.
Quel que soit le contenu prévu d'une table de données, il est possible, a priori, que les données correspondant à un des champs ne soient pas disponibles, pour quelque raison que ce soit. Par exemple, dans une table de données sur des personnes, il est possible qu'une personne ne soit pas abonnée au téléphone; on n'aurait donc rien à inscrire dans ce champ pour cette personne.
Les modèles textuel et relationnel empruntent deux avenues différentes pour traiter les données manquantes. Dans le modèle textuel, comme le nombre d'occurrences d'un champ dans une fiche est variable, la solution la plus simple est de permettre qu'un champ n'ait aucune (zéro) occurrence dans une fiche. Cette solution n'est cependant pas possible dans le modèle relationnel, puisque (rappelons-nous) toute occurrence contient nécessairement et obligatoirement une valeur et, dans le modèle relationnel, chaque champ a forcément et toujours exactement une occurrence dans chaque fiche. La solution adoptée dans le modèle relationnel est d'introduire une valeur spéciale, unique pour tous les types de données, dont la seule finalité est de représenter une donnée manquante: c'est la valeur NULL.
Résumons donc: dans une table textuelle, une donnée manquante est représentée par l'absence d'occurrence (zéro occurrence) dans le champ correspondant; dans une table relationnelle, une donnée manquante est représentée par l'inscription de la valeur NULL dans l'occurrence (unique et obligatoire) du champ correspondant.
Évidemment, nous avons besoin de conventions de notation pour représenter ces nouveaux phénomènes. Dans une table textuelle, l'absence d'occurrence d'un champ dans une fiche sera représentée en grisant le champ pour cette fiche. Dans une table relationnelle, une valeur NULL dans un champ sera indiquée par l'inscription de la mention NULL—sans guillemets ou autre décoration—dans l'occurrence correspondante (si jamais on voulait inscrire la chaîne de quatre caractères "NULL", on utiliserait les guillemets doubles pour la distinguer de la valeur NULL).
Voici des exemples de tables textuelle et relationnelle avec données manquantes:
|
|
Il est important de comprendre que la valeur NULL n'est pas la chaîne vide. En fait, ce n'est même pas une chaîne de caractères; c'est la seule valeur qui n'est pas une chaîne de caractères mais qui est quand même admise dans une occurrence d'un champ de type "text".
Il est également important de comprendre que l'absence d'occurrence dans un champ n'est pas la même chose que la présence d'une occurrence contenant la chaîne vide. Si l'on reprend la table "Contacts4", en inscrivant cette fois la chaîne vide comme numéro de téléphone pour Roger Drapeau, on obtient:
Contacts6 | |||
---|---|---|---|
Numéro | Prénom | Nom | Téléphone |
integer | text | text | text |
1 | Jeanne | Tremblay | 555-1212 |
666-1212 | |||
777-1212 | |||
2 | Roger | Drapeau | "" |
3 | Bernard | Larue | 555-1214 |
777-1214 |
Évidemment, un humain comprendrait probablement assez vite, devant cette table, que le numéro de téléphone de Roger Drapeau n'est pas disponible, mais un programme informatique pourrait être dérouté par la présence d'une chaîne de caractères vide à un endroit où il s'attend à trouver une chaîne contenant un numéro de téléphone. C'est pour cette raison (entre autres) que la façon « normale » de représenter une donnée manquante dans le modèle textuel est l'absence d'occurrence, plutôt que la présence d'une occurrence contenant la chaîne vide (ou n'importe quelle autre chaîne spécifique qui pourrait symboliser l'absence de données, comme "N/D" ou "NIL").
Le lecteur aguerri se demandera peut-être pourquoi la valeur NULL n'est pas admise dans le modèle textuel. En fait, le modèle aurait très bien pu être défini différemment et admettre la valeur NULL. Mais tout modèle de données traduit un souci d'économie de moyens qui fait qu'un concept n'est intégré que s'il est absolument nécessaire. Comme le modèle textuel intégrait déjà la notion de nombre variable d'occurrences, il n'y avait aucune nécessité de recourir à une valeur spéciale pour représenter les données manquantes. Il s'agit donc d'une décision de conception du modèle lui-même tout à fait délibérée. Une raison similaire explique l'absence d'occurrences multiples dans le modèle relationnel, la différence étant que nous n'avons pas encore vu les caractéristiques du modèle qui font que les occurrences multiples n'y sont pas absolument nécessaires; nous y arriverons sous peu.
Nous avons déjà vu que le type de données qu'on spécifie pour chaque champ restreint les valeurs admissibles dans ce champ. Mais, au-delà de l'attribution d'un type de données, on peut vouloir restreindre davantage ce qui est acceptable dans un champ. En effet, souvent la nature même des données à stocker dans un champ fait que certains contenus seulement sont possibles. Le fait d'imposer une contrainte sur le contenu d'un champ au niveau de la table de données met en place une « validation » des données, qui permet la détection de certaines erreurs dès la saisie.
Ainsi, par exemple, on pourrait vouloir rendre un champ « obligatoire ». Concrètement, cela veut dire interdire la saisie de la valeur NULL dans ce champ pour une table relationnelle et obliger la saisie d'au moins une occurrence de ce champ dans chaque fiche pour un table textuelle. On pourrait aussi vouloir interdire la saisie d'occurrences multiples dans certains champs d'une table textuelle (par exemple, le champ "Nom" d'une fiche d'employé). On pourrait enfin vouloir imposer une forme particulière aux données saisies dans les différentes occurrences d'un champ; par exemple, s'assurer que toutes les chaînes inscrites dans un champ "Téléphone" respectent la forme "nnn-nnnn", où n représente un chiffre (0-9).
Pour permettre de telles « validations des données » à la saisie, les modèles textuel et relationnel offrent la possibilité de définir, au moment de la création d'une table, des restrictions sur ce qui est admissible comme contenu de chaque champ. Pour chaque champ, on donne donc non seulement un nom et un type de données, mais aussi un ensemble de restrictions applicables au contenu de ce champ dans les différentes fiches. Les restrictions de contenu associées à un champ donné seront les mêmes pour toutes les fiches.
Un exemple de restrictions sur le contenu d'un champ concerne le nombre d'occurrences. Évidemment, ce type de restrictions ne s'applique pas au modèle relationnel, puisque dans celui-là, il y a toujours exactement une occurrence dans chaque champ de chaque fiche. Cependant, à la création d'une table textuelle, il est possible de limiter le nombre d'occurrences dans un champ, de deux façons:
La première restriction revient à rendre le champ obligatoire. La seconde revient à interdire les occurrences multiples. Si on spécifie les deux contraintes à la fois, alors le champ sera analogue à un champ d'une table relationnelle: exactement une occurrence du champ dans chaque fiche (sans toutefois la possibilité d'inscrire la valeur NULL, puisque celle-là n'existe pas dans le modèle textuel).
Il serait possible d'indiquer visuellement les restrictions sur le nombre d'occurrences dans la représentation graphique d'une table textuelle. Si nous voulions discuter en profondeur du modèle textuel, il nous faudrait adopter une convention à cet effet. Cependant, comme le présent texte s'intéresse principalement au modèle relationnel, nous ne nous donnerons pas cette peine.
À partir de maintenant, d'ailleurs, nous allons parler exclusivement du modèle relationnel. Nous nous permettrons donc de parler de « la valeur inscrite dans tel champ de telle fiche », plutôt que de « la valeur inscrite dans l'occurrence (unique et obligatoire) présente dans tel champ de telle fiche ». Ce léger abus de langage allégera considérablement le texte.
Nous allons ci-après introduire plusieurs façons de restreindre le contenu d'un champ dans une table relationnelle. Comme nous avons dit au début de ce texte, nous adopterons des conventions de notation pour indiquer ces restrictions dans la représentation visuelle d'une table de données, mais la façon concrète de spécifier ces restrictions dans l'interface de définition d'une table dépendra du SGBD spécifique utilisé et nous importe peu; l'important est qu'il y aura toujours une façon de le faire. Rappelons aussi que nous tenons pour acquis que l'environnement de saisie des données refuse d'enregistrer une fiche dont le contenu contreviendrait aux restrictions applicables aux différents champs, peu importe comment ce refus se manifeste concrètement dans l'interface-utilisateur.
Une première façon de restreindre le contenu d'un champ dans une table relationnelle est d'interdire l'inscription de la valeur NULL. Évidemment, cette restriction n'aurait pas de sens pour le modèle textuel, puisque dans celui-là, la valeur NULL n'existe pas. Dans le modèle relationnel, cependant, il est possible, à la création d'une table, de spécifier que la valeur NULL est interdite dans un certain champ, et ce, peu importe le type de données du champ. Le fait d'interdire la valeur NULL dans un champ revient à rendre ce champ obligatoire.
L'interdiction d'inscrire la valeur NULL dans un champ sera
indiquée par le signe N dans la ligne des restrictions
(séparé du type de données par une virgule). Ainsi, pour
rendre obligatoires les champs "Numéro", "Prénom" et "Nom" dans
la table "Contacts5" déjà présentée ci-dessus, on
inscrirait un N sous ces trois champs dans la ligne des
restrictions:
Contacts7 | |||
---|---|---|---|
Numéro | Prénom | Nom | Téléphone |
integer, |
text, |
text, |
text |
1 | Jeanne | Tremblay | 555-1212 |
2 | Roger | Drapeau | NULL |
3 | Bernard | Larue | 555-1214 |
Quand un champ est de type "text", une autre façon de restreindre le contenu est de limiter la longueur des chaînes que l'on peut inscrire dans ce champ. Il est à noter qu'une telle restriction s'applique après que la transformation de saisie est effectuée. Il faut aussi noter que ces restrictions n'empêchent pas l'inscription de la valeur NULL, qui (comme nous venons de voir) est contrôlée séparément.
Le premier type de restriction lié à la longueur des
chaînes est l'interdiction d'enregistrer la chaîne vide dans un
champ. Concrètement, cela veut dire qu'on ne peut pas saisir la
chaîne vide dans ce champ, ni une chaîne constituée
exclusivement d'espaces. L'interdiction d'enregistrer la chaîne vide dans
un champ sera indiquée par le signe V (séparé
de ce qui précède par une virgule) dans la ligne des
restrictions.
On peut aussi vouloir fixer une longueur maximale pour les chaînes enregistrées dans un champ. Dans ce cas, on inscrira (nnn) immédiatement après le mot "text" dans la ligne des restrictions, où nnn est la longueur maximum que l'on veut accepter dans le champ. Dans la plupart des SGBD relationnels, nnn doit être un nombre entre 1 et 255.
Par exemple, la table suivante interdirait l'inscription de la chaîne vide dans les champs "Prénom", "Nom" et "Téléphone", et imposerait une longueur maximale de 30 caractères pour le prénom, 50 caractères pour le nom et 8 caractères pour le numéro de téléphone:
Contacts8 | |||
---|---|---|---|
Numéro | Prénom | Nom | Téléphone |
integer, |
text(30), |
text(50), |
text(8), |
1 | Jeanne | Tremblay | 555-1212 |
2 | Roger | Drapeau | NULL |
3 | Bernard | Larue | 555-1214 |
Les SGBD relationnels courants offrent de nombreux types de données. À titre d'exemples, nous introduisons ici les types "logical" et "date" (ces types ne correspondent pas exactement aux types de mêmes noms présents dans les SGBD commerciaux, mais ils en sont assez proches).
Notre type "logical" utilise une représentation interne en binaire sur 1 seul bit; en conséquence, elle n'a que deux valeurs possibles: 0 et 1 (en plus de la valeur NULL, qui est toujours admise, sauf si on prend la peine de l'interdire explicitement). Avec une convention appropriée, on peut utiliser un champ de ce type pour représenter n'importe quelle donnée n'ayant que deux valeurs possibles, comme "vrai" et "faux", "oui" et "non", "homme" et "femme", "permanent" et "temporaire", etc.
La transformation de saisie n'accepte que les quatre chaînes "true", "yes", "false" et "no"; elle transforme les deux premières en 1 et les deux dernières en 0. La transformation d'affichage transforme 1 en "true" et 0 en "false".
Notre type date utilise comme représentation interne des chaînes de la forme "AAAA-MM-JJ" représentant une date valide, par exemple "2025-12-31". La transformation de saisie n'accepte que les chaînes de cette forme et la transformation d'affichage est l'identité.
Aucun des types de données vus jusqu'ici n'accepte la chaîne vide, sauf "text". Dans un champ qui n'est pas de type "text", on peut donc adopter la convention suivante: au lieu d'indiquer la valeur NULL en inscrivant NULL, on peut simplement laisser le champ blanc. Le seul cas où cette convention serait ambiguë est un champ de type "text" où la chaîne vide est permise.
Pour les champs de type "text", nous adoptons la convention suivante: si la valeur NULL est interdite, alors un champ blanc signifiera la chaîne vide; si la chaîne vide est interdite, alors un champ blanc signifiera la valeur NULL. Le seul cas où nous aurions besoin d'inscrire explicitement la chaîne vide ("") ou la valeur NULL est donc celui d'un champ "text" qui admet à la fois la valeur NULL et la chaîne vide, mais nous éviterons cette situation dans nos exemples.
Dorénavant, donc, nous adoptons la convention que la chaîne vide et la valeur NULL sont toutes les deux représentées—et saisies—en laissant le champ blanc. Il faut noter que, lorsqu'un champ est laissé blanc à la saisie, la transformation de saisie associée au type du champ n'est pas appliquée; la valeur appropriée est immédiatement inscrite dans le champ.
Avec cette convention, la table "Contacts8" ci-dessus peut-être réécrite comme suit:
Contacts9 | |||
---|---|---|---|
Numéro | Prénom | Nom | Téléphone |
integer, |
text(30), |
text(50), |
text(8), |
1 | Jeanne | Tremblay | 555-1212 |
2 | Roger | Drapeau | |
3 | Bernard | Larue | 555-1214 |
On note que la valeur NULL dans le champ "Téléphone" de la fiche de Roger Drapeau n'est plus inscrite explicitement, mais représentée implicitement, par un champ blanc. Cela est possible parce que les chaînes vides sont interdites dans ce champ.
Notons que dans la plupart des SGBD commerciaux—incluant Access—, la chaîne vide est automatiquement interdite dans un champ si la valeur NULL y est permise; il n'y a donc jamais d'ambiguïté potentielle. En conséquence, la même convention qu'ici est habituellement adoptée, à savoir que la chaîne vide et la valeur NULL sont toutes les deux représentées (et saisies) en laissant le champ blanc.
Considérons le champ "Téléphone" de la table "Contacts9" ci-dessus. Déjà, nous avons éliminé la chaîne vide et limité la longueur du contenu à 8 caractères dans ce champ, mais rien n'empêcherait d'y saisir "abc" ou "-123456-". Pourtant, il serait possible d'être beaucoup plus restrictif, en imposant par exemple la forme nnn-nnnn, où n représente un chiffre. C'est à ce genre de restrictions que servent les masques de saisie.
Un masque de saisie pour un champ sera indiqué dans sa ligne des restrictions par l'inscription "mask:" (séparée de ce qui précède par une virgule) suivie du masque proprement dit. Un masque de saisie est une chaîne de caractères qui précise, de façon générique, la forme des chaînes acceptées en entrée. L'aspect "générique" du masque vient du fait que certains caractères—dits justement génériques—en représentent plusieurs, par exemple "0" représente n'importe quel chiffre et "L" représente n'importe quelle lettre (une liste exhaustive des caractères génériques figure ci-dessous).
Le mieux, pour comprendre le langage des masques de saisie, est d'analyser quelques exemples. Pour obtenir la forme de numéro de téléphone évoquée ci-dessus (nnn-nnnn), on utiliserait:
mask:"000-0000"
Comme nous l'avons dit, chaque "0" représente un chiffre. Mais le tiret "-", qui n'a aucune signification particulière dans le langage des masques de saisie, indique qu'un tiret doit figurer tel quel à cet endroit dans la chaîne saisie.
Si on voulait avoir une forme un peu plus développée, avec un code régional, on pourrait utiliser:
mask:"(000)_000-0000"
Chaque "0" représente encore un chiffre, et les caractères "(", ")", l'espace "_" et le tiret "-" doivent figurer tels quels.
Pour imposer la forme évoquée précédemment aux numéros de téléphones dans le champ "Téléphone", la table "Contacts9" pourrait donc être réécrite comme suit:
Contacts10 | |||
---|---|---|---|
Numéro | Prénom | Nom | Téléphone |
integer, |
text(30), |
text(50), |
text(8), |
1 | Jeanne | Tremblay | 555-1212 |
2 | Roger | Drapeau | |
3 | Bernard | Larue | 555-1214 |
Voici la liste exhaustive des caractères génériques utilisables dans nos masques de saisie:
Caractère | Représente |
---|---|
0 | N'importe quel chiffre, entrée obligatoire |
9 | N'importe quel chiffre, entrée facultative |
L | N'importe quelle lettre, entrée obligatoire |
? | N'importe quelle lettre, entrée facultative |
& | N'importe quel caractère, entrée obligatoire |
C | N'importe quel caractère, entrée facultative |
Les caractères pour lesquels figure la mention "entrée facultative", en plus de représenter une gamme de caractères, peuvent aussi représenter la chaîne vide; en d'autres termes, ils représentent un caractère qui peut ou non se trouver dans la chaîne saisie. Par exemple, "L?99L" représente toute suite de deux ou trois lettres, ou encore toute suite d'une ou deux lettres suivie d'un ou deux chiffres, suivis d'une lettre.
Dans la forme que nous présentons, un masque de saisie ne sert qu'à restreindre les chaînes acceptées en entrée; cependant, dans plusieurs SGBD commerciaux, ils peuvent effectivement effectuer des transformations, par exemple, transformer les lettres minuscules en majuscules. C'est la principale raison pour laquelle nous les avons introduits comme "transformations additionnelles". (Il faut dire également que, dans les SGBD commerciaux, le langage des masques de saisie inclut des conventions permettant de traiter les cas où un caractère générique—par exemple "&"—doit lui-même figurer tel quel dans la chaîne saisie. Comme nous nous en tenons à des exemples simples, nous ne nous donnons pas la peine d'introduire ici de telles conventions.)
Il ne faut pas sous-estimer la puissance des masques de saisie, même dans la forme restreinte que nous présentons ici. Ils peuvent servir, par exemple, à imposer une valeur maximum dans un champ de type "integer". Ainsi, le masque suivant n'acceptera que les valeurs entre 0 et 9999:
mask:"9990"
Il faut souligner qu'un masque de saisie n'empêche pas de laisser un champ blanc, si la chaîne vide ou la valeur NULL est permise dans ce champ. Si on laisse blanc un champ dans lequel la valeur NULL ou la chaîne vide est permise, le masque est simplement ignoré et la valeur appropriée est inscrite dans le champ.
Le lecteur aguerri aura remarqué que, comme les masques de saisie sont définis en termes de chaînes de caractères, ils correspondent à des transformations qui sont effectuées avant la transformation de saisie du type de données.
Le modèle relationnel exige que, pour toute table de données, l'on spécifie une clé primaire. Une clé primaire est un champ ou un groupe de champs dans le(s)quel(s) la valeur NULL est interdite et dont la valeur (ou combinaison des valeurs) est distincte pour chaque fiche possible. Le modèle exige l'attribution d'une clé primaire afin qu'il soit toujours possible d'identifier individuellement chaque fiche.
Le fait qu'un groupe de champs donné puisse servir de clé primaire dépend de la situation à modéliser. C'est donc par la connaissance de cette situation que l'on peut déterminer quel(s) champ(s) constitue(nt) le meilleur choix de clé primaire.
Habituellement, plusieurs groupes de champs peuvent constituer une clé primaire acceptable (par exemple, l'ensemble des champs dans lesquels la valeur NULL est interdite). Mais on cherchera en général à utiliser une clé primaire « minimale », c'est-à-dire contenant juste assez de champs pour assurer une valeur distincte pour chaque fiche.
Prenons la table "Contacts10" ci-dessus. On peut imaginer que l'intention sous-jacente au champ "Numéro" est justement de fournir une identification unique à chaque fiche, peu importe la valeur des autres champs. Ce serait donc un choix naturel de clé primaire. Mais il faut remarquer que les groupes de champs (Numéro, Prénom), (Numéro, Nom) et (Numéro, Prénom, Nom) constitueraient aussi des choix valables de clé primaire, puisque le champ "Numéro" à lui seul assure une valeur distincte pour chaque fiche. Ce ne seraient cependant pas des choix « minimaux ». Le choix minimal (et donc, le plus naturel) est le champ "Numéro" seul.
On dit d'un champ qui fait partie de la clé primaire d'une table qu'il participe à la clé primaire. Lorsque la clé primaire est constituée d'un seul champ, alors seul ce champ participe à la clé primaire. Lorsque la clé primaire est constituée de plusieurs champs, on la dit multichamp. Le lecteur est référé au texte « Clé primaire multichamp » pour un exemple où le choix de clé primaire le plus naturel est multichamp.
Nous indiquerons les champs qui participent à la clé primaire d'une table par l'inscription "K" (séparée de ce qui précède par une virgule) dans la ligne des restrictions. Ainsi, après institution du champ "Numéro" comme clé primaire, la table "Contacts10" deviendrait:
Contacts11 | |||
---|---|---|---|
Numéro | Prénom | Nom | Téléphone |
integer, |
text(30), |
text(50), |
text(8), |
1 | Jeanne | Tremblay | 555-1212 |
2 | Roger | Drapeau | |
3 | Bernard | Larue | 555-1214 |
Avec cette définition de table, le système refuserait d'enregistrer toute nouvelle ligne qui comporterait comme valeur dans le champ "Numéro" une valeur déjà inscrite dans ce même champ dans une autre fiche.
Il convient de noter que notre mécanique de restrictions des valeurs acceptables dans un champ est devenue suffisamment puissante pour que certaines situations particulières puissent survenir.
Le premier type de situations particulières est que deux contraintes peuvent être redondantes. Prenons par exemple le champ "Téléphone" ci-dessus. Il est soumis (entre autres) aux contraintes suivantes: "text(8)" et mask:"000-0000". Mais le masque de saisie rend inutile la longueur maximum de 8 caractères, puisque toute chaîne respectant le masque sera forcément de longueur exactement 8.
Le second type de situations particulières est que deux contraintes peuvent être contradictoires. Par exemple, si on avait déclaré le champ "Téléphone" ci-dessus comme "text(7)" au lieu de "text(8)", avec le même masque de saisie mask:"000-0000", alors il serait impossible de saisir quoi que ce soit dans la table, parce qu'une même valeur ne peut à la fois satisfaire le masque et être de longueur 7.
Les contraintes contradictictoires constituent bien sûr un problème, puisqu'elles empêchent la saisie de toute information dans une table. Les contraintes redondantes, cependant, ne sont pas nocives et n'ont pas besoin d'être évitées. Au contraire, elles rendent parfois explicites et évidentes des contraintes qui, autrement, ne sauteraient pas aux yeux.
Le modèle n'empêche ni les contraintes redondantes ni les contraintes contradictoires. C'est au concepteur d'une base de données de voir à spécifier un ensemble de restrictions cohérent et qui corresponde aux besoins auxquels la base doit répondre.
Une des différences fondamentales entre les modèles textuel et relationnel n'a pas encore été mentionnée: c'est le nombre de tables de données qui constituent une base de données. Dans le modèle textuel, une base de données est constituée d'une seule table, alors que dans le modèle relationnel, une base de données comporte au moins une table, mais peut en comporter plus d'une.
Notons au passage que, fondamentalement, c'est cette possibilité d'avoir plus d'une table par base de données, combinée à un langage d'interrogation capable d'exploiter simultanément plusieurs tables au sein d'une même requête, qui fait que les occurrences multiples ne sont pas absolument nécessaires dans le modèle relationnel. Cela deviendra plus évident quand nous traiterons du langage d'interrogation.
Comme une base de données est une entité distincte des tables qui la composent, elle doit être nommée séparément, et ce, même si elle n'est composée que d'une seule table. Cette « disponibilité » d'un nom additionnel peut d'ailleurs être une occasion d'être plus précis dans la dénomination des choses. Par exemple, la table "Contacts11" ci-dessus pourrait être renommée "Personnes" et la base de données, elle, baptisée "Contacts" (en fait, "Contacts12", pour la distinguer de la table "Contacts" présentée au tout début du texte). Ceci se traduirait visuellement comme suit:
Contacts12
|
Comme on peut le constater, la convention est de donner le nom de la base seul sur une ligne, puis ensuite chacune des tables qui composent la base, sous la forme élaborée jusqu'ici. Comme ici la base est composée d'une seule table, le seul ajout par rapport à la présentation de la table en elle-même est le nom de la base de données.
Quand il y a plus d'une table, l'ordre dans lequel elles sont présentées n'a pas d'importance.
L'existence de clés primaires et la possibilité d'avoir plusieurs tables dans une même base de données ouvre la porte à un autre type de contrôle du contenu d'un champ: les clés externes. Pour illustrer cette notion, nous allons enrichir l'exemple utilisé jusqu'ici, en ajoutant de l'information sur l'affiliation des personnes contacts. Chaque personne sera affiliée à une organisation, dont veut connaître le nom, ainsi que le nombre d'employés.
En première analyse, on pourrait penser à la structure suivante:
Contacts13
|
On remarquera que nous n'avons pas rendu obligatoire l'inscription de l'affiliation, comme en témoigne la nouvelle fiche inscrite (Numéro 4), qui possède la valeur NULL dans ses champs "Organisation" et "NbEmployés". Cela est une décision de modélisation tout à fait valable et que nous supposons conforme aux besoins des utilisateurs de la base.
Le problème avec cette structure, c'est que l'information sur une organisation est dupliquée si plus d'une personne est associée à la même organisation. C'est le cas dans notre exemple pour l'organisation OASIS Inc. Une telle duplication, en plus d'alourdir la saisie, peut ouvrir la porte à une incohérence des données, puisque, par exemple, on pourrait (à la suite d'une erreur ou pour toute autre raison) se retrouver avec des nombres d'employés différents dans les fiches 1 et 3; quel nombre faudrait-il alors croire?
Cette structure n'est donc pas idéale et elle n'exploite pas les possibilités du modèle relationnel. Une meilleure solution est d'introduire une seconde table pour consigner, sans redondance, l'information sur les organisations:
Organisations | ||
---|---|---|
Numéro | Nom | NbEmployés |
integer, |
text(50), |
integer |
1 | OASIS Inc. | 63 |
2 | Fun Island AB | 224 |
Dans les fiches de contacts, au lieu de conserver toute l'information sur l'organisation d'affiliation, on peut maintenant se permettre de ne conserver que le numéro d'organisation, lequel identifie une organisation spécifique dans la table des organisations:
Personnes2 | ||||
---|---|---|---|---|
Numéro | Prénom | Nom | Téléphone | Organisation |
integer, |
text(30), |
text(50), |
text(8), |
integer |
1 | Jeanne | Tremblay | 555-1212 | 1 |
2 | Roger | Drapeau | 2 | |
3 | Bernard | Larue | 555-1214 | 1 |
4 | Sylvie | Drapeau |
La base de données complète se présenterait donc comme suit:
Contacts14
|
Notons au passage que deux des noms de champs dans "Personnes2" ("Numéro" et "Nom") sont identiques à des noms de champs dans "Organisations"; cela est permis par le modèle relationnel, pourvu que les champs de même nom ne soient pas dans la même table. Dans le contexte d'une base de données à plusieurs tables, on peut « désambiguïser » un nom de champ en le faisant précéder du nom de la table suivi d'un point. Ainsi, on distinguerait les champs "Personnes2.Numéro" de "Organisations.Numéro" et "Personnes2.Nom" de "Organisations.Nom". On peut toujours désambiguïser un nom de champ, même s'il n'y a pas réellement d'ambiguïté; par exemple, on peut parler du champ "Organisations.NbEmployés", même s'il n'y a pas d'autre champ que celui-là qui s'appelle "NbEmployés" dans la base de données.
Évidemment, l'idée du champ "Personnes2.Organisation" est qu'il « pointe » à une organisation existante dans la table "Organisations". Mais avec les restrictions actuelles, il serait possible d'inscrire n'importe quelle valeur numérique. La restriction de clé externe va justement nous permettre de restreindre les valeurs admises dans "Personnes2.Organisation" aux seuls numéros d'organisations réellement présents dans la table "Organisations".
Cette restriction consiste à déclarer que le champ "Personnes2.Organisation" est une clé externe vers la table "Organisations". Cela sera indiqué dans la ligne des restrictions du champ "Personnes2.Organisation" par l'inscription "X(Organisations)" (précédé de ce qui précède par une virgule). La table "Personnes2" serait donc réécrite comme suite:
Personnes3 | ||||
---|---|---|---|---|
Numéro | Prénom | Nom | Téléphone | Organisation |
integer, |
text(30), |
text(50), |
text(8), |
integer, X(Organisations) |
1 | Jeanne | Tremblay | 555-1212 | 1 |
2 | Roger | Drapeau | 2 | |
3 | Bernard | Larue | 555-1214 | 1 |
4 | Sylvie | Drapeau |
Avec cette restriction, les seules valeurs admises dans le champ "Organisation" sont la valeur NULL (si elle n'est pas par ailleurs interdite) et les valeurs existant réellement dans la clé primaire de la table "Organisations".
Évidemment, si la clé primaire de la table à laquelle on veut pointer est multichamp, alors la clé externe devra aussi être multichamp. Dans ce cas, on inscrira la contrainte "X(nom-de-table)" sous chacun des champs qui participe à la clé externe. (Note pour lecteurs aguerris: D'autres conventions de notation sont requises dans les cas où il existe dans une même table deux clés externes ou plus vers une même autre table. Par exemple, si nous voulions conserver une trace de l'affiliation passée des personnes, nous pourrions ajouter à "Personnes3" un champ "AncienneOrganisation", qui contiendrait l'ancienne affiliation de la personne (le cas échéant, et NULL autrement). Comme le champ "Organisation", ce champ serait lui aussi une clé externe vers "Organisations". Nous aurions donc au sein d'une même table ("Personnes3") deux clés externes vers une même autre table ("Organisations"). Cependant, comme nous n'auront pas de tels cas dans nos exemples, nous faisons l'économie d'une telle convention.)
Notre base de données complète se présenterait donc maintenant comme suit:
Contacts15
|
Rappelons qu'un modèle de données est un modèle abstrait représentant les deux aspects suivants d'une classe de SGBD: la structure des données que ces SGBD peuvent traiter et les opérations de stockage et de repérage que l'on peut effectuer sur ces données structurées. Jusqu'ici, à part formuler quelques hypothèses sur la saisie des données (par exemple, l'hypothèse que le SGBD refuse d'enregistrer dans une table toute fiche dont le contenu d'au moins un champ contrevient aux restrictions spécifiées dans la définition de la table), nous n'avons traité que de l'aspect structurel du modèle relationnel. (En fait, nous avons pratiquement terminé ce volet; il ne nous manque qu'un élément, les conditions de validité, que nous gardons pour plus tard, puisqu'il est basé sur les expressions du langage de recherche, qu'il est beaucoup plus naturel de présenter en même temps que le langage de recherche lui-même.) Nous passons maintenant aux opérations que l'on peut effectuer sur les données structurées selon ce que nous avons présenté jusqu'ici du modèle relationnel.
Quels genres d'opérations sont possibles sur une base de données relationnelle? On distingue en général les opérations de recherche (ou extraction), qui produisent de l'information à partir de ce qui est stocké dans les différentes tables de la base, mais sans rien y modifier, et les opérations de modification, qui ont le potentiel de changer quelque chose dans le contenu de la base. Nous ne parlons ici que d'opérations de recherche.
Dans le modèle relationnel, les opérations de recherche s'expriment le plus souvent dans un langage (normalisé) appelé SQL (pour Structured Query Language). Il faut savoir que SQL inclut d'autres volets que les opérations de recherche. En particulier, il inclut non seulement des opérations de modification, mais même des opérations qui permettent de définir un base de données et ses différentes tables.
Globalement, SQL peut être découpé comme suit:
Nous ne parlons donc ici que d'une petite partie de SQL (les opérations de recherche). Nous justifions notre approche par le fait que, dans le monde des bases de données documentaires, les opérations de recherche sont énormément plus fréquentes que les opérations de modification (qui se résument essentiellement à la saisie de nouvelles informations, à la correction d'erreurs et à un élagage périodique).
Le scénario de travail sur une base de données relationnelle est le suivant: On démarre (ou ouvre) d'abord une « session de travail » auprès d'un SGBD, en identifiant sur quelle base on désire travailler au cours de la session (on ne peut travailler que sur une base à la fois au cours de la même session). Une fois la session ouverte, on soumet au SGBD une première requête. Le SGBD exécute la requête et nous informe du résultat. On peut alors soumettre une deuxième requête, dont le SGBD nous transmet le résultat, et ainsi de suite, jusqu'à ce que l'on mette fin à (ou ferme) la session.
Bien qu'il existe souvent plusieurs façons d'exprimer les requêtes que l'on soumet au SGBD, la plus universelle est de les exprimer dans le langage SQL, et nous ne parlerons que de celle-là.
Les requêtes SQL qui correspondent aux opérations de recherche sont appelées des « requêtes SELECT » (ou simplement des « SELECT »), pour la simple raison qu'elles débutent toutes par le mot « SELECT ». (Nous écrirons tous les mots-clés du langage SQL en majuscules, mais la plupart des SGBD les acceptent sans égard à la casse.)
Les requêtes SELECT incarnent toute la puissance du modèle relationnel et leur forme peut être très variée. Nous n'en verrons pour débuter qu'une forme très spécifique, qui nous permettra cependant de discuter d'aspects fondamentaux de la recherche d'information dans une base de données relationnelle. Nous appellerons cette forme de requêtes les SELECT restreints.
Voici la forme générale d'un SELECT restreint:
SELECT * FROM nom-de-table
WHERE nom-de-champ opérateur-de-comparaison constante ;
Donnons tout de suite un exemple; la requête suivante est un SELECT restreint qui pourrait être exécuté sur la base "Contacts15" ci-dessus:
SELECT * FROM Personnes3 WHERE Nom = "drapeau" ;
Mentionnons d'emblée que les sauts de ligne et le retrait des différentes lignes importent peu dans une requête SQL (pas seulement dans les SELECT restreints); ces éléments peuvent donc être utilisés librement pour améliorer la lisibilité des requêtes.
Tout ce qu'on a souligné, à la fois dans la forme générale et dans l'exemple, est fixe et doit figurer tel quel dans un SELECT restreint. Le reste de la forme générale, à savoir les symboles nom-de-table, nom-de-champ, opérateur-de-comparaison et constante, doivent être remplacés pour obtenir un SELECT restreint valide.
Le découpage suivant met en évidence la correspondance entre la forme générale et l'exemple:
La forme générale:
SELECT * FROM nom-de-table1
WHERE nom-de-champ2 opérateur-de-comparaison3 constante4 ;
L'exemple:
SELECT * FROM Personnes31 WHERE Nom2 =3 "drapeau"4;
Les encadrés de même numéro correspondent. On constate que le nom-de-table est "Personne3", que le nom-de-champ est "Nom", que l'opérateur-de-comparaison est "=" et que la constante est "drapeau".
Les éléments variables de la forme générale (nom-de-table, nom-de-champ, opérateur-de-comparaison et constante) ne peuvent évidemment pas être substitués n'importe comment:
Qu'est-ce qu'une « constante » d'un certain type? Pour le moment, disons simplement qu'une constante de type "text" est une chaîne de caractères, écrite selon les conventions exposées précédemment, mais obligatoirement délimitée par des guillemets doubles. Nous reviendrons sous peu aux constantes des autres types.
Comme on peut constater, notre exemple rencontre toutes les exigences mentionnées.
Les trois éléments nom-de-champ, opérateur-de-comparaison et constante forment ce qu'on appelle la condition de sélection du SELECT restreint. Dans notre exemple, la condition de sélection est donc:
Nom = "drapeau"
Nous nous apprêtons à parler de comparaison de chaînes de caractères, notamment à nous demander dans quelles circonstances deux chaînes, de provenances diverses, peuvent être considérées comme égales.
Contrairement à ce qu'on pourrait penser de prime abord, la forme la plus utile de comparaison n'est pas l'égalité stricte des chaînes (exactement les mêmes caractères, dans le même ordre). En fait, la forme la plus courante de comparaison est définie différemment; pour éviter la confusion avec l'égalité stricte, nous l'appelons quasi-égalité. La quasi-égalité est définie comme suit:
Deux chaînes de caractères sont quasi-égales si elles ne diffèrent que par des espaces finales et/ou par la casse des lettres.
Donc, en particulier, deux chaînes quasi-égales ne sont pas forcément de la même longueur. En effet, les espaces finales qui pourraient se trouver dans les chaînes comparées ne sont pas prises en compte. Ainsi, par exemple, "Faite" est quasi-égale à "faite", de même qu'à "Faite_" et à "faite___". Par contre, elle n'est pas quasi-égale à "_Faite", ni à "Faîte" (les signes diacritiques ne sont pas ignorés).
Lorsque deux chaînes a et b sont quasi-égales, on écrit a = b; autrement, on écrit a <> b. Ainsi, on écrirait:
Profitons-en pour noter que "" = "_" = "___" .
Un autre type de comparaison de chaînes est la préséance dans l'ordre alphabétique. Si a vient avant b dans l'ordre alphabétique, alors on écrira a < b; si elle vient après, on écrira a > b. Notons que, si a < b ou a > b, alors forcément, a <> b.
Le lecteur aguerri se demandera de quel « ordre alphabétique » exactement il s'agit. La réponse dépend en fait du système utilisé. Il s'agit habituellement d'une comparaison caractère par caractère, qui traite les signes diacritiques selon les règles de la langue active spécifiée dans la configuration du système d'exploitation actif. Dans nos exemples, nous supposerons les règles du français.
Ainsi, par exemple:
Notons également que, même pour des chaînes consitutées exclusivement de chiffres, l'ordre alphabétique ne correspond pas à l'ordre numérique; par exemple:
Finalement, notons que la chaîne vide (ou toute chaîne constituée exclusivement d'espaces) est "plus petite" que n'importe quelle autre chaîne. Ainsi, a > "" est toujours vrai, sauf si a est vide (ou constituée exclusivement d'espaces).
La notation a <= b signifie que a < b ou a = b; la notation a >= b signifie que a > b ou a = b.
Que fait donc un SELECT restreint? Eu égard au scénario d'interrogation exposé ci-dessus, la question revient à demander: qu'est-ce qu'un SGBD me retournera comme résultat si je lui soumets un SELECT restreint valide? La réponse s'avère en fait extrêmement naturelle. À preuve, on peut pratiquement « deviner » que la requête suivante, exécutée sur la base "Contacts15", repêchera les fiches 2 et 4 de la table "Personnes3":
SELECT * FROM Personnes3 WHERE Nom = "drapeau" ;
Et, effectivement, le résultat obtenu est le suivant:
Numéro | Prénom | Nom | Téléphone | Organisation |
---|---|---|---|---|
integer, |
text(30), |
text(50), |
text(8), |
integer |
2 | Roger | Drapeau | 2 | |
4 | Sylvie | Drapeau |
qui inclut bel et bien les fiches 2 et 4. Mais comment, exactement, le SGBD arrive-t-il à ce résultat?
D'abord, profitons-en pour faire un énoncé universel sur les requêtes SELECT (pas seulement les SELECT restreints): le résultat d'un SELECT est toujours une table relationnelle. Ou presque. En fait, il s'agit d'une table relationnelle qui ne porte pas de nom et ne comporte ni clé primaire ni clés externes. Le fait que la table ne porte pas de nom n'est pas gênant, parce que la table résultat d'un SELECT est temporaire: elle n'existe que le temps de présenter les résultats et est ensuite détruite. Le résultat d'un SELECT ne devient donc pas une table additionnelle dans la base de données.
Une première chose que l'on peut dire par rapport à la table résultat d'un SELECT restreint, c'est qu'elle a exactement la même définition que la table mentionnée dans la requête (la table « nom-de-table »), mais sans le nom de table, et sans la clé primaire et les clés externes. La table mentionnée dans notre requête exemple est "Personnes3", et le résultat a donc la définition suivante:
Numéro | Prénom | Nom | Téléphone | Organisation |
---|---|---|---|---|
integer, |
text(30), |
text(50), |
text(8), |
integer |
qui est exactement la définition de "Personnes3", clés primaire et externe en moins.
Maintenant, quelles fiches contient cette table? Réponse: exactement les fiches de la table nom-de-table (ici "Personnes3") pour lesquelles la condition de sélection est vraie.
Rappelons-nous que, dans notre exemple, la condition de sélection est:
Nom = "drapeau"
Cela veut dire que le résultat de la requête contient exactement les fiches de "Personnes3" pour lesquelles le champ "Nom" contient la valeur "drapeau".
De façon générale, dire que la « condition de sélection est vraie pour une fiche » veut dire que, si l'on remplace le nom de champ (dans la condition de sélection) par la valeur de ce champ dans la fiche, on obtient une expression vraie. Par exemple, pour la fiche 1 de "Personnes3", l'expression obtenue en remplaçant le nom de champ par la valeur de ce champ est:
"Tremblay" = "drapeau"
qui est évidemment fausse. C'est pour cette raison que la fiche 1 ne fait pas partie du résultat. Pour la fiche 2, cependant, on obtient l'expression:
"Drapeau" = "drapeau"
qui est vraie; c'est pour cela que la fiche 2 fait partie du résultat retourné. Et ainsi de suite. Toutes les fiches de la table mentionnée dans la requête sont passées, l'une après l'autre, et celles pour lesquelles la condition de sélection est vraie sont incluses dans le résultat.
On voit donc que le contenu du résultat d'un SELECT restreint est toujours un sous-ensemble des fiches de la table mentionnée dans la requête. Il pourrait arriver que ce sous-ensemble soit vide (si la condition de sélection n'est vraie pour aucune fiche), ce qui ne constituerait aucunement une erreur. Il se pourrait aussi que le résultat contienne toutes les fiches de la table mentionnée (si la condition de sélection est vraie pour toutes les fiches), ce qui ne serait pas non plus une erreur.
Testons notre compréhension sur d'autres SELECT restreints. Par exemple:
SELECT * FROM Personnes3 WHERE Nom <> "drapeau" ;
donnera:
Numéro | Prénom | Nom | Téléphone | Organisation |
---|---|---|---|---|
integer, |
text(30), |
text(50), |
text(8), |
integer |
1 | Jeanne | Tremblay | 555-1212 | 1 |
3 | Bernard | Larue | 555-1214 | 1 |
Et la requête:
SELECT * FROM Personnes3 WHERE Prénom > "jeanne" ;
donnera:
Numéro | Prénom | Nom | Téléphone | Organisation |
---|---|---|---|---|
integer, |
text(30), |
text(50), |
text(8), |
integer |
2 | Roger | Drapeau | 2 | |
4 | Sylvie | Drapeau |
La constante présente dans un SELECT restreint doit être du même type que le champ mentionné. De quoi a l'air une constante de type "integer", "date" ou "logical"? Sans surprise, la réponse réside dans la transformation de saisie du type en question, assaisonnée d'un soupçon de conventions d'écriture:
Les comparaisons de valeurs entières (de type "integer") suivent les règles habituelles de l'arithmétique; par exemple, 3 >= 1 est vraie. Les comparaisons de valeurs "date" correspondent à l'ordre chronologique; par exemple, #2011-12-31# < #2012-01-01# est vraie.
Ce nouveau vocabulaire peut être mis en pratique dans des SELECT restreints. Par exemple:
SELECT * FROM Personnes3 WHERE Numéro >= 3 ;
donne:
Numéro | Prénom | Nom | Téléphone | Organisation |
---|---|---|---|---|
integer, |
text(30), |
text(50), |
text(8), |
integer |
3 | Bernard | Larue | 555-1214 | 1 |
4 | Sylvie | Drapeau |
Et la requête:
SELECT * FROM Personnes3 WHERE Organisation = 1 ;
donne:
Numéro | Prénom | Nom | Téléphone | Organisation |
---|---|---|---|---|
integer, |
text(30), |
text(50), |
text(8), |
integer |
1 | Jeanne | Tremblay | 555-1212 | 1 |
3 | Bernard | Larue | 555-1214 | 1 |
La valeur NULL se comporte d'une manière particulière dans les comparaisons. En l'occurrence, une comparaison avec la valeur NULL n'est jamais vraie, quels que soient l'opérateur de comparaison et la valeur comparée! Plus précisément, une expression de la forme:
NULL opérateur-de-comparaison constante
n'est jamais vraie. (Techniquement, l'expression retourne la valeur NULL.) Ainsi, aucune des expressions suivantes n'est vraie:
Ceci amène parfois des résultats à première vue surprenants. Par exemple, nous venons juste de voir que:
SELECT * FROM Personnes3 WHERE Organisation = 1 ;
donne le résultat:
Numéro | Prénom | Nom | Téléphone | Organisation |
---|---|---|---|---|
integer, |
text(30), |
text(50), |
text(8), |
integer |
1 | Jeanne | Tremblay | 555-1212 | 1 |
3 | Bernard | Larue | 555-1214 | 1 |
On pourrait penser qu'en inversant l'opérateur de comparaison, on inverse les fiches repêchées, c'est-à-dire qu'on repêche maintenant les fiches 2 et 4. Or:
SELECT * FROM Personnes3 WHERE Organisation <> 1 ;
ne repêche que la fiche 2:
Numéro | Prénom | Nom | Téléphone | Organisation |
---|---|---|---|---|
integer, |
text(30), |
text(50), |
text(8), |
integer |
2 | Roger | Drapeau | 2 |
En effet, pour la fiche 4, le remplacement du nom de champ par sa valeur dans la condition de sélection donne:
NULL <> 1
expression qui n'est pas vraie. La fiche 4 n'est donc pas repêchée.
Comme nous avons dit précédemment, les requêtes SELECT non restreintes peuvent prendre des formes très variées et offrent énormément de flexibilité et de possibilités pour l'extration et la recherche d'information dans une base de données. Les deux ressources suivantes (associées au cours BLT6306 Bases de données documentaires du programme de MSI à l'EBSI) permettront au lecteur de se familiariser avec la syntaxe et la sémantique des requêtes SELECT non restreintes:
La section 3 (Les expressions dans Access) de la ressource Exemples de requêtes SQL avec explications (base INSCRIP) introduit la notion d'expression SQL et, en particulier, de condition, qui est une expression donnant comme résultat une valeur logique (ou booléenne). Une valeur logique (ou booléenne) correspond pour nous à une valeur de type "logical". Les conditions de sélection des SELECT restreints vues ci-dessus sont un cas particulier de conditions et d'expressions SQL.
La plupart des SGBD relationnels offrent la possibilité d'associer à chaque champ une condition de validité, qui est une condition qui doit obligatoirement s'évaluer à "true" au moment d'enregistrer une valeur dans le champ (donc, à la saisie). Par exemple, si dans un champ "Durée" de type "integer" on ne voulait stocker que des nombres pairs, on pourrait associer au champ la condition de validité suivante:
(Durée mod 2) = 0
En effet, dans le langage des expressions de SQL, mod représente l'opération arithmétique "modulo", qui calcule le reste de la division d'un nombre (celui de gauche) par un autre (celui de droite). Si on essayait d'inscrire 27 dans ce champ, la condition de validité s'évaluerait à "false", puisque le reste de la division de 27 par 2 est 1, et la fiche serait rejetée (non enregistrée dans la table).
La condition de validité associée à un champ est vérifiée après les vérifications associées au type et au masque de saisie. Elle constitue un mécanisme de validation de plus à la disposition du concepteur d'une base de données relationnelle.
Nous n'en dirons pas plus sur les conditions de validité; nous voulions simplement en mentionner l'existence. En particulier, nous ne prendrons pas la peine d'adopter une convention particulière pour l'inscription d'une convention de validité dans la définition d'une table relationnelle.
Certains SGBD permettent de spécifier une condition de validité globale associée à une table au complet (et non à un champ précis). Ce genre de conditions de validité permet, par exemple, de s'assurer que la valeur inscrite dans un certain champ (par exemple, une date de fin) est supérieure à celle inscrite dans un autre champ (par exemple, une date de début). La condition de validité globale associée à une table est évaluée après les conditions de validité spécifiques à chaque champ, au moment de l'inscription d'une nouvelle fiche dans un table. Si la condition de validité globale n'est pas satisfaite (c'est-à-dire si elle ne s'évalue pas à "true"), alors la fiche est rejetée (non enregistrée dans la table).