Le langage SQL, la synthèse

Frédéric BROUARD

Blog :
https://www.developpez.net/forums/blogs/3170-sqlpro/
Le site :
https://sqlpro.developpez.com

Pour réagir au contenu de ce livre, un espace de dialogue vous est proposé sur le forum. Commentez

- Titre : Le langage SQL, la synthèse

- Auteur : Frédéric BROUARD

- Parution : 8 février 2024

- Mise à jour : 18 septembre 2024

- Public visé :

- Licence : Copyright © 2019 - Frédéric BROUARD. 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

Sommaire

- I. Chapitre 1 - Les bases de données et le langage SQL

- I-1. Historique des systèmes de gestion de bases de données (SGBD)

- I-2. Évolution des SGBD – années 80-90

- I-2-1. SGBD relationnels

- I-2-2. SGBD objet

- I-2-3. SGBD objet-relationnel

- I-2-4. SGBD NoSQL

- I-3. Le langage SQL

- I-3-1. Un langage de requête

- I-3-2. Limites du langage SQL

- I-3-3. Les normes SQL

- I-3-4. Structure du langage

- I-3-4-1. SQL déclaratif

- I-3-4-2. SQL procédural

- I-3-4-3. Notation des syntaxes du langage

- I-4. Du monde réel à SQL : la modélisation des données

- I-4-1. Le modèle conceptuel (ou MCD)

- I-4-2. Du modèle Entité-Association à UML

- I-4-2-1. E-R et UML

- I-4-2-2. Formalismes graphiques

- I-4-3. Du conceptuel aux relations : le modèle logique de données (ou MLD)

- I-4-4. Du relationnel aux tables : le modèle physique de données (ou MPD)

- I-5. algèbre relationnelle

- I-5-1. RESTRICTION (symbole σ )

- I-5-2. PROJECTION (symbole Π)

- I-5-3. UNION (symbole U)

- I-5-4. DIFFERENCE (symbole –)

- I-5-5. INTERSECTION (symbole ∩)

- I-5-6. PRODUIT CARTÉSIEN (symbole x)

- I-5-7. DIVISION (symbole ÷)

- I-5-8. JOINTURE (symbole ⋈ )

- I-6. Le modèle externe de données (ou MED)

- I-7. La syntaxe

- I-8. Organisation interne d’un SGBD Relationnel

- I-8-1. La notion de base de données ou CATALOG

- I-8-2. La notion de schéma SQL

- I-9. Importance des bases de données

- I-9-1. Valeur d’une base de données

- I-9-2. Intelligence d’une base

- I-10. Références pour ce chapitre

- II. Chapitre 2 - Définition des données : types, domaines, valeurs et NULL

- II-1. Types SQL

- II-1-1. Types chaîne de caractères

- II-1-1-1. ASCII

- II-1-1-2. EBCDIC

- II-1-1-3. UNICODE

- II-1-1-4. UTF-8

- II-1-1-5. UTF-16

- II-1-1-6. UCS-2

- II-1-2. Types temporels

- II-1-3. Types numériques

- II-1-3-1. Types entiers (exacts)

- II-1-3-2. Types réels

- II-1-3-2-1. Types flottants (approchés)

- II-1-3-2-2. Types décimaux (exacts)

- II-1-4. Types binaires

- II-1-5. Les types « larges » (LOB)

- II-2. Problématique d'expression des littéraux

- II-2-1. Encodage et classement

- II-2-2. Collations

- II-2-3. La solution SQL

- II-2-4. La solution des éditeurs

- II-2-5. Remarque sur la complétion des littéraux

- II-3. Expression des valeurs de données

- II-3-1. Expression des littéraux

- II-3-2. Expression des nombres

- II-3-3. Expression des chaînes binaires

- II-3-4. Expression des données temporelles

- II-3-5. Typage explicite avec la fonction CAST

- II-4. Domaines (DOMAIN) et autoincréments

- II-4-1. Syntaxe SQL de création d’un domaine

- II-4-2. Syntaxe SQL de création des contraintes de domaine

- II-4-3. Les incréments ou décréments automatiques

- II-5. Les apports de typages de SQL:1999

- II-5-1. Les tableaux (ARRAY)

- II-5-2. Ligne (ROW)

- II-5-3. Le type MULTISET (collection)

- II-5-4. Héritage de table

- II-5-5. Les types « utilisateur » ou UDT (User Data Type)

- II-5-5-1. Types DISTINCT

- II-5-5-2. Type hérité

- II-5-5-3. Types abstraits

- II-5-6. Type TABLE ou table objet

- II-5-7. Type référence

- II-5-8. Pointeur (LOCATOR)

- II-5-9. Datalink

- II-5-10. Pour conclure sur les types « objet »

- II-6. Marqueur NULL

- II-7. Commentaire dans les requêtes

- II-7-1. Commentaire en ligne

- II-7-2. Commentaire en bloc

- II-8. Le « point-virgule » comme terminaison d’une commande SQL

- II-9. Références pour ce chapitre

- III. Chapitre 3 - Création des objets :schémas, tables, vues, assertions

- III-1. Règles de nommage

- III-2. Création d'une base : connexion, session, catalogue et schéma…

- III-2-1. La connexion

- III-2-2. La session

- III-2-3. Catalogues et schémas

- III-2-3-1. Le CATALOG, ou la base de données

- III-2-3-2. Le SCHEMA, ou module d'une base de données

- III-2-4. La notion de propriétaire

- III-3. Créer des tables

- III-3-1. Contraintes de colonne

- III-3-2. Contraintes de table

- III-3-2-1. Contraintes validant les valeurs des colonnes

- III-3-2-1-1. Obligation de valeur ([NOT] NULL)

- III-3-2-1-1-I. Valeur par défaut d'une colonne (DEFAULT)

- III-3-2-1-2. Contrainte de validation (CHECK)

- III-3-2-2. Contrainte de clé

- III-3-2-2-1. Contrainte de clé primaire (PRIMARY KEY)

- III-3-2-2-2. Contrainte de clé subrogée (ou alternative – UNIQUE)

- III-3-2-2-3. Contrainte de clé étrangère (FOREIGN KEY)

- III-4. Gestion de l'intégrité référentielle

- III-4-1. Mode de validation de la référence, clause MATCH

- III-4-2. Mode de gestion de l'intégrité, clauses ON UPDATE / ON DELETE

- III-5. Les assertions

- III-6. La déférabilité des contraintes

- III-7. En guise de conclusion sur les contraintes

- III-7-1. Limites de la contrainte CHECK

- III-7-2. Désintérêt de la notion d’ASSERTION

- III-7-3. La dangereuse Déferrabilité des contraintes

- III-7-4. Optimisation par les contraintes

- III-7-5. Indexation des contraintes

- III-8. Les vues

- III-8-1. Mise à jour des données à travers les vues

- III-8-1-1. Mise à jour directe dans une vue mono-tabulaire

- III-8-1-2. Mise à jour directe dans une vue multi-tabulaire

- III-8-1-3. Mise à jour d’une vue calculée

- III-8-2. Restriction de mise à jour des vues modifiables

- III-8-3. De l’intérêt des vues

- III-8-3-1. Simplification de la présentation des données

- III-8-3-2. Facilitation de la gestion de la sécurité

- III-8-3-3. Abstraction des tables

- III-8-3-4. Simulation du comportement objet (CRUD)

- III-9. Colonne calculées et auto incréments

- III-9-1. Colonnes calculées

- III-9-2. Auto incréments

- III-9-2-1. Propriété IDENTITY

- III-9-2-2. Objet SEQUENCE

- III-10. Modification et suppression des objets

- III-10-1. Suppression des objets

- III-10-2. Modifier une base de données ou la supprimer

- III-10-3. Modifier un schéma SQL

- III-10-4. Modifier un DOMAIN

- III-10-5. Modifier une séquence ou un IDENTITY

- III-10-6. Supprimer une table ou en modifier la structure

- III-10-7. modifier une ASSERTION

- III-10-8. Renommer un objet

- III-10-9. Création ou modification

- III-10-10. Suppression si présence

- III-11. Création d'une table à la volée

- III-11-1. Création d'une table basée sur une autre table

- III-11-2. Création d'une table basée sur une requête

- IV. Chapitre 4 - Recherche de l'information dans une table

- V. Chapitre 5 - Les jointures

- VI. Chapitre 6 - Opérations d’ensembles

- VII. Chapitre 7 -

- VIII. Chapitre 8 - Mise à jour (INSERT, UPDATE, DELETE, MERGE, TRUNCATE)

- IX. Chapitre 9 - Fonction utilisateur

- X. Chapitre 10 - Exploitation des vues

- XI. Chapitre 11 – Transactions

- XII. Chapitre 12 – Déclencheurs

- XIII. Chapitre 13 – Procédures

- XIV. Chapitre 14 - : XML

- XV. Chapitre 15 – JSON

- XVI. Chapitre 16 - Système d’information géographique

- XVII. Chapitre 17 - Tables temporelles

- XVIII. Chapitre 18 - table de graphe

- XIX. Chapitre 19 - Tables « in memory »

- XX. Chapitre 20 - Médonnées (information_schema et sys)

- XXI. Chapitre 21 - La sécuritité (roles et privilèges)

- XXII. Chapitre 22 - Sauvegardes et restaurations

- XXIII. Chapitre 23 – L’indexation

- XXIV. Chapitre 24 - Maintenance basique

- XXV. Remerciements Developpez.com

I - Chapitre 1 - Les bases de données et le langage SQL

Le langage SQL est le fruit d'années de réflexion sur la problématique de manipulation des données. Formalisé et normalisé, ce langage est né dans les années 80 et a vite été adopté par la majorité des éditeurs.

Grâce à un comité de normalisation dynamique, il s’est constamment adapté aux besoins des utilisateurs. Ainsi, après la norme SQL 2 de 1992 formant le cœur du langage, on trouve des versions majeures et mineures en 1999, 2003, 2006, 2008, 2011, 2016, 2019 et 2023 !

SQL repose sur deux principes fondamentaux : l'algèbre relationnelle (une branche des mathématiques conçue pour traiter des données de manière ensembliste) et la modélisation des données à base d'entités et de relations qui elle-même repose sur des concepts mathématiques (théorèmes de Heath et Fagin-Date, axiomes d’Armstrong…) traduits en un formalisme pratique (formes normales).

Ce sont les sujets que nous allons aborder dans ce chapitre et nous terminerons en présentant la base de données qui nous servira de fil rouge pour la majorité des exemples du livre et certains exercices.

I-1 - Historique des systèmes de gestion de bases de données (SGBD)

Comme beaucoup de technologies de l’informatique aujourd'hui matures, les bases de données relationnelles sont nées des travaux d’IBM entre les années 1960 et 1970. De nombreux travaux de recherche ont été menés au cours de cette période avec les modèles de données hiérarchiques, réseaux et relationnels. Finalement le relationnel a été unanimement adopté par la suite et programmé à l’aide du langage SQL (Structured Query Language).

Le succès du langage SQL et sa longévité sont liés au fait qu’il repose sur une théorie mathématique et un langage complet au sens de Turing, à la différence de tous les autres systèmes de bases de données. Il est donc parfaitement adapté au traitement des données dans un contexte ou ces données sont fortement structurées, comme c’est le cas de l’informatique de gestion (ressources, humaines, comptabilité, relation client, ventes, gestion des stocks, etc.).

La théorie sur laquelle repose SQL a été énoncée par le professeur Edgar Frank Codd (1924-2003), un mathématicien d’Oxford, alors qu’il travaillait en tant que chercheur pour le compte d’IBM au laboratoire de San Jose. L’article « A relational model of data for large shared data banks », publié en juin 1970 dans la revue Association for Computing Machinery, fait toujours office de référence en la matière et a inspiré tous les travaux qui ont suivi et qui perdurent.

Figure 1.1 – Article original de 1970 de Codd sur le relationnel

Codd voulait créer un système où l’interrogation des données devait utiliser le vocabulaire anglais. Après le voyage sur la lune, les travaux de la NASA sur la base de données modélisant les rochers rapportés ont aidé au développement de ce langage en lui donnant de la crédibilité.

Dès 1974, IBM entamait le prototype de bases de données relationnelles appelé System/R. Ce projet se terminait en 1979 prouvant ainsi la viabilité d’un tel système. Le langage de programmation utilisé par IBM pour System/R fut appelé SEQUEL (Structured English Query Language) rebaptisé par la suite SQL (Structured Query Language) car le nom SEQUEL était une marque de l’avionneur Hawkker-Siddeley, mais la prononciation de SQL est restée celle du SEQUEL (entendre en français si-cou-elle ). Comme tout langage, SQL a eu ses concurrents, le plus connu fut QUEL du SGBD Ingres du début des années 1980. Il y eut aussi SQUARE, ISBL, QBE, …

Bien qu’étant le précurseur de SQL, IBM ne fut pas le premier éditeur de SGBD. Ce privilège revint à Honeywell Information Systems, qui commercialisa un produit en juin 1976 tombé depuis en désuétude. À cette même période, un groupe d’ingénieurs attentifs aux résultats du projet System/R réalisèrent le premier produit commercial vraiment opérationnel au sein de la société Relational Software. En 1979, ils appelèrent leur système Oracle Version 2, premier d’une longue lignée, la version 1 n’ayant jamais existé (question de marketing…). Concurremment System R évolua vers le produit commercial SQL/DS qui devint DB2, nom toujours utilisé pour désigner le SGBDR d’IBM.

Microsoft n’est entrée qu’en 1994 dans ce marché, après un accord de partage du code du serveur de données de Sybase. Le contrat consistait pour Microsoft à porter ce SGBDR sous Windows et à en rétrocéder le code. À l’époque, ce SGBDR était le plus avancé : Sybase ayant inventé, avec SQL Server, le client/serveur, les déclencheurs, les procédures stockées, l’optimisation basée sur les coûts et le cache des plans d’exécution. … Rien que cela ! Mais Sybase ayant plus de confiance dans l’imposant IBM, entrepris une couteuse refonte de son produit pour le porter dans le système d’exploitation OS/2, en partenariat avec Ashton-Tate… En 1993, Sybase finit par vendre le code à Microsoft et les deux produits devinrent indépendants. Pour ne pas faire de confusion avec Microsoft SQL Server, Sybase rebaptisa son SGBDR ASE (Adaptive Server Enterprise).

Aujourd’hui IBM avec DB2, Oracle avec Oracle Database et Microsoft avec SQL-Server se partagent majoritairement le marché des SGBDR d’entreprise. Tandis que quelques outsiders du libre se sont implantés à partir de la fin des années 90.

I-2 - Évolution des SGBD – années 80-90

Au cours des vingt années qui vont de 1980 à 2000, l'évolution des SGBD s'est effectuée en trois étapes :

- SGBD relationnels mettant en oeuvre les théories de Codd (INGRES par exemple) ;

- SGBD orientés objet afin de coller à la conception et la programmation objet (O² par exemple) ;

- SGBD relationnels objet (Oracle, IBM DB2, MS SQL Server par exemple).

Ces derniers sont aujourd'hui les plus répandus, car ils constituent une approche mixte – comme les langages C++ ou java – aujourd'hui reconnue et entérinée par la norme SQL, car proposant une évolution souple vers l'objet, en conservant les avantages et la simplicité de l'approche relationnelle.

I-2-1 - SGBD relationnels

Le succès des SGBDR repose sur :

- le modèle de données relationnel qui s’appuie sur une théorie rigoureuse (théorie des bases de données et algèbre relationnelle) avec des principes simples ;

- l’indépendance entre données et traitements qui améliore la maintenance des programmes d’application (la modification d’une structure de données a peu de répercussions en théorie sur les programmes) ;

- l’indépendance vis-à-vis des systèmes d’exploitation et des couches basses réseaux (les données de la base sont indépendantes de ces derniers) ;

- la gestion des privilèges mixant éléments de la base de données et actions (ordre SQL) pour une sécurité maximale ;

- des systèmes bien adaptés aux grandes applications informatiques de gestion et qui ont acquis une maturité sur le plan de la fiabilité et des performances (évolution d’échelle – "scalabilité") ;

- Un langage SQL puissant et concis ; il peut souvent s’interfacer avec des langages de troisième génération (C, Ada, Cobol), mais aussi avec des langages plus récents (C++, Java, C#) ;

- des systèmes répondant parfaitement à des architectures de type client-serveur (passerelles ODBC et JDBC notamment) et Intranet ou Internet (configurations à plusieurs tiers).

Les limitations de la majorité des systèmes actuels sont les suivantes :

- la simplicité du modèle de données et le fait que le langage SQL soit natif et déclaratif nécessitent d’interfacer le SGBDR avec un langage de programmation évolué. De ce fait, le dialogue entre la base et le langage n’est plus direct et implique de maîtriser plusieurs technologies ;

- la normalisation conduit à l’accroissement du nombre de relations. Ainsi, si deux objets doivent être liés en mémoire, il faut simuler ce lien au niveau de la base par un mécanisme de clefs étrangères ou de tables de corrélation. Parcourir un lien implique souvent une jointure dans la base. Il en résulte un problème de performance dès que le style d’interrogation devient navigationnel : manipulation d’arbres, de graphes ou toute autre application mettant en relation un grand nombre d’objets ;

- la faible capacité de modélisation : seules les structures de données tabulaires sont permises. Il est ainsi difficile de représenter directement des objets complexes.

I-2-2 - SGBD objet

Gemstone a été le premier SGBD objet, dérivé du langage Smalltalk. Des produits commerciaux existent, citons db4o, Objectivity, ObjectStore, Orient, Ozone, FastObjects, Versant (produit issu du système O^2). Ces systèmes permettent de manipuler des objets persistants. Ils concernent toujours un segment très limité du marché des SGBD.

Parallèlement à ces initiatives individuelles, l’ODMG (Object Database Management Group) propose une API objet standard s’adaptant à tout SGBD par passerelles C++, Java et Smalltalk. En 1998, les compagnies qui soutenaient l’action de l’ODMG étaient Computer Associates, ObjectDesign, Versant, Poet, Objectivity, Ardent Software et Objectmatter. L’ODMG a soumis à la communauté Java la partie « Java Binding » pour définir la spécification JDO (Java Data Objects).

La technologie des bases de données objet n’a pas tenu ses promesses. En particulier il s’est avéré impossible d’indexer n’importe quel type d’objet, rendant les SGBD Orientés Objet, rapidement obsolètes tandis que les acteurs traditionnels comme Oracle, Microsoft ou IBM, rajoutaient une couche objet à leur SGBDR les rendant SGBDRO. Oracle avec java, SQL Server avec .net et IBM de façon purement normative.

I-2-3 - SGBD objet-relationnel

La technologie objet-relationnelle est apparue en 1992 avec les SGBD UNISQL, Open ODB d’Hewlett-Packard (appelé par la suite Odapter). En 1993, la firme Montage Systems (devenue Illustra) achète la première version commerciale du système Postgres.

À la fin de l’année 1996, Informix adopte la technologie objet avec l’achat du SGBD d’Illustra. La stratégie d’Informix repose sur la spécialisation exclusive du SGBD. Il se différencie d’autres éditeurs comme Oracle qui propose, outre son serveur de données, une offre que certains jugent disparate (outils de messagerie, AGL, etc.). En juillet 1995, IBM inclut des aspects objets dans DB2 puis rachète en 2001 Informix. Oracle 8 propose en juin 1997 des aspects objet, mais les premières versions (avant la 8.1.7) étaient bien limitées en termes de fonctionnalités au niveau des méthodes et l’héritage n’était pas supporté.

Microsoft SQL-Server n’offre pas de fonctions objet au niveau du langage SQL qu’il propose. En revanche, il permet d'intégrer des objets et méthodes à SQL grâce au langage C#, par l'intermédiaire d'un "run time" (CLR). Computer Associates propose à son catalogue le produit Jasmine (fruit des travaux menés depuis 1996 avec Fujitsu). Le système PostgreSQL est un autre dérivé du SGBD objet Postgres développé en 1986 à l’université de Berkeley par Michael Stonebraker et Eugene Wong, les concepteurs d’Ingres. PostgreSQL est aujourd’hui un SGBD libre et open source, tandis que MySQL, autre SGBDR pseudo libre, est fourni avec la majorité des distributions Linux. Il est à noter que SAP DB (SGBD open source issu du logiciel Adabas) propose des extensions objet. D’autres produits commerciaux existent, citons UniSQL, Matisse, ObjectSpark.

Le succès de cette approche provient de :

- l’encapsulation des données des tables. Les méthodes définies sur les types composant les tables permettent de programmer explicitement l’encapsulation (il faudra en même temps que le programmeur interdise les accès directs aux objets par SQL) ;

- la préservation des acquis des systèmes relationnels (indépendance données/traitements), fiabilité et performances, compatibilité ascendante : l’utilisation de tables relationnelles est possible à travers des vues objet et leur mise à jour à travers des procédures stockées ;

- l’enrichissement du langage SQL par des extensions qui sont désormais normalisées (SQL:1999) ;

- la mise en œuvre des concepts objet (classes, héritage, méthodes) qui ont indéniablement démontré leurs apports dans la maintenance des applications (modularité extensibilité et réutilisabilité).

Les risques qu’encourent les programmeurs à miser tout sur cette façon de programmer les données, mais aussi les traitements sont les suivants :

- le modèle de données ne repose plus sur une théorie rigoureuse et sur des principes simples. Il s’affranchit de la première forme normale par exemple. De ce fait, la conception peut induire plus facilement des bases de données contenant des redondances, synonymes de problèmes potentiels d’intégrité des données, mais aussi de performances dégradées ;

- à l'exception de IBM DB2 qui est fortement calé sur la partie objet de la norme SQL:1999, les différents éditeurs n’ont pas adopté une syntaxe commune pour décrire les extensions proposées, d'autant que la norme autorise la coexistence de langages externes pour l'utilisation d'objets dans SQL. En conséquence, la migration d’une base objet-relationnelle d’un SGBD vers une base d’un autre SGBD est un travail très difficile ;

- le fait de migrer une base relationnelle vers l’objet pourra se faire en douceur en utilisant les vues objet. Par contre, le retour en arrière sera bien plus périlleux.

Notons enfin que la théorie de la normalisation n’a pas évolué pour intégrer les concepts objet. Aujourd’hui, la plupart des bases de données objet ont été rangées dans le placard des modes obsolètes de l’informatique, résultat de performances poussives et d’une forte complexité de développement. Cette voie de garage a néanmoins permis de dégager l’horizon pour des outils plus adaptés tels que les FrameWorks ou les ORMs(1), censés régler le pont entre le monde de la programmation orientée objet et l’univers des bases de données relationnelles.

I-2-4 - SGBD NoSQL

De tout temps, compte tenu de leur hégémonie justifiée par leurs performances, la facilité d’exploitation et la qualité des produits, les SGBDR ont été attaqués. Dès leur naissance, les éditeurs d’anciens produits non relationnels tentent de s’immiscer dans le monde du relationnel à l’aide du marketing, mais sans les fonctionnalités. Telle sera par exemple la bataille au milieu des années 80 entre Franck Edgar Codd et ses compères – inventeurs du relationnel – d’un côté, et John Cullinam, sa firme « Cullinet » et son produit IDMS, lorsqu’il tente d’introduire la version « R » de son système de bases de données. Mais rien n’est relationnel là-dedans. Et ce n’est pas en collant un R sur l’étiquette qu’il le devient ! Notons qu’aujourd’hui, la bataille se situe plus sur l’acronyme SQL… Certaines bases de données « libres », mais surtout légères, comme PostGreSQL ou MySQL prétendre être relationnelles, mais ne le sont véritablement qu’à un degré bien moindre que leurs ainés (IBM DB2, Oracle Database ou Microsoft SQL Server).

Au cours de la vie des bases de données relationnelles dont la théorie remonte à 1970 et la pratique à 1979, beaucoup d’autres modèles ont vu le jour et se sont rapidement éclipsés. Tel est le cas des bases de données multivaluées comme Pick System, des bases XML comme Tamino d’AG Software ou encore des moteurs d’inférence des systèmes experts, premières tentatives de faire de l’IA (Intelligence Artificielle) avec des masses de données. L’un de ces modèles a quelque peu résisté, il s’agit des bases de données analytiques (OLAP : On Line Analytical Process) utilisées par la BI (Business Intelligence), mais dont la tendance, dans les années à venir, serait la disparition, au profit de l’analytique temps réel qui suppose d’attaquer directement les bases de données relationnelles du point de vue analytique en utilisant des techniques de stockage et d’indexation particulières dont on accède plutôt sur des réplicas que de manière directe, ceci afin de répartir la charge.

Un mouvement intéressant, mais non dénué de vénalité, nait avec les géants du Web. Voyant les coûts exorbitants des licences pour se fournir en systèmes de bases de données auprès des grands éditeurs, les FaceBook, Twitter, Google et autres mastodontes (Amazon, eBay, Instagram…) décidèrent au début des années 2000 d’investiguer le champ de bases plus légères, c’est-à-dire dénuées de certaines des fonctionnalités propres aux systèmes relationnels en se fondant sur un mensonge au sujet du théorème CAP de Brewer(2). Elles reprennent ainsi d’anciens principes en vigueur dans les bases de données d’antan et proposent notamment que le principe des transactions soit exclu de leur fonctionnement. C’est le mouvement NoSQL, un mouvement de contestation qui simplement veut la mort des bases de données relationnelles en scandant l’abandon du langage phare des SGBDR, le SQL !

Naissent alors des bases de données exploitant différents segments techniques (big table, document, graphe…). L’apogée du discours de bataille culmine en 2009 à San Francisco où, lors du premier sommet sur le sujet, les tenants du NoSQL racontent « comment ils ont renversé la tyrannie des coûteux et lents SGBD relationnels par des moyens plus simples et plus rapides de manipuler des données ». Jon Travis, l’un des présentateurs de la conférence, ira même jusqu’à dire « les SGBD relationnels en font trop, alors que les produits NoSQL font exactement ce dont vous avez besoin ».

Petit à petit, ces produits censés combattre et voulant tuer le relationnel, ont tous trouvé leurs limites et sont cantonnés dans des marchés de niche avec une intégration technique verticale. Le discours haineux envers les SGBDR s’amoindrit, et l’on ne parle plus d’affronter de face l’ancien ennemi, mais plutôt de le compléter. Le slogan NoSQL qui signifiait qu’on ne voulait plus du SQL est aboli et devient NotOnly SQL, abrégé en NOSQL !

Plus récemment, un grand nombre de ces systèmes de bases de données, cherchant un langage plus moderne, se confronte aux problématiques d’expression et retrouve une forme tellement semblable au SQL, qu’on en vient à réintégrer ce langage en le nommant New SQL !

Bref, le pétard NoSQL censé tuer les SGBD Relationnels a fait long feu… Un grand nombre de projets d’entreprise ont souffert de cette mode et l’on constate un retour vers un usage plus rationnel des différentes offres de SGBD. Du NoSQL, il ne reste que quelques acteurs, autrefois gratuits, mais maintenant de plus en plus chers, dans différentes niches dont les principales sont des bases dont les tables sont orientées :

paires clef/valeur en mémoire

document

graphe

série chronologique (time series)

larges colonnes (wide columns)

verticales (columnar)

Bien entendu le monde des grands éditeurs, constatant le succès modeste et relatif de ces petites niches, n’a pas attendu pour les intégrer au sein des SGBDR. On peut par exemple citer le SGBDR Microsoft SQL Server qui aujourd’hui intègre toutes ces technologies dans toutes ses éditions, y compris les versions gratuites !

Figure 1.2 – Distance de « popularité » entre les SGBDR et le NoSQL (source DB-Engines Ranking)

La figure 1.2 montre qu’en 2023, sur le plan de la popularité, les SGBD relationnels ont un écart d’un facteur 3 avec le plus populaire des SGBD NoSQL. Mais popularité ne veut pas dire utilisation, et les SGBD Relationnels restent très majoritaires en nombre d’installations et plus encore en volume de données traitées…

I-3 - Le langage SQL

SQL signifie Langage de Requêtes Structurées. À priori, cet acronyme ne dit plus grand-chose. Mais le mot le plus important est « requête » dont le synonyme est « demande ». Du point de vue mathématique, le langage SQL est considéré comme complet au sens de Turing, puisqu’il offre la possibilité de requêtes récursives, introduites par la norme SQL:1999.

I-3-1 - Un langage de requête

Contrairement aux langages de développement d’applications, SQL ne permet pas d’indiquer à la machine les différentes étapes à exécuter séquentiellement, mais plus simplement le résultat à obtenir. L’intelligence du SGBDR qui exécute la requête doit établir préalablement la décomposition et l’ordonnancement des différentes opérations nécessaires à l’obtention du résultat.

Le SGBDR a donc toute latitude pour choisir et composer son plan d’exécution de la requête, c’est-à-dire un arbre algébrique présentant toutes les étapes de réalisation du traitement (sous forme littérale ou graphique), que le moteur élabore en utilisant son « optimiseur », qui est un module d’analyse des différentes hypothèses de construction de la solution, en évaluant chaque variante pour finalement choisir celle qu’il estime être optimale.

Figure 1.3 – Plan d’exécution de requête choisi par l’optimiseur (Sentry SQL)

Pour mettre en évidence ce phénomène, Christopher Date, compagnon de route de Franck Edgar Codd, mit au point le test suivant (dans un article intitulé « A Cure for Madness »)(3) :

Exemple 1.1 – test « d’intelligence » de l’optimiseur d’un SGBDR

Avec la table T_DATA et les données suivantes :

1. CREATE TABLE T_DATA

2. (TYP   CHAR(4),

3. VAL   VARCHAR(16));

4. INSERT INTO T_DATA VALUES ('ALFA', 'abc'), ('ALFA', 'zzz'),

5. ('ALFA', 'F. E. Codd'), ('ALFA', 'Chris Date'),('NUM', '123'), ('NUM', '999'),

6. ('NUM', '1789'), ('NUM', '32765');

La requête :

SELECT *
FROM   T_DATA
WHERE  VAL = 1000;

doit renvoyer une exception avec un message d’erreur indiquant que la conversion d’une valeur de type VARCHAR de la colonne VAL en type de données INT a échoué… Cette conversion implicite est normale. L’utilisateur exige une comparaison numérique (le nombre 1000) et non littérale (cela aurait été '1000').

En revanche, aucune des requêtes suivantes ne doit échouer :

1. SELECT *

2. FROM   T_DATA

3. WHERE  TYP = 'NUM'

4. AND  VAL = 1000;

6. SELECT *

7. FROM   (SELECT *

8. FROM   T_DATA

9. WHERE  TYP = 'NUM') AS TABLE_DERIVEE

10. WHERE  VAL = 1000;

1. SELECT *

2. FROM   T_DATA

3. WHERE  VAL = 1000

4. AND  TYP = 'NUM';

6. SELECT *

7. FROM   (SELECT *

8. FROM   T_DATA

9. WHERE VAL = 1000) AS TABLE_DERIVEE

10. WHERE  TYP = 'NUM';

Alors qu’initialement, la requête interne de cette dernière sous requête (en bas à droite) est celle qui a causé notre erreur initiale, le SGBDR ne remonte plus d’erreur… Cela est dû au fait que toutes ces requêtes ne font qu’une ! En effet nous posons toujours la même question au système, et ce n’est pas parce que nous l’écrivons de plusieurs manières différentes que cela change quoi que ce soit au plan d’exécution d’une demande sémantiquement identique(4) :

Figure 1.4 – les 4 plans d’exécution identiques des 4 différentes requêtes (Microsoft SQL Server)

Comme le montre la figure 1.4, tous les plans d’exécution des 4 versions différentes de la requête sont identiques. Il est d’ailleurs à noter qu’a chaque fois, le filtrage des données s’effectue pour les deux valeurs simultanément, ce que l’on peut voir dans le détail de l’opération intitulée « Analyse de table » que montre la figure 1.5 :

Figure 1.5 – Détail de l’opération d’analyse de table (SQL Server)

L’optimiseur, dans un SGBDR, possède le rôle crucial de déterminer, par des règles complexes, dont certaines sont tenues secrètes par les grands éditeurs (la R&D, coûte cher !), le plan d’exécution optimal de la requête, c’est-à-dire celui qui obtiendra probablement les meilleures performances. Ceci est obtenu par différents niveaux de règles :

- l’algébrisation : consiste à simplifier la requête par des règles mathématiques de type permutation, substitution… ;

- l’heuristique : élimination de certaines opérations inutiles en fonction de l’écriture de la requête ;

- sémantique : simplification de la requête en fonction de la structure des objets et notamment des contraintes ;

- statistiques : choix des algorithmes et séquencement des opérations en se fondant sur des statistiques de distribution des données, permettant de déterminer la cardinalité de chaque opération.

L’ensemble de ces règles est étudié plus ou moins profondément pour chaque requête, mais comme il se pourrait que l’établissement d’un plan optimal mette plus de temps à être élaboré que l’exécution d’un plan peu optimal (trivial), alors l’optimiseur dispose d’une limite en nombre d’actions à effectuer, ou bien d’un temps de calcul maximal, pour trouver le plan optimal.

I-3-2 - Limites du langage SQL

SQL est le langage des bases de données relationnelles répondant à la fois à la problématique de création des objets de bases de données (modèle), de manipulation des données (algèbre relationnelle), de gestion de la sécurité (« droits d'accès »), de traitements locaux de données (procédures) et désormais doté d'extensions objet.

SQL n’est pas destiné à présenter les données au niveau cosmétique, ni pour interagir avec les utilisateurs. Il doit donc être encapsulé dans les applications qui utilisent des « middleware », c’est-à-dire des bibliothèques de code offrant un pont entre le SGBDR et le code applicatif pour la transmission des requêtes et la réception des données en résultant.

Pour les requêtes de lecture, le résultat est un jeu de données, c’est-à-dire un ensemble de lignes et de colonnes, à la manière d’un tableau(5), accompagné du descriptif technique des éléments (nom et type des colonnes, cardinalité…)(6). Pour les requêtes de mise à jour ou bien celles opérant sur la structure de la base (création, modification, suppression des objets), ou encore celles gérant la sécurité, le résultat est un code en retour, signalant la réussite ou l’échec, et dans ce dernier cas, avec un code d’erreur accompagné d’un message.

Mais avant tout, SQL est une norme.

I-3-3 - Les normes SQL

Le langage SQL est normalisé depuis 1986(7). Cette norme s'est enrichie au fil du temps (publication X3.135-1986 de l’ANSI reprise par l’ISO peu de mois après : ISO 9075-1987). Cette première mouture, appelée SQL86 ou SQL1 (une centaine de pages), était le résultat de compromis entre constructeurs, mais fortement influencée par le dialecte d’IBM. En 1989, d’importantes mises à jour ont été faites en matière d’intégrité référentielle (ANSI X3.135-1989 et ISO/IEC 9075:1989).

La seconde norme (appelé SQL92 ou SQL2 d’un volume de 600 pages) a été finalisée en 1992. Elle définissait quatre niveaux de conformité : le niveau d’entrée (entry level), les niveaux intermédiaires (transitional et intermediate levels) et le niveau supérieur (full level). Les langages SQL des principaux éditeurs sont à peu près tous conformes au premier niveau et certains ont de nombreuses caractéristiques relevant des niveaux supérieurs.

Les groupes de travail X3H2 de l’ANSI et WG3 de l’ISO se sont penchés depuis 1993 sur les extensions qu’il fallait apporter à la précédente norme. Les optimistes prévoyaient SQL3 pour 1996… Rien ne s’est passé comme prévu. Il a fallu attendre 1999 pour voir établir le projet « SQL:1999 » appelé aussi SQL3 (volume de 1600 pages à l’époque). Ce retard était dû aux nombreux protagonistes (Oracle, IBM, Microsoft, Digital, Computer Associates, etc.) qui voulaient difficilement remettre en cause leur mode de pensée et risquaient de ne pouvoir assurer la compatibilité des bases de leurs clients.

Les évolutions de l’ancienne norme ne sont pas limitées qu’aux extensions objet. Bien d’autres mécanismes ont été introduits dans ce projet (géographie, temps, réel, séries temporelles, multimédia, OLAP, données et routines externes).

SQL:1999 (longtemps surnommée SQL3 avant sa formalisation) est désormais une norme (au sens International Standard de l’ISO). Cette norme apparaît dans le domaine Information technology -- Database languages –SQL. Elle porte principalement la référence ISO/IEC 9075. Les parties 1, 2 et 11 décrivent les bases du langage. Les autres parties constituent les extensions. Cet ouvrage ne traite pas des techniques OLAP, serveurs distants et fournisseurs de données, multimédia et langages objet, étant donné que ces éléments sont encore trop spécifiques à chaque éditeur. Les références aux spécifications (disponibles, mais payantes sur http://www.iso.org) sont, à ce jour, les suivantes :

SQL:1999 (ANSI / ISO / IEC)

9075-1:1999 Framework

9075-1:1999/Amd 1.2001 OLAP : On Line Analytical Process

9075-2:1999 Foundation

9075-2:1999/Amd 1.2001 OLAP : On Line Analytical Process

9075-3:1999 CLI : Call Level Interface

9075-4:1999 PSM : Persistent Stored Module

9075-5:1999 Host Language Bindings (SQL/Bindings)

9075-5:1999/Amd 1:2001 On-Line Analytical Processing (SQL/OLAP)

9075-9:2001 MED : Management of External Data

9075-10:2000 OLB : Object Langage Bindings

9075-13:2002 JRT : Java Routines and Types

9079:2000 Remote databases access for SQL with security enhancement

13249-1:2000 SQL Multimedia and application packages - Part 1 : Framework

13249-2:2000 SQL Multimedia and application packages - Part 2 : Full text

13249-3:1999 SQL Multimedia and application packages - Part 3 : Spatial

13249-5:2001 SQL Multimedia and application packages - Part 5 : Still image

13249-6:2002 SQL multimedia and application packages -- Part 6: Data mining

Peu après, une nouvelle version de SQL (SQL:2003) apportant des modifications mineures est apparue. Ses apports concernent l'auto incrément des clefs, de nouvelles fonctions d'agrégation, de rangement/ordonnancement et de calculs statistiques, les colonnes calculées, l'ordre MERGE combinant UPDATE et INSERT et surtout, un support complet de XML en particulier à l'aide de « wrapper ».

Les références de ces nouvelles normes sont les suivantes :

SQL:2003 (ANSI / ISO / IEC)

9075-1:2003 Framework (SQL/Framework)

9075-2:2003 Foundation (SQL/Foundation)

9075-3:2003 Call-Level Interface (SQL/CLI)

9075-4:2003 Persistent Stored Modules (SQL/PSM)

9075-9:2003 Management of External Data (SQL/MED)

9075-10:2003 Object Language Bindings (SQL/OLB)

9075-11:2003 Information and Definition Schemas (SQL/Schemata)

9075-13:2003 SQL Routines and Types Using the Java TM Programming Language (SQL/JRT)

9075-14:2003 XML-Related Specifications (SQL/XML)

19125-2:2004 Geographic information -- Simple feature access -- Part 2: SQL option

Les normes de 2006, 2008, 2011, 2016, 2019 et 2023 vont compléter ces éléments et en ajouter d’autres :

- amélioration de la prise en charge de XML avec SQL:2006 ;

- déclencheurs INSTEAD OF, pagination de résultat avec OFFSET FETCH dans la clause ORDER BY et commande TRUNCATE avec SQL:2008 ;

- tables temporelles et ajout de nouvelles fonctions fenêtrées avec SQL:2011 ;

- ROW PATTERN RECOGNITION, tables temporelles et le support de JSON avec SQL:2016 ;

- tableaux multidimensionnels avec SQL:2019 ;

- traitementg des NULLs dans l’unicité, fonctions GREATEST, LEAST, LPAD, RPAD, LTRIM, RTRIM, et BTRIM, fonction d’agrégation ANY_VALUE, tri externe dans un groupage avec SQL:2023 ;

Enfin, actuellement en cours d’évaluation, l’incorporation de certaines fonctionnalités du NoSQL…

Le succès que connaissent les grands éditeurs de SGBDR a plusieurs origines et repose notamment sur SQL :

- SQL peut s’interfacer avec des langages de troisième génération comme C, Ada ou Cobol, mais aussi avec des langages plus évolués comme C++, Java, C#.... Mais certains considèrent ainsi que le langage SQL n’est pas assez complet (le dialogue entre la base et l’interface n’est pas direct) et la littérature parle de « défaut d’impédance » (impedance mismatch) ;

- les SGBD rendent indépendants programmes et données (la modification d’une structure de données n’entraîne pas forcément une importante refonte des programmes d’application) ;

- ces systèmes sont bien adaptés aux grandes applications informatiques de gestion (architectures type client-serveur et Internet) et ont acquis une maturité sur le plan de la fiabilité et des performances, même avec de forts volumes (actuellement plus d’une centaine de Téra octets) ;

- ils intègrent des outils de développement comme les précompilateurs, les générateurs de code, d’états, de formulaires, de BI(8), et des outils d'administration, de réplication, de clusterisation, de sauvegarde, de surveillance ;

- ils offrent la possibilité de stocker des informations non structurées (comme du texte, du XML, du JSON… ou binaires comme du son, de l’image, de la vidéo...) et même du spatial (géométrie, géographie) dans des types de la famille des LOBs (Large OBject ).

I-3-4 - Structure du langage

SQL est un langage composé de deux blocs bien distincts, ces deux blocs faisant l’objet de diverses subdivisions.

Le premier bloc SQL est constitué de la partie déclarative du langage, c'est-à-dire d'ordres SQL que le SGBDR doit exécuter. En d'autres termes, on spécifie ce que l'on veut obtenir ou faire et c'est la machine qui décide comment elle doit l'exécuter.

Le second bloc est constitué par un langage plus classique de type procédural dans lequel on retrouve les notions de fonctions, méthodes, procédures...

I-3-4-1 - SQL déclaratif

Le bloc déclaratif de SQL est, lui-même, subdivisé en quatre parties :

- le DDL (Data Definition Language), c'est à dire les ordres SQL permettant de créer (CREATE) modifier (ALTER) ou supprimer (DROP) les objets de la base ;

- le DML (Data Manipulation Language), c'est-à-dire les ordres SQL permettant d'ajouter (INSERT) de modifier (UPDATE), de supprimer (DELETE) ou d'extraire des données ;

- le DCL (Data Control Language), c'est à dire les ordres SQL permettant de définir les privilèges afférents aux utilisateurs (GRANT, REVOKE) ;

- enfin, le TCL (Transaction Control Language) qui permet de gérer des transactions englobant des ordres des trois premières subdivisions (COMMIT, ROLLBACK…).

I-3-4-2 - SQL procédural

Ce second bloc du SQL est lui-même subdivisé en différents modules parmi lesquels les plus importants sont :

- PSM (Persistent Stored Module) : concerne les fonctions, procédures et déclencheurs (triggers) en tant qu'objets de la base (donc stockés dans la base et par conséquent persistants) ;

- CLI (Call Level Interface) : en fait des « API » destinées à piloter des objets encapsulant des données dans des langages hôtes par l'intermédiaire, la plupart du temps, d'un middleware (BDE, dbExpress de Borland, OBDC, ADO, OleDB de Microsoft, JDBC...) ;

- Embedded SQL : le lancement d'ordres SQL depuis un langage hôte et la récupération des données dans un programme grâce à l'utilisation de CURSOR.

Voici un tableau de synthèse présentant les différentes subdivisions de SQL :

TCL (Transaction Control Language)
BEGIN TRANSACTION, COMMIT TRANSACTION, ROLLBACK TRANSACTION
SET TRANSACTION ISOLATION LEVEL ...

DDL
(Data Definition Language)
CREATE, ALTER, DROP …

DML
(Data Manipulation Language)
INSERT, UPDATE, DELETE, SELECT, MERGE, TRUNCATE

DCL
(Data Control Language)
GRANT, REVOKE

SQL procedural : PSM, CLI, Embedded SQL...

I-3-4-3 - Notation des syntaxes du langage

Pour exprimer la syntaxe des commandes du langage SQL, nous avons adopté une notation basée sur la BNF (Backus Naur Form) bien connue des informaticiens. Les principes que nous avons appliqués sont les suivants :

symbole

signification

::=

défini comme suit

|

ou logique

< >

concept (nom d'objet, valeur...)

[ ]

option possible

{ }

item à choisir

Les noms des objets étant formulés en italique et les mots clefs en majuscule.

I-4 - Du monde réel à SQL : la modélisation des données

La modélisation d’un problème, c'est-à-dire le passage du monde réel à sa représentation informatique, se définit en plusieurs étapes pour parvenir à son intégration dans un SGBD et permettre la manipulation des données par le langage SQL.

Classiquement, le processus de modélisation des données passe par deux phases : la réalisation d'un modèle conceptuel et sa traduction en un modèle relationnel.

Le premier niveau de modélisation, dit conceptuel (MCD : Modèle Conceptuel de Données), consiste en une phase d’analyse du problème réel. Cette phase d’analyse est assez délicate et permet de définir les données que l’on utilisera, leur mode d’évolution dans le temps (leur vie en somme) et les affinités qu’elles auront entre elles. C’est le moment où l’on se pose les questions essentielles comme celle de savoir à quel usage on destine le modèle informatique ainsi constitué.

C’est un labeur spécifique qui, par les compétences nécessaires, est plutôt réalisé par des spécialistes de l'analyse. Il s’exprime dans un formalisme de type entité-association (avec ou sans les extensions Merise/2) ou bien avec la notation UML (diagramme de classes). Notons qu'il existe une différence entre un modèle (par exemple le modèle conceptuel de données) et un formalisme (dans lequel est décrit un modèle) : ainsi, on peut parler de la modélisation conceptuelle des données suivant le formalisme entité-association ou le formalisme UML (la notation n’exprime que l’aspect représentation).

Le second niveau conduit à élaborer l’ensemble des objets manipulables par un SGBD. C’est un métier plus proche des bases de données et du SGBD employé. En pratique, cette opération est souvent réalisée par un architecte de données, un gestionnaire ou administrateur du SGBD.

Le modèle précédent (modèle conceptuel) est traduit, à l'aide de règles simples, en un modèle relationnel, c'est à dire dans un schéma de relations qui agence les différentes structures de données entre elles (relations...) , interprétable comme base de données pour tout SGBDR. Ce processus peut être facilement automatisé.

Cette seconde phase peut elle-même être découpée en deux : la conception d'un modèle de données logique (MLD : Modèle Logique de Données), c'est à dire une représentation sous forme de relations des données, indépendamment de tout SGBD spécifique, et la traduction en un modèle physique (MPD : Modèle Physique de Données), c'est à dire propre à un SGBD particulier, tous les SGBD n'ayant pas les mêmes caractéristiques et possibilités en matière de SQL.

Enfin, le dernier niveau de modélisation est constitué par les vues externes et les objets de code facilitant la manipulation des données (fonctions utilisateurs, déclencheurs, procédures…). C’est le modèle externe (MED : Modèle Externe de Données).

I-4-1 - Le modèle conceptuel (ou MCD)

Un modèle conceptuel représente :

- les faits et les événements qui décrivent le monde à modéliser ;

- certaines contraintes (exemples : une compagnie aérienne n’affrète pas ses propres avions, un pilote ne doit voler que s’il détient une licence en cours de validité et une qualification valide pour le type d’avion en question, etc.) appelées règles de gestion ;

- l’héritage.

Il existe plusieurs techniques de modélisation conceptuelles : modèle entité-association (E-R pour Entity Relationship) à la notation américaine (qu’on doit à Peter Chen), modèle binaire NIAM (Natural Language Information Analysis Method), méthode Z...

Afin de préserver l’indépendance données/traitements, le modèle conceptuel ne doit pas comporter d’indications concernant la structure de mémorisation ou la technique d’accès. Pas question donc ici d’indiquer sur un diagramme, une quelconque information sur l’indexage, l’adressage, le partionnement ou tout autre détail concernant l’accès à la mémoire.

Le modèle conceptuel doit contenir plus d’informations qu’on pouvait trouver au début des sources COBOL lorsqu’il s’agissait de déclarer les structures de données manipulées par les programmes eux-mêmes. En effet, le concepteur devra ajouter sur les diagrammes conceptuels les règles de gestion (aussi appelées « règles de sécurité », « d’intégrité » ou « de fonctionnement »). Cela représente tout ce qu’il faudra assurer pour que la base de données reste cohérente durant toute la vie des applicatifs. L’objectif d’un modèle conceptuel ne peut pas être, comme certains l’affirment, de décrire complètement un système. Il modélise seulement l’aspect statique des données et il est souhaitable de composer des modules afin de réduire la taille des diagrammes.

Dans le monde professionnel, ces diagrammes ne sont plus manuscrits, mais saisis à l’aide d’outils graphiques informatiques. Ces mêmes outils génèrent le modèle logique de la base (le plus souvent le modèle relationnel) puis un script permettant de créer la base de données (le plus souvent un script SQL créant les différentes tables et contraintes). Dans cet ouvrage nous avons utilisé l'outil Power AMC de Sybase devenu Power Designer lors de son rachat par SAP.

La modélisation est un processus important et indispensable, car elle conditionne la structure de la base de données. Les structures de données permettant de stocker des objets complexes dans la base seront déduites des différents éléments issus du modèle conceptuel : entités (ou classes), associations et éventuellement contraintes. Le problème le plus courant que l'on rencontre au sein de bases de données mal conçues, est la redondance des données (répétition d’une information à plusieurs endroits). Cet avatar entraîne à terme des incohérences en modification, insertion et suppression.

Il n'entre pas dans le périmètre du présent ouvrage de décrire dans le détail les différentes techniques de modélisation et nous donnons dans la bibliographie les principaux ouvrages disponibles sur ce sujet. Notons néanmoins un abus de langage classique qui consiste à confondre la notion de lien avec le terme mathématique « relation », ce dernier portant sur l’objet contenant l’information, autrement dit une sorte de table, dotée de propriétés complémentaires.

I-4-2 - Du modèle Entité-Association à UML

L’idée force du modèle entité association est de représenter par un modèle standardisé les différents éléments constitutifs du système d’information, appelés attributs (exemples : nom, date de naissance) que l’on regroupe en entités (exemple : les personnes), et les règles sémantiques qui les unissent, appelées associations.

Une manière simple de modéliser est de décrire la réalité par une phrase. Le sujet et le complément représentent des entités, et le verbe, l’association.

Exemple 1.2 : deux entités et une association entre elles

Un utilisateur (entité) publie (association) une information (entité).

Les cardinalités servent à mesurer le nombre de participation d'une entité à une association. Elles donnent des informations plus dynamiques sur la nature des associations :

- un-à-un : une personne ne pourrait publier qu’une et une seule information, et une information ne pourrait être publiée que par une seule personne ;

- un-à-plusieurs : une personne pourrait oybkuer un nombre indéfini d'informations (une ou plusieurs), mais une information ne pourrait être publiée que par une seule personne

- plusieurs-à-plusieurs : une personne pourrait publier un nombre indéfini d'informations (une ou plusieurs), et une information pourrait être publiée par plusieurs personnes.

Il y a de fortes similitudes entre le modèle de données Entité-Association (E-R pour Entity Relationship en anglais) et le diagramme de classes UML. Les concepts de base de ces modèles sont analogues :

- Attribut (attribute): donnée élémentaire, également appelée « propriété », qui sert à caractériser les entités et les associations ;

- Entité ou classe (entity, class) : concept concret ou abstrait (fait, moment etc.) du monde à modéliser. Elle se représente par un cadre contenant les attributs (et méthodes pour UML) ;

- Identifiant (identify) : attribut particulier permettant d’identifier chaque occurrence d’une entité (ou d’une classe) ;

- Association (relationship) : l’association permet de relier plusieurs entités ou classes entre elles. Une association se représente à l’aide d’un ovale (ou lien pour UML) ;

- Cardinalités ou multiplicités (cardinality-multiplicity) : couple de valeurs (minimum, maximum) indiqué à l’extrémité de chaque lien d’une association. Il caractérise la nature de l’association en fonction des occurrences des entités ou classes concernées.

I-4-2-1 - E-R et UML

Les différents types d’associations (un-à-un, un-à-plusieurs, plusieurs-à-plusieurs, n-aires, héritage) sont modélisables. UML dispose du concept de classe-association pour les associations plusieurs-à-plusieurs et n-aires. Seule l’interprétation des cardinalités diffère entre le modèle de Merise, celui de Chen et UML. La majorité des outils ont pris en compte ces différences et génèrent automatiquement un modèle à partir d’un autre (round-trip engineering). Les traductions entre modèles ne sont pas encore parfaites mais s’améliorent au fil des versions des outils.

Les différentes contraintes qu’on peut définir dans un modèle conceptuel sont aussi supportées par les formalismes Entité-Association et UML. Il s’agit des contraintes de partition, d’exclusion, de totalité, d’inclusion et de simultanéité. Les trois premières contraintes sont appliquées aux entités (classes) ou aux associations. Les contraintes d’inclusion et de simultanéité sont dédiées aux associations. En général, les outils prennent en compte graphiquement certaines contraintes, mais ne génèrent aucune contrainte additionnelle au niveau de SQL.

L’agrégation d’UML (partagée ou composition) représente une association qui n’est pas symétrique et pour laquelle l’une des extrémités joue un rôle prédominant par rapport à l’autre. L’agrégation concerne un seul côté d’une association. Ainsi, elle devra se traduire au niveau du code SQL par des déclencheurs (triggers) ou des contraintes. Les outils la prennent en compte graphiquement mais ne génèrent aucun code additionnel au niveau de SQL.

Les règles de validation et de normalisation qui existaient avec Merise peuvent aisément être utilisables sous UML.

L’encapsulation et la visibilité devront être programmées manuellement et explicitement au niveau de la base. C’est là où se trouve le plus grand fossé entre classes d’objets et enregistrements de la base. Alors que l’encapsulation et la visibilité sont assurées intrinsèquement par les langages objets (C++ et Java par exemple), ces concepts ne sont pas natifs aux bases de données. Pour l’heure, les outils de conception ne répercutent pas automatiquement ces notions dans les scripts SQL. Et tant que les bases de données cibles seront relationnelles, il est probable que la situation restera en l’état.

La notation UML est relativement adaptée à la modélisation d’une base de données lorsqu’elle est utilisée à bon escient :

- la plupart des concepts fondamentaux de Merise peuvent être représentés dans le diagramme de classes d’UML qui offre en plus la possibilité de définir des stéréotypes personnalisés ;

- la représentation de certaines contraintes sur des associations n-aires est implicite avec les classes associations ;

- l’utilisation de la notation UML permet aux concepteurs de travailler dans un même environnement graphique, notamment avec la possibilité d’interfacer plus facilement les langages évolués C++ ou Java.

Mais UML pêche par un défaut majeur : son typage flou, ce qui pose généralement de gros problèmes de performance…

I-4-2-2 - Formalismes graphiques

Nous présentons les deux formalismes graphiques les plus utilisés par les outils à savoir le MCD type Merise et le diagramme de classe UML. Il s’agit de modéliser la base de données des clients d’une banque. Un compte est rattaché à un client. Un compte épargne est rémunéré à un taux. Un compte courant est caractérisé par un nombre d’opérations de carte bleue. Un compte courant peut avoir plusieurs signataires. Pour un compte courant donné, un signataire peut avoir différents droits. On veut stocker les opérations faites par un client sur un compte courant.

Dans le MCD Merise suivant (figure 1.6), apparaîssent :

- six entités ;

- trois associations binaires (Possède et Propriétaire de type un-à-plusieurs et Signataire plusieurs-à-plusieurs) ;

- une association n-aire (Operations) ;

- un héritage simple ayant comme sur-entité Compte.

Figure.1.6 : modèle conceptuel MERISE

Un diagramme de classes UML équivalent au diagramme MERISE est présenté en figure 1.7. Il y apparaît :

- autant de classes que d’entités Merise ;

- des cardinalités inversées pour les associations binaires ;

- des classes-associations qui traduisent les associations plusieurs-à-plusieurs et n-aires.

Figure 1.7 : diagramme de classes UML

I-4-3 - Du conceptuel aux relations : le modèle logique de données (ou MLD)

Une fois le modèle conceptuel validé, c’est-à-dire comprenant toutes les informations (exceptées les informations calculables), sans oubli et sans redondance et exprimé sous la forme d’entités (ou classe si UML) et associations, le modèle logique constitué des relations au sens mathématique peut être établi par des règles simples :

- toute entité est traduite en table ;

- suivant les cardinalités des associations, des éléments sont ajoutés :

- un à un : l’identifiant de chacune des entités est glissé sous forme de clef étrangère dans l’autre entité et une assertion assure la correspondance des paires de clefs,

- un à plusieurs : l’identifiant de l’entité côté n est glissé sous forme de clef étrangère côté 1,

- plusieurs à plusieurs : une table associative est créée dont la clef est formée des identifiants des entités participant à l’association;

- les contraintes d’intégrité fonctionnelles sont traduites en clefs alternatives, déclencheurs ou vues.

Ce modèle est affiné notamment par l’adjonction de contraintes complémentaires répondant aux règles de gestion.

Cet ensemble de relations constitue le Modèle Logique de Données (MLD).

I-4-4 - Du relationnel aux tables : le modèle physique de données (ou MPD)

La traduction du modèle logique de données en tables relationnelles dépend du SGBD Relationnel utilisé. Tous ne disposent pas des mêmes concepts, types et objets. Ce niveau de modélisation ajoute toute la partie physique invisible aux modèles précédents, notamment :

- la persistance ;

- le stockage ;

- l’indexation ;

- d’éventuels partitionnements.

De loin, le plus important est la partie persistance qui concerne la gestion des transactions. Un chapitre de cet ouvrage en parlera en détail.

Le modèle de données que SQL manipule est constitué de tables, correspondant aux relations décrites dans le modèle logique de Codd. La table relationnelle est la structure de données de base qui contient des entrées de données appelés lignes. Une table est composée de colonnes qui décrivent les lignes.

Considérons la figure suivante qui présente deux tables relationnelles permettant de stocker des compagnies, des pilotes et le fait qu’un pilote soit embauché par une compagnie :

Figure 1.8 : exemple de tables relationnelles

La clef primaire (primary key) d’une table est l’ensemble minimal de colonnes qui permet d’identifier de manière unique chaque ligne. Dans la figure précédente, les colonnes « clefs primaires » sont notées en gras. La colonne comp représente le code de la compagnie et la colonne brevet décrit le numéro du brevet.

Une clef est dite « candidate » (candidate key) si elle peut se substituer à la clef primaire à tout instant. Une table peut contenir plusieurs clefs candidates ou aucune. Dans notre exemple, les colonnes nomComp et nom peuvent être des clefs candidates si l’on suppose qu’aucun homonyme n’est permis.

Une clef étrangère (foreign key) référence dans la majorité des cas une clef primaire d’une autre table (sinon une clef candidate sur laquelle un index unique aura été défini). Une clef étrangère est composée d’une ou plusieurs colonnes. Une table peut contenir plusieurs clefs étrangères ou aucune. Dans notre exemple, la colonne compa (notée en italique dans la figure) est une clef étrangère, car elle permet de référencer une ligne unique de la table Compagnie grâce à sa clef primaire comp.

Le modèle relationnel est ainsi fondamentalement basé sur les valeurs. Les associations entre tables sont toujours binaires et assurées par les clefs étrangères. Les théoriciens considèrent celles-ci comme des pointeurs logiques. Les clefs primaires et étrangères seront définies dans les tables en SQL à l’aide de contraintes.

La figure 1.9 illustre le modèle relationnel de la gestion de la banque décrit au paragraphe précédent. Les clefs étrangères sont ici représentées sous la forme de flèches.

Figure 1.9 : modèle relationnel des données de l’exemple de la banque

I-5 - algèbre relationnelle

Étudions à présent brièvement les opérateurs qui constituent une partie de la base mathématique du modèle de donnée relationnel. L’algèbre relationnelle propose en tout et pour tout 8 opérateurs divisés en deux catégories :

- Ensemblistes (UNION, INTERSECTION, DIFFERENCE)

- Relationnels (RESTRICTION, PROJECTION, JOINTURE, DIVISION, PRODUIT CARTÉSIEN)

On distingue les opérateurs unaires (ou monadiques) : projection et restriction qui opèrent sur une relation à la fois des opérateurs binaires (ou dyadiques) : union, différence, intersection, division, produit cartésien, jointure qui opèrent sur deux relations.

Le résultat d'une opération de l'algèbre relationnelle est une nouvelle relation ce qui permet de combiner les résultats entre eux afin de créer de nouvelles opérations bâties sur des expressions plus complexes. Il n’y a donc pas de limite à la « calculabilité » dans l’algèbre relationnelle.

NOTE

Dans le langage SQL, le résultat n'est pas une table – la différence est subtile – mais un jeu de donné constitué par un curseur, pouvant être considéré comme une table.

I-5-1 - RESTRICTION (symbole σ )

La restriction de la relation R1[a1, ..., an], selon le prédicat p donne une relation R2[a1, ..., an] qui est notée σ(p)[R1]. R2 contient les tuples de R1 filtrés selon le prédicat p. La lettre grecque σ (sigma) est utilisée pour cette opération.

Figure 1.10 : effet d'une opération de restriction

Dans le langage SQL, cette opération qui consiste à ne retenir que certaines lignes de la table, est réalisée par les clauses WHERE et HAVING, ainsi que dans la clause ORDER BY par l’intermédiaire des options OFFSET / FETCH(9).

I-5-2 - PROJECTION (symbole Π)

La projection de la relation R1[a1, ..., an] sur les attributs ai, ..., am avec (ai, ..., am a1, ..., an) donne une relation R2[ai,...,am], qui est notée Π(ai,...,am)[R1]. . R2 contient seulement les données présentes dans les colonnes ai, ..., am issues de R1. La lettre grecque Π (pi) est utilisée pour cette opération.

Figure 1.11 : effet d'une opération de projection

Dans le langage SQL, cette opération qui consiste à ne présenter qu’un sous ensemble des colonnes de la table est réalisée dans la clause SELECT.

I-5-3 - UNION (symbole U)

L’union des relations R1[a1, ..., an] et R2[b1, ..., bn] donne la relation R3 qui contient les tuples de R1 et les tuples de R2 qui n’appartiennent pas à R1, elle est notée
U [R1, R2]. La lettre « U » en majuscule symbolise cette opération.

Figure 1.12 : effet d'une opération d'union

Dans le langage SQL, cette opération est réalisée par l’opérateur UNION.

I-5-4 - DIFFERENCE (symbole –)

La différence des relations R1[a1, ..., an] et R2[b1, ..., bn] donne la relation R3 qui contient les tuples de R1 qui n’appartiennent pas à R2, elle est notée – [R1, R2] et symbolisée par le signe « moins » –.

Figure 1.13 : effet d'une opération de différence

Dans le langage SQL, cette opération est réalisée par l’opérateur EXCEPT.

I-5-5 - INTERSECTION (symbole ∩)

L’intersection des relations R1[a1, ..., an] et R2[b1, ..., bn] donne la relation R3 qui contient les tuples qui appartiennent à la fois à R1 et à R2, elle est notée ∩ [R1, R2] et symbolisée avec la lettre « U » inversée ∩.

Figure 1.14 : effet d'une opération d'intersection

Dans le langage SQL, cette opération est réalisée par l’opérateur INTERSECT.

La figure 1.15 présente les trois opérateurs précédents, appelées opérateurs « ensemblistes ». Les attributs des relations doivent avoir des supports sémantiques similaires, autrement dit des « types » compatibles…

Figure 1.15 : opérateurs ensemblistes

I-5-6 - PRODUIT CARTÉSIEN (symbole x)

Le produit cartésien de R1[a1, ..., an] par R2[b1, ..., bn] donne la relation R3[a1, ..., an,, b1, ..., bn] qui contient les combinaisons des tuples de R1 et de R2, il est noté x[R1, R2]. Il est symbolisé par le signe x.

Figure 1.16 : effet d'une opération de produit cartésien

Le produit cartésien est le nom savant de la multiplication. C’est aussi l’inverse de la division. Dans le langage SQL, cette opération est réalisée par l’opérateur CROSS JOIN.

I-5-7 - DIVISION (symbole ÷)

La division de R1[a1, ..., an,, b1, ..., bn] par R2[b1, ..., bn] donne la relation R3[a1, ..., an] qui contient les tuples ti vérifiant : ti ∈ ( R3 tels que tj ∈ R2 et ti, tj ∈ R1, elle est notée ÷ [R1, R2]. Elle symbolisée par le signe ÷.

Figure 1.17 : effet d'une opération de division

Le langage SQL ne dispose d’aucun opérateur particulier pour réaliser cette opération compte tenu des diverses formes qu’elle peut prendre (exacte ou approchée, c’est à dire avec ou sans reste, ou encore valuée). On la réalise alors à l’aide de sous-requêtes.

I-5-8 - JOINTURE (symbole ⋈ )

La jointure de R1[a1, ..., an] avec R2[b1, ..., bn] suivant le prédicat p donne la relation R3[a1, ...,, an,, b1, ..., bn] qui contient les tuples de R1 et de R2 vérifiant le prédicat de jointure, elle est notée : ⋈(p)[R1,R2]. Symbolisée par un « diabolo » ⋈.

Figure 1.18 : effet d'une opération de jointure

Dans le langage SQL, cette opération est réalisée par l’opération de jointure (JOIN) déclinée en différentes versions : INNER JOIN, LEFT OUTER JOIN, RIGHT OUTER JOIN et FULL OUTER JOIN. Les jointures externes (OUTER) étant symbolisées par ⟕⟖⟗.

I-6 - Le modèle externe de données (ou MED)

Le modèle externe de données ajoute les couches de présentation (vues) et l’intelligence au niveau de la gestion interne des données par le biais des fonctions définies par l’utilisateur (User Defined Function ou UDF), des déclencheurs et des procédures stockées et des fonctions utilisateurs. Nous découvrirons tous ces éléments dans un prochain chapitre.

En conclusion nous pouvons synthétiser la succession des modèles de données en 4 couches que résume la figure 1.20 :

Figure 1.19 – les différents niveaux de modélisation des données

I-7 - La syntaxe

La syntaxe, nous apprennent les dictionnaires, est la partie de la composition d’une langue qui décrit les règles de combinaison des différentes entités lexicales entres elles.

En informatique où le terme « langue » doit être remplacé par « langage », la syntaxe définit la grammaire formelle du langage. La syntaxe est donc peu permissive, et les outils qui traduisent ce langage en code exécutable, appelés « compilateurs », ne pardonnent pas la moindre erreur.

Par exemple, si en français, tout le monde comprendra une phrase comme : « Pierre est allé au coiffeur en moto », alors que la bonne syntaxe est « Pierre est allé chez le coiffeur à moto », il n’en va pas de même avec n’importe lequel des langages informatiques. La moindre divagation dans la précision des éléments d’une commande et donc plus généralement du code, provoquera immanquablement une erreur de syntaxe ou pire, un résultat faux…

Parmi les principales difficultés rencontrées par les débutants, notons :

- la précision du placement des virgules ;

- la gestion de l’imbrication des parenthèses ;

- la mauvaise orthographe des noms des objets ;

- l’oubli de fermeture des délimiteurs (apostrophe, guillemets…).

Ces difficultés sont normales et s’acquièrent avec le temps notamment par l’observation des syntaxes présentées, mais aussi, et surtout, avec la lecture et la reproduction des exemples… Bref, comme dans beaucoup de disciplines, la pratique entraîne l’habitude, et l’habitude une sûreté du codage.

Il n’en reste pas moins vrai que certains outils d’édition des requêtes SQL et certains SGBDR facilitent la vie. Nous prendrons deux exemples très contraires pour prouver notre propos… D’un côté le SGBDR Microsoft SQL Server et son outil d’édition SQL Server Management Studio (SSMS) qui pratique l’auto-complétion et de l’autre côté MySQL et son outil MySQL Workbench relativement frustre… En lançant simplement la requête de l’exemple 1.3, syntaxiquement erronée, voici le comportement de ces deux outils :

Exemple 1.3 – requête SQL dont la syntaxe est erronée :

FROM T
SELECT 1
ORDER BY 1;

Figure 1.20 – Effet d’une erreur de syntaxe dans Microsoft SQL Server / SSMS

Tandis que le compilateur de Microsoft SQL Server (voir figure 1.20) indique clairement, non seulement la nature de l’erreur (syntaxe incorrecte) et l’élément en défaut (la clause FROM), mais en sus, l’outil SSMS positionne le curseur au début de la ligne où se situe l’erreur si l’on clique sur le message d’erreur.

En revanche, dans MySQL Workbench, le message est plus que sibyllin : rien n’indique la nature de l’erreur (reportez-vous au manuel…), pas plus qu’il n’aide l’utilisateur à positionner le curseur à l’endroit du code où son écriture est fautive (voir figure 1.21)…

Figure 1.21 – Effet d’une erreur de syntaxe dans MySQL / Workbench

On aura compris que certains produits, venant en particulier du monde libre (MySQL / MariaDB, PostGreSQL…), offrent un niveau de service bien moindre que les SGBDR professionnels d’entreprise comme Microsoft SQL Server ou Oracle Database, où la productivité est la règle qui dicte la commercialisation.

On peut néanmoins opter pour un outil client d’édition des requêtes autre que celui fourni par le concepteur du SGBDR(10), mais aucun n’offre la richesse et la facilité de codage que l’on trouve dans Microsoft SQL Server Studio Management ou Oracle SQL Developper…

I-8 - Organisation interne d’un SGBD Relationnel

Un SGBD Relationnel est en fait un programme informatique de type « service » et qui tourne en permanence. Il est à l’écoute des demandes des nombreux utilisateurs. Il reçoit des requêtes qu’il analyse et exécute suivant les permissions accordées aux utilisateurs sur les objets.

Il peut se faire que sur une même machine(11) physique ou virtuelle soit installées plusieurs instances(12) d’un même type de SGBDR. Ceci est fortement déconseillé en production, mais c’est courant pour l’analyse et les test dans les entreprises assurant le développement ou la maintenance d’applications.

Le terme de « base de données » (en anglais « database ») n’existait pas à l’époque de Ted Codd. On parlait alors de « databank » (banques de données) dans lesquelles on trouvait des « catalogues » de données. Le terme « catalogue » est cependant resté dans certaines vues de métadonnées et nous le rencontrerons au cours de cet ouvrage. Le terme « catalog » est un équivalent du terme database (bases de données) dans le langage SQL.

I-8-1 - La notion de base de données ou CATALOG

Un SGBD Relationnel moderne peut comporter de nombreuses bases de données chacune devant être utilisée par une application précise. Par exemple, dans une entreprise, on pourra trouver une base de données pour les ressources humaines, une autre pour la comptabilité et une troisième pour la gestion des clients (actions commerciales, commandes, facturation, livraisons…). Le cloisonnement par base doit être relativement étanche, mais certaines données de chacune de ces bases peuvent alimenter d’autres bases. À titre d’exemple, les factures de la base client seront poussées vers la base comptable.

Une commande SQL spécifique, généralement CREATE DATABASE, permet de créer une nouvelle base de données dans le SGBDR.

Certains SGBD relationnels permettent de faire des jointures entre les tables des différentes bases de manière naturelle et optimisée. C’est le cas de Microsoft SQL Server. D’autres nécessitent l’établissement d’une liaison entre deux bases par un mécanisme à créer (DBLink), et la jointure s’effectue en migrant les données des tables distantes dans la base locale (remote join). Cette dernière technique adoptée par Oracle Database et PostGreSQL ne permet aucune optimisation de la jointure interbase.

I-8-2 - La notion de schéma SQL

À l’intérieur d’une base de données relationnelle on trouve des schéma SQL qui sont en fait des conteneurs logiques. Tout objet relationnel doit figurer dans un schéma et le nom complet d’un objet doit être préfixé par le nom du schéma SQL. Toutefois, il existe systématiquement un schéma par défaut dans toutes les bases, voire un chemin de recherche dans différents schémas. De même, il existe un schéma par défaut attribué pour chacun des utilisateurs SQL qui sont les entités de sécurité sous lesquelles naviguent les applications accédant à la base.

Il n’est pas possible de créer un objet relationnel hors d’un schéma SQL. En revanche, certains objets non relationnels comme ceux permettant de gérer le stockage des données de la base ne peuvent pas être placés dans un schéma SQL.

Le concept de schéma est proche de celui de « bibliothèque » de code des langages procéduraux ou d’espace de noms des langages plus moderne comme C++, Java ou C#, à la différence près que l’on ne peut pas imbriquer les schémas SQL les uns dans les autres pour former une hiérarchie. En d’autres termes, il n’existe qu’un seul niveau de schéma SQL.

NOTE

Un même nom de table peut être présent dans différents schémas SQL. En effet, l’unicité des noms des objets se fait sur le nom complet de l’objet nom_schema.nom_objet. Pour les SGBDR multibase, cette unicité se fait aussi avec le nom de la base : nom_base.nom_schema.nom_objet.

L’idée sous-jacente aux schémas SQL est de modulariser la base de données. Par exemple un ERP, souvent doté de plusieurs milliers de tables et vues, gagnera à voir ces objets répartis en différents schémas comme RH, COMPTA, MARKETING, VENTE, PRODUCTION… qui permettent de présenter la base de données sous un aspect silo souvent utile à la mode actuelle des micro-services. Un autre découpage, souvent complémentaire, consiste à utiliser des schémas SQL transverses, notamment pour des données techniques ou référentielles. À titre d’exemple, on pourra créer les schémas suivants : SYSTEME, META, REFERENTIEL, IMPEX, EXT… Ceci pour, respectivement, des tables d’utilisateurs, des dictionnaires de données, un référentiel interne, des tables tampons, les données d’un référentiel externe.

Enfin, il faut savoir que l’on peut affecter des permissions spécifiques (appelés privilèges) aux schémas SQL, ce qui permet une grande souplesse en matière de sécurité préventive en plus de la modularité apportée naturellement par le découpage d’une base en de multiples schémas SQL.

Notez que certains schémas SQL sont fournis par les éditeurs. En principe, on trouve dans toutes les bases le schéma SQL normatif INFORMATION_SCHEMA(13) dans lequel sont implantées des vues présentant les métadonnées logiques de la base (tables, colonnes, contraintes…) et le schéma SQL « sys »(14) dans lequel on trouve tous les objets systèmes propres à chaque éditeur de SGBD Relationnels.

ATTENTION

MySQL / MariaDB font confusion entre les notions de schéma SQL et de base de données, mélangeant allègrement ces différents concepts sans les apports normatifs nécessaires à la sécurité ou la modularité (les sauvegardes pouvant s’avérer inconsistantes)…

NOTE

Oracle Database n’a jamais voulu intégrer le schéma normatif INFORMATION_SCHEMA présentant les vues logiques des métadonnées des objets relationnels.

I-9 - Importance des bases de données

Jusqu’au début des années 90, les entreprises ont peu considéré les informations détenues dans leurs bases de données. Il en est résulté une faible appréciation de la valeur patrimoniale des stocks de données disponibles dans les bases. Il a fallu attendre une loi permettant de valoriser comptablement le capital représenté par le stockage de l’information et une prise de conscience avec l’arrivée des GAFA, pour que les entreprises commencent à se questionner dans le bon sens au sujet des informations qu’elles détiennent.

La valeur d’une base de données est dans la qualité de l’information qu’elle stocke. L’intelligence est dans la structuration de ces informations…

I-9-1 - Valeur d’une base de données

Il est aujourd’hui comptablement et commercialement admis, que les informations d’une base de données constituent un capital. De nombreuses entreprises commercialisent les données qu’elles ont acquises, certaines ayant fait leur métier exclusivement sur ce concept en offrant des services apparemment gratuits (Google, FaceBook, Twitter, Tik Tok…).

Tout comme les brevets, les marques, les licences, les logiciels… les bases de données peuvent faire l’objet d’une appréciation financière au titre des actifs immatériels. En effet, il est tout à fait possible de revendre toute ou partie des informations contenues dans les bases de données de l’entreprise… ou encore de les exploiter d’une manière différente de celle prévue à l’origine.

I-9-2 - Intelligence d’une base

La valeur d’un tel patrimoine, la base de données, est plus ou moins élevée pour les informations contenues dans celle-ci, en fonction de la facilité avec laquelle on peut exploiter ou non les données. C’est là qu’intervient la notion d’intelligence. En effet, selon que la structure de la base, c’est-à-dire l’organisation intime des informations dans les différentes tables, est plus ou moins efficiente, il en résultera une facilité ou une difficulté à accéder rapidement à l’information et une plus ou moins grande qualité des données stockées.

L’art de la modélisation d’une base de données repose sur des principes simples :

- des informations atomiques ;

- le stockage de l’information et seulement l’information (sans « bruit »)(15) ;

- la présence d’une clef pour chaque table ;

- aucune valeur inconnue (pas de « NULL ») ;

- la présence de contraintes de domaine ;

- la modification d’une information ne doit pas conduire à la mise à jour de plus d’une ligne d’une table.

L’ensemble de ces règles assure cette intelligence qui fournira à la fois des performances exceptionnelles et la garantie que les données soient saines et facilement exploitables.

Hélas, en pratique, on s’aperçoit que nombre de bases de données ne respectent pas ces principes et cela conduit à des bases fortement redondantes, des tables obèses (voir figure 1.22), des données fausses ou incomplètes et par conséquence des performances médiocres, voire difficilement acceptables. Il en résulte alors de nombreux coûts pour l’entreprise : surdimensionnement du matériel pour compenser les mauvaises performances (stockage exagéré), perte de temps et donc d’agent (développement complexe, maintenance lourde, administration exorbitante…), dévalorisation du capital des données…

Figure 1.22 – temps de réponse comparatif d’un SGBD Relationnel en présence de tables obèses

L’application stricte des règles de l’art de la modélisation des bases de données relationnelles donne généralement des tables de très faible degré, c’est-à-dire un nombre de colonnes en moyenne en dessous de 10. Il est alors facile d’indexer correctement ces tables afin d’obtenir de bonnes performances. Mais en présence de tables obèses, c’est-à-dire présentant un nombre de colonnes anormalement élevé, une indexation efficace devient impossible, tant le nombre d’index qu’il faudrait poser est démesuré (voir notre chapitre consacré à l’indexation). De plus, les SGBDR travaillant en mémoire, il est nécessaire que les données visées par une requête figurent en cache. Pour une recherche en index, cela ne représente que quelques pages. En l’absence d’index adéquat, il faut mettre en cache toute la table… C’est pourquoi la RAM est vite saturée en présence de tables obèses, et la majorité des accès se faisant par un balayage (lecture séquentielle de toutes les lignes de la table), les temps de réponse s’accroissent rapidement.

Le syndrome habituel, pour une base de données aux tables obèses, est un basculement relativement abrupt des performances vers le bas, signe que l’on a commencé à saturer le cache, et que désormais, une grande partie des requêtes nécessite des accès disques, épouvantablement plus lents que ceux effectués en mémoire…

I-10 - Références pour ce chapitre

Toutes nos sources sont celles de publications scientifiques professionnelles (université, associations reconnues), instituts gouvernementaux ou plus rarement de publications personnelles vérifiées par croisement. Toutes sont librement accessibles.

Fondement des SGBDR :

Article 1970 de Franck Edgar Codd « A relational model of data for large shared data banks » :

https://dl.acm.org/doi/10.1145/362384.362685

« Relational Completness of Data Base Sublanguages » F. E. Codd - 1972

https://www.inf.unibz.it/~franconi/teaching/2006/kbdb/Codd72a.pdf

« The Relational Model for Database Management » Version 2 : F. E. Codd – Addison Wesley 1990.

https://dl.acm.org/doi/book/10.5555/77708

Sytem-R d’IBM :

« A History and Evaluation of System R »

https://dsf.berkeley.edu/cs262/SystemR-annotated.pdf

« System R: Relational Approach to Database Management »

https://dl.acm.org/doi/10.1145/320455.320457

Historique Sybase :

https://doc.ispirer.com/sqlways/Output/SQLWays-1-178.html

http://www.fundinguniverse.com/company-histories/sybase-inc-history/

https://seniordba.wordpress.com/2019/04/22/history-of-sql-server-2/

https://blogs.sap.com/2011/04/15/a-deeper-look-at-sybase-history-of-ase/

SGBD Objet :

« Object Database Management Systems - Concepts and Features »

https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication500-179.pdf

« An Introduction to Object-Oriented Databases and Database Systems »

http://reports-archive.adm.cs.cmu.edu/anon/itc/CMU-ITC-103.pdf

SGBD NoSQL :

« NOSQL Database and Its Comparison with RDBMS »

https://www.ripublication.com/ijcir17/ijcirv13n7_08.pdf

« Research on NoSQL Database Technology »

https://www.atlantis-press.com/article/25897262.pdf

Histoire du langage SQL :

« Early History of SQL » D. Chamberlin

https://ieeexplore.ieee.org/document/6359709

Modélisation :

« Modélisation des bases de données » C. Soutou…

https://www.eyrolles.com/Informatique/Livre/modelisation-des-bases-de-donnees-9782416007507/

« Bases de données relationnelles et normalisation : de la première à la sixième forme normale » François de Sainte Marie (alias fsmrel) :

https://fsmrel.developpez.com/basesrelationnelles/normalisation/normalisation.pdf

« Ingénierie des systèmes d’information : Merise Deuxième Génération » 4e édition : D. Nancy, B. Espinasse

https://pageperso.lis-lab.fr/bernard.espinasse/index.php/livre-merise/

Algèbre relationnelle :

« Relational Model and Relational Algebra » Todd J. Green University of California, Davis

https://www.cs.ucdavis.edu/~green/courses/ecs165a-w11/3-ra.pdf

« Conception de Bases de Données Relationnelles » Marc Plantevit

Faculté de Sciences et Technologie - Laboratoire LIRIS - Université Lyon 1

https://perso.liris.cnrs.fr/marc.plantevit/documents/LIF10_CM.pdf

Modèle Entité Association :

« The Entity-Relationship Model-Toward a Unified View of Data » Peter Chen

https://www.csc.lsu.edu/~chen/pdf/erd-5-pages.pdf

Théorie de l’information de Claude Shanon :

« A Mathematical Theory of Communication »

https://people.math.harvard.edu/~ctm/home/text/others/shannon/entropy/entropy.pdf

II - Chapitre 2 - Définition des données : types, domaines, valeurs et NULL

Toute base de données repose sur un formalisme de données très important. Parce qu'elle est censée stocker des données, une base de données doit posséder un typage fort qui permet de définir au mieux la façon dont les valeurs vont être placées dans les objets de la base.

Nous allons voir les types et les domaines ainsi que la spécification de valeurs (c'est à dire comment on « assigne » une valeur à un objet typé). Nous montrerons aussi le typage et le marqueur NULL qui indique l'absence de valeur. Nous donnerons un aperçu des apports de la norme SQL:1999 en matière de définition des données, qui s'est tournée vers le relationnel objet, mais pêne à s'implanter dans les SGBDR.

On utilise les définitions de données dans la plupart des objets SQL : les tables SQL et les vues sont constituées en majeure partie d'une collection de colonnes ayant chacune un type de données. Dans une requête on peut spécifier une valeur et la typer à l’aide de la fonction CAST à des fins de comparaison. Enfin, l'assignation est réservée aux requêtes d'insertion et de mise à jour.

II-1 - Types SQL

Les types SQL sont regroupés dans quatre grandes familles : chaînes de caractères, nombres, temporels et binaires. Ces grandes familles faisant l’objet de plusieurs sous-familles…

Chaines de caractères

Nombres

Temporels

Binaires

ASCII fixe :
CHAR

ASCII variable :
VARCHAR

Exacts :

Entiers :
SMALLINT
INTEGER
BIGINT

Datation :
DATE
TIME
TIMESTAMP
TIME WITH TIMEZONE
TIMESTAMP WITH TIMEZONE

Booléen :
BIT

UNICODE fixe :
NCHAR

UNICODE variable :
NVARCHAR

DCB :
NUMERIC
DECIMAL

Chaine fixe :
BINARY

Approchés :

REAL
FLOAT
DOUBLE PRECISION

Durée :
INTERVAL

Chaine variable :
VARBINARY

Tableau 2.1 - Présentation des types SQL les plus courants exceptés les LOBs (Large OBjects).

II-1-1 - Types chaîne de caractères

SQL permet de codifier les chaînes de caractères dans des formats où chaque caractère s'exprime sur une base de 1 octet (ASCII, EBCDIC) ou bien 2 octets (UNICODE).

II-1-1-1 - ASCII

L’ASCII est un format d'encodage de caractères sur 8 bits, soit 1 octet. Il permet donc 256 combinaisons de signes. Les codes de 0 à 127 représentent toujours les mêmes signes dans lesquels on trouve les lettres de « a » à « z » aussi bien en minuscule qu'en majuscule, les chiffres de « 0 » à « 9 », divers signes typographiques et de ponctuation et des signaux destinés à piloter les imprimantes (retour chariot, tabulation...). Étant donné que certaines langues admettent des caractères diacritiques (accents, cédille, tilde, ligature...) et que la plage de codes allant de 128 à 255 ne permettait pas de les représenter tous, il a fallu inventer un stratagème pour obtenir une telle représentation. C'est la fameuse « page de code », qui permet de charger un panel de signes différents dans les codes 128 à 255, panel spécifique à la langue de l'utilisateur. Ces pages de codes ont fait l'objet d'une standardisation, on y trouve les versions les plus courantes numérotées 437 (américain, graphique) ou 850 (multilingue Européen)... ASCII est un standard des éditeurs de matériel typographique américains qui a été établi à l'origine pour les télétypes. Ce standard a fait l'objet d'une norme ANSI (American National Standard Institute) largement reprise en informatique dans le monde entier.

II-1-1-2 - EBCDIC

EBCDIC est un codage semblable à ASCII, mais propre à la société IBM. EBCDIC n'est maintenant gardé que pour la compatibilité descendante, afin que les fichiers informatiques des ordinateurs des années 60 à 90, produit sur des machines IBM, puissent encore être lus. Ce format d’encodage, décliné en six variantes et d’autres sous variantes, était censé être optimal pour la saisie des cartes perforées… En fait, il s’agissait d’un coup marketing et pour IBM de ferrer le client afin que sa migration vers d’autres systèmes soit plus difficile !

II-1-1-3 - UNICODE

L’UNICODE (norme ISO 10646) est une évolution importante de la problématique de communication et d'échange mondial. En effet, pour l'ASCII, du fait de l'emploi d'une page de code spécifique, la communication de documents électroniques entre utilisateurs ayant des pages de code différentes provoquait l'apparition de caractères étranges et souvent incompréhensibles dans les fichiers. Il fut alors décidé d'étudier un nouveau format d'encodage des chaînes de caractères permettant de codifier l'ensemble des alphabets les plus représentatifs de la planète et de toutes leurs spécificités. La dernière version, UNICODE 15.0, a été publiée en septembre 2022 et comporte 149 186 caractères, couvrant plus de 150 écritures alphabétiques ou idéographiques, chaque caractère est codé sur 4, cinq ou 6 octets. On y trouve entre autres, les alphabets latin, grec, cyrillique, hébreu, arabe, thaï, ainsi que les idéogrammes unifiés chinois, japonais et coréens. Bien que l’UNICODE définisse les codes des différents caractères, cette norme n’est pas utilisée directement, mais par le biais d’encodages spécifiques tels que l’UTF-8, l’UTF-16 (Unicode Transformation Format) ou l’UCS.

Type

Chaine de caractères

CHARACTER [ (n) ]

taille fixe de longueur n, dont les caractères sont codés sur 1 octets (ASCII, EBCDIC),

CHARACTER VARYING [ (n) ]

taille variable de longueur maximale n, dont les caractères sont codés sur 1octets (ASCII, EBCDIC)

NATIONAL CHARACTER [ (n) ]

taille fixe de longueur n, dont les caractères sont codés sur 2 octets (UNICODE)

NATIONAL CHARACTER VARYING [ (n) ]

taille variable de longueur maximale n, dont les caractères sont codés sur 2 octets (UNICODE)

CHARACTER LARGE OBJECT (n [u])

taille variable limitée dont les caractères sont codés sur 1 octets (ASCII ou EBCDIC), fait partie des « LOBs »

NATIONAL CHARACTER LARGE OBJECT [(n [u])]

taille variable limitée dont les caractères sont codés sur 2 octets (UNICODE), fait partie des « LOBs »

n est un numérique entier spécifiant la taille
u est une méta unité parmi les symboles K, M ou G signifiant Kilo, Méga ou Giga.

Tableau 2.2 – Types chaines de caractères

Les chaines de taille fixe sont complétées à blanc, dans le sens de l’écriture (qui varie selon la langue) lorsque la donnée saisie est d’une taille inférieure à la taille du type. Par exemple, la chaine de caractères « SQL » inscrite dans un CHAR(6) sera complétée par 3 caractères « blanc » à droite.

NOTE

En ce qui concerne les chaines de caractères de type LOBs (CLOB, NCLOB) nous verrons que certaines opérations sont impossibles. Il faut donc les réserver à des informations strictement documentaires.

II-1-1-4 - UTF-8

L’UTF-8 est un encodage dont les caractères ont des tailles différentes selon le type de caractères et variant de 1 à 4 octets. Ainsi, les signes de base, les nombres et les lettres simples sont encodés sur un octet dont le bit de poids fort vaut 0, ce qui permet 128 caractères strictement équivalents aux 128 premiers caractères de l’ASCII (lettres de A à Z en majuscules et minuscules, nombres, signes de ponctuation, espace, caractères non imprimables…). Les principaux signes diacritiques et alphabets proches du latin (grec, cyrillique…) sont codés sur 2 octets dont les 2 bits de poids fort sont à 1 ce qui permet 16 256 caractères. Les principaux idéogrammes sont encodés sur 3 octets dont les 3 bits de poids fort valent 1, ce qui permet de représenter 2 080 768 symboles. Enfin des symboles exotiques (note de musique, émoticon…) sont représentés sur 4 octets, avec 4 bits de poids fort à 1, ce qui laisse 266 338 304 combinaisons possibles.

Si cet encodage est censé présenter un avantage pour Internet, il favorise essentiellement l’anglais et complexifie de nombreux algorithmes comme le tri ou la comparaison. La difficulté étant de traiter des chaines de caractères dont les différents caractères n’ont pas la même longueur. Cela s’en ressent sur les performances de certaines opérations dans le cadre des SGBDR…

II-1-1-5 - UTF-16

L’UTF-16 est un encodage dont les caractères ont des tailles différentes selon le type de caractères et utilise 2 ou 4 octets. Sur deux octets 1024 caractères sont disponibles et correspondent aux 128 premiers caractères de l’ASCII ainsi qu’aux caractères diacritiques et alphabets cyrillique et grec. Sur 4 octets sont encodés la plupart des autres alphabets comptant jusqu’à 1 048 576 symboles.

II-1-1-6 - UCS-2

L’UCS-2 est un encodage qui utilise 2 octets pour tout caractère et représente les 32 768 premiers caractères de l’UTF-16.

Quel que soit l’encodage UNICODE (UTF ou UCS) tous les codes ne sont pas utilisés par des symboles, certains étant réservés, et d’autres sont des codes de contrôle comme les fameux BiDi (Bi Directionnels) permettant d’indiquer si les caractères se lisent de gauche à droite ou l’inverse et donc opérer un changement de direction de la lecture des caractères de la chaine en fonction de l’alphabet qui y est présent.

ATTENTION

Les types de données NATIONAL (NCHAR/NVARCHAR) utilisent plus d’octets que les types CHAR/VARCHAR. Il est donc déconseillé de les utiliser si votre base de données n’utilise qu’une langue « latine ». En effet tout volume supplémentaire inutile dégrade les performances.

Dans un but de simplification, SQL admet des synonymes aux noms des types de chaînes de caractères

Type

Synonymes

CHARACTER

CHAR

CHARACTER VARYING

VARCHAR

NATIONAL CHARACTER

NCHAR, NATIONAL CHAR

NATIONAL CHARACTER VARYING

NVARCHAR, NATIONAL VARCHAR

CHARACTER LARGE OBJECT

CLOB

NATIONAL CHARACTER LARGE OBJECT

NCLOB

Tableau 2.3 – Synonymes pour les types de chaines de caractères

NOTE

- La longueur maximale n que l'on peut spécifier dépend du SGBDR.

- Les limites de longueur sont généralement inférieures à quelques milliers de caractères.

- En l'absence de précision, la valeur par défaut de la longueur d'un CLOB/NCLOB est la plus grande taille fixée par le SGBDR.

- Pour les types à taille variables (VARCHAR, NVARCHAR, VARBINARY…) le SGBDR rajoute aux données, soit la taille réelle de valeur stockée, soit un marqueur de séparation des valeurs pour chaque ligne. Dans tous les cas quelques octets supplémentaires.

Exemples 2.1 – Types littéraux :

NCHAR (200) => Chaîne de 200 caractères UNICODE

CHARACTER VARYING => Chaîne de caractères ASCII ou EBCDIC d'au plus 32 caractères

NCLOB (2M) => Chaîne de caractères UNICODE de taille maximale limitée à 2 Méga octets

CHARACTER LARGE OBJECT (32765) => Chaîne de caractères ASCII ou EBCDIC d'au plus 32765 caractères

CONSEIL

Pour des raisons de performances, il est conseillé d'utiliser des types de longueur fixe et la plus courte possible, dès que les colonnes sont fortement sollicitées en recherche et mise à jour. Dans les autres cas (grande longueur, peu de mise à jour) utilisez des types de longueur variable. Si vous utilisez une langue latine, préférez dans ce cas du VARCHAR au NVARCHAR et du CHAR au NCHAR.

ATTENTION

Depuis la norme SQL de 2023, la longueur d’un littéral (n) peut ne pas être précisée. Dans ce cas, la norme SQL considère que le SGBDR doit prendre la plus grande longueur dans les limites spécifiques au SGBDR utilisé. Ceci n’est généralement pas le cas des SGBDR actuels… Il est donc préférable de toujours préciser la longueur pour lever toute ambiguïté.

Dans le cas d’omission de la taille du littéral et à titre d’exemple, l’expression d’un type CHAR sans spécification de taille vaut 1 caractère ASCII pour Microsoft SQL Server, Oracle Database comme pour PostGreSQL.

II-1-2 - Types temporels

Les types temporels comprennent la datation, la mesure du temps, l'horodatation et la durée (intervalle de temps).

Types temporels SQL

Type

Description

DATE

Date allant de 1/1/1 à 31/12/9999

TIME [(s)]

Heure allant de 00:00:00 à 23:59:61.9999999…

TIMESTAMP [(s)]

Combiné date + heure

TIME WITH TIME ZONE [(s)]

Heure avec encapsulation du fuseau horaire spécifié en décalage relatif par rapport au temps universel sous la forme : { + | – } HH:MM

TIMESTAMP WITH TIME ZONE [(s)]

Combiné date + heure avec encapsulation du fuseau horaire

INTERVAL

Intervalle de temps (durée), spécifiable parmi :
YEAR [(n)] [TO MONTH]
MONTH [(n)]
DAY [(n)] [{ TO HOUR | TO MINUTE | TO SECOND }]
HOUR [(n)] [{ TO MINUTE | TO SECOND }]
MINUTE [(n)] [TO SECOND]
SECOND [(n [,s])]

s est la précision en nombre de chiffres après la virgule pour les tantièmes de seconde et cette précision diffère selon les différents SGBDR
n est le nombre de chiffres sur lequel est spécifié l'intervalle

Tableau 2.4 – Types temporels

NOTE – La seconde intercalaire

Dans sa norme, le langage SQL accepte le principe des secondes intercalaires pour lesquelles certaines minutes peuvent contenir au maximum, 59, 60, 61 ou 62 secondes afin de rattraper le décalage entre temps atomique et temps astronomique. Ces secondes intercalaires sont placées à la dernière minute des 30 juin ou 31 décembre.
Rares sont les SGBDR a permettre le stockage des secondes intercalaires supérieures à 59.

ATTENTION – La seconde intercalaire

De par leur construction, les systèmes UNIX (dont Linux) ne savent pas gérer la seconde intercalaire, car ils ont été conçus en supposant que la durée du jour serait toujours de 86 400 secondes…
L’OS Windows n’a pas cet inconvénient. Il respecte la seconde intercalaire.

NOTE – Le type INTERVAL

La norme SQL a volontairement interdit tout type INTERVAL qui intégrerait la granularité MONTH entre les précisions maximales et intermédiaires de l'intervalle, car le mois est le seul élément de durée temporelle à ne pas être fixe (28, 29, 30 ou 31 jours sont les durées des mois…).

ATTENTION – Limites des dates

SQL précise qu'une date peut aller du 1er janvier de l'an 1 au 31 décembre de l'an 9999. Certains SGBDR dépassent la limite de l’an 9999, tandis que d’autres ne descendent pas en dessous du moment de l'adoption du calendrier grégorien.

ATTENTION – Ne pas utiliser le type « epoch »

Le type « epoch », propre à Linux, et que bon nombre de développeurs préfèrent en lieu et place d’un type SQL temporel comme TIMESTAMP, n’a rien à voir avec une date (c’est une durée théorique) et donne des résultats faux ! En effet, UNIX a été construit sur une affirmation fausse qui prétend que tous les jours ont 86 400 secondes. Or, du fait de la seconde intercalaire, ce calcul est erroné, car « epoch » compte le temps comme une durée et non comme une date… De plus ce type est limité aux dates allant du 01-01-1970 au 19-01-2038 à 3h14m8s…

Figure 2.1 – Fuseaux horaires dans la région indo-iranienne (extrait de Wikipedia)

NOTE – Fuseaux horaires

À l'Ouest de Greenwich (près de Londres) le décalage horaire est négatif, car le soleil se lève à l'Est. À l'Est, il est positif. La plupart des fuseaux horaires sont à l’heure près, mais certains pays ont un décalage à la demi-heure (Iran, Afghanistan, Inde, Birmanie… en France, la Polynésie) voire au quart d’heure (le Népal, la Nouvelle-Zélande, une partie de l’Australie).

Le calendrier grégorien

Au cours de l'Histoire et compte tenu de la précision accrue des calculs astronomiques, on constate que le calendrier julien (datant de Jules César) souffre d'un manque de précision. Celui-ci s’établissait à 365 jours plus une journée intercalaire tous les 4 ans. Néanmoins la durée de l'année julienne est trop longue d'un peu plus de 11 minutes (365,25 jours au lieu de 365,2425) et au fil du temps, le calendrier finit par retarder de plusieurs jours. C'est le pape Grégoire XIII qui entreprend de réformer le calendrier : pour cela, 10 jours furent supprimés de l'année 1582, où le 4 octobre fut immédiatement suivi par le 15 octobre. Pour éviter de nouvelles dérives, la surévaluation de l'année julienne fut corrigée par la suppression de 3 jours tous les 400 ans. On ignore donc la règle des années bissextiles les années séculaires, sauf pour celles qui sont divisibles par 400. Il y a donc 97 années bissextiles par période de 400 ans et la durée moyenne d'une année grégorienne est 365 + 97/100, c'est-à-dire 365,2425 jours.

Mais cette réforme nécessaire n'a pas été adoptée à la même date dans tous les pays :

- L'Italie, l’Espagne, le Portugal et la Pologne sont passées du 4 octobre 1582 au 15 octobre 1582.

- La France (sauf l’Alsace et la Lorraine) est passée du 9 décembre 1582 au 20 décembre 1582. Entre 1793 et 1806, le calendrier républicain, né de la Révolution française, est utilisé. Il se compose de douze mois de 30 jours et de 5 à 6 jours supplémentaires.

- Le Luxembourg est passé du 14 décembre 1582 au 25 décembre 1582.

- La Belgique (alors province des Pays-Bas) est passée du 21 décembre 1582 au 1er janvier 1583.

- Le Valais Suisse est passé du 28 février 1655 au 11 mars 1655.

- L'Alsace est passée du 4 février 1682 au 16 février 1682.

- Zurich, Berne, Bâle et Genève sont passées du 31 décembre 1700 au 12 janvier 1701.

- L'Angleterre est passée du 2 septembre 1752 au 14 septembre 1752.

- La Lorraine est passée du 16 février 1760 au 28 février 1760.

- L'URSS est passée du 31 janvier 1917 au 14 février 1917, mais une réforme révolutionnaire entra en vigueur de 1930 à 1932 avec un calendrier dans lequel chaque mois avait trente jours. Les cinq ou six jours en excès étaient des congés (à la manière du calendrier de la Révolution française).

Exemples 2.2 – types temporels :

DATE --> dates

TIME WITH TIME ZONE + 02:00 --> heure avec décalage de 2h
--> par rapport au fuseau horaire d'origine
--> (Premier méridien, dit « Greenwich »)

INTERVAL YEAR (4) TO MONTH  --> intervalle an/mois pouvant aller de zéro an
--> et zéro mois à 999 ans et 11 mois

INTERVAL SECOND (2,3)       --> intervalle permettant d'exprimer des secondes
--> allant de 0 à 62 secondes et 999 millièmes

TIME (2)                    --> Valeur de temps allant de 00:00 à 23:59:59.99

II-1-3 - Types numériques

Les types numériques comprennent des types exacts et des types approchés. Parmi les types exacts, on distingue deux sous-familles : les types entiers et les types réels à représentation décimale exacte (DCB : Décimal Codé Binaire). Les types approchés sont constitués par des types réels à représentation binaire. Ils ne sont pas exacts par rapport à la numération décimale du fait que la représentation de nombres fractionnaires dans un système de calcul binaire tombe rarement juste en décimal.

II-1-3-1 - Types entiers (exacts)

Les entiers sont des valeurs numériques discrètes signées.

Type SQL

Plage de valeurs

INTEGER (ou INT synonyme)

entier 32 bits signé [ -2^31 à 2^31-1 ]

SMALLINT

entier 16 bits signé [ -2^15 à 2^15-1 ]

BIGINT

entier 64 bits signé [ -2^63 à 2^63-1 ]

Tableau 2.5 – Types numériques entiers

II-1-3-2 - Types réels

Ils se subdivisent en deux catégories : les types flottants, qui sont des approximations de réels décimaux codés en binaire et les décimaux, qui sont une représentation exacte de réels exprimés en base 10.

II-1-3-2-1 - Types flottants (approchés)

Les types réels flottants sont encodés en binaires et sont donc des valeurs approchées de la spécification décimale. En effet, il n'est pas possible de représenter dans une machine calculant en binaire les valeurs exactes de la plupart des chiffres décimaux à virgule. Par exemple, le chiffre décimal 3,875 (soit 8/31) ne peut être représenté par une composition finie de puissances inverses de 2. En effet, 3,875 se décompose en une série infinie de puissances inverses de 2 :

Pour l'ordinateur, machine finie donc limitée au calcul binaire, cette approximation successive basée sur les puissances inverses de deux ne permettra pas de représenter la valeur exacte de l'occurrence. Elle sera donc stockée sous forme binaire induisant un arrondi possible. Lorsque la valeur sera restituée, il se peut qu'il y ait une erreur d'écart d'arrondi due à la reconversion en base dix.

Type réel flottant SQL

Description

FLOAT [(n)]

réel signé dont la mantisse n peut être précisée en incluant le bit de signe.

REAL

réel signé

DOUBLE PRECISION

réel signé dont les valeurs sont supérieures au REAL

Valeurs maximale et minimale spécifiques au SGBDR
n étant la longueur en nombre de bits

Tableau 2.6 - Types réels flottants approchés

II-1-3-2-2 - Types décimaux (exacts)

Ce sont des réels dont l'encodage décimal restitue l'exacte valeur.

Types réels décimaux SQL

Description

NUMERIC [ (p [, s] ) ]

réel signé représentant exactement un nombre décimal.

DECIMAL [ (p [, s] ) ]

idem au NUMERIC, dont les valeurs sont supérieures au NUMERIC

p étant la précision (nombre de chiffres significatifs incluant les chiffres après la virgule)
s l’échelle, c’est-à-dire le nombre de décimales (chiffres après la virgule)

Tableau 2.7 - Types réels décimaux exacts

De manière interne, la représentation du NUMERIC ou du DECIMAL est constituée par deux entiers. Le premier représente le nombre dépourvu de décimale, le second contient la valeur de l’échelle, c’est-à-dire le nombre de chiffres significatifs après la virgule. Par exemple pour un nombre tel que 123.45 codé en DECIMAL(8,4), la représentation interne sera : 1234500(4).

NOTE

- sans spécification, les valeurs sont les suivantes : n vaut 1, p vaut 0. Par exemple, NUMERIC est équivalent à NUMERIC (1) ou NUMERIC(1,0).

- la limite maximale de longueur des paramètres n et p dépend du SGBDR.

- p ne peut être supérieur à n.

Exemples 2.3 – types numériques :

INT        un entier allant de -231 à 231-1
DECIMAL (1)    un réel représentant exactement un nombre décimal pouvant aller de -9 à +9
DECIMAL    un réel représentant exactement un nombre décimal pouvant aller de -9 à +9

II-1-4 - Types binaires

Les types binaires ne sont autres que des chaînes de bits. Comme pour les littéraux, on distingue les chaînes de taille fixe et celles de taille variable.

Type

Description

BIT [(n)]

Une chaîne de bits de taille fixe de longueur n bits

BIT VARYING [(n)]

Une chaîne de bits de taille variable limitée au maximum à n bits

BOOLEAN

Booléen égal à un bit et valant 0 ou 1

BINARY LARGE OBJECT [(l [u])]

Une chaîne binaire de grande taille limitée à l octets.

UNIQUIDENTIFIER /
UUID

Binaire de 16 octets dont les valeurs sont aléatoires

n étant le nombre de bits
l étant le nombre d’octets
u l’unité avec les suffixes K pour Kilo octets, M pour Mega octets, G pour Giga octets

Tableau 2.9 – types binaires

NOTE

- Par défaut, n vaut 1 pour les types BIT et BIT VARYING.

- La limite maximale de longueur du paramètre n dépend du SGBDR.

- Le type UUID ou GUID est implémenté sous différents noms dans les principaux SGBDR.

ATTENTION

Les types BIT (n) et BIT VARYING [(n)] bien que présents dans la norme SQL:1999 ont disparu de la version SQL:2003. Cela est dû à un manque d'implémentation de ces types de la part des principaux éditeurs. Ils sont souvent remplacés par des types spécifiques aux SGBDR considérés.

II-1-5 - Les types « larges » (LOB)

Outre les types CHARACTER LARGE OBJECT (ou CLOB), NATIONAL CHARACTER LARGE OBJECT (ou NCLOB) et BINARY LARGE OBJECT (ou BLOB), SQL dispose de nombreux types « larges », pour stocker des objets de grandes dimensions, comme :

- XML pour le stockage de fragments XML valides ;

- JSON pour le stockage de données JSON ;

- GEOMETRY pour le stockage des données géométriques d’un SIG ;

- GEOGRAPHY pour le stockage des données géographiques d’un SIG.

La limite de longueur de ces types est spécifique aux éditeurs.

II-2 - Problématique d'expression des littéraux

L'expression des valeurs typées répond à quelques règles simples dans la majeure partie des cas. Mais pour la chaîne de caractères, SQL introduit quelques spécificités qui donnent une grande souplesse de traitement. Cette faculté de souplesse est donnée par les mécanismes SQL de jeu de caractères et de collations, afin de faire correspondre les données littérales à l'usage que l'on en fait en non à la logique de la machine.

II-2-1 - Encodage et classement

Si nous supposons maîtriser l'alphabet et son ordre dit alphabétique, qu'en est-il de l'ordinateur et de son jeu de caractères sous-jacent ?

La table de correspondance des codes de caractères du jeu 850 (multilingue Européen) est la suivante :

Figure 2.2 : table de caractères du jeu ASCII 850 (OEM Latin 1) – Source Wikipedia

À chaque signe typographique est associé un code numérique. Pour autant, ce code ne permet pas le classement des lettres parce qu'il ne respecte pas un ordre alphabétique que nous estimerions naturel... Prenons quelques mots : béton, Mâcon, maçon, BÊTES, bedon, masse, BUTIN, MATOU et analysons leur ordonnancement en fonction du code de caractères :

Mot

Code décimal

Rang calculé

béton

098 130 116 111 110

6

Mâcon

077 131 099 111 110

4

maçon

109 097 135 111 110

8

BÊTES

066 210 084 069 083

2

bedon

098 101 100 111 110

5

masse

109 097 115 115 101

7

BUTIN

066 085 084 073 078

1

MATOU

077 065 084 079 085

3

Si nous acceptions l'ordre induit par ce code, alors nous classerions ces mots comme suit : BUTIN, BÊTES, MATOU, Mâcon, bedon, béton, masse, maçon. C'est un ordre logique pour la machine et insensé pour notre esprit.

C'est pourquoi les éditeurs de systèmes d'exploitation ont ajouté à leur programme des bibliothèques de classement spécifiques à une ou plusieurs langues en correspondance avec les jeux de caractères sous-jacents. Dans l'ordre alphabétique dit « latin », nos mots sont donc classés de la façon suivante : bedaine, BÊTE, béton, BUTIN, Mâcon, maçons, massons, MATONS, ce qui ressemble plus à ce que nous attendions intuitivement. Le seul inconvénient des classements proposés par les éditeurs d'OS est qu'ils sont souvent spécifiques et parfois interprétés tant les combinaisons sont grandes et les normes imprécises. De manière à proposer un mécanisme de classement inter plateformes, SQL a imposé l'ajout la notion de collation.

II-2-2 - Collations

Une collation est une énumération de signes typographiques et leurs équivalences afin d’en déduire le comportement des traitements des chaînes de caractères notamment dans les opérations de comparaison et de tri spécifiques à une langue.

Une collation SQL permet de définir la position ordinale des caractères de base et des caractères particuliers que sont :

- les caractères diacritiques : accents, cédille, tilde...

- les ligatures: æ ç, œ, ß...

Mais le mécanisme de collation offre quelques attraits supplémentaires, comme la possibilité de définir :

- l'équivalence ou non entre majuscules et minuscules ('a' <=> 'A') autrement dit, la sensibilité à la casse ;

- l'équivalence ou non entre caractères diacritiques et caractères simples ('à' <=> 'a') autrement dit, la sensibilité aux accents et autres caractères diacritiques ;

- l'équivalence ou non de la largeur du caractère ('2' <=> '²') autrement dit la sensibilité à la largeur du caractère ;

- l’équivalence entre les formes Katakana et Hiragana des caractères du japonais () autrement dit la sensibilité aux kanatypes (équivalente aux écritures cursives et d’imprimerie – lettres capitales – de notre langue).

En fonction de la collation, les expressions suivantes donneront des résultats différents :

comportement

expression

sensibilité

insensibilité

Casse

'a' = 'A'

CS

CI

Diacritiques

'a' = 'à'

AS

AI

Largeur

'2' = '²'

WS

Kanatypes

'か' = 'カ'

KS

Le symbole japonais utilisé dans notre exemple est le « KA »

Tableau 2.10 – paramètres du comportement des chaines de caractères

En outre, la collation définit la langue dans laquelle le tri des lettres doit s’effectuer. Si cet ordre parait trivial pour les lettres de A à Z que présente notre alphabet français, qu’en est-il si l’on distingue les majuscules des minuscules ?

- Quelle est la place des caractères accentués face aux caractères basiques ?

- Quel est l’ordre des différents accents ?

Les règles diffèrent d’une langue à l’autre. Pour le cas du français, l’ordre pour toutes les formes de la première lettre de notre alphabet est le suivant :

a, A, á, Á, à, À, â, Â, ä, Ä, æ, Æ

Où l’on voit que les lettres minuscules précèdent les lettres majuscules et que l’ordre des accents est : aigu, grave, circonflexe, tréma, suivi en dernier par la ligature « æ » qui figure notamment dans les mots cæcum, ex æquo et le prénom Lætitia.

Mais la langue française complique la chose lorsque plusieurs accents figurent dans le mot et pour les mots composés…

Dans le cas d’accents multiples dans des littéraux homographes, alors le premier accent rencontré dans l’ordre de lecture classe le littéral après et le second avant… Le cas classique, présenté dans l’exemple 2.4 est celui de l’élève…

Exemple 2.4 – Classement des littéraux homographes comprenant des caractères diacritiques différents :

élève, élevé

Pour les mots composés, par exemple ceux contenant un tiret, le tri ne porte que sur les lettres dont les caractères de séparation ont été expurgés…

Exemple 2.5 – Classement des littéraux avec des mots composés ou non :

vicelard, vice-président, vices

Dans l’exemple 2.5, le classement des différents mots ignore le tiret et place le vice-président entre un vicelard et les vices !

Pour les ligatures, le classement est effectué comme si la ligature était décomposée en deux lettres…

Exemple 2.6 – Classement des littéraux ayant des ligatures ou non :

coercitif, cœur, coexistant

Ainsi, le nom d’une collation est constitué généralement du nom de la langue suivi des paramètres indiquant le traitement de la casse, des diacritiques, de la largeur et des kanatypes, certains paramètres pouvant être ignorés.

L’exemple 2.7 montre une requête de contrôle effectuée sur le SGBDR Microsoft SQL Server.

Exemple 2.7 – Requête de contrôle :

1. WITH T AS

2. (SELECT *

3. FROM (VALUES ('À'), ('A'), ('a'), ('à'),

4. ('élève'), ('élevé'),

5. ('vice-président'), ('vicelard'), ('vices'),

6. ('cœur'),

7. ('coercitif'),

8. ('coexistant'))

9. AS W(m))

10. SELECT m AS mot

11. FROM   T

12. ORDER  BY m COLLATE French_CS_AS;

Dont le résultat est :

mot
--------------
a
A
à
À
coercitif
cœur
coexistant
élève
élevé
vicelard
vice-président
vices

Pour exécuter la requête de l’exemple 2.7 sur différents SGBDR, changez le nom de la collation (ici French_CS_AS) par une collation disponible sur le SGBDR visé.

II-2-3 - La solution SQL

SQL offre la possibilité de définir le jeu de caractères utilisable et, dans ce jeu de caractères, de définir la collation.

L'implémentation normative de SQL offre le choix entre les jeux de caractères suivants :

- SQL_TEXT

- SQL_CHARACTER

- GRAPHIC_IRV (aussi appelé ASCII_GRAPHIC)

- LATIN1

- ISO8BIT (aussi appelé ASCII_FULL)

- CODEPAGE_0437

- CODEPAGE_0850

- CODEPAGE_1250

- CODEPAGE_1251

- CODEPAGE_1253

- CODEPAGE_1254

- CODEPAGE_1257

- UNICODE (aussi appelé ISO10646)

Les collations prédéfinies par la norme SQL possèdent le même nom que le jeu de caractères auquel ils se réfèrent.

SQL utilise les mots clefs CHARACTER SET et COLLATE pour définir le jeu de caractères et la collation sous-jacente à un littéral. Lorsque l'on utilise un type littéral, il est possible de définir en même temps son jeu de caractères et/ou sa collation. Il y a toujours un jeu de caractères et une collation par défaut, soit au niveau du schéma (la base de données), soit au niveau du SGBDR.

Ainsi un littéral de type CHARACTER peut s'exprimer de la façon suivante :

CHARACTER (n)
| CHARACTER VARYING (n)
| NATIONAL CHARACTER  (n)
| NATIONAL CHARACTER VARYING (n)
[ CHARACTER SET jeu_de_caractères ]
[ COLLATE collation ]

Exemples 2.8 – définition d’un jeu de caractères et de sa collation selon la norme SQL :

CHARACTER (32) CHARACTER SET LATIN1 COLLATE LATIN1
-- Littéral codé sur trente-deux octets, de taille fixe 32 caractères,
-- basé sur jeu de caractères LATIN1 et collation LATIN1

II-2-4 - La solution des éditeurs

Les éditeurs ont généralement travaillé à l’envers de ce que la norme propose en imposant d’abord de préciser la collation et par ce choix d’imposer le jeu qui caractères pour supporter une telle collation (collations sémantiques), ou bien encore offrir des collations qui représentent des jeux de caractères précis (collations techniques). Ceci parce que tous les OS(16) ne permettent pas d’implémenter tous les jeux de caractères…

Figure 2.3 – liste partielle des collations disponibles dans Microsoft SQL Server 2022

La figure 2.3 montre quelques-unes des 5 502 collations disponibles dans la version 2022 de Microsoft SQL Server. On y distingue des collations purement sémantiques dont celle consacrée aux français avec ses nombreuses dérivations et quelques collations techniques dont celles représentant le jeu de caractères EBCDIC d’IBM et ses dérivés.

En règle générale, le serveur de base de données est installé avec une collation qui deviendra celle de toutes les bases de données à défaut de précision.

Pour appliquer une collation par défaut au niveau de la base, il convient de la spécifier dans la commande de création de la base, généralement CREATE DATABASE.

La collation de chaque colonne littérale des tables est à défaut celle de la base. Sinon, il faut préciser cette collation dans la commande décrivant la création de la table, pour la colonne considérée, ou lors du rajout d’une colonne à une table existante. Cela se fait juste après mention du type ou du domaine de la colonne.

Enfin, l’opérateur COLLATE peut être appliqué à n’importe quelle expression littérale figurant à n’importe quel endroit d’une requête afin d’appliquer une collation différente à une opération, ou bien résoudre un problème de conflit de collation, lorsqu’un tel problème apparait quand est appliquée une comparaison de deux littéraux ayant des collations différentes.

II-2-5 - Remarque sur la complétion des littéraux

Nous avons dit que les chaines de caractères de longueur fixe se voyaient compléter par le caractère "blanc" dès lors que l'information était moins longue que la longueur de la chaine. Mais ce n'est pas pour autant que les chaines de longueur variable sont dénuées de caractère de complétion. Ce dernier n'est pas ajouté automatiquement à la chaine, mais il peut être présent dans la saisie... Et cela provoque des effets de bords que nous allons voir.

Exemple 2.9 – Effet du caractère de complétion sur différentes opérations

-- Avec la table suivante :

CREATE TABLE T_STR (STR_ID INT, STR_1 VARCHAR(16), STR_2 VARCHAR(16));
INSERT INTO T_STR VALUES (1, 'chaine', 'chaine');
INSERT INTO T_STR VALUES (2, 'chaine', 'chaine ');
INSERT INTO T_STR VALUES (3, 'chaine ', 'chaine ');
INSERT INTO T_STR VALUES (4, '', '');
INSERT INTO T_STR VALUES (5, '', NULL);
INSERT INTO T_STR VALUES (6, NULL, NULL);

-- Dont le contenu est :

STR_ID      STR_1            STR_2
----------- ---------------- ----------------
1           chaine           chaine
2           chaine           chaine
3           chaine           chaine
4
5                            NULL
6           NULL             NULL

-- À la lecture, les paires de requêtes suivantes
-- semble donner le même résultat ; or il n'en est rien...

SELECT * FROM T_STR WHERE CHARACTER_LENGTH(STR_1) = CHARACTER_LENGTH(STR_2);
SELECT * FROM T_STR WHERE OCTET_LENGTH(STR_1) = OCTET_LENGTH(STR_2);

-- La fonction CHARACTER_LENGTH donne la longueur en nombre de caractères
-- La fonction OCTET_LENGTH donne la longueur en nombre d’octets

STR_ID      STR_1            STR_2
----------- ---------------- ----------------
1           chaine           chaine
2           chaine           chaine
3           chaine           chaine
4

STR_ID      STR_1            STR_2
----------- ---------------- ----------------
1           chaine           chaine
3           chaine           chaine
4

SELECT * FROM T_STR WHERE STR_1 LIKE 'chaine_'
SELECT * FROM T_STR WHERE STR_1 = 'chaine '

-- L’opérateur LIKE recherche un motif dans une chaine :
-- le caractère « blanc souligné » indique qu’un caractère quelconque
-- doit impérativement être présent

STR_ID      STR_1            STR_2
----------- ---------------- ----------------
3           chaine           chaine

STR_ID      STR_1            STR_2
----------- ---------------- ----------------
1           chaine           chaine
2           chaine           chaine
3           chaine           chaine

SELECT * FROM T_STR WHERE SUBSTRING(STR_1 FROM 7 FOR 1) = ' ';
SELECT * FROM T_STR WHERE STR_1 LIKE '______ '

-- La fonction SUBSTRING renvoie une sous-chaine de la chaine de caractères,
-- commençant à (FROM) pour une longueur de (TO)

STR_ID      STR_1            STR_2
----------- ---------------- ----------------
1           chaine           chaine
2           chaine           chaine
3           chaine           chaine
4
5                            NULL

STR_ID      STR_1            STR_2
----------- ---------------- ----------------
3           chaine           chaine

Autrement dit, deux chaînes de caractères peuvent avoir la même valeur (égalités) sans pour autant être identiques !

II-3 - Expression des valeurs de données

Nous allons maintenant voir comment une valeur doit être spécifiée pour correspondre à un type SQL.

II-3-1 - Expression des littéraux

Un littéral s'exprime sous la forme d'une suite de caractères délimitée par une paire d'apostrophes. Si une chaîne de caractères comporte une apostrophe, il convient de la doubler.

Tout littéral est implicitement typé par défaut sous forme de VARCHAR.

Si vous désirez spécifier un encodage UNICODE, il convient de préfixer la chaîne avec la lettre N (en majuscule).

Exemple 2.10 – Expression de littéraux :

'BONJOUR'
-- Chaîne VARCHAR contenant "BONJOUR"
'AUJOURD''HUI'
-- Chaîne VARCHAR contenant "AUJOURD'HUI"
N'Mercredi 3 septembre'
-- Chaîne NVARCHAR contenant "Mercredi 3 septembre"
'Un' 'grand' 'pas' 'pour' 'l''humanité'
-- cinq chaînes de caractères VARCHAR

II-3-2 - Expression des nombres

Les nombres s'expriment sans espaces, le point servant de séparateur décimal. Les puissances de dix nécessitent l'usage du symbole E. Les signes + et - sont utilisables.

Exemple 2.11 – Expression des nombres :

123    entier 123
1E2    entier 10
1.0    réel 1,0
1.0E7    réel 10000000,0
-2.78E-3    réel -0,00278

Plus récemment, la norme SQL:2023 introduit la possibilité d’utiliser comme séparateur entre les caractères numériques le blanc souligné (underscore) à tout endroit entre deux chiffres. Ainsi un nombre exprimé comme suit : 123_45_6.25_1 est valide.

II-3-3 - Expression des chaînes binaires

Les chaînes binaires sont délimitées par des apostrophes et se composent soit d'une suite de zéro préfixée B, soit d'une suite de symboles hexadécimaux préfixée X. Toute chaîne binaire est implicitement typée sous forme de VARBINARY.

Exemple 2.12 – Expression des chaines binaires :

X'FF3A00' -- valeur hexadécimale du binaire du chiffre décimal16726528
B'111111110011101000000000' -- valeur binaire du chiffre décimal16726528

Plus récemment, la norme SQL:2023 a défini trois nouvelles expressions pour les chaines binaires, composées d’un préfixe de deux caractères (le premier étant toujours le zéro) suivi de la chaine exprimée par des symboles (liste de caractères autorisés) limités au type que le préfixe indique :

préfixe

type

symboles

0x

héxadécimal

0 à 9 puis A, B, C, D, E, F

0o

octal

0 à 7

0b

binaire

0 à 1

Ainsi que la possibilité d’utiliser le caractère blanc souligné (underscore) comme séparateur entre les chiffres comme cité au §2.3.2 en complément avec les préfixes 0x, 0o et 0b. Par exemple, la chaine numérique binaire 0x123_ABF est un binaire valide.

II-3-4 - Expression des données temporelles

SQL fixe comme principe que les dates et heures doivent être spécifiées dans l'ordre logique de la plus grande composante de temps à la plus petite sous la forme d’un littéral :

- une date s'exprime en commençant par l'année sur quatre chiffres (de 1 à 9999), suivie du mois sur deux chiffres (de 01 à 12) et du jour sur deux chiffres (de 01 à 31) ;

- un temps, commence par l'heure sur deux chiffres allant de 00 à 23, suivie de la minute sur deux chiffres allant de 00 à 59, de la seconde sur deux chiffres allant de 00 à 61 * et les éventuelles décimales (tantièmes de secondes) sur 1 à n positions en fonction de la précision n ;

- le séparateur des éléments de date est le tiret (« - ») et pour le temps, le deux-points (« : ») ou le point (« . ») pour les fractions de décimales ;

- le séparateur entre la partie date et la partie heure, si besoin,est l’espace ou le caractère T en majuscule. La partie heure d’un composé date + temps est toujours facultative, l’heure par défaut étant 0 ;

- le fuseau horaire est exprimé sous forme du signe + ou – suivi de l’heure sur deux chiffres (de 00 à 14), puis du séparateur deux-points et enfin des minutes sur deux chiffres (de 00 à 59).

Les formes possibles d’expression des temporels sont :

- Date seule :

'AAAA-MM-JJ'

- Temps seul :

'hh:mm'
'hh:mm:ss'
'hh:mm:ss.n'
'hh:mm:ss.nn'
…

- Temps seul avec fuseau horaire

'hh:mm:ss ±hh:mm'
'hh:mm:SS.n ±hh:mm'
'hh:mm:SS.nn ±hh:mm'
...

- Combiné Date + Temps (horodatage) :

'AAAA-MM-JJ hh:mm'
'AAAA-MM-JJ hh:mm:ss'
'AAAA-MM-JJ hh:mm:ss.n'
'AAAA-MM-JJ hh:mm:ss.nn'
...

… ou encore :

'AAAA-MM-JJThh:mm'
'AAAA-MM-JJThh:mm:ss'
'AAAA-MM-JJThh:mm:ss.n'
'AAAA-MM-JJThh:mm:ss.nn'
...

- Combiné Date + Temps (horodatage) avec fuseau horaire :

'AAAA-MM-JJ hh:MM:ss ±hh:mm'
'AAAA-MM-JJ hh:mm:ss.n ±hh:mm'
'AAAA-MM-JJ hh:mm:ss.nn ±hh:mm'
...

Le nombre de tantièmes de seconde possible (précision) dépend du SGBDR utilisé.

Ou :

- A représente un chiffre d’année ;

- M représente un chiffre de mois ;

- J représente un chiffre de jour ;

- h représente un chiffre d’heure ;

- m représente un chiffre de minute ;

- s représente un chiffre de seconde ;

- n représente un chiffre de tantièmes de secondes ;

- T est à prendre au sens littéral pour la séparation entre partie jour et partie heure.

Pour le type INTERVAL, les durées peuvent être exprimées en :

- années

- années et mois

- et toutes les composantes possibles entre Jours et secondes.

Le mot clef INTERVAL doit préciser la durée exprimée sous forme littérale, suivie d’un préfixe indiquant la précision, soit d’une seule composante, soit de la composante la plus grande à la plus petite, séparée avec le mot « TO » avec les mots clefs représentant les composantes (YEAR, MONTH, DAY, HOUR, MINUTE, SECOND).

Exemple 2.13 – Expression de données temporelles :

'2004-11-22'    type DATE au 22 novembre 2004
'00:11:22.333'    type TIME à 0h 11m 22s 333 millièmes de seconde
'2000-12-31 23:59:59.999'    type TIMESTAMP  pour le 31 décembre 2000 à 23h 59m 59s et 999 millièmes de seconde
'18:20:00+02:00'    type TIME WITH TIMEZONE pour 18h 20m avec décalage de 2 heures sur UTC
INTERVAL '0003-04' YEAR TO MONTH    type INTERVAL de durée 3 ans 4 mois
INTERVAL '-22 11:33' DAY TO MINUTE    type INTERVAL de durée moins 22 jours, 11 heures et 33 minutes
INTERVAL '08' HOUR    type INTERVAL de durée 8 heures
CAST('2004-11-22') AS TIMESTAMP     type TIMESTAMP avec heure à 0

II-3-5 - Typage explicite avec la fonction CAST

Lorsqu’on exprime une valeur, on peut la transtyper de manière explicite en faisant appel à la fonction SQL CAST.

La syntaxe de la fonction CAST est la suivante :

CAST ( expression AS <type_sql> )

Ou expression est une expression SQL renvoyant une valeur et type_sql un type de données SQL.

Exemple 2.14 – Expression de données temporelles avec typage explicite :

CAST('2004-11-22' AS DATE)    Conversion d'un littéral ASCII ou EBCDIC en date
CAST('123') AS INTEGER    Conversion d'un littéral ASCII ou EBCDIC en entier
CAST('ABC' AS BINARY(3))    Conversion d'un littéral ASCII ou EBCDIC en chaîne binaire de longueur 3 octets
CAST(3.1416 AS VARCHAR(16))    Conversion d'un nombre réel en chaine de caractères de taille variables ASCII ou EBCDIC
CAST(N'ZOO' AS BINARY(6))    Conversion d'un littéral UNICODE en chaîne binaire de longueur 6 octets

NOTE

Dans bien des cas, SQL est capable d'un transtypage implicite si l'expression de valeur d'un type donné s'accorde avec le type cible. C'est le cas par exemple du transtypage d'un nombre ou d’un temporel dont le type cible est une chaîne de caractères. Néanmoins, dans ce cas, le format littéral des données sera celui prévu par défaut dans la norme SQL (voir §2.3.4).

II-4 - Domaines (DOMAIN) et autoincréments

Un domaine au sens SQL est un type SQL pouvant être doté d'une valeur par défaut prédéfinie, d'autant de contraintes dites « de domaine » qu'on le souhaite et d'éventuelles clauses CHARACTER SET et COLLATE s'il s'agit d'un type chaîne de caractères. Un domaine est alors utilisable partout dans le SQL ou l'on a besoin d'un type de données.

II-4-1 - Syntaxe SQL de création d’un domaine

La syntaxe de création des domaines est la suivante :

CREATE DOMAIN [ nom_schema.]nom_domaine
[ AS ] <type_sql>
[ <valeur_par_defaut> ]
[ <liste_de_contraintes_de_domaine> ]
[ <collate clause> ]

Avec :

<liste_de_contraintes_de_domaine> ::=
<definition_contrainte_1>
[, <definition_contrainte_2>
[, … <definition_contrainte_n> ] ]

II-4-2 - Syntaxe SQL de création des contraintes de domaine

La syntaxe des contraintes de domaine dans la liste est la suivante :

<definition_contrainte :: =
[ CONSTRAINT nom_contrainte ] CHECK ( prédicat ) [ <déférabilité> ]

NOTE

La déférabilité des contraintes est abordée dans le chapitre 3 consacré à la création des objets et détaillée au chapitre 9 consacré aux transactions.
Par défaut les contraintes de domaine sont INITIALLY IMMEDIATE NOT DEFERRABLE.

Une contrainte sert à valider la valeur de l'occurrence dans l'objet qui la reçoit et empêche toute pollution de l'objet par des données qui ne respecteraient pas le prédicat. Rappelons qu'un prédicat est une expression évaluable booléennement et qui dans ce cas doit être évaluée à Vrai.

On peut spécifier plusieurs contraintes. Dans le prédicat, le mot clef VALUE est utilisé comme référence à la valeur qu'induit le domaine. Il ne peut être utilisé que dans l'expression de validation.

Les valeurs par défaut peuvent être une expression de valeur, le marqueur NULL (absence de valeur), comme une fonction ou une expression SQL renvoyant une valeur de même type ou implicitement transtypable, mais obligatoirement déterministe. Pour empêcher l'absence de valeur dans le domaine, spécifiez une contrainte CHECK (VALUE IS NOT NULL).

Exemples 2.15 – Création de différents domaines :

Creation de différents domaines.sql

Création d'un domaine permettant la définition d'un nom de jour.

CREATE DOMAIN S_TYPE.D_JOUR_SEMAINE
AS CHAR(8)
DEFAULT 'Dimanche'
CONSTRAINT CK_D_JOUR CHECK
(VALUE COLLATE Latin_CI_AS
IN ('Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi',
'Samedi', 'Dimanche'));

Création d'un domaine pour spécification d'un pourcentage par défaut à zéro

CREATE DOMAIN S_COM.D_POURCENT
AS FLOAT
DEFAULT 0
CONSTRAINT CK_D_PC CHECK (VALUE IS NOT NULL AND
VALUE BETWEEN 0 AND 100) ;

Les mots clefs suivants peuvent être utilisés :

VALUE représente n’importe quelle valeur qui sera confrontée au domaine ;

IS NOT NULL signifie qu’aucune valeur ne pourra être absente ;

IN signifie que les valeurs seront comparées à une liste de valeurs autorisées figurant entre parenthèses ;

BETWEEN permet de définir une fourchette de valeurs bornes incluses.

En outre, les contraintes peuvent utiliser des fonctions. En voici quelques exemples :

Création d'un domaine pour spécifier une date de naissance

CREATE DOMAIN S_CALEND.D_DATE_NAISSANCE
AS DATE
DEFAULT CURRENT_DATE
CONSTRAINT CK_D_DN (VALUE <= CURRENT_DATE) ;

Création d'un domaine à valeur obligatoire pour les noms spécifiés en majuscules

CREATE DOMAIN S_GENERIC.D_NOM
AS CHAR(16) COLLATE French_CI_AI
CONSTRAINT CK_D_NOM_LETTRES CHECK
(TRANSLATE(VALUE , 'ABCDEFGHIJKLMNOPQRSTUVWXYZ –''',
'                             ') = ''),
CONSTRAINT CK_D_NOM_COMMENCE CHECK
(LEFT(VALUE, 1) NOT IN (' ', '-' , '''')),
CONSTRAINT CK_D_NOM_COMMENCE CHECK
(RIGHT(VALUE, 1) NOT IN (' ', '-' , '''')),
CONSTRAINT CK_D_NOM_MAJ CHECK
(VALUE COLLATE French_CS_AI = UPPER(VALUE));

NOTE

Les contraintes de ces exemples de domaines utilisent les opérateurs IN, BETWEEN et COLLATE du SQL, ainsi que les fonctions CURRENT_DATE, TRANSLATE, LEFT, RIGHT et UPPER qui permettent d’obtenir la date courante, une substitution de caractères, l’extraction des premiers caractères à droite ou à gauche et la mise en majuscule. Nous verrons ces fonctions et les opérateurs du SQL dans le chapitre 4.

Quel que soit le nombre de lignes visées par un ordre SQL (INSERT, UPDATE, DELETE), si une seule violation de contrainte intervient, alors tout le lot de lignes est rejeté, même si la violation de contrainte ne portait que sur une seule valeur.

Pour une syntaxe plus détaillée de la contrainte CHECK, veuillez vous reporter au chapitre suivant.

Il est possible de modifier un domaine à l'aide de l'ordre ALTER et de le supprimer à l'aide de l'ordre DROP.

L'avantage des domaines réside dans la standardisation des types : un même domaine est utilisable pour toutes les colonnes ayant une sémantique similaire (nom de client / nom d'employé par exemple). En outre, dans les SGBDR, le recours aux domaines minimise les entrées/sorties puisqu'un type et ses contraintes n'utilisent qu'un seul et même espace mémoire pour exécuter le code de validation des contraintes de domaine afférent aux différentes colonnes.

II-4-3 - Les incréments ou décréments automatiques

Une technique assez classique pour définir automatiquement une valeur de clef consiste à utiliser un mécanisme d’incrémentation ou de décrementation automatique. Il existe pour ce faire, deux types d’objets normalisés que l’on retrouve dans la plupart des SGBDR : la propriété IDENTITY attachée à une colonne d’une table et l’objet SEQUENCE indépendant de toute table. Nous verrons ces deux mécanismes et comment les utiliser dans le chapitre 3 consacré aux commandes de création, modification et suppression des objets (tables, vues, contraintes…), c’est-à-dire dans la partie DDL (Data Definition Language) du langage SQL.

II-5 - Les apports de typages de SQL:1999

La norme SQL:1999 a apporté les fondements du relationnel objet, c'est-à-dire l'introduction des principaux concepts objet au niveau des mécanismes relationnels. C'est ainsi que de nouveaux types de données sont apparus : tableaux, lignes, collections, références et méthodes, tous regroupés sous la forme de types abstraits incluant la notion d’héritage.

Notons cependant que ces types structurés sont contraires aux fondements des bases de données relationnelles et leur usage peut se révéler plus problématique qu'il n'apparait.

II-5-1 - Les tableaux (ARRAY)

Le type ARRAY (tableau), n'est pas à proprement parler un type, mais un constructeur de types puisque l'on doit décrire sa structure. SQL limite le type tableau à une seule dimension, la limite pouvant être fermée ou ouverte (unbounded arrays). La première cellule du tableau est d’indice 1.

Par exemple PRS_ADRESSE CHAR(38) ARRAY [4] construit une colonne de nom PRS_ADRESSE contenant 4 cellules, numérotées de 1 à 4, chacune de 38 caractères.

Certaines opérations particulières sur les tableaux sont possibles : outre la lecture, la modification et l'écriture dans les cellules, vous pouvez concaténer des tableaux et obtenir la cardinalité d'un tableau (c'est à dire le nombre de cellules).

La concaténation de tableaux s'obtient à l'aide de la fonction CONCATENATE :

Exemple 2.16 – concaténation d’éléments à un tableau de deux façons différentes :

CONCATENATE('Monseigneur', ARRAY['Monsieur', 'Madame', 'Mademoiselle']);
CONCATENATE('Monseigneur' WITH ARRAY['Monsieur', 'Madame', 'Mademoiselle']);

Rajoute en fin de tableau la cellule 4 contenant la valeur 'Monseigneur'.

La fonction CARDINALITY renseigne sur le nombre de cellules d'un tableau :

Exemple 2.17 – cardinalité d’un tableau :

CARDINALITY(ARRAY['Monsieur', 'Madame', 'Mademoiselle']);

donne : 3

L’exemple 2.18 montre comment manipuler des valeurs dans une colonne de type tableau :

Exemple 2.18* – manipulation de tableaux en SQL :

Manipulation de tableaux en SQL

CREATE TABLE T_PERSONNE_PRS
(PRS_ID  INTEGER NOT NULL PRIMARY KEY,
PRS_NOM CHAR(32),
PRS_ADR CHAR(38) ARRAY[3]) ;

INSERT INTO T_PERSONNE_PRS (PRS_ID, PRS_NOM, PRS_ADR)
VALUES (1, 'DUPONT', ARRAY['1 rue de la liberté', 'bâtiment D', NULL]) ;

UPDATE T_PERSONNE_PRS
SET PRS_ADR[3] = 'BP 484' ;

SELECT PRS_ADR[1]
FROM T_PERSONNE_PRS ;

Pour transformer un tableau en table à la volée, on peut utiliser indifféremment la fonction TABLE ou la fonction UNNEST. Le résultat est une table d’une seule colonne.

Exemple 2.19* – les deux façons de transformer un tableau en table :

Les deux façons de transformer un tableau en table

SELECT *
FROM TABLE(ARRAY[1, 7, 3]) AS T_TAB (COL);

COL
-----
1
7
3

SELECT UNNSET(ARRAY['coeur', 'carreau', 'trèfle', 'pique'])
AS COULEUR_CARTE WITH OFFSET ORD;

COULEUR_CARTE    ORD
-------------    ------
cœur             1
carreau          2
trèfle3          3
pique            4

La fonction UNNEST permet de rajouter l’ordre de prise en compte des lignes résultantes en ajoutant une colonne contenant la valeur ordinale.

La fonction inverse ARRAY_AGG permet de transformer un tableau multi entrées en un tableau d’une seule case agrégeant toutes les valeurs :

Exemple 2.20* – agrégation des entrées d’un tableau :

Agrégation des entrées d’un tableau

SELECT ARRAY_AGG(ARRAY['coeur', 'carreau', 'trèfle', 'pique'])[1] AS AAG;

AAG
--------------------------------------
"coeur", "carreau", "trèfle", "pique"

Notons aussi l’existence de la fonction TRIM_ARRAY pour expurger les cellules vides de la fin du tableau.

II-5-2 - Ligne (ROW)

Le type ligne, permettant de définir un sous-ensemble composite de données un peu à la manière des « record » de certains langages comme Pascal.

La syntaxe de la définition d'une ligne commence par le nom de type ROW :

ROW (<col1> [,<col2>...])
Coln ::= nom type [reference] [COLLATE collation]

Exemple 2.21* – utilisation du type ROW au sein d’un table :

Utilisation du type ROW au sein d’un table

CREATE TABLE T_PERSONNE_PRS
(PRS_ID  INTEGER NOT NULL PRIMARY KEY,
PRS_NOM CHAR(32),
PRS_ADR ROW (LIGNE CHAR(38) ARRAY[3],
VILLE CHAR(32),
CODE_POSTAL CHAR(5))) ;

Définit une colonne de nom PRS_ADR de type ligne contenant un tableau de 3 lignes d'adresse, suivie d'une colonne contenant la ville et d'une colonne contenant le code postal.

Pour assigner des valeurs à une ligne, on peut utiliser la technique du constructeur de ligne valorisée (row value constructor) :

Exemple 2.22 – construction d’un objet ligne :

ROW (ARRAY['1 rue de la liberté', 'bâtiment D', NULL], 'PARIS', '75015') ;

Les comparaisons de lignes peuvent s'effectuer globalement :

Exemple 2.23 – comparaison de tableau :

...
WHERE PRS_ADR = ROW((ARRAY['1 rue de la liberté', 'bâtiment D', 'BP 484'], 'PARIS', '75015') ;

On peut aussi utiliser une notation pointée pour accéder à une colonne d'une ligne :

Exemple 2.24 – comparaison d’éléments de tableau :

...
WHERE PRS_ADR.LIGNE[3] = 'BP 484' ;

ATTENTION

Ne pas confondre une ligne d’une table et une colonne de type ROW. Même si la différence est subtile, l’un est un type objet, l’autre un n-uplet (tuple en anglais)

II-5-3 - Le type MULTISET (collection)

Le MULTISET est une collection de valeurs atomiques ou de lignes (ROW).

Exemple 2.25 – deux multisets simples :

MULTISET ['Coeur', 'Carreau', 'Pique', 'Trefle']
MULTISET (SELECT DISTINCT USR_PRENOM
FROM T_UTILISATEUR_USR)

Exemple 2.26 – un multiset composite :

MULTISET (SELECT ROW(USR_NOM, USR_PRENOM)
FROM T_UTILISATEUR_USR)

L'emploi du mot clef MULTISET est obligatoire pour construire un MULTISET valorisé.

Ces types de données peuvent faire l’objet d’opérations ensemblistes : MULTISET UNION, MULTISET INTERSECT, MULTISET EXCEPT.

ATTENTION – ROW, ARRAY, MULTISET et inégalités

Les comparaisons par inégalité répondent aux mêmes principe qu’un tri multicolonne, pour lequel chaque élément est d’abord trié par rapport aux valeurs d’indice 1, puis pour des valeurs d’indice 1 identiques, trié sur l’indice 2, etc. Nous étudierons cela dans le chapitre 4 consacré au sujet du constructeur de lignes valorisées.

II-5-4 - Héritage de table

SQL:1999 permet de définir des tables héritées de tables. La table racine est dite « super table » et les tables héritées sont dites « sous-tables ». Voici un exemple de super table et de sous-table :

Exemple 2.27* – principe de l’héritage de table :

Principe de l’héritage de table

-- la super table
CREATE TABLE T_PERSONNE_PRS
(PRS_ID             INTEGER NOT NULL PRIMARY KEY,
PRS_NOM            CHAR(32) NOT NULL,
PRS_PRENOM         VARCHAR(32),
PRS_DATE_NAISSANCE DATE);

-- une sous table
CREATE TABLE T_EMPLOYEE_EMP UNDER T_PERSONNE_PRS
(EMP_MATRICULE CHAR(8) NOT NULL);

-- une autre sous table
CREATE TABLE T_CLIENT_CLI UNDER T_PERSONNE_PRS
(CLI_REMISE_NEGOCIEE FLOAT);

L’exemple 2.27 nous montre que la sous-table T_EMPLOYEE_EMP comporte les colonnes de la table T_PERSONNE_PRS auxquelles on a ajouté ses propres définitions de colonnes, ici EMP_MATRICULE.

ATTENTION

Ne pas confondre l’héritage de table et le concept de type table. Dans le cas d’un héritage de table, les modifications de la table mère sont répercutées dans les tables fille, alors que dans le cadre du type table, la table créée d’après le type emprunte la structure du type uniquement au moment de la création de la table, mais reste dissociée des évolutions du type.

II-5-5 - Les types « utilisateur » ou UDT (User Data Type)

SQL:1999 introduit la possibilité de définir trois formes de nouveaux types : les types distincts (typage fort), les types hérités et les types abstraits. La syntaxe complète de la création d'un type ne comporte pas moins de sept clauses possédant pour la plupart de nombreuses options et spécifications.

Nous allons juste faire un aperçu par l'exemple de ces différents types.

II-5-5-1 - Types DISTINCT

Un type DISTINCT est un type bâti depuis un type ordinaire de SQL comme le type INTEGER, avec cette particularité que, même dérivé d'un type de base comme INTEGER, il ne peut être combiné dans des opérations de calcul ou de comparaison qu'avec des types de même nature ou à l'aide d'opérations de transtypage. En un mot, il s'agit d'un typage fort.

Il s'agit de créer de toutes pièces de nouveaux types de données, ce qui n'était pas possible dans la notion de domaine qui couvrait un type existant restreint à des règles de validation.

La syntaxe pour exprimer un type DISTINCT est :

CREATE DISTINCT TYPE <nom_type>
AS <type_sql>
FINAL

Le mot clef FINAL indique que ce type est instanciable et peut donc être utilisé dans la définition d'une colonne de table. En revanche, il ne peut pas être utilisé pour servir d'ancêtre à un nouveau type hérité. Dans le cas du type DISTINCT, le mot clef FINAL est obligatoire... À contrario, un type non FINAL est utilisé comme définition intermédiaire afin de construire d'autres types (héritage de type).

Exemple 2.28* – création de types :

Creation de types.sql

CREATE DISTINCT TYPE MONNAIE_DOLLAR AS DECIMAL(16,2) FINAL;

CREATE DISTINCT TYPE MONNAIE_EURO AS DECIMAL(16,2) FINAL;

Dès lors, l'utilisateur dispose d'un typage fort lié au type de colonne de la table. On utilise un tel type, comme un type SQL habituel dans la définition d'une table.

Exemple 2.29* – utilisation de type dans la définition d’une table :

Utilisation de type dans la définition d’une table.sql

CREATE TABLE T_COMPTABILITE_CPT
(CPT_ID                 INTEGER        NOT NULL PRIMARY KEY,
CPT_LIBELLE_ECRITURE   VARCHAR(32)    NOT NULL,
CPT_DATE               DATE           NOT NULL DEFAULT CURRENT_DATE,
CPT_DEBIT_US           MONNAIE_DOLLAR  NOT NULL DEFAULT 0
CHECK VALUE >=0,
CPT_DEBIT_UE           MONNAIE_EURO   NOT NULL DEFAULT 0
CHECK VALUE >=0,
CPT_CREDIT_US          MONNAIE_DOLLAR  NOT NULL DEFAULT 0
CHECK VALUE >=0,
CPT_CREDIT_UE          MONNAIE_EURO   NOT NULL DEFAULT 0
CHECK VALUE >=0,
CPT_COURS_DOLLAR_EURO  FLOAT) ;

Désormais, toute comparaison ou opération entre types distincts différents est impossible.

Exemple 2.30* – utilisation de colonnes ayant des types distincts dans une requête :

Utilisation de colonnes ayant des types distincts dans une requête

SELECT *
FROM   T_COMPTABILITE_CPT
WHERE  CPT_DEBIT_US * CPT_COURS_DOLLAR_EURO = CPT_DEBIT_UE

Ainsi, la requête de l’exemple 2.28 génère une exception, cela afin de ne pas mélanger les serviettes et les torchons !

En effet, on ne peut comparer que ce qui est comparable. L'adition de litre d'essence avec des heures de vol n'ayant aucun sens, la comparaison directe de valeurs financières de différentes monnaies n'a pas non plus de sens... à moins que vous ne spécifiiez dans votre requête une conversion explicite...

Exemple 2.31* – utilisation de colonnes ayant des types distincts dans une requête à l’aide d’un transtypage :

Utilisation de colonnes ayant des types distincts dans une requête à l’aide d’un transtypage

SELECT *
FROM   T_COMPTABILITE_CPT
WHERE  CAST(CPT_DEBIT_FRANC AS FLOAT) * 6.55957 = CAST(CPT_DEBIT_EURO AS FLOAT)

La norme SQL:1999 précise qu'un type utilisateur peut être construit avec des méthodes (type abstrait), dont certaines peuvent servir à définir des opérations de transtypage.

II-5-5-2 - Type hérité

Un type hérité est un type qui prend pour référence un type parent et lui ajoute ses propres éléments. Il s'agit de l'héritage classique que l'on trouve dans la plupart des langages de programmation actuels, connus sous le terme générique « langage objet ».

Dans un type hérité, le mot clef INSTANTIABLE signifie que le type peut servir à la définition d'une variable comme d'une colonne de table. Dans le cas contraire (NOT INSTANTIABLE) il ne peut en aucun cas être utilisé pour y recevoir des valeurs et ne sert qu'à la définition d'autres types (en un sens il s'agit d'un objet abstrait). Comme évoqué précédemment, FINAL signifie que le type ne peut qu'être distinct. Un type héritable ne peut donc qu'être NOT FINAL !

NOTE

SQL, ne supporte que l'héritage simple, contrairement à certains langages comme SmallTalk ou C++ qui autorisent l'héritage multiple.

L’exemple 2.30 montre les concepts de type hérité, de « super type » et de « sous type » :

Exemple 2.32* – types hérités, super-type et sous-type :

Types hérités, super-type et sous-type

CREATE TYPE oeuvre
AS (titre VARCHAR(256),
date_creation DATE))
NOT INSTANTIABLE
NOT FINAL;

CREATE TYPE original UNDER oeuvre
INSTANTIABLE
NOT FINAL;

CREATE TYPE objet UNDER oeuvre
AS (longueur FLOAT,
largeur  FLOAT,
hauteur  FLOAT,
poids    FLOAT)
NOT INSTANTIABLE
NOT FINAL;

CREATE TYPE peinture UNDER objet
AS (support VARCHAR(64))
INSTANTIABLE
NOT FINAL;

CREATE TYPE sculpture UNDER objet
AS (matière VARCHAR(32))
INSTANTIABLE
FINAL;

CREATE TYPE livre UNDER oeuvre
AS (date_premiere_edition DATE,
langue varchar(32))
INSTANTIABLE
FINAL;

Quelques remarques concernant ce script SQL :

- « oeuvre » est un super type. C'est l'ancêtre de tous les types que nous allons dériver. Notons que « œuvre » n'est pas instanciable. C'est une coquille qui sert de « moule » pour réaliser nos autres types ;

- ainsi « original" est un type instanciable, c'est-à-dire que l'on peut s'en servir pour y placer une œuvre dotée des seuls attributs titre et date_creation ;

- « objet » est à nouveau un titre non instanciable dans le sens où il sert de moule complémentaire à œuvre, en lui adjoignant de nouvelles caractéristiques ;

- « peinture » est un type instanciable dont les caractéristiques sont, en définitive : titre, date_creation, longueur, largeur, hauteur, poids et support ;

- de même, « sculpture » est aussi un type instanciable dont les caractéristiques sont, en définitive : titre, date_creation, longueur, largeur, hauteur, poids et matière. Notez qu'il ne peut pas être repris pour créer un nouveau sous type du fait du mot clef FINAL ;

- enfin, « livre » est un type instanciable construit à partir du type générique œuvre. Il possède donc les caractéristiques suivantes : titre, date_creation, date_premiere_edition, langue. Notez, comme dans le cas de « sculpture » qu'il ne peut pas être repris pour créer un nouveau sous type du fait du mot clef FINAL ;

- « original », « objet », « peinture », « sculpture » et « livre » sont des sous types ;

- « sculpture » et « livre » sont en outre des types distincts à cause du mot clef FINAL.

Voici une syntaxe de base pour la définition d'un type SQL :

CREATE TYPE <nom_type> [ UNDER nom_super_type ]
[ <external_java_type_clause> ]
[ AS représentation ]
[ [ NOT ] INSTANTIABLE ]
[ [ NOT ] FINAL ]

- [ <external java type clause> ] permet d'utiliser un type d'après les types du langage Java ;

- [ AS représentation ] définition de la liste des attributs et de leurs types ;

- [ [ NOT ] INSTANTIABLE ] peut (ou ne peut pas) être utilisé pour la définition d'une colonne (objet abstrait) ;

- [ [ NOT ] FINAL ] ne peut pas (ou peut) être repris pour la définition d'un sous type.

II-5-5-3 - Types abstraits

Un type abstrait (ADT Abstract Data Type) peut être construit sous la forme d'une structure de données (attributs) et posséder des routines qui lui sont applicables (méthodes). Le type abstrait peut aussi être construit par héritage.

On décrit donc une structure de type abstrait au niveau des attributs, d'une manière semblable à la liste des colonnes d'une table.

Exemple 2.33* – un type abstrait :

Un type abstrait

CREATE TYPE ADT_ADRESSE
AS
(ADR_LIGNE1       VARCHAR(38),
ADR_LIGNE2       VARCHAR(38),
ADR_LIGNE3       VARCHAR(38),
ADR_CODE_POSTAL  CHAR(5),
ADR_VILLE        CHAR(32))
NOT FINAL;

Bien entendu, on peut composer un type abstrait à partir d’un autre type abstrait et ainsi de suite...

Exemple 2.34* – types abstraits emboités :

Types abstraits emboités

CREATE TYPE ADT_PERSONNE
AS
(PRS_NOM             CHAR(32),
PRS_PRENOM          VARCHAR(32),
PRS_DATE_NAISSANCE  DATE,
PRS_SEXE            CHAR(1),
PRS_ADRESSE         ADT_ADRESSE,
METHOD PRS_AGE() RETURNS FLOAT)
NOT FINAL;

Dans cet exemple de types abstraits « gigogne », on trouve une adresse dans la définition du type abstrait de la personne !

Pour implémenter une méthode applicable à un attribut, SQL:1999 se sert de la notion « d'attribut virtuel ». Cela nécessite la définition d'une fonction qui sera greffée sur le type abstrait. Ainsi, pour donner l'âge de notre personne, nous pouvons créer une méthode comme le montre l’exemple 2.33.

Exemple 2.35* – ajout d’une méthode à un type abstrait :

Ajout d’une méthode à un type abstrait

CREATE METHOD AGE() FOR ADT_PERSONNE
RETURNS INT
SELF AS RESULT
LANGUAGE SQL
DETERMINISTIC
CONTAINS SQL
RETURN NULL ON NULL INPUT
RETURN INTERVAL YEAR (CURRENT_DATE - ADT_PERSONNE.PSR_DATE_NAISSANCE) -
CASE WHEN EXTRACT(MONTH FROM ADT_PERSONNE.PSR_DATE_NAISSANCE)
> EXTRACT(MONTH CURENT_DATE) THEN 1
WHEN EXTRACT(MONTH FROM ADT_PERSONNE.PSR_DATE_NAISSANCE)
= EXTRACT(MONTH CURENT_DATE) AND
EXTRACT(DAY FROM ADT_PERSONNE.PSR_DATE_NAISSANCE)
> EXTRACT(DAY CURENT_DATE) THEN 1
ELSE 0
END;

Étant donné que le type abstrait est souvent une structure de données complexe, il n'est pas toujours possible d'utiliser un classement implicite. C'est pourquoi SQL a défini la possibilité de créer le schéma d'ordonnancement des données du type à l'aide de l'ordre CREATE ORDERING qui permet de définir soit le comportement de l'opérateur d'égalité, soit le comportement du tri à l'aide d'une routine.

NOTE
La méthode AGE de l’exemple 2.33 utilise les fonctions YEAR, MONTH, DAY, EXTRACT qui manipulent des données temporelles et renvoient respectivement l’année, le mois, le jour du mois et une partie de date. Nous verrons ces fonctions et les opérateurs du SQL dans le chapitre 4.

II-5-6 - Type TABLE ou table objet

Il est possible de créer une table à partir d'un type utilisateur. En fait, plutôt que de parler d'un type TABLE, on devrait parler de table « type » (un modèle de table), ou encore de table objet, car elle est définie à partir d'un type abstrait.

L’exemple 2.34 va permettre de mieux comprendre l'utilité de ce type et sa syntaxe. Supposons que nous voulons créer un type disque (contenant des chansons) :

Exemple 2.36* – type TABLE instanciable :

type TABLE instanciable

CREATE TYPE DISQUE
AS (titre VARCHAR(256),
date_creation DATE,
nombre_plage INTEGER)
INSTANTIABLE
NOT FINAL
REF IS SYSTEM GENERATED;

Comme ce type va servir à générer des sous-types, il est indispensable de préciser comment la référence à une ligne de ce type est générée. Ici c'est le SGBDR qui s'occupe de générer la valeur de référence, le « pointeur » en quelque sorte (REF IS SYSTEM GENERATED).

À partir de ce type, nous pouvons créer le type CD :

Exemple 2.37* – création d’une table d’après un type table père :

Création d’une table d’après un type table père

CREATE TYPE CD UNDER DISQUE
AS (editeur VARCHAR(32),
numero_serie CHAR(16)))
INSTANTIABLE
NOT FINAL;

Dès lors, notre CD comporte 5 colonnes : celles du disque et celles propres au CD.

Maintenant, voyons comment créer des tables à partir de ces types :

Exemple 2.38* – création d’une table d’après un type, table :

création d’une table d’après un type, table

CREATE TABLE T_CD OF CD
(REF IS CD_ID SYSTEM_GENERATED);

Notez le mot clef OF qui permet à partir d'un type d'instancier une table.

Le mécanisme de référence permet de créer une colonne particulière dont la valeur « transparente » est unique et peut servir de référence à la manière d'un pointeur. Dans le présent cas, la colonne CD_ID de type REF possède une valeur générée par le SGBDR...

Créons maintenant une table permettant de spécifier des CD « courts » (des « single » ne comportant pas plus de deux titres !) :

Exemple 2.39* - création d’une table d’après un type table avec colonnes supplémentaires :

création d’une table d’après un type table avec colonnes supplémentaires

CREATE TABLE T_SINGLE_CD OF CD
UNDER T_CD
( nombre_plage WITH OPTIONS
CONSTRAINT pas_plus_de_deux_titres CHECK (nombre_plage <= 2 ));

Cette nouvelle table emprunte sa structure à la table T_CD depuis le type CD et ajoute une contrainte à la colonne nombre_plage.

T_CD est une table « type ». Notons qu'à ce stade une table « type » ne peut qu'être ancêtre, jamais sous table et doit toujours comporter une déclaration de référence.

II-5-7 - Type référence

Nous avons vu dans les exemples 2.34 et 2.36, un nouveau concept avec le mot clef REF…

Un type référence (REF) est une colonne qui permet de pointer directement vers un objet ligne d'une autre table. Cela permet d'automatiser le principe des tables de référence (par exemple une table des codes postaux). Mais contrairement à l'intégrité référentielle, la valeur n'est pas contenue dans la table qui comporte la colonne référencée.

Une référence se déclare lors de la création de la table. La table référencée doit être définie au préalable.

Exemple 2.40* – utilisation du type REF :

Utilisation du type REF

-- création des UDT
CREATE TYPE UDT_CP_VILLE
AS
(CODE_POSTAL CHAR(5) NOT NULL,
VILLE VARCHAR(32) NOT NULL)
NOT FINAL ;

CREATE TYPE UDT_PAYS
AS
(CODE_PAYS VARCHAR(2) DEFAULT 'F' NOT NULL,
NOM_PAYS  VARCHAR(64) NOT NULL)
NOT FINAL ;

-- création des tables de référence
CREATE TABLE TR_CODE_POSTAL_CPL
(CPL UDT_CP_VILLE) ;

CREATE TABLE TR_PAYS_PAY
(PAY UDT_PAYS) ;

-- création de la table adresse utilisant les références
CREATE TABLE T_ADRESSE_ADR
(ADR_ID         INTEGER NOT NULL PRIMARY KEY,
ADR_LIGNE1     VARCHAR(38),
ADR_LIGNE2     VARCHAR(38),
ADR_LIGNE3     VARCHAR(38),
ADR_CP_VILLE   REF UDT_CP_VILLE SCOPE TR_CODE_POSTAL_CPL
REFERENCES ARE NOT CHECKED
ON DELETE SET NULL,
ADR_PAYS       REF UDT_PAYS SCOPE TR_PAYS_PAY
REFERENCES ARE CHECKED
ON DELETE SET DEFAULT) ;

-- remplissage des tables de référence avec des valeurs pré établies
INSERT INTO TR_PAYS_PAY (PAY..CODE_PAYS, PAY..NOM_PAYS)
VALUES ('F', 'France') ;
INSERT INTO TR_PAYS_PAY (PAY..CODE_PAYS, PAY..NOM_PAYS)
VALUES ('US', 'États Unis d''Amérique') ;

INSERT INTO TR_CODE_POSTAL_CPL (CPL..CODE_POSTAL, CPL..VILLE)
VALUES ('75001', 'Paris, 1er arrondissement') ;
INSERT INTO TR_CODE_POSTAL_CPL (CPL..CODE_POSTAL, CPL..VILLE)
VALUES ('06000', 'Nice') ;

-- exemple d’insertion d’une adresse ...
INSERT INTO T_ADRESSE_ADR
VALUES (423, '1 rue de la Paix', NULL, NULL,
(SELECT REF(CPL)
FROM   TR_CODE_POSTAL_CPL CPL
WHERE  CPL.CODE_POSTAL = '06000'),
(SELECT REF(PAY)
FROM   TR_PAYS_PAY PAY
WHERE  PAY.CODE_PAY = 'F'));

REF est un opérateur qui renvoie l'adresse de la ligne de la table référencée.

Pour obtenir la valeur du type cible d'une ligne référencée, il faut utiliser l'opérateur DEREF ou le symbole ->. Dans le cas contraire on obtiendrait simplement la valeur du pointeur (adresse de la ligne), ce qui ne présente à priori par d'intérêt !

Exemple 2.41* – valeur de référence et pointeur REF :

valeur de référence et pointeur REF

-- obtention des données
SELECT ADR_ID, DEREF(ADR).ADR_CP_VILLE
FROM   T_ADRESSE_ADR ADR
WHERE  ADR_ID = 423;

ADR_ID          ADR_CP_VILLE..CODE_POSTAL ADR_CP_VILLE..VILLE
--------------- ------------------------- -------------------------
423             06000                     NICE

-- obtention du pointeur
SELECT ADR_ID, ADR.ADR_CP_VILLE
FROM   T_ADRESSE_ADR ADR
WHERE  ADR_ID = 423;

ADR_ID          ADR_CP_VILLE
--------------- ----------------
423             51A0245154F3C681

SELECT ADR_ID, ADR->ADR_CP_VILLE
FROM   T_ADRESSE_ADR ADR
WHERE  ADR_ID = 423

ADR_ID          ADR_CP_VILLE..CODE_POSTAL ADR_CP_VILLE..VILLE
--------------- ------------------------- -------------------------
423             06000                     NICE

II-5-8 - Pointeur (LOCATOR)

Afin de faciliter la migration de données de très fort volume, la norme SQL:1999 a prévu l'utilisation d'un attribut pointeur (LOCATOR) que toute colonne peut posséder, permettant de stocker les données côté client, un peu à la manière de ce que fait un navigateur Web en plaçant les ressources sur le poste client afin de minimiser les flux sur le réseau. Mais le mécanisme n'est pas automatique et c'est au développeur de monter les ressources avant toute requête SQL utilisant un tel pointeur.

À l’exception de IBM DB2 aucun autre SGBDR n’utilise ce type particulier.

II-5-9 - Datalink

Le type DATALINK est une référence à un fichier externe, permettant par exemple de stocker des images, de la vidéo, des sons et des fichiers binaires. Les fichiers ainsi référencés restent sur un disque quelconque du système informatique. Les données sont donc externes à la base, mais il est possible de maintenir une intégrité de manière à interdire modifications et suppressions des fichiers autrement que par un ordre SQL de la base.

Un tel type de données se déclare de la sorte :

DATALINK [ <options_de_contrôle> ]

<options_de_contrôle> ::=
NO LINK CONTROL | FILE LINK CONTROL <option_de_contrôle_fichier>

<option_de_contrôle_fichier> ::=
<option_intégrité> <autorisation_lecture> <autorisation_écriture>
<option_récupération> [ <option_détachement> ]

<option_intégrité> ::=
INTEGRITY ALL
| INTEGRITY SELECTIVE
| INTEGRITY NONE

<autorisation_lecture> ::=
READ PERMISSION FS
| READ PERMISSION DB

<autorisation_écriture> ::=
WRITE PERMISSION FS
| WRITE PERMISSION BLOCKED

<option_récupération> ::=
RECOVERY NO
| RECOVERY YES

<option_détachement> ::=
ON UNLINK RESTORE
| ON UNLINK DELETE
| ON UNLINK NONE

Voici la description des principaux paramètres du DATALINK :

- NO LINK CONTROL signifie que les liens ne sont connus par la base que par leurs noms (URI : Uniform Ressource Identifier).
Dans le cas contraire, FILE LINK CONTROL, il est possible de piloter les liens par les options de la base de données décrites ci dessous :

- INTEGRITY ALL : seul un ordre SQL peut supprimer ou modifier les fichiers afférents au lien ;

- INTEGRITY SELECTIVE : un ordre SQL, comme le système de fichier de l'OS, peut supprimer ou modifier les fichiers afférents au lien ;

- INTEGRITY NONE : seul le système de fichiers de l'OS peut supprimer ou modifier les fichiers afférents au lien ;

- READ PERMISSION FS : les droits en lecture sont définis par le système de fichiers ;

- READ PERMISSION DB : les droits en lecture sont définis par la base de données ;

- WRITE PERMISSION FS : les droits en écriture sont définis par le système de fichiers ;

- WRITE PERMISSION BLOCKED : aucun droit en écriture n'est autorisé ;

- RECOVERY YES : sauvegarde et restauration possible dans le cadre de la base de données ;

- RECOVERY NO : sauvegarde et restauration impossible dans le cadre de la base de données ;

- ON UNLINK RESTORE : restaure les droits et le propriétaire système originels si le lien est détaché de la base ;

- ON UNLINK DELETE : suppression du fichier en cas de détachement de la base ;

- ON UNLINK NONE : en cas de détachement de la base, le fichier n'est pas supprimé et les droits restent pendants (des droits systèmes sont appliqués par défaut si cela est possible).

Exemple 2.42* – table avec DATLINK pour stoker des vidéos :

table avec DATLINK pour stoker des vidéos

CREATE TABLE T_FILM_FLM
(FLM_ID    INT NOT NULL PRIMARY KEY,
FLM_TITRE VARCHAR(256),
FLM_FILM  DATALINK FILE LINK CONTROL
INTEGRITY ALL
READ PERMISSION DB
WRITE PERMISION BLOCKED
RECOVERY YES
ON UNLINK NONE,
FLM_ANNEE INT);

SQL fournit en outre des fonctions afin de piloter l'insertion, la restitution, comme l'extraction de certaines parties de l'URI :

- DLVALUE : instancie la valeur de l'URI pour la colonne de type DATALINK (constructeur) ;

- DLURLCOMPLETE : restitue la valeur de l'URI contenue dans la colonne de type DATALINK ;

- DLURLPATH : restitue le chemin de l'URI, avec son origine ;

- DLURLPATHONLY : restitue le chemin de l'URI, sans son origine ;

- DLURLSCHEME : restitue le mode de flux (http ou file) de l'URI ;

- DLURLSERVER : restitue la ressource de l'URI.

Exemple 2.43* – Insertion d’un vidéo dans une colonne DATALINK :

Insertion d’un vidéo dans une colonne DATALINK

INSERT INTO T_FILM_FLM (FLM_ID, FLM_TITRE, FLM_FILM, FLM_ANNEE)
VALUES (178, 'West Side Story',
DLVALUE('file://srvmed/films/WesSideStory.mpeg'),
1960)

L’exemple 2.41 montre l’insertion d’une ligne dans la table T_FILM_FLM construite dans l’exemple 2.40, avec enregistrement du fichier vidéo du film « West Side Story ». L’exemple 2.42 montre comment récupérer l’accès au fichier :

Exemple 2.42* – accéder à un fichier à travers le DATALINK :

accéder à un fichier à travers le DATALINK

SELECT FLM_TITRE, FML_ANNEE, DRURLCOMPLETE(FLM_FILM) AS FLM_MPEG
FROM   T_FILM_FLM
WHERE  FLM_ID = 178

FLM_TITRE         FML_ANNEE    FLM_MPEG
----------------- ------------ ---------------------------------------
West Side Story   1960         file://srvmed/films/WesSideStory.mpeg

NOTE

Oracle Database propose le type BFILE, mais supporte très faiblement les principes du DATALINK.
Microsoft SQL Server fournit un service complet du DATALINK sous deux formes différentes : le FILESTREAM et le FILETABLE, mais cela reste limité à l’OS Windows.

II-5-10 - Pour conclure sur les types « objet »

Même si certains types objets simples comme les types tableau ou mutisets (collection), paraissent séduisants, il ne faut pas oublier qu’il n’est pas possible d’en indexer la totalité du contenu… Et d’ailleurs, qu’est-ce qu’un type tableau ? Ne serait-ce pas une table ? Pourquoi utiliser un tableau à l’intérieur d’une colonne de table alors qu’une table sera plus performante et mieux documentée ? Quant au type multisets, même combat… Mieux vaut dans ces deux cas utiliser une table.

Concernant le type REF, il semble que nous soyons revenus aux temps anciens des pointeurs et du COBOL !

Si l’on peut comprendre l’intérêt d’intégrer parfois des objets sophistiqués au sein d’une table dans un SGBD relationnel, il ne faut pas perdre de vue l’objectif final qui consiste à brasser le plus rapidement possible des quantités importantes de données. Ces objets ne pouvant être indexés globalement, il faudra externaliser certaines des données de l’objet dans des colonnes atomiques et indexer ces dernières afin de pouvoir retrouver rapidement les informations contenues dans ces objets, ce qui ajoute de la redondance à la base…

II-6 - Marqueur NULL

NULL est un mot clef de SQL qui spécifie l'absence de valeur. NULL n'est donc en aucun cas une valeur et ne peut être comparé à aucune valeur. Il faut comprendre que NULL est un « marqueur » qui indique qu'aucune assignation n'a encore été effectuée, ou bien que l'on a « vidé » l'objet de sa valeur.

Ce marqueur ne pouvant être de fait comparé à aucun autre élément (l’inexistence conférant l’impossibilité de tout rapprochement), des opérateurs spécialisés comme IS NULL, IS NOT NULL et des fonctions ou opérateurs comme COALESCE permettent de rechercher ces valeurs manquantes ou bien de les contourner. Nous verrons cela au chapitre 4.

La présence du NULL provoque parfois des résultats qui peuvent surprendre au premier abord parce que parfois contre intuitifs dans certains prédicats. Voici quelques exemples des problématiques dont il faut avoir conscience...

Exemple 2.45* – influence du NULL…

influence du NULL

-- Avec la table suivante :
CREATE TABLE T_NUL (NUL_X INT, NUL_Y INT);
INSERT INTO T_NUL VALUES (1,    1);
INSERT INTO T_NUL VALUES (1,    2);
INSERT INTO T_NUL VALUES (NULL, 2);
INSERT INTO T_NUL VALUES (1,    NULL);
INSERT INTO T_NUL VALUES (NULL, NULL);

-- dont le contenu est :
NUL_X       NUL_Y
----------- -----------
1           1
1           2
NULL        2
1           NULL
NULL        NULL

-- À la lecture, les paires de requêtes suivantes
-- semblent donner le même résultat ; or il n'en est rien...

SELECT * FROM T_NUL WHERE  NUL_X = NUL_Y OR NOT (NUL_X = NUL_Y);
SELECT * FROM T_NUL;

NUL_X       NUL_Y
----------- -----------
1           1
1           2

NUL_X       NUL_Y
----------- -----------
1           1
1           2
NULL        2
1           NULL
NULL        NULL

SELECT SUM(NUL_X) + SUM(NUL_Y) AS N FROM T_NUL;
SELECT SUM(NUL_X + NUL_Y) AS N FROM T_NUL;

N
-----------
8

N
-----------
5

SELECT CASE WHEN NUL_X = NUL_Y THEN 1 ELSE 0 END AS TRUTH FROM T_NUL;
SELECT CASE WHEN NOT(NUL_X=NUL_Y) THEN 1 ELSE 0 END AS TRUTH FROM T_NUL;

TRUTH
-----------
1
0
0
0
0

TRUTH
-----------
0
1
0
0
0

II-7 - Commentaire dans les requêtes

À tout moment, il est possible d’ajouter au code SQL du commentaire. Il existe deux formes de commentaires :

- le commentaire en ligne ;

- le commentaire en bloc.

Tout commentaire incorporé à une requête est passé au moteur SQL qui l’ignore.

Plusieurs de nos exemples précédents (2.38, 2.39…) sont ornés de commentaires.

II-7-1 - Commentaire en ligne

Le commentaire en ligne commence par deux tirets -- (signe « moins ») et tout ce qui suit jusqu’à la fin de la ligne est considéré comme commentaire et donc non interprété en langage SQL.

Exemple 2.46* – Commentaire en ligne :

Commentaire en ligne

SELECT EMP_ID, EMP_NOM, EMP_DATE_ENTREE, EMP_SALAIRE_NA, EMP_INDICE
FROM  S_RH.T_EMPLOYE_EMP
WHERE EMP_NOM = 'DUFOUR' -- le nom recherché est DUFOUR
AND EMP_DATE_ENTREE > '2001-12-31' -- entré à partir de 2002
OR (EMP_SALAIRE_NA > 23456-- ou bien ayant un salaire supérieur à 231456
AND (EMP_INDICE < 450 OR EMP_INDICE IS NULL));
--et un indice inférieur à 450 ou non renseigné

II-7-2 - Commentaire en bloc

Le commentaire en bloc commence par les symboles /* (barre oblique à droite + étoile) et se termine par la combinaison inverse */ (étoile + barre oblique à droite). Ce type de commentaires peut sauter de nombreuses lignes…

Exemple 2.47* – Commentaire en bloc :

Commentaire en bloc

SELECT EMP_ID, EMP_NOM, EMP_DATE_ENTREE, EMP_SALAIRE_NA, EMP_INDICE
FROM  S_RH.T_EMPLOYE_EMP
/* on recherche les employés dont le nom est DUFOUR, entrés à partir de 2002 ou bien ayant un salaire supérieur à 231456 et un indice inférieur à 450 ou non rensigné*/
WHERE EMP_NOM = 'DUFOUR'
AND EMP_DATE_ENTREE > '2001-12-31'
OR (EMP_SALAIRE_NA > 23456
AND (EMP_INDICE < 450 OR EMP_INDICE IS NULL));

II-8 - Le « point-virgule » comme terminaison d’une commande SQL

Bien que cela ne soit pas obligatoire, il est courant de terminer toutes les commandes du SQL avec le symbole « ; » (point-virgule). C’est une bonne pratique qui élimine toute confusion.

II-9 - Références pour ce chapitre

- Data & Databases: Concepts in Practice - Joe Celko - Morgan Kaufmann – 1999

- SQL Développement - Frédéric Brouard - Campus Press – 2001

- SQL-99 Complete Really - Peter Gulutzan & Trudy Pelzer - R&D Books (Miller Freeman) – 1999

- SQL:1999, Understanding Relational Language Components - Jim Melton, Alan R. Simon - Morgan Kaufmann – 2002

- Advanced SQL:1999, Understandig Object-Relational and Other Advanced Features - Jim Melton - Morgan Kaufmann – 2003

- Data, Measurements and Standards - Joe Celko - Morgan Kaufmann – 2010

- SQL, collection Synthex - 4e édition – Frédéric Brouard, Christian Soutou, Rudi Bruchez – 2012

- SQL and the Relational Theory, How to Write Accurate SQL Code - 3re Edition - Christopher J. Date - O'Reilly – 2015

- Practical SQL, A Beginner's Guide to Storytelling with Data - Anthony DeBarros - No Starch Press – 2018

- SQL Queries for Mere Mortals - 4th Edition - John L. Viescas - Pearson – 2018

- Learning SQL, Generate, Manipulate, and Retrieve Data - 3rd Edition - Alan Beaulieu - O'Reilly - 2021

III - Chapitre 3 - Création des objets :schémas, tables, vues, assertions

Si la modélisation conduit à la structuration de la base, le type des données, comme la composition des objets et les règles de validation par contraintes sont une composante fondamentale de la qualité d'une base de données :

- une qualité de données de plus en plus demandée notamment pour couvrir les besoins du décisionnel (analyse de cubes OLAP en particulier, BI (Business Intelligence) temps réel…) ;

- une structuration de plus en plus précise pour coller à la réalité des objets procéduraux (mapping relationnel objet par exemple) ;

- des règles de validation de plus en plus sophistiquées se rapprochant de la logique « métiers » des applicatifs.

La création des objets SQL répond donc à cette triple approche.

Dans ce chapitre nous allons nous intéresser à la construction des tables (CREATE TABLE), à leurs interdépendances au travers de l'intégrité référentielle, et nous montrerons l'intérêt des vues. Nous terminerons par la modification et la suppression des objets existants (ordre SQL ALTER et DROP) et finalement présenterons un moyen de créer des tables à la volée. Pour cela nous aurons besoin de définir ce que sont les contraintes parmi lesquelles nous avons déjà mentionné au chapitre précédent celles de domaines et nous découvrirons les assertions qui sont des contraintes générales exprimées au niveau de la base de données. Tous ces éléments, et les types du chapitre précédent, composent le DDL (Data Definition Language), la subdivision du SQL qui s'occupe de créer, modifier et supprimer les schémas et les objets qu'ils contiennent.

Mais avant tout cela il nous faut porter un regard attentif à la règle de formation des noms des objets SQL ainsi qu’à la façon dont on se connecte à un serveur de bases de données relationnelles…

III-1 - Règles de nommage

SQL impose un certain nombre de règles concernant les noms des objets d'une base de données. Ces noms sont appelés identifiants SQL, dans le sens où ils doivent être uniques de manière relative à leur conteneur et donc servir d’identifiant pour tout objet : nom de table, nom de colonne, nom de contrainte...

Un nom d'objet (table, colonne, contrainte, vue...) doit avoir les caractéristiques suivantes :

- ne pas dépasser 128 caractères ;

- ne pas commencer par un chiffre ;

- être composé de lettres basiques(17), de chiffres et du caractère blanc souligné ;

- ne pas être un mot réservé de SQL(18) sauf à être entouré de guillemets.

En outre les noms d'objet SQL sont insensibles à la casse sauf pour ceux délimités.

Même si certains SGBDR permettent des noms exotiques, comportant des accents ou des blancs, il ne faudrait jamais utiliser de caractères diacritiques ou non autorisés dans un nom d'objet SQL, en effet les caractères de bits supérieurs à 128 dans le jeu ASCII étant dépendants des pages de codes, il se peut que la correspondance ne s'établisse pas correctement entre la base de données et l'application cliente. Notez en outre que l'espace, le point, le tiret et l'apostrophe sont interdits.

Le tableau suivant présente quelques identifiants SQL bien et mal formés :

Valable

Interdit

T_CLIENT

TAB CLIENT

Xyz123

123xyz

IBM_COM

ibm.com

"SELECT"

SELECT

CLI_NUM

CLI#

MS_sql_Server

Microsoft/SQLserver

Tableau 3.1 – identifiants SQL bien et mal formés

ATTENTION
Un nom d'objet doit être unique au sein de l'objet qui le contient. Ainsi :

- un nom de schéma SQL doit être unique au sein de la base de données ;

- un nom d’objet (table, vue procédure…) doit être unique au sein du schéma SQL ;

- un nom de colonne doit être unique au sein de la table.

CONSEIL

Une bonne habitude consiste à donner un nom pertinent aux objets des bases de données. En particulier abstenez vous d’utiliser un mot réservé du SQL comme TYPE ou DATE. Voir en annexe 1 la convention de nommage que nous avons utilisé pour cet ouvrage. Mieux, faites en sorte que tous les noms d’objets d’une même base de données soient différents.

III-2 - Création d'une base : connexion, session, catalogue et schéma…

Nous avons regroupé ces concepts car ils sont très dépendants des spécificités de chaque éditeur de SGBDR. Ce n'est donc que pour le principe que nous étudierons rapidement ces éléments.

Pour rappel, une instance est un exécutable d’un SGBD Relationnel particulier et tourne en tant que service sur une machine qui peut être virtuelle ou physique. Plusieurs instances d’un même SGBDR ou de différents SGBDR peuvent être installées sur une même machine, bien que ceci ne soit pas conseillé en production. La figure 3.0a montre différentes instances installées sur une même machine, chaque instance ayant ses propres bases de données.

Figure 3.0a – Une machine avec différentes instances de SGBDR et leurs bases respectives

III-2-1 - La connexion

Avant de créer une base de données, il convient de se connecter au serveur. C'est la phase d'authentification. Mais cette connexion suppose au moins la création d'un utilisateur, qui est un objet de la base de données. SQL nous donne la clé en considérant qu'il doit toujours exister un utilisateur particulier de nom PUBLIC. Cet utilisateur est d'ailleurs plus proche de la notion de rôle que de celle d'un véritable utilisateur SQL.

Le choix de l'utilisateur « public » comme connexion par défaut à l'installation est très critiquable pour des raisons de sécurité car cet utilisateur ne peut en aucun cas être supprimé. C'est pourquoi les éditeurs de SGBDR professionnels ont opté pour d'autres comptes, comme c'est le cas avec les utilisateurs de noms « administrator » et « default » pour Oracle Database ou encore "sa" pour Microsoft SQL Server.

Pour SQL la connexion à un SGBDR prend la syntaxe suivante :

CONNECT TO {DEFAULT | nom_serveur [AS surnom_serveur ]
[USER nom_utilisateur] }

Exemple 3.1 – Connexion au serveur par défaut

CONNECT TO DEFAULT;

L'ordre SQL de l’exemple 3.1 permet de se connecter au serveur de base de données défini par défaut. La plupart du temps un serveur de bases de données est installé sur une machine dédiée, ce qui fait qu'il n'y a pas d'ambiguïté. En revanche, si plusieurs serveurs sont installés sur la même machine, il faut nommer le serveur.

Exemple 3.2 – Connexion à un serveur par son nom

CONNECT TO S_MON_SERVEUR;

Dans l’exemple 3.2 on se connecte au serveur identifié « S_MON_SERVEUR ».

Il est même possible de renommer ce serveur, c'est à dire se servir d'un alias :

Exemple 3.3 – Connexion à un serveur par son nom, renommé par son alias

CONNECT TO S_MON_SERVEUR AS SRV1;

Qui se connecte à « S_MON_SERVEUR », mais le surnomme SRV1. Dès lors ce nouveau nom pourra être utilisé dans divers ordres SQL en lieu et place du nom authentique du serveur.

Enfin, il est possible de préciser le nom de l'utilisateur qui doit exister préalablement dans le SGBDR et qui sera associé à la connexion. Sans cette précision, le SGBDR emprunte le nom par défaut implanté par le constructeur ou encore l'utilisateur PUBLIC.

Exemple 3.4 – Connexion à un serveur par son nom, avec association d’un utilisateur SQL

CONNECT TO S_MON_SERVEUR USER U_MONA_LISA;

Se connecte au serveur « S_MON_SERVEUR » en empruntant l'identité U_MONA_LISA comme utilisateur.

Vous l'aurez compris, à une connexion est toujours associée un nom d'utilisateur. Un utilisateur SQL est un objet de la base de données et se définit aussi par un ordre SQL. Nous verrons cela au chapitre 8.

La norme SQL propose en outre la possibilité de basculer d'une connexion à l'autre (à condition que l'autre existe et soit dormante à l'aide de l'ordre :

SET CONNECTION { DEFAULT | nom_session }

Ou nom_session représente une connexion déjà établie et dormante (c'est à dire une connexion ouverte mais qui n'est pas activée par le passage d'ordre SQL..)

Bien entendu il est possible de fermer une connexion en utilisant l'ordre SQL DISCONNECT :

DISCONNECT { DEFAULT | CURRENT | ALL | nom_session }

NOTE

Les langages de développement de haut niveau de type L4G incorporent des objets d'accès directs aux composants d'un SGBDR et plus particulièrement pour accéder à la base. Il suffit donc d'utiliser un objet de type connexion avec son middleware associé (ODBC, JDBC, ...) et de le paramétrer avec une chaîne de connexion comportant l'ensemble des paramètres nécessaires pour accéder à travers différents objets (requête, table, batch, ...) aux données des différentes bases.

Nous vous conseillons d'utiliser le site Web "The Connection Strings Reference" https://www.connectionstrings.com/ pour savoir comment paramétrer la chaîne de connexion propre à votre SGBDR, au middleware utilisé, et à la sécurité que vous voulez définir. Par exemple une chaîne de connexion pour Oracle Database via ODBC en sécurité SQL se définit de la manière suivante : Driver={Microsoft ODBC for Oracle};Server=myServerAddress;Uid=myUsername;Pwd=myPassword;

III-2-2 - La session

Une session au sens de la norme SQL est une connexion activée et possède certains attributs particuliers. Les attributs d'une session sont :

- un identifiant d'autorisation (AUTHORIZATION) ;

- un nom de catalogue (CATALOG) ;

- un nom de schéma (SCHEMA) ;

- un fuseau horaire (TIME ZONE) ;

- un jeu de caractères (CHARACTER SET).

AUTHORIZATION : l'identifiant d'autorisation doit impérativement être choisi parmi les mots clés suivants USER, CURRENT_USER, SESSION_USER et SYSTEM_USER ou bien encore en donnant un nom d'utilisateur spécifique.

CATALOG : Un catalogue n’est ni plus ni moins qu’une base de données. Ce terme peut paraître curieux, mais souvenons-nous que le terme database n’était pas encore en vogue à l’époque de la naissance du langage SQL ou l’on parlait de databank…

SCHEMA : un schéma est un conteneur logique au sein d’une base de données, une base de données pouvant contenir de nombreux schémas destinés à isoler les différents pans logiques des objets de la base.

TIME ZONE : un fuseau horaire est l'indication du décalage de l'heure locale de la session par rapport au temps universel (UTC : Unified Time Coordination). Chaque session peut donc se connecter avec une synchronisation de fuseau horaire différente. C'est assez pratique lorsque l'on veut développer une application internationale, notamment pour les sites web et que les internautes viennent se connecter de toute la planète.

CHARACTER SET : le jeu de caractères permet de définir quel sous ensemble de symboles est utilisé pour codifier les littéraux.

NOTE

La plupart du temps, le mécanisme de connexion et d'authentification par ouverture de session est masqué par une interface graphique, ou encore par l'imbrication du serveur de bases de données et de son OS.
Des paramètres supplémentaires sont couramment fournis par les éditeurs des SGBDR, comme la langue et/ou la culture, le format d'insertion des dates dans les requêtes et divers paramètres de contrôle d'exécution.

III-2-3 - Catalogues et schémas

Le terme SQL CATALOG désigne communément ce que nous appelons base de données. Le terme SCHEMA, désigne le seul type d'objet logique directement lié au CATALOG, c’est-à-dire un conteneur de tables, vues, procédures…

III-2-3-1 - Le CATALOG, ou la base de données

Comme nous venons de le définir, le terme CATALOG permet de définir une base de données.

Il n'existe pas d'ordre SQL de création de CATALOG (base de données) dans la norme SQL. La raison en est simple : SQL laisse toute latitude aux éditeurs des SGBD relationnels d'inventer leur propre commande pour ce faire. En effet la définition d'une base de données pose le problème du stockage des données et nécessite donc des paramètres physiques particuliers comme la spécification des espaces de stockage et la manière dont sont journalisées les transactions. Or par principe le langage SQL ne concerne que des objets logiques.

Cependant, la plupart des éditeurs se sont accordés pour prévoir l'ordre CREATE DATABASE afin de générer dans le serveur une base de données.

À titre d'exemple, voici la syntaxe de création d'une base de données et de ses espaces de stockage pour SQL Server :

Exemple 3.5* – Création de bases de données avec définition du stockage pour Microsoft SQL Server

1. CREATE DATABASE B_FORUM

2. ON PRIMARY

3. ( NAME = F_CATALOG,

4. FILENAME = 'D:\DATASQL\FORUM\F_CATALOG.D0',

5. SIZE = 10 GB,

6. FILEGROWTH = 100 MB ),

7. FILEGROUP FG_DATA1

8. ( NAME = F_DATA1,

9. FILENAME = 'E:\DATASQL\FORUM\F_DATA1.D1',

10. SIZE = 80 GB,

11. MAXSIZE = 160 GB,

12. FILEGROWTH = 250 MB ),

13. ( NAME = F_DATA2,

14. FILENAME = 'E:\DATASQL\FORUM\F_DATA1.D2',

15. SIZE = 80 GB,

16. MAXSIZE = 160 GB,

17. FILEGROWTH = 250 MB ),

18. FILEGROUP FG_INDEX1

19. ( NAME = F_INDEX1,

20. FILENAME = 'F:\DATASQL\FORUM\F_INDEX1.I1',

21. SIZE = 80 GB,

22. MAXSIZE = 160 GB,

23. FILEGROWTH = 250 MB )

24. LOG ON

25. ( NAME = 'F_LOG',

26. FILENAME = 'G:\LOGSQL\FORUM\F_LOG1.L1',

27. SIZE = 100 GB)

28. COLLATE French_CI_AI;

Ceci définit une base de données de nom B_FORUM dont les données sont réparties dans quatre fichiers distincts dont les noms logiques sont F_CATALOG, F_DATA1, F_DATA2, F_INDEX1, associés aux espaces de stockage (file group) PRIMARY, FG_DATA1, FG_INDEX1, dont les fichiers physiques sont situés sur quatre disques différents, le journal de transaction (fichier F_LOG) étant placé sur un cinquième disque. En outre la collation par défaut de cette base sera French_CI_AI.

Plus simplement, pour ce même SGBDR qu’est Microsoft SQL Server, on peut se contenter de la commande :

CREATE DATABASE nom_base ;

Dans ce cas, la base de données SQL Server sera créée avec les paramètres de stockage et de collation hérités de la base template "model". Pour Microsoft SQL Server ce stockage sera constitué de deux fichiers, l’un pour les données, l’autre pour la transaction.

NOTE

À l’origine une instance Oracle Database ne permet la création que d’une unique base de données. Depuis la version 12 C le concept de « multitenant »(19) (en français « multilocataire », ça ne s’invente pas !) permet de créer plusieurs bases de données au sein d’une même instance, mais il s’agit d’un module payant…

III-2-3-2 - Le SCHEMA, ou module d'une base de données

La notion de SCHEMA permet de modulariser une base de données. En fait une base de données (donc un CATALOG selon la norme) comporte au moins un schéma par défaut dans lequel on trouvera les objets de la base (tables, vues, routines...). Comprenez qu'il n'est pas possible de créer une table directement dans le CATALOG, mais qu'il faut passer par le schéma. Ainsi une base de données peut posséder autant de SCHEMA qu'on le désire, chaque schéma pouvant correspondre à un module dans le sens que l'on donne à ce mot pour désigner une bibliothèque de code ou un espace de nom dans un langage itératif. Seule différence avec ces derniers, SQL ne permet qu'un seul niveau de schéma.

Ainsi, dans l'exemple donné ci-avant, la base de données créée comporte automatiquement un schéma.

De fait, une base de données comporte toujours au moins un schéma par défaut. Nous verrons que de manière similaire, tout utilisateur SQL est associé à un schéma par défaut qui peut être différent du schéma par défaut de la base de données (voir chapitre 8).

ATTENTION
Dans PostGreSQL, le schéma par défaut est « public » ce qui présente un défaut de sécurité majeur dans le sens ou tout utilisateur a en permanence les permissions sur tous les objets de ce schéma. Préférez utiliser tout autre schéma que vous devrez créer.

Les avantages de la notion de SCHEMA sont considérables : outre la modularisation des objets de la base (permettant de la répartir dans différentes « unités » par exemple sur le plan fonctionnel ou encore sur le plan technique), le schéma permet de gérer des privilèges (autorisations) de manière souple et efficace, mais aussi de créer de toutes pièces tous les éléments d'une base de données sans que l'ordre ait une importance.

La figure 3.0b ci-après montre un exemple de base de données avec différents schémas dont certains sont administratifs (en jaune) :

Figure 3.0b – Une base de données et ses schémas

En fait un SCHEMA est un nom logique d'une subdivision d'une base de données (un conteneur) et tout objet d'une base devrait être systématiquement préfixé par le schéma dans lequel il repose.

Notons enfin qu'un schéma appartient toujours à un utilisateur, par défaut celui qui l'a créé.

ATTENTION
MySQL / MariaDB font confusion de la notion de base de données et de schéma SQL.
Parfois la documentation présente le terme schéma SQL pour désigner la base de données et d'autre fois parle du concept de base de données...

La commande CREATE SCHEMA est une méta-commande dans le sens ou elle permet de créer de nombreux objets qui, à défaut de préciser le préfixe de schéma, seront créés dans le schéma indiqué par la commande. On peut ainsi commencer par créer un utilisateur et ses privilèges sur une vue qui sera créée après, vue qui elle-même peut précéder la création des tables sur laquelle elle repose. Cette particularité peut paraître surprenante, mais elle possède deux avantages :

- ne pas se soucier de l'ordre de dépendance des objets, et par ce fait permettre de créer des contraintes mutuellement dépendantes ;

- résoudre de fait un problème auquel se heurtent les développeurs débutants : celui de connaître l'ordre de création des tables liées par l'intégrité référentielle...

Notez que cette méta-commande ne peut incorporer que des ordres SQL du DDL (définition des données, tel que CREATE, ALTER ou DROP) mais en aucun cas des ordres du DML (Data Manipulation Language), ne serait-ce que pour ajouter des lignes aux tables (INSERT…).

L'ordre SQL de création d'un schéma revêt la syntaxe suivante :

1. CREATE SCHEMA nom_schéma [ AUTHORIZATION propriétaire ]

2. [ DEFAULT CHARACTER SET jeu_de_caractères ]

3. [ PATH nom_schéma {, nom_schéma ... } ] ]

4. [ <objet> | <privilège> [, <objet> | <privilège> ...] ]

6. <objet> ::=

7. CREATE { DOMAIN | TABLE | VIEW | ASSERTION |

8. CHARACTER SET | COLLATION |

9. TRANSLATION | TRIGGER | TYPE |

10. PROCEDURE | FUNCTION | ROLE } définition_objet

12. <privilège> ::=

13. GRANT définition_privilège

Le propriétaire est en fait l'utilisateur qui créé les objets dans le schéma. Un schéma a toujours un propriétaire.

La clause PATH permet de définir les schémas dans lesquels il faudra rechercher, dans l'ordre de spécification, les différentes routines à utiliser (fonctions, procédures).

NOTE

Pour des raisons de sécurité, ni Oracle Database ni Microsoft SQL Server n’utilisent la clause PATH. Dans Oracle le schéma par défaut est celui du même nom que l’utilisateur qui en est propriétaire. Dans SQL Server, dbo est le schéma par défaut de la base, mais chaque utilisateur SQL peut-être doté de son propre schéma par défaut prioritaire devant celui de la base (à défaut c’est encore dbo).

Exemple 3.6* – Création d’un schéma incorporant de nombreux objets

1. CREATE SCHEMA S_COMMERCIAL AUTHORIZATION U_MONA_LISA

2. DEFAULT CHARACTER SET LATIN1

4. CREATE VIEW V_CLIENT_CLI

5. AS

6. SELECT *

7. FROM   T_CLIENT_CLI

8. FULL OUTER JOIN T_PROSPECT_PSP

9. ON T_CLIENT_CLI.PRS_ID = T_PROSPECT_PSP.PRS_ID

11. CREATE TABLE T_CLIENT_CLI

12. (PRS_ID INTEGER NOT NULL PRIMARY KEY REFERENCES T_PERSONNE_PRS (PRS_ID),

13. CLI_NOM VARCHAR(100),

14. CLI_SIRET CHAR(14) NOT NULL)

16. CREATE TABLE T_PROSPECT_PSP

17. (PRS_ID INTEGER NOT NULL PRIMARY KEY REFERENCES T_PERSONNE_PRS (PRS_ID),

18. PSP_NOM CHAR(16),

19. PSP_SIRET CHAR(14) NOT NULL)

21. CREATE TABLE T_PERSONNE_PRS

22. (PRS_ID INTEGER NOT NULL PRIMARY KEY)

24. CREATE ASSERTION AK_SIRET

25. CHECK NOT EXISTS

26. (SELECT *

27. FROM   T_CLIENT_CLI

28. INNER JOIN T_PROSPECT_PSP

29. ON T_CLIENT_CLI.PRS_ID = T_PROSPECT_PSP.PRS_ID

30. AND T_CLIENT_CLI.CLI_SIRET_ID <> T_PROSPECT_PSP.PSP_SIRET);

Dans cet exemple nous commençons par créer l'objet V_CLIENT qui est une vue basée sur les tables T_CLIENT_CLI et T_PROSPECT_PSP qui n'ont pas encore été créées. Ensuite nous créons les objets table T_CLIENT_CLI et T_PROSPECT_PSP qui référencent la table T_PERSONE_PRS qui n’a pas encore été créée. Enfin nous créons une assertion (contraintes de niveau base) qui vérifie qu’une personne à la fois cliente et prospect dont les informations de numéro de SIRET (identification de l’entreprise) sont renseignées, correspondent bien.

Tous ces objets seront créés dans le schéma S_COMMERCIAL propriété de l’utilisateur U_MONA_LISA (utilisateur SQL supposé pré exister).

Le concept de création de schéma est double : pouvoir décrire toute une base de données sans jamais avoir à se préoccuper du sens dans lequel chaque objet doit être créé du fait des interdépendances, et pouvoir créer des objets mutuellement dépendants. C'est le cas dans notre exemple entre les tables T_CLIENT_CLI et T_PROSPECT_CLI qui se référencent l'une l'autre.

Figure 3.0c – Un schéma SQL et ses différents objets relationnels

La figure 3.0c ci-avant présente un exemple de schéma SQL de nom « COMMERCIAL » contenant différents objets relationnels (tables, vues, procédures…

Bien entendu SQL propose un ordre de suppression de schéma :

DROP SCHEMA [ nom_schema ] { RESTRICT | CASCADE }

Qui permet de supprimer le schéma courant : s'il ne contient rien à l'aide de l'option RESTRICT, sinon, en détruisant en chaîne tous les objets créés ou référencés dans ce schéma à l'aide de l'option CASCADE. RESTRICT est par défaut.

La navigation à travers les objets dans les différents schémas de la base repose sur trois éléments :

- tout utilisateur est lié à un schéma par défaut ;

- toute base possède un schéma par défaut ;

- à défaut de préciser le schéma dans lequel l'objet repose, c'est le schéma par défaut de l'utilisateur qui est utilisé et si l’objet n’est pas trouvé dans le schéma utilisateur, alors il est cherché dans le schéma par défaut de la base.

Tant est si bien qu'une même table (même nom, voire mêmes colonnes), peut parfaitement être créée dans deux schémas différents.

NOTE
Le schéma INFORMATION_SCHEMA de la norme SQL contient les vues de métadonnées présentant l’aspect logique des objets de la base. Il existe dans tous les SGBDR sauf Oracle Database. Le schéma sys présent dans SQL Server, Oracle Database et MySQL/MariaDB contient des objets systèmes pour présenter à la fois les objets logiques et physiques des bases.

ATTENTION
Schémas SQL et espaces de stockage sont deux notions différentes. Les schémas sont des espaces logiques permettant de structurer les objets logiques de la base tel que table, vues, procédures… Un espace de stockage est un concept physique qui permet d’indiquer pour une table ou un index à quel endroit physique sont stockées les données. Il n’existe aucune relation entre les deux.

III-2-4 - La notion de propriétaire

Nous verrons au chapitre 8 consacré à la sécurité, ce qu’est la notion de propriétaire, mais disons rapidement que c’est un utilisateur SQL, propriétaire de certains objets, et par conséquent détenant tous les pouvoirs sur tous les objets qu’il créé afin de transmettre ses privilèges et finalement supprimer ces objets. Sans cette notion de propriétaire il serait bien difficile de donner à d’autres utilisateurs des privilèges (droits d’exécuter telle ou telle commande sur tel ou tel objet) et plus encore de prendre la responsabilité de la destruction des objets.

NOTE
Dans Oracle Database les notions de schéma et propriétaires sont intimement liés. Le fait de créer un schéma SQL entraîine la création d’un utilisateur SQL de même nom que le schéma.

III-3 - Créer des tables

Une table peut être créée de manière temporaire (locale ou globale) ou bien durable. Elle possède au moins une colonne, la plupart du temps plusieurs, et d'éventuelles contraintes de table. Les colonnes peuvent être éventuellement contraintes.

Une colonne permet de recevoir une donnée pour chaque ligne de la table. Une contrainte est une règle de vérification d'un prédicat dont le but est d'empêcher certaines actions ou d'obliger à respecter certains critères.

Voici une syntaxe simplifiée de la création de table :

1. CREATE [ { GLOBAL | LOCAL } TEMPORARY ] TABLE [nom_schema.]nom_table

2. ( <colonne> | <contrainte_de_table>

3. [ { , <colonne> | <contrainte_de_table> }... ] )

4. [ ON COMMIT { PRESERVE | DELETE } ROWS ]

6. <colonne> ::=

7. nom_colonne { <type> | <domaine> }

8. [ DEFAULT valeur_default ]

9. [ <contrainte_de_colonne>. ]

10. [ COLLATE collation ]

NOTE
Nous avons spécifié dans cette syntaxe un nom de la table préfixé par son schéma. Ceci est valable quel que soit l'objet créé dans la base. A défaut de le spécifier, c'est dans le schéma par défaut qu'est créé l'objet

Cette syntaxe appelle plusieurs remarques :

- une table peut être créée de manière durable (par défaut) ou temporaire (TEMPORARY) et dans ce dernier cas uniquement pour l'utilisateur et la connexion qui l'a créé (LOCAL) ou bien pour l'ensemble des utilisateurs de la base (GLOBAL) ;

- lorsqu'une table est créée de manière temporaire et si elle n'est pas détruite explicitement, elle sera détruite lorsque la session dans laquelle elle a été créée prend fin ;

- une table comporte au moins une colonne et zéro ou plusieurs contraintes de table ;

- une colonne peut être spécifiée d'après un type SQL ou un domaine créé par l'utilisateur ;

- une colonne définie peut être dotée d'une valeur par défaut (DEFAULT) qui peut être une expression de valeur, une fonction SQL système, une fonction utilisateur ou encore le marqueur NULL ;

- une colonne peut être dotée de contraintes de colonnes telles que : obligatoire ou non ( NULL / NOT NULL), clé (PRIMARY KEY), unicité de valeur (UNIQUE), intégrité référentielle (FOREIGN KEY...) et validation (CHECK) ;

- sans spécification NOT NULL par défaut une colonne est « nullable » (NULL) ;

- indépendamment des contraintes de colonne, on peut ajouter des contraintes de table qui porte sur une ou plusieurs colonnes et permettent de définir la clé, l'unicité des valeurs, la validation des données et l'intégrité référentielle au niveau des lignes ;

- on peut spécifier une collation à une colonne de type littéral ;

- l'ensemble formé par les noms des contraintes et des colonnes (identifiant) doit être unique au sein de la table ;

- l'option ON COMMIT... n'est valable que pour les tables temporaires. Avec ...DELETE ROWS, les lignées insérées au cours d'une transaction sont supprimées en fin de transaction. Avec PRESERVE ROWS, les lignes sont conservées (voir au chapitre 7).

Exemple 3.7* – Création d’une table persistante et ses colonnes

1. CREATE TABLE S_ADM.T_UTILISATEUR_USR

2. (USR_ID             INTEGER,

3. USR_TITRE          CHAR(6),

4. USR_NOM            CHAR(32) COLLATE Latin1_General_CS_AS,

5. USR_PRENOM         VARCHAR(32)  COLLATE Latin1_General_CS_AS,

6. USR_ORGANISATION   VARCHAR(128));

Cet exemple créé une table de nom T_UTILISATEUR_USR dans le schéma S_ADM dotée de colonnes dont certaines ont une spécification de collation, les autres colonnes littérales sont basées sur la collation paramétrée au niveau du schéma ou à défaut celle du serveur.

Exemple 3.8* – Création d’une table temporaire et ses colonnes

1. CREATE LOCAL TEMPORARY TABLE T_BON_CLIENT_BCL

2. (BCL_NOM        VARCHAR(32),

3. BCL_CREATION   DATE,

4. BCL_REMISE     FLOAT);

Cet exemple créé une table temporaire locale de nom T_BONT_CLIENT_BCL dans le schéma par défaut. Elle n'est visible que pour l'utilisateur courant. Elle peut être détruite avant la fin de la session par un ordre explicite de suppression de la table, sinon elle sera détruite en fin de session.

III-3-1 - Contraintes de colonne

Il n’existe que deux types de contraintes propres aux colonnes :

- la contrainte d’obligation (NOT NULL) ;

- la contrainte de valeur par défaut (DEFAULT …).

Ces contraintes ne portent jamais que sur la définition de la colonne, mais nous devons les assimiler plus globalement aux contraintes de table…

III-3-2 - Contraintes de table

Une contrainte de table permet de restreindre les conditions d'acceptation des données de la table. Rappelons que, comme pour les contraintes de domaines, dans le cas où une condition de contrainte n'est pas vérifiée, il y a violation de contrainte et le lot de lignes que l'on tentait de mettre à jour (insertion, modification ou suppression) est rejeté en bloc, même si la violation ne porte que sur une seule occurrence d'une seule colonne pour un seule ligne dans un lot comportant de nombreuses lignes.

Les contraintes permettent de rendre la base cohérente en respectant l'intégrité des données.

Outre les contraintes propres aux colonnes que nous avons vu au paragraphe précédent, SQL admet les contraintes suivantes :

- clé primaire (PRIMARY KEY) ;

- clé subrogée, unicité (UNIQUE) ;

- clé étrangère (FOREIGN KEY) ;

- validation de valeur (CHECK).

Nous les avons réparti en deux principales fonctionnalités : les contraintes validant les valeurs d'une colonne (NOT NULL, DEFAULT, CHECK) et les contraintes de clé (PRIMARY KEY, UNIQUE, FOREIGN KEY).

NOTE
Les contraintes sont des objets des tables, comme les colonnes et peuvent être explicitement nommées ou non lors de l'écriture de leur définition. Dans le cas où la contrainte est anonyme, c'est le SGBDR qui lève l'anonymat par application d'un nom calculé.

- Les noms des contraintes doivent être uniques au sein d'un même schéma ainsi que dans les objets de la table (colonnes et contraintes).

- Le schéma des contraintes est toujours celui de la table.

- Nommer une contrainte est une bonne pratique. En cas de viol de contrainte, le nom de la contrainte est souvent rapporté dans le message d'erreur, et cela facilite la suppression de la contrainte.

ATTENTION
Aucune contrainte de validation, comme de clé ne peut porter sur des types de grande dimension (LOB) tels que BLOB, CLOB, NCLOB, XML, JSON, GEOMETRY, GEOGRAPHY. Cependant, certains éditeurs acceptent des contraintes de validation sur certains types de LOBs par exemple pour contretyper les valeurs GEO à stocker dans une table, ou bien formaliser la grammaire d’une information XML ou JSON.

Comme nous l'avons fait pour les objets de la base, une bonne habitude consiste à donner un nom pertinent aux contraintes. Vous trouverez en annexe 2 la convention de nommage utilisée pour les contraintes.

III-3-2-1 - Contraintes validant les valeurs des colonnes

Une contrainte validant les valeurs d'une colonne permet de restreindre les conditions d'acceptation des données dans la ou les colonnes. Lors de la mise à jour des données, la contrainte est appliquée. On dit qu'il y a viol de contrainte lorsque la contrainte n'est pas respectée. Dans ce cas, l'action de mise à jour entreprise est empêchée.

Il y a trois contraintes de valeurs propres : NOT NULL (obligation de valeur), DEFAULT (valeur à défaut) et CHECK (contrôle des valeurs).

La syntaxe de création d'une contrainte validant la valeur propre d'une colonne est la suivante :

1. <contrainte_de_valeur> ::=

2. [ CONSTRAINT nom_contrainte]

3. { [ NOT ] NULL |

4. DEFAULT <expression_défaut> |

5. CHECK ( <expression_validation> ) }

NULL / NOT NULL rend la spécification de valeur obligatoire. DEFAULT exprime qu'en cas d'absence de valeur, alors la valeur de la colonne est calculée par l'expression spécifiée. CHECK permet de valider les valeurs de différentes colonnes à l'aide d'une expression admettant diverses valeurs même calculées provenant d'autres colonnes. Dans ce cas, sa syntaxe revête la forme :

1. CONSTRAINT nom_contrainte

2. CHECK ( <expression_validation> )

Nous allons maintenant détailler ces différentes contraintes.

III-3-2-1-1 - Obligation de valeur ([NOT] NULL)

On peut rendre la saisie d'une colonne obligatoire en apposant le mot clé NOT NULL. Dans ce cas, il ne sera jamais possible de faire en sorte qu'il y ait absence de valeur dans la colonne Autrement dit, la colonne devra toujours être renseignée de manière explicite (expression de valeur) ou implicite (valeur par défaut) lors d'une insertion via l'ordre SQL INSERT. En outre l'application d'une modification de donnée d'une telle colonne, via l'ordre SQL de modification UPDATE, ne peut conduire à l'absence de valeur.

ATTENTION
À ce stade une erreur majeure à ne pas commettre est de confondre un littéral vide et une valeur absente, ou encore un numérique valué à zéro et l'ensemble vide. En effet, NULL marque l'absence de valeur. Ce n'est donc pas un zéro qui possède un sens ni même une chaîne vide. Différente de toute valeur, NULL est un marqueur.

Syntaxe  :

[ CONSTRAINT nom_contrainte ] [ NOT ] NULL

Si l'on désire que la colonne puisse ne pas être renseignée (donc accepter les marqueurs NULL), il n'est pas nécessaire de préciser le mot clé NULL, car c'est l'option par défaut, mais il est courant qu'on le fasse par facilité de lecture.

Exemple 3.9* – Création d’une table avec des colonnes ayant une obligation de valeur

1. CREATE TABLE S_ADM.T_UTILISATEUR_USR

2. (USR_ID             INTEGER      NOT NULL,

3. USR_LOGIN          VARCHAR(256) CONSTRAINT NK_MAIL NOT NULL,

4. USR_TITRE          CHAR(6)      NULL,

5. USR_NOM            CHAR(32)     NULL,

6. USR_PRENOM         VARCHAR(32)  NULL,

7. USR_ORGANISATION   VARCHAR(128));

L’exemple 3.9 montre la création d’une table dans le schéma S_ADM dont les colonnes USR_ID et USR_LOGIN doivent obligatoirement être renseignées.

NOTE
En cas de viol de la contrainte NULL ou précisément du « NOT NULL » l'ensemble des modifications entreprises par l'ordre SQL de mise à jour (INSERT ou UPDATE) est annulé, même si le viol de la contrainte ne porte que sur une unique ligne et que le lot en comporte plusieurs.

III-3-2-1-1-I - Valeur par défaut d'une colonne (DEFAULT)

Le mot clé DEFAULT permet de spécifier une valeur ou toute expression renvoyant une valeur qui sera affectée à une colonne dans le cas ou lors de l'insertion de données il n'est pas fait référence de manière explicite ou implicite à cette colonne. Notons qu’à défaut de contrainte par défaut, le marqueur NULL sera appliqué sauf si la colonne est NOT NULL.

Syntaxe :

[ CONSTRAINT nom_contrainte ] DEFAULT <expression_défaut>

La valeur par défaut d'une colonne peut être une expression de valeur, le marqueur NULL ou encore le résultat d’une fonction niladique (fonctions SQL « système ») comme les quelques-unes figurant dans le tableau 3.2 ci-dessous :

Nom fonction

Description

CURRENT_DATE

date courante

CURRENT_TIME(p)

heure courante avec précision p de fractions de seconde

CURRENT_TIMESTAMP(p)

combiné date heure avec précision p de fractions de seconde

LOCALTIME(p)

heure locale (par rapport au décalage horaire paramétré dans la session)

LOCALTIMESTAMP(p)

combiné date & heure locales (par rapport au décalage horaire paramétré dans la session)

USER, CURRENT_USER

utilisateur courant

SESSION_USER

utilisateur qui s'est connecté et à initialisé la session (compte de connexion)

Tableau 3.2 – quelques unes des fonctions utiles pour la contrainte de défaut

Il existe de nombreuses autres fonctions qui sont normatives ou bien spécifiques à votre SGBDR.

NOTE
La contrainte DEFAULT peut être violée dans le cas où l’insertion (INSERT) ou la modification (UPDATE) utilise le mot clé DEFAULT pour forcer la valeur par défaut, alors que la colonne n’est pas nullable et qu’il n’y a pas de contrainte par défaut. Dans ce cas l'ensemble des modifications entreprise par l'ordre SQL de mise à jour est annulé, même si le viol de la contrainte ne porte que sur une unique ligne.

De plus, la plupart des SGBDR acceptent des expressions plus complexes pour la spécification d’une valeur par défaut.

Exemple 3.10* – Création d’une table avec des colonnes ayant des valeurs par défaut

1. CREATE TABLE S_COMMERCIAL.T_CLIENT_CLI

2. (CLI_NOM        VARCHAR(32),

3. CLI_CREATION   DATE DEFAULT CURRENT_DATE,

4. CLI_REMISE     D_POURCENT CONSTRAINT DK_REMISE DEFAULT 10.0);

Cet exemple créé une table de nom T_CLIENT_CLI avec trois colonnes. La colonne CLI_CREATION prend par défaut la valeur de la date courante lors de l'insertion de la ligne.

La colonne CLI_REMISE est spécifiée par un domaine plutôt que par un type SQL et reçoit par défaut la valeur 10. La table créée est située dans le schéma S_COMMERCIAL.

Exemple 3.11* – Création d’une table avec des colonnes ayant des valeurs par défaut

1. CREATE TABLE S_COMPTA.T_FACTURE_FCT

2. (FCT_NUMERO         INTEGER,

3. FCT_DATE_EMISSION  DATE     CONSTRAINT DF_FCT_DTE DEFAULT CURRENT_DATE,

4. FCT_DATE_PAIEMENT  DATE     DEFAULT CURRENT_DATE + 30 DAY);

Cet exemple créé une table de nom T_FACTURE_FCT dans le schéma S_COMPTA. La colonne date d'émission est pourvue d'une contrainte nommée DF_FCT_DTE, spécifiant qu'à défaut de valeur, la date du jour est appliquée.

La colonne FCT_DATE_PAIEMENT est pourvue d'une contrainte de défaut anonyme spécifiant qu'à défaut de valeur la date de paiement est calculée sur la date actuelle à laquelle on rajoute 30 jours.

NOTE
Si la colonne est spécifiée par un domaine, alors la valeur par défaut de cette colonne est empruntée au domaine, si le domaine en est pourvu, sauf si l'on a explicitement spécifié la valeur par défaut pour cette colonne dans la création de la table.

Ainsi dans notre exemple de définition du domaine D_POURCENT vu au chapitre précédent :

1. CREATE DOMAIN D_POURCENT

2. AS FLOAT

3. DEFAULT 0.0

4. CHECK (VALUE IS NOT NULL

5. AND VALUE BETWEEN 0 AND 100)

Malgré que la valeur par défaut du domaine soit de 0, c’est la valeur 10 qui sera insérée dans la colonne CLI_REMISE de la table S_COMMERCIAL.T_CLIENT_CLI, car au niveau de la table, la contrainte de défaut de la table supplante celle du domaine.

III-3-2-1-2 - Contrainte de validation (CHECK)

Une contrainte de colonne de validation de données CHECK permet de restreindre les valeurs acceptables pour les colonnes visées en appliquant un prédicat. Lorsque le prédicat est évalué à Vrai, l'occurrence est acceptée et la ligne peut être validée. Dans le cas contraire il y a violation de contrainte et les lignes visées par l'ordre SQL sont rejetées.

Une contrainte de validation peut valider une seule colonne ou plusieurs simultanément. Dans le cas où elle vise une seule colonne alors elle peut faire partie intégrante de la définition de la colonne. Dans le cas contraire elle doit être exprimé à la suite de la définition des colonnes de la table.

Lorsque la contrainte ne vise qu'une seule ligne et que sa définition fait partie intégrante de la définition de la colonne, le prédicat peut contenir le mot clé VALUE en lieu et place de la colonne visée, dans une expression booléenne. La contrainte est respectée si le prédicat de validation vaut Vrai. Dans le cas contraire, une exception est levée et la commande de mise à jour est annulée.

Syntaxe de la contrainte de validation :

[ CONSTRAINT nom_contrainte ] CHECK ( <expression_validation> ) }

L'expression booléenne de validation peut contenir :

- des valeurs explicites ;

- le marqueur NULL ;

- des valeurs sous forme de fonctions SQL ou d'UDF (User Define Function) ;

- des opérateurs algébriques (+, -, *, /) ;

- l'opérateur de concaténation de chaîne ( || ) ;

- des opérateurs de comparaison (>, <, >=, <=, <>) ;

- des connecteurs logiques (AND, OR) ;

- l'opérateur de négation (NOT) ;

- la hiérarchisation des opérateurs à l'aide de parenthèses ;

- des expressions SQL spécifiques.

Ainsi que l’ensemble des opérateurs spécifique au SQL suivant :

Expression

Description

BETWEEN

plage de valeurs

LIKE

comparaison partielle de chaîne de caractères à l’aide de motifs

IN

liste de valeur possible

CASE

branchement de différentes valeurs

SIMILAR

expression régulière (style SQL)

Tableau 3.3 – Quelques opérateurs utilisables pour les contraintes de validation

Exemple 3.12* – Création d’une table avec des contraintes de validation faisant partie de la définition de la colonne

1. CREATE TABLE S_ADM.T_UTILISATEUR_USR

2. (USR_NOM CHAR(32)

3. CONSTRAINT CK_USR_NOM

4. CHECK (TRANSLATE(VALUE COLLATE French_CI_AI,

5. 'ABCDEFGHIJKLMNOPQRSTUVWXYZ –''',

6. '                             ') = ''

7. AND SUBSTRING(VALUE FROM 1 FOR 1)

8. NOT IN (' ', '-' , '''')

9. AND SUBSTRING(VALUE FROM CHARACTER_LENGTH(VALUE) -1 FOR 1)

10. NOT IN (' ', '-' , '''')

11. AND VALUE COLLATE French_CS_AI = UPPER(VALUE)),

12. USR_SEXE CHAR(1)

13. CHECK (VALUE IN ('H', 'F')),

14. USR_PASS NVARCHAR(32) NOT NULL

15. CHECK (CHARACTER_LENGTH(VALUE) >= 8

16. AND VALUE <> UPPER(VALUE) COLLATE French_CS_AS

17. AND VALUE <> TRANSLATE(VALUE, '0123456789',

18. '         ')

19. AND VALUE <> TRANSLATE(VALUE COLLATE French_CI_AS,

20. 'àâäéèêëîïôöùûüÿ',

21. '               ')),

22. USR_ORGANISATION VARCHAR(128)

23. CHECK (VALUE COLLATE French_CS_AI = UPPER(VALUE)));

Dans cette table les validations suivantes sont effectuées :

- le nom de l'utilisateur ne doit comprendre que des lettres, ou encore les caractères «   » (blanc),.« - » (trait d’union) ou « ‘ » (apostrophe) sauf aux extrémités ;

- le sexe de l'utilisateur peut être H (homme) ou F (femme) ou rester vide ;

- le mot de passe doit comporter au moins : 8 caractères, une majuscule et une minuscule, un chiffre et une lettre accentuée en majuscule ou minuscule ;

- l'organisation doit être spécifiée en majuscule (la fonction SQL UPPER remplace les lettres minuscules en majuscules.

Seule, la première contrainte CHECK a été nommée (CK_USR_NOM) les autres auront un nom décidé par le SGBDR.

Notez l’utilisation des fonctions normalisées suivantes :

- TRANSLATE : substitution de caractères ;

- SUBSTRING : extraction d’une sous-chaîne ;

- CHARACTER_LENGTH : longueur de la chaîne ;

- POSITION : position d’une sous chaîne ;

- UPPER : mise en majuscule.

Ainsi que des opérateurs :

- IN : vérifie qu’une valeur est dans une liste ;

- NOT IN : vérifie qu’une valeur n’est pas dans une liste ;

- COLLATE : impose une collation.

Exemple 3.13* – Création d’une table avec de nombreuses contraintes de validation

1. CREATE TABLE S_COM.T_FACTURE_FCT

2. (FCT_NUMERO                 INTEGER,

3. FCT_MONTANT_TOTAL          DECIMAL(16,2),

4. FCT_DATE_EMISSION          DATE,

5. FCT_DATE_LIMITE_PAIEMENT   DATE,

6. CONSTRAINT CK_FCT_DLM

7. CHECK (FCT_DATE_LIMITE_PAIEMENT <= FCT_DATE_EMISSION + 30 DAY),

8. FCT_MODE_PAIEMENT          VARCHAR(16)

9. CHECK(VALUE IN ('CHEQUE', 'ESPECE', 'CARTE BANCAIRE', 'VIREMENT')),

10. CONSTRAINT CK_FCT_MDP

11. CHECK (   ( FCT_MODE_PAIEMENT = 'ESPECE'

12. AND FCT_MONTANT_TOTAL <= 3000 )

13. OR ( FCT_MODE_PAIEMENT = 'CARTE BANCAIRE'

14. AND FCT_MONTANT_TOTAL > 15 )));

Cet exemple présente trois contraintes de validation : une est décrite dans la définition de colonne, les deux autres sont décrites spécifiquement en dehors des définitions de colonne.

- La contrainte anonyme CHECK(VALUE IN ('CHÈQUE', 'ESPÈCES', 'CARTE BANCAIRE', 'VIREMENT') oblige la valeur de la colonne FCT_MODE_PAIEMENT à correspondre exclusivement à l'une des quatre mentions CHÈQUE, ESPÈCES, CARTE BANCAIRE ou VIREMENT.

- La contrainte nommée CK_FCT_DLM oblige à ce que la date limite de paiement (colonne FCT_DATE_LIMITE_PAIEMENT) ne dépasse pas 30 jours après la date d'émission de la facture. Bien que portant sur une unique colonne, elle n'a pas été décrite dans la définition de la colonne et ne peut donc bénéficier du mot clé VALUE.

- Plus subtile, la contrainte CK_FCT_MDP oblige que le paiement par espèces soit limité à 3 000 € et celui par carte bancaire supérieur à 15 €. Elle combine les colonnes FCT_MODE_PAIEMENT et FCT_MONTANT_TOTAL.

Notez que cette dernière contrainte vise plusieurs colonnes simultanément. Il n'aurait pas été possible de la décrire dans la spécification d'une quelconque colonne de la table.

ATTENTION
SQL ne limite pas la complexité des expressions des prédicats de validation de la contrainte CHECK à la ligne courante de la table. Ainsi sont interdits des fonctions de type agrégat portant sur un lot de lignes de la table, ou encore sur des valeurs de tables externes. Cependant certains éditeurs de SGBDR ont permis une telle approche que montre l'exemple suivant (3.14). Mais ceci est fortement déconseillé à cause de potentiels effets de bords…

Exemple 3.14* – Création d’une table avec une contraintes de validation faisant appel à une table externe

1. CREATE TABLE S_COM.T_CLIENT_CLI

2. (CLI_NUMERO                      INTEGER,

3. CLI_NOM                         VARCHAR(32),

4. CLI_REMISE_MAXI_POURCENT        FLOAT,

5. CLI_REMISE_MAXI_MONTANT         DECIMAL(16,2));

7. CREATE TABLE S_COM.T_FACTURE_FCT

8. (FCT_NUMERO                      INTEGER,

9. CLI_NUMERO                      INTEGER,

10. FCT_DATE_EMISSION               DATE,

11. FCT_MONTANT_TOTAL_AVANT_REMISE  DECIMAL(16,2),

12. FCT_MONTANT_REMISE_TOTALE       DECIMAL(16,2),

13. CONSTRAINT CK_FCT_REMISES

14. CHECK (FCT_MONTANT_REMISE_TOTALE

15. <= (SELECT CLI_REMISE_MAXI_MONTANT

16. FROM   S_COM.T_CLIENT_CLI

17. WHERE  S_COM.T_CLIENT_CLI.CLI_NUMERO

18. = S_COM.T_FACTURE_FCT.CLI_NUMERO)

19. AND FCT_MONTANT_REMISE_TOTALE

20. <= (SELECT CAST(CLI_REMISE_MAXI_POURCENT

21. * FCT_MONTANT_TOTAL_AVANT_REMISE

22. / 100.0 AS DECIMAL(16,2))

23. FROM   S_COM.T_CLIENT_CLI

24. WHERE  S_COM.T_CLIENT_CLI.CLI_NUMERO

25. = S_COM.T_FACTURE_FCT.CLI_NUMERO)));

La contrainte CK_FCT_REMISES permet de vérifier pour toute commande que la remise ne dépasse ni le montant global de remise prévu pour le client (CLI_REMISE_MAXI_MONTANT) ni le pourcentage maximal de réduction admis pour ce même client CLI_REMISE_MAXI_POURCENT), informations toutes deux situées dans la table des clients.

NOTE
La création de contraintes de validation complexes nécessite de bien connaître les possibilités offertes par l'ordre SQL SELECT d'extraction de données ainsi que l'écriture des UDF (User Define Function).

Exemple 3.15* – Création d’une table avec des contraintes de validation faisant appel à des UDF

1. CREATE TABLE S_COM.T_PATIENT_PTN

2. (PTN_NUM_INSEE_SEXE              CHAR(1),

3. PTN_NUM_INSEE_DATE_NAISSANCE    CHAR(4),

4. PTN_NUM_INSEE_COMMUNE           CHAR(5),

5. PTN_NUM_INSEE_RANG              CHAR(3),

6. PTN_CLE_INSEE                   CHAR(2),

7. PTN_NOM                         CHAR(32),

8. PTN_PRENOM                      VARCHAR(25),

9. CONSTRAINT CK_PTN_NNI

10. CHECK (S_ROUTINES.F_VALIDE_NNI(PTN_NUMERO_INSEE_SEXE,

11. PTN_NUM_INSEE_DATE_NAISSANCE,

12. PTN_NUM_INSEE_COMMUNE,

13. PTN_NUM_INSEE_RANG,

14. PTN_CLE_INSEE)),

15. CONSTRAINT CK_PTN_NOM

16. CHECK (S_ROUTINES.F_VALIDE_NOM(PTN_NOM, 'MAJUSCULES')),

17. CONSTRAINT CK_PTN_PRENOM

18. CHECK (S_ROUTINES.F_VALIDE_NOM(PTN_NOM, 'INITMAJ')));

Cet exemple présente trois contraintes de validation faisant appel à des fonctions utilisateur (UDF), c'est à dire à des routines spécifiquement écrites pour un traitement particulier.

À la lecture de la définition de cette table on peut en déduire que :

- la cohérence du numéro national d'identité (vulgairement appelé numéro de sécurité sociale) est vérifié par la routine S_ROUTINES.F_VALIDE_NNI du schéma S_ROUTINES ;

- le nom du patient doit respecter certaines règles (par exemple commencer par une lettre et pour le reste, n'être composé que des caractères A à Z et leurs déclinaisons avec accents, ainsi que les caractères espace, tiret, apostrophe) et figurer en majuscule, ceci étant obtenu à l'aide de la routine S_ROUTINES.F_VALIDE_NOM du schéma S_ROUTINES ;

- le prénom du patient obéira aux mêmes règles que ci dessus mais en autorisant un mélange de majuscule et minuscule avec que, seules les premières lettres de chaque partie du prénom seront en majuscules et les autres en minuscules).

III-3-2-2 - Contrainte de clé

Une contrainte de clé permet de restreindre les valeurs des données de différentes colonnes par rapport à un ensemble de données pris dans l'intégralité des lignes d'une table. De la même façon que pour les précédentes contraintes, lors de la mise à jour des données, la contrainte est appliquée. On dit qu'il y a viol de contrainte lorsque la contrainte n'est pas respectée. Dans ce cas, l'action de mise à jour entreprise est empêchée.

Il y a trois contraintes de clé : PRIMARY KEY (clé primaire), UNIQUE (clé subrogée ou candidate) et FOREIGN KEY (clé étrangère).

La syntaxe de création d'une contrainte de clé est la suivante :

Exprimée dans la définition de la colonne :

1. <contrainte_de_clé> ::=

2. [ CONSTRAINT nom_contrainte ]

3. { PRIMARY KEY |

4. UNIQUE |

5. [FOREIGN KEY ] REFERENCES <table_mere> ( <colonne_de_référence> ) }

Exprimée en tant que contrainte libre :

1. <contrainte_de_clé> ::=

2. [ CONSTRAINT nom_contrainte ]

3. { PRIMARY KEY ( <liste_colonne> ) |

4. UNIQUE ( <liste_colonne> ) |

5. FOREIGN KEY ( <liste_colonne> )

6. REFERENCES <table_mere> ( <liste_colonnes_de_référence> )

7. [ <clause_de_validation>           ]

8. [ <clause_de_gestion_modification> ]

9. [ <clause_de_gestion_suppression>  ] }

Nous allons maintenant détailler ces différentes contraintes.

III-3-2-2-1 - Contrainte de clé primaire (PRIMARY KEY)

La contrainte de clé primaire PRIMARY KEY, aussi appelée plus simplement clé de la table, concerne, la, ou les colonnes, dont les valeurs serviront à identifier de manière unique une ligne de la table. Si la clé primaire est composée d’une unique colonne, alors les valeurs contenues dans cette colonne doivent être toutes différentes. Si la clé primaire est composée de plusieurs colonnes, alors la combinaison des valeurs de ces colonnes prise dans une même ligne doit être unique. Elles doivent toujours être renseignées (le marqueur NULL n'est pas accepté dans de telles colonnes).

Syntaxe exprimée dans la définition de la colonne :

[ CONSTRAINT nom_contrainte ] PRIMARY KEY

Syntaxe exprimée en tant que contrainte libre :

[ CONSTRAINT nom_contrainte ] PRIMARY KEY ( <liste_colonne> )

Exemple 3.16* – Création d’une table avec une contrainte de clé primaire

1. CREATE TABLE S_ADM.T_UTILISATEUR_USR

2. (USR_ID             INTEGER      NOT NULL CONSTRAINT PK_USR PRIMARY KEY,

3. USR_LOGIN          VARCHAR(256) NOT NULL);

Créée une table dont la colonne USR_ID est la clé de la table. Cette contrainte de clé primaire est nommée PK_USR.

Notez que cet exemple est strictement similaire à l’exemple suivant :

1. CREATE TABLE S_ADM.T_UTILISATEUR_USR

2. (USR_ID             INTEGER      CONSTRAINT PK_USR PRIMARY KEY,

3. USR_LOGIN           VARCHAR(256) NOT NULL);

En effet, de facto, la contrainte PRIMARY KEY appliquée à la colonne entraîne obligatoirement la contrainte d’obligation de valeur (NOT NULL). Il n’est donc pas obligatoire de la mentionner explicitement.

Exemple 3.17* – Création d’une table avec une contrainte de clé primaire multi-colonne

1. CREATE TABLE S_GEO.T_COMMUNE_CMN

2. (CMN_CODE_DEPARTEMENT    VARCHAR(3) NOT NULL,

3. CMN_CODE_COMMUNE        VARCHAR(3) NOT NULL,

4. CMN_NOM                 VARCHAR(64),

5. CONSTRAINT PK_CMN (CMN_CODE_DEPARTEMENT, CMN_CODE_COMMUNE));

Créé une table dont les colonnes CMN_CODE_DEPARTEMENT et CMN_CODE_COMMUNE constituent la clé de la table. Cette contrainte de clé primaire est nommée PK_CMN.

NOTE

- Il ne peut y avoir qu'une seule contrainte de type PRIMARY KEY par table.

- Lorsque la contrainte PRIMARY KEY est spécifiée au niveau table il faut impérativement que la ou les colonnes enrôlées soient NOT NULL.

- Par défaut une colonne créée sans spécifier NULL ou NOT NULL est « nullable ». Cependant, si la contrainte PRIMARY KEY est spécifiée au niveau de la colonne, cette dernière sera automatiquement pourvue de la contrainte d’obligation (NOT NULL).

III-3-2-2-2 - Contrainte de clé subrogée (ou alternative – UNIQUE)

La contrainte de clé subrogée ou alternative (les deux termes étant similaires) impose que les occurrences de toutes les valeurs renseignées des colonnes définissant la contrainte UNIQUE soient différentes, autrement dit qu'il n'y ait pas de doublons. À l'exception du marqueur NULL, qui peut être présent plusieurs fois ou non, il ne doit jamais y avoir plus d'une fois la même valeur dans les colonnes de la table.

Syntaxe exprimée dans la définition de la colonne :

[ CONSTRAINT nom_contrainte ] UNIQUE [ NULLS [ NOT ] DISTINCT ]

Syntaxe exprimée en tant que contrainte libre :

1. [ CONSTRAINT nom_contrainte ]

2. UNIQUE [ NULLS [ NOT ] DISTINCT ] ( <liste_colonne> )

L’option NULLS … DISTINCT est apparue avec la version 2023 de la norme SQL et se comporte comme suit :

- NULLS DISTINCT considère que toute valeur non exprimée est toujours différente de tout autre valeur. Pour l’exprimer différemment, tout marqueur NULL est toujours distinct de tout autre marqueur NULL.

- NULLS NOT DISTINCT considère que l’absence de valeur doit être considérée comme une valeur à distinguer des autres. Pour l’exprimer différemment, tout marqueur NULL est égal à tout autre marqueur NULL

À l’origine, certains SGBDR ont considéré que les NULLs devaient être distincts comme c’est le cas de MySQL/MariaDB et PostGreSQL tandis que d’autres comme d’Oracle Database et Microsoft SQL Server ont considéré que les NULLs étaient égaux.

Exemple 3.18* – Création d’une table avec contrainte de clé primaire et contrainte d’unicité :

1. CREATE TABLE S_ADM.T_UTILISATEUR_USR

2. (USR_ID             INTEGER      NOT NULL PRIMARY KEY,

3. USR_NOM            CHAR(32)     NOT NULL,

4. USR_LOGIN          VARCHAR(256) UNIQUE);

Créé une table dont la colonne USR_LOGIN peut être non renseignée, ou bien contenir une valeur distincte de toutes les autres valeurs déjà présentes. Notons en outre que la clé de cette table est implicitement dotée d'une contrainte d'unicité.

NOTE

- À la différence de la clé primaire, il peut y avoir autant de contraintes UNIQUE que l'on veut.

- La contrainte UNIQUE n'impose pas le NOT NULL. En cas de NULL l’unicité ne peut être vérifiée. L’unicité ne porte donc que si les valeurs sont exprimées.

- La notion de clé subrogée ou alternative, découlant du modèle relationnel, indique qu'à tout moment cette contrainte peut remplacer la clé primaire de la table, mais cette substitution n’est possible qu’à l’exception des éléments non valués.

Exemple 3.19* – Création d’une table avec une contrainte de clé primaire et une contrainte d’unicité multicolonne et deux contraintes d’unicité mono-colonne

1. CREATE TABLE S_RH.T_EMPLOYE_EMP

2. (EMP_ID                          INT NOT NULL PRIMARY KEY,

3. EMP_NUM_INSEE_SEXE              CHAR(1),

4. EMP_NUM_INSEE_DATE_NAISSANCE    CHAR(4),

5. EMP_NUM_INSEE_COMMUNE           CHAR(5),

6. EMP_NUM_INSEE_RANG              CHAR(3),

7. EMP_CLE_INSEE                   CHAR(2),

8. CONSTRAINT UK_EMP_NNI

9. UNIQUE(EMP_NUM_INSEE_SEXE,

10. EMP_NUM_INSEE_DATE_NAISSANCE,

11. EMP_NUM_INSEE_COMMUNE,

12. EMP_NUM_INSEE_RANG),

13. EMP_NOM                         CHAR(32),

14. EMP_PRENOM                      VARCHAR(25),

15. EMP_MATRICULE                   SMALLINT UNIQUE,

16. EMP_LOGIN                       VARCHAR(256) UNIQUE,

17. EMP_SOIGNANT                    BOOLEAN,

18. EMP_RPPS                        CHAR(12),

19. UNIQUE (EMP_SOIGNANT, EMP_RPPS);

Une table des employés avec trois clés subrogées : l'une concerne le numéro national d'identité (dit « numéro de sécurité sociale ») composé de quatre colonnes distinctes, l'autre le matricule et la troisième, un booléen indiquant s’il est soignant et le numéro RPPS du professionnel de santé (il s’agit d’employés d’un hôpital…).

ATTENTION
Une colonne ou un groupe de colonnes ayant une contrainte d'unicité peut contenir une pluralité de marqueurs NULL, le NULL étant un marqueur dépourvu de signification.

III-3-2-2-3 - Contrainte de clé étrangère (FOREIGN KEY)

La contrainte de clé étrangère est le moyen par lequel on référence les tables du modèle relationnel entres elles. Une telle contrainte permet, pour les valeurs de la table fille, de vérifier si elles sont référencées dans les colonnes d'une contrainte clé primaire ou subrogée/alternative fixée dans la table mère. Ce mécanisme s'appelle l'intégrité référentielle déclarative.

Il existe un autre moyen de garantir cette intégrité en passant par le biais de déclencheurs. C’est l’intégrité référentielle procédurale. Elle est plus complexe et moins performante, raison pour laquelle nous la déconseillons. Elle sera néanmoins abordée au chapitre 7 à titre d’exemple.

Une table mère, pour figurer dans une intégrité référentielle d'une la table fille, doit impérativement être pourvue d'une clé primaire ou d'une contrainte d'unicité.

Il faut comprendre en fait qu'une contrainte de clé étrangère se greffe sur une contrainte de clé primaire ou de clé subrogée par le biais de la liste de ses colonnes.

Syntaxe exprimée dans la définition de la colonne :

1. [ CONSTRAINT nom_contrainte ]

2. [FOREIGN KEY ] REFERENCES <table_mere> ( <colonne_de_référence> )

3. [ <clause_de _validation>           ]

4. [ <clause_de _gestion_modification> ]

5. [ <clause_de _gestion_suppression>  ]

Syntaxe exprimée en tant que contrainte libre :

1. CONSTRAINT nom_contrainte

2. FOREIGN KEY ( <liste_colonne> )

3. REFERENCES <table_mere> ( <liste_colonnes_de_référence> )

4. [ <clause_de _validation>           ]

5. [ <clause_de _gestion_modification> ]

6. [ <clause_de _gestion_suppression>  ]

De manière générale, les contraintes d’intégrité référentielle déclarative (FOREIGN KEY … REFERENCES), c’est-à-dire les contraintes de clés étrangères obéissent aux règles suivantes :

- pour insérer ou modifier une valeur de colonne pourvue d'une clé étrangère, la valeur doit exister préalablement dans la table mère. Dans le cas contraire il y a violation de contrainte ;

- le type de données de la colonne cible (table fille) et de la colonne source (table mère) doit être le même ;

- les noms des colonnes cibles et sources de l'intégrité référentielle peuvent être différents, mais il est conseillé qu'elles portent le même nom car elles ont sémantiquement la même signification (cela résulte de la modélisation conceptuelle des données). Dans le cas contraire il ne sera pas possible d'utiliser la technique du NATURAL JOIN (voir chapitre 5) ;

- si la clé étrangère porte sur plusieurs colonnes, le rang de chacune des colonnes dans la liste doit être le même dans la définition de la contrainte d’unicité (PRIMARY KEY ou UNIQUE) dans la table mère comme dans la table fille (dans la liste des colonnes de la contrainte FOREIGN KEY) ;

- en cas de suppression ou de modification de la clé primaire de la ligne référencée dans la table mère, il se peut qu'une violation de contrainte se produise, cela dépend de la façon dont l'intégrité référentielle est gérée.

NOTE
L’intégrité référentielle peut être mise en place même si les tables mère et fille sont situées dans des schéma SQL différents.
Nous étudierons au paragraphe suivant les finesses de cette contrainte introduite par les clauses de validation et de gestion pour la modification ou la suppression

Exemple 3.20* – Création d’une table avec une contrainte de clé étrangère

1. CREATE TABLE S_ADM.T_PRENOM_PRN

2. (PRN_ID             INTEGER NOT NULL PRIMARY KEY,

3. PRN_PRENOM_USUEL   VARCHAR(32) NOT NULL UNIQUE)

5. CREATE TABLE S_ADM.T_UTILISATEUR_USR

6. (USR_NOM            CHAR(32) NOT NULL,

7. PRN_ID             INTEGER

8. REFERENCES S_ADM.T_PRENOM_PRN (PRN_ID))

Dans la table des utilisateurs, nous faisons référence pour la colonne PRN_ID à une valeur clé de la table des prénoms. Cela permet d'aller chercher le prénom de l'utilisateur dans la table des prénoms si la colonne PRN_ID de la table des utilisateurs est renseignée.

Exemple 3.21* – Création de différentes tables avec de nombreuses contraintes

1. CREATE TABLE S_HOPITAL.T_PERSONNE_PHYSIQUE_PPS

2. (PPS_ID                          INT NOT NULL PRIMARY KEY,

3. PPS_NOM                         CHAR(32) NOT NULL,

4. PPS_PRENOM                      VARCHAR(25));

6. CREATE TABLE S_HOPITAL.T_PATIENT_PAT

7. (PRS_ID                          INT NOT NULL PRIMARY KEY

8. REFERENCES S_HOPITAL.T_PERSONNE_PHYSIQUE_PPS (PPS_ID),

9. PPS_NUM_INSEE_SEXE              CHAR(1) NOT NULL,

10. PPS_NUM_INSEE_DATE_NAISSANCE    CHAR(4) NOT NULL,

11. PPS_NUM_INSEE_COMMUNE           CHAR(5) NOT NULL,

12. PPS_NUM_INSEE_RANG              CHAR(3) NOT NULL,

13. CONSTRAINT UK_EMP_NNI

14. UNIQUE(PPS_NUM_INSEE_SEXE,

15. PPS_NUM_INSEE_DATE_NAISSANCE,

16. PPS_NUM_INSEE_COMMUNE,

17. PPS_NUM_INSEE_RANG),

18. PAT_DATE_NAISSANCE              DATE NOT NULL);

20. CREATE TABLE S_HOPITAL.T_SOIGNANT_TYPE_SGT

21. (SGT_ID                          INT NOT NULL PRIMARY KEY,

22. SGT_LIBELLE                     VARCHAR(32) NOT NULL UNIQUE);

24. CREATE TABLE S_HOPITAL.T_SOIGNANT_SPECIALITE_SST

25. (SST_ID                          INT NOT NULL PRIMARY KEY,

26. SST_LIBELLE                     VARCHAR(32) NOT NULL UNIQUE);

28. CREATE TABLE S_HOPITAL.T_SOIGNANT_SGN

29. (PRS_ID        INT NOT NULL PRIMARY KEY

30. REFERENCES S_HOPITAL.T_PERSONNE_PHYSIQUE_PPS (PPS_ID),

31. SGT_ID        INT NOT NULL

32. REFERENCES S_HOPITAL.T_SOIGNANT_TYPE_SGT (SGT_ID),

33. SST_ID        INT

34. REFERENCES S_HOPITAL.T_SOIGNANT_SPECIALITE_SST (SST_ID),

35. CONSTRAINTS CK_SGN_SI_MEDECIN

36. CHECK (SST_ID IS NULL

37. AND SGT_ID = (SELECT SGT_ID

38. FROM S_HOPITAL.T_SOIGNANT_TYPE_SGT

39. WHERE SGT_LIBELLE <> 'Médecin')

40. OR  SST_ID IS NOT NULL

41. AND SGT_ID = (SELECT SGT_ID

42. FROM S_HOPITAL.T_SOIGNANT_TYPE_SGT

43. WHERE SGT_LIBELLE = 'Médecin')) ,

44. SGN_RPPS                        CHAR(12) UNIQUE);

46. CREATE TABLE S_HOPITAL.T_ORDONANCE_ODC

47. (ODC_ID                          INT NOT NULL PRIMARY KEY,

48. ODC_DATE                        DATE NOT NULL DEFAULT CURRENT_DATE,

49. PPS_NUM_INSEE_SEXE              CHAR(1) NOT NULL,

50. PPS_NUM_INSEE_DATE_NAISSANCE    CHAR(4) NOT NULL,

51. PPS_NUM_INSEE_COMMUNE           CHAR(5) NOT NULL,

52. PPS_NUM_INSEE_RANG              CHAR(3) NOT NULL,

53. SGN_RPPS                        CHAR(12) NOT NULL

54. REFERENCES S_HOPITAL.T_SOIGNANT_SGN (SGN_RPPS)

55. CHECK (VALUE IN (SELECT SGN_RPPS

56. FROM S_HOPITAL.T_SOIGNANT_SGN

57. WHERE SGT_ID = (SELECT SGT_ID

58. FROM   S_HOPITAL.T_SOIGNANT_TYPE_SGT

59. WHERE  SGT_LIBELLE = 'Médecin'))),

60. FOREIGN KEY (PPS_NUM_INSEE_SEXE,

61. PPS_NUM_INSEE_DATE_NAISSANCE,

62. PPS_NUM_INSEE_COMMUNE,

63. PPS_NUM_INSEE_RANG)

64. REFERENCES S_HOPITAL.T_PATIENT_PAT

65. (PPS_NUM_INSEE_SEXE,

66. PPS_NUM_INSEE_DATE_NAISSANCE,

67. PPS_NUM_INSEE_COMMUNE,

68. PPS_NUM_INSEE_RANG));

La table des personnes physiques sert de référence aux tables des patients et des soignants. Les soignants font référence à leur type (infirmier, kiné, médecin…) et la contrainte de validation CK_SGN_SI_MEDECIN qui impose que les médecins aient une spécialité ce qui est paradoxalement une contrainte d’intégrité référentielle partielle formée par une contrainte CHECK… Enfin dans la table des ordonnances figure deux contraintes de clé étrangères l’une portant sur le numéro INSEE de sécurité sociale du patient et l’autre sur le numéro RPPS du soignant.

Notez enfin que la contrainte de clé étrangère portant sur la colonne SGN_RPPS est redondante avec la contrainte CHECK afférente à cette même colonne, mais que l’inverse n’est pas vrai, étant entendu que la contrainte de validation vérifie l’existance de la valeur d’un RPPS dans la table des soignants, uniquement s’il est médecin…

Le fait que des tables se référencent entres elles posent le problème de la précédence de l’insertion des données. Ainsi avant d’insérer une nouvelle facture, donc une ligne dans la table des factures pour un nouveau client, il faudra préalablement créer le client en insérant une ligne dans la table des clients.

L’exemple 3.22 montre un scénario typique d’ordonnancement des insertions de lignes dans différentes tables liées par l’intégrité référentielle.

Exemple 3.22* – Insertion des lignes dans l’ordre logique pour les différentes tables ayant des contraintes d’intégrité référentielle

1. INSERT INTO S_HOPITAL.T_PERSONNE_PHYSIQUE_PPS VALUES

2. (1, 'PARÉ', 'Ambroise'),     (2, 'CHARCOT', 'Jean-Martin'),

3. (3, 'SCHWEITZER', 'Albert'), (4, 'BARNARD', 'Christiaan'),

4. (5, 'ALZHEIMER', 'Aloïs'),   (6, 'FLEMMING', 'Alexander'),

5. (7, 'FREUD', 'Sigmund'),     (8, 'HIPPOCRATE', NULL),

6. (9, 'KOCH', 'Robert'),       (10, 'PAVLOV', 'Ivan'),

7. (11, 'MORIN', 'Marie'),      (12, 'LADOIX', 'Albert'),

8. (13, 'DUPONT', 'Patrick'),   (14, 'DUPONT', 'Marc'),

9. (15, 'ROUX', 'Paul'),        (16, 'GENTILUCI', 'Maurizio'),

10. (17, 'BEN BOUZA', 'Mounir'), (18, 'KLEIN', 'Maurice'),

11. (19, 'SANCHEZ', 'Manuel'),   (20, 'MAC CULLOCH', 'Kenneth');

1. INSERT INTO S_HOPITAL.T_PATIENT_PAT VALUES

2. (17, '2', '8812', '43120', '007', '1988-12-20'),

3. (20, '1', '6001', '75112', '124', '1960-01-13');

1. INSERT INTO S_HOPITAL.T_SOIGNANT_TYPE_SGT VALUES

2. (201, 'Médecin'), (202, 'Kiné'), (203, 'Infirmier');

1. INSERT INTO S_HOPITAL.T_SOIGNANT_SPECIALITE_SST VALUES

2. (311, 'Urologue'),     (312, 'Gastro-enterologue'),

3. (313, 'Gynécologue'),  (314, 'Neurologue'),

4. (316, 'Orthopédiste'), (317, 'Pédiatre');

1. INSERT INTO S_HOPITAL.T_SOIGNANT_SGN VALUES

2. (1, 201, 316, '14578415'),   (2, 201, 314, '12548785'),

3. (11, 203, NULL, '74519854'), (12, 202, NULL, '65875414');

1. INSERT INTO S_HOPITAL.T_ORDONANCE_ODC VALUES

2. (1001, '2022-12-08', '2', '8812', '43120', '007', '14578415'),

3. (1002, '2021-09-16', '2', '8812', '43120', '007', '14578415'),

4. (1003, '2022-12-05', '1', '6001', '75112', '124', '12548785');

Les insertions doivent se faire dans cet ordre précis, sinon il y aurait viol de l’intégrité référentielle. Pour la suppression, l’ordre logique d’exécution des requête doit être inversé. Par exemple :

1. DELETE FROM S_HOPITAL.T_ORDONANCE_ODC;

2. DELETE FROM S_HOPITAL.T_SOIGNANT_SGN;

3. DELETE FROM S_HOPITAL.T_SOIGNANT_SPECIALITE_SST;

4. DELETE FROM S_HOPITAL.T_SOIGNANT_TYPE_SGT;

5. DELETE FROM S_HOPITAL.T_PATIENT_PAT;

6. DELETE FROM S_HOPITAL.T_PERSONNE_PHYSIQUE_PPS;

III-4 - Gestion de l'intégrité référentielle

La contrainte de type FOREIGN KEY permet de mettre en place une intégrité référentielle entre une (ou plusieurs) colonne(s) d'une table et la (ou les) colonne(s) en regard composant la clé d'une autre table (la table de référence) afin de garantir les dépendances existantes et permettant d’assurer la jointure « naturelle » entre les tables dans la requête selon le modèle relationnel que l'on a défini. On parle alors de relation maître/esclave, parent/enfant ou mère/fille.

Figure. 3.1 : Intégrité référentielle entre la table mère des clients et la table fille des factures.

Le but de l'intégrité référentielle est de maintenir les « liens » d’intégrité entre les tables quelque soient les modifications engendrées sur les données dans l'une ou l'autre table.

Cette intégrité référentielle est validée lors de l'insertion grâce à la clause MATCH de la contrainte et maintenue au cours de la vie des données par les clauses ON DELETE et ON UPDATE lors des modifications de clés ou suppressions de lignes.

Voici une syntaxe plus complète de la contrainte de clé étrangère avec les clauses de validation et de gestion de l'intégrité référentielle :

1. CONSTRAINT nom_contrainte

2. FOREIGN KEY ( <liste_colonne_table> )

3. REFERENCES table_référencée ( <liste_colonne_référencées> )

4. [ MATCH { FULL | PARTIAL | SIMPLE } ]

5. [ ON UPDATE { NO ACTION | RESTRICT | CASCADE | SET NULL | SET DEFAULT } ]

6. [ ON DELETE { NO ACTION | RESTRICT | CASCADE | SET NULL | SET DEFAULT } ]

Nous allons maintenant détailler les différentes clauses de cette contrainte, à savoir :

- la clause MATCH de validation de la référence ;

- la clause ON UPDATE de gestion de la mise à jour ;

- la clause ON DELETE de gestion de la suppression.

En fait il faut considérer que la clause MATCH précise comment se comporte l'insertion ou la mise à jour dans la table fille, tandis que les clauses ON DELETE / ON UPDATE concernent le comportement des données dans la table fille lorsque les références des valeurs de clés de la table mère changent ou sont supprimées.

Autrement dit la clause MATCH, de validation, se voit du point de vue de la table qui utilise la référence (table fille) tandis que les clauses ON DELETE / ON UPDATE, de gestion, se voient du point de vue de la table référencée (table mère).

III-4-1 - Mode de validation de la référence, clause MATCH

La clause MATCH n’a d’intérêt que si la clé étrangère est composée de plusieurs colonnes. Elle précise la manière dont la contrainte valide ou refuse l'opération d'insertion ou de mise à jour en fonction des colonnes en jeu lorsque les valeurs de certaines colonnes sont manquantes (marqueurs NULL).

MATCH SIMPLE implique que :

- la contrainte s'applique si toutes les colonnes de la clé étrangère sont renseignées ;

- si une colonne au moins possède un marqueur NULL, la contrainte de clé étrangère ne s'applique pas.

Il y a donc violation de contrainte si les valeurs divergent et qu'elles sont toutes renseignées. Dans le cas ou une colonne n’est pas renseignée, la contrainte est ignorée.

MATCH PARTIAL implique que :

- La contrainte de clé étrangère s'applique pour toutes les colonnes renseignées, c’est-à-dire qu’il doit exister au moins une ligne dans la table de référence qui vérifie toutes les valeurs des colonnes renseignées.

Il y a donc violation de la contrainte si au moins une valeur renseignée diverge.

MATCH FULL implique que :

- Les colonnes composant la clé doivent être toutes vides (NULL) ou toutes renseignées, et dans ce dernier cas seulement, la référence est vérifiée.

Il y a donc violation de la contrainte si les valeurs divergent ou si une colonne est renseignée et l'autre pas.

A défaut de la spécifier c'est l'option MATCH SIMPLE qui s'impose par défaut.

NOTE
Préciser la clause MATCH n'a donc d'intérêt que si l'intégrité référentielle porte sur plusieurs colonnes.

Pour mieux comprendre le fonctionnement de cette clause, les exemples 3.23 à 3.25 montrent un modèle constitué d’une table mère (calendrier), d’une table fille (faits historiques) et une tentative d’insertion de lignes dans la table des événements de l’Histoire…

Exemple 3.23 – 3.25

Exemple 3.23* – Une table de référence avec une contrainte UNIQUE multicolonne

1. CREATE TABLE S_TALLY.T_CALENDRIER_CLD

2. (CLD_DATE       DATE PRIMARY KEY,

3. CLD_AN         SMALLINT NOT NULL CHECK(VALUE BETWEEN 1 AND 9999),

4. CLD_MOIS       SMALLINT NOT NULL CHECK(VALUE BETWEEN 1 AND 12),

5. CLD_JOUR       SMALLINT NOT NULL CHECK(VALUE BETWEEN 1 AND 31),

6. CONSTRAINT UK_CLD_AN_MOIS_JOUR UNIQUE (CLD_AN, CLD_MOIS, CLD_JOUR)) ;

Supposons que la table S_TALLY.T_CALENDRIER_CLD soit remplie avec toutes les données des dates des années de 1900 à l’an 2000, les colonnes CLD_AN, CLD_MOIS et CLD_JOUR étant respectivement, l’année, le mois et le jour du mois de chaque CLD_DATE… :

Et une table de faits historiques constituée comme montre l’exemple 3.24 :

Exemple 3.24* – Table de faits historiques faisant référence aux dates d’une table de calendrier

1. CREATE TABLE S_HISTORY.T_EVENEMENT_EVT

2. (EVT_ID            INTEGER PRIMARY KEY,

3. CLD_AN            SMALLINT,

4. CLD_MOIS          SMALLINT,

5. CLD_JOUR          SMALLINT,

6. EVT_DESCRIPTION   VARCHAR(256) NOT NULL,

7. CONSTRAINT FK_EVT_AN_MOIS_JOUR

8. FOREIGN KEY (CLD_AN, CLD_MOIS, CLD_JOUR)

9. REFERENCES S_TALLY.T_CALENDRIER_CLD (CLD_AN, CLD_MOIS, CLD_JOUR));

Si la contrainte FK_EVT_AN_MOIS_JOUR est en MATCH SIMPLE, alors les insertions suivantes sont possibles :

Exemple 3.25* – Tentatives d’insertion dans la table des faits historiques liée par intégrité référentielle à la table de calendrier

1. INSERT INTO S_HISTORY.T_EVENEMENT_EVT VALUES

2. (1, 1969, 7, 20, 'Premier pas de l''homme sur la lune'),

3. (2, 1945, 8, 6, 'Explosion de la bombe atomique à Hiroshima, Japon'),

4. (3, NULL, NULL, NULL, 'J.-M. Le Pen devient Président de la République'),

5. (4, NULL, 25, 212, 'Disparition d''Harry Potter dans la 4e dimension'),

6. (5, 1968, 5, NULL, 'Manifestations d''étudiants et grèves générales');

Si la contrainte FK_EVT_AN_MOIS_JOUR est en MATCH PARTIAL, alors la quatrième ligne (disparition d’Harry Potter) fait échouer le lot de requête car un mois 25 comme un jour de mois 212 n’existe pas.

Si la contrainte FK_EVT_AN_MOIS_JOUR est en MATCH FULL, alors la quatrième ligne (disparition d’Harry Potter) comme la cinquième ligne (Mai 68) font échouer le lot de requête car la clé étrangère est partiellement vide.

Rares sont les SGBDR qui en pratique mettent en œuvre toutes les options possibles de l’option MATCH pour la validation des références. En l’absence de cette clause, il est possible de programmer des déclencheurs car l’intégrité référentielle, comme nous l’avons déjà dit, peut être réalisée de manière procédurale… Nous verrons un tel cas au chapitre 7.

III-4-2 - Mode de gestion de l'intégrité, clauses ON UPDATE / ON DELETE

Au cours de la vie de la base de données, il est possible que l'on soit amené à supprimer la ligne qui sert de référence (DELETE), comme à en modifier la valeur de la clé (UPDATE). Une telle manipulation des données de la table mère n'est pas sans conséquence dans le maintient de l'intégrité référentielle en regard des lignes des tables filles.

Le mode de gestion de l'intégrité consiste à se poser la question de la règle qui doit être mise en œuvre dans le cas ou l'on tente de modifier les données de référence qu’utilise différentes intégrités référentielles.

Les différents modes de gestion de l'intégrité référentielle que propose SQL sont les suivants :

NO ACTION, RESTRICT, CASCADE, SET DEFAULT, SET NULL et ne peuvent s'appliquer qu'aux ordres SQL UPDATE (modification de données) et DELETE (suppression de lignes).

ON DELETE NO ACTION / ON UPDATE NO ACTION :

Toute action de modification de clé ou de suppression de ligne dans la table mère échoue pour les lignes en relation d'intégrité référentielle entre table mère et fille.

Dans le cas du NO ACTION, il y a blocage de l'ordre SQL UPDATE ou DELETE dans le but de maintenir le lien d'intégrité tel quel. Ce blocage intervient en fin de transaction si la contrainte est déférée.

ON DELETE RESTRICT / ON UPDATE RESTRICT :

Similaire à NO ACTION, mais le blocage intervient immédiatement sur l'ordre SQL qui a engendré le viol de la contrainte.

ON DELETE CASCADE / ON UPDATE CASCADE :

Toute modification de clé ou suppression de ligne dans la table mère est répercutée dans la table fille : si l'on entreprend une suppression de lignes de référence les lignes de la table fille en regard sont elles aussi supprimées. Si l'on modifie des valeurs de clés qui servent de référence, alors ces valeurs modifiées sont répercutées dans la table fille.

Dans le cas du CASCADE, il y répercussion de l'ordre SQL avec modification de valeur ou suppression de lignes afin de maintenir ou d'évacuer le lien d'intégrité.

ON DELETE SET NULL / ON UPDATE SET NULL :

Toute action de modification ou de suppression dans la table mère est répercutée dans la table fille par la suppression des valeurs des clés étrangères, qui sont remplacées par des marqueurs NULL.

Dans le cas du SET NULL, il y suppression des valeurs des clés étrangères pour les lignes concernées, ce qui conduit à un déréférencement du lien d'intégrité.

En pratique, cela suppose que la clé étrangère est « NULLable » sinon cela n’a aucun intérêt.

ON DELETE SET DEFAULT / ON UPDATE SET DEFAULT :

Toute action de modification ou de suppression dans la table mère est répercutée dans la table fille par la mise en place des valeurs spécifiées par défaut pour les colonnes des clés étrangères.

Dans le cas du SET DEFAULT, il y mise en place des valeurs par défaut des clés étrangères pour les lignes concernées, ce qui conduit à un glissement des valeurs de références du lien d'intégrité.

En pratique, cela suppose que la clé étrangère est pourvue d’une contrainte de défaut et que cette valeur est renseignée dans la référence, sinon cela n’a aucun intérêt.

À ce stade il convient de préciser les éléments suivants concernant le fonctionnement du mode de gestion de la référence :

- Autant le mode NO ACTION / RESTRICT est toujours possible et s'avère être celui qui opère par défaut, autant les autres modes sont dépendants de facteurs complémentaires tel que d'autres contraintes de clé étrangères ou bien l'absence de spécification de valeurs par défaut ou encore l'obligation de spécification de valeur (NOT NULL).

- Le mode cascade est très tentant, mais son coût de traitement peut s'avérer élevé. Mal maîtrisé il peut se révéler bloquant et devenir totalement contre performant, notamment dans le cas de cascades multiples ou de combinaisons de cascades et NO ACTION / RESTRICT.

- L'intérêt du SET NULL est de permettre la suppression des lignes devenues orphelines de manière différée, par exemple dans un traitement par lot intervenant aux heures creuses.

- L'intérêt du SET DEFAULT réside dans la possibilité de définir une référence particulière qui concentre les efforts de gestion des références obsolètes dans un traitement spécifique, par exemple le client 0 ou moi-même et, bien entendu de différer la suppression physique des lignes…

- NO ACTION est le mode par défaut en l'absence de spécification.

Exemple 3.26* – Tables référencées par différentes intégrités référentielles de différents modes

1. CREATE TABLE S_COM.T_CLIENT_CLI

2. (CLI_ID           INTEGER NOT NULL PRIMARY KEY,

3. CLI_NOM          CHAR(32) NOT NULL);

5. CREATE TABLE S_COM.T_COMMANDE_CMD

6. (CMD_ID           INTEGER NOT NULL PRIMARY KEY,

7. CMD_DATE         DATE NOT NULL DEFAULT CURRENT_DATE,

8. CLI_ID           INTEGER NOT NULL

9. REFERENCES S_COM.T_CLIENT_CLI (CLI_ID)

10. ON DELETE CASCADE);

12. CREATE TABLE S_COM.T_FACTURE_FCT

13. (FCT_ID           INTEGER NOT NULL PRIMARY KEY,

14. FCT_DATE         DATE NOT NULL DEFAULT CURRENT_DATE,

15. CLI_ID           INTEGER NOT NULL

16. REFERENCES S_COM.T_CLIENT_CLI (CLI_ID)

17. ON DELETE NO ACTION

18. ON UPDATE NO ACTION,

19. FCT_TOTAL_TTC    DECIMAL(16,2) NOT NULL,

20. FCT_ECHEANCE     DATE NOT NULL DEFAULT CURRENT_DATE + 30 DAY);

L’exemple 3.26 montre les tables client, produit, commande, ligne de commande et facture dont l’intégrité référentielle entre les tables est gérée par différents modes (NO ACTION et CASCADE).

La représentation schématique de cette intrication est montrée par la figure 3.2 :

Figure 3.2 – Représentation graphique de la clause de gestion dans différentes intégrités référentielles

Avec une telle base la suppression d'un client entraîne la suppression de ses commandes. Mais d'un autre côté la suppression d'un client n'est pas possible si des factures portent la référence de ce client.

La figure 3.2 montre les identifiants des clients dans les différentes tables. La suppression des clients dont CLI_ID vaut 1 ou 2 réussie car l’intégrité en mode CASCADE est gérée entre la table des commandes et celle des clients.

Pour le client 3, rien n’est supprimé, ni le client, ni ses commandes ni les factures car la transaction échoue au niveau de la facture (NO ACTION).

Pour le client 4 là aussi la suppression est impossible car il a des factures…

Comme la suppression d'un client est un ordre atomique, si ce client a des factures il ne sera pas possible de le supprimer et la violation de la contrainte entraînant l'abandon de l'ordre, aucune commande ne sera supprimée.

En revanche la suppression en cascade se produira bien si ce client a des commandes mais n'a pas encore été facturé. Et c’est bien ce que nous voudrions dans la vraie vie, supprimer les commandes qui ne font l’objet d’aucun paiement !

III-5 - Les assertions

Les assertions sont des contraintes dont l'étendue porte sur la base entière notamment pour permettre des règles de validation entre différentes colonnes de différentes tables ou vues à l'aide de prédicats. Les assertions au sens de la norme SQL sont donc des objets de la base de données.

Pour créer une assertion, il faut utiliser la commande CREATE ASSERTION et utiliser une contrainte de validation CHECK.

La syntaxe de création d'une assertion est la suivante :

1. CREATE ASSERTION [nom_schéma.]nom_assertion

2. CHECK ( predicat )

3. [ <attribut_assertion> ]

5. <attribut_assertion> ::

6. {INITIALLY DEFERRED | INITIALLY IMMEDIATE} [ [ NOT ] DEFERRABLE ]

7. | [NOT] DEFERRABLE [INITIALLY DEFERRED | INITIALLY IMMEDIATE]

Les règles de « differabilité », introduites par la clause <attribut_assertion> seront discutées au paragraphe suivant.

Nous prendrons comme exemple une unicité de clé devant porter sur deux tables distinctes, ce qui est le cas dans tous les héritages « exclusifs » :

Exemple 3.27* – Tables en héritage exclusif nécessitant une assertion d’exclusion

1. CREATE TABLE S_PRS.T_PERSONNE_GENERIQUE_PSG

2. (PSG_ID              INTEGER PRIMARY KEY,

3. PSG_DATE_CREATION   DATE NOT NULL);

5. CREATE TABLE S_PRS.T_PERSONNE_PHYSIQUE_PSP

6. (PSG_ID              INTEGER PRIMARY KEY

7. REFERENCES S_PRS.T_PERSONNE_GENERIQUE_PSG(PSG_ID),

8. PSP_NOM             VARCHAR(32) NOT NULL,

9. PSP_PRENOM          VARCHAR(25),

10. PSP_DATE_NAISSANCE  DATE);

12. CREATE TABLE S_PRS.T_PERSONNE_MORALE_PSM

13. (PSG_ID              INTEGER PRIMARY KEY

14. REFERENCES S_PRS.T_PERSONNE_GENERIQUE_PSG(PSG_ID),

15. PSM_RAISON_SOCIALE  VARCHAR(256) NOT NULL,

16. PSM_ENSEIGNE        VARCHAR(64),

17. PSM_SIRET           CHAR(14) NOT NULL UNIQUE);

Dans ce type de modélisation, les personnes morales et physiques héritent des caractéristiques d’une personne générique. Au niveau inférieur, il faut dissocier les PRS_ID en deux groupes, les valeurs affectées aux personnes physiques ne devant en aucun cas se retrouver dans la table des personnes morales et vice-versa.

Dans un tel cas on recoure à une assertion comme celle-ci :

Exemple 3.28* – Assertion d’exclusion pour le cas d’un héritage mutuellement exclusif

1. CREATE ASSERTION A_UNIQUE_ID_PRS

2. CHECK

3. (NOT EXISTS

4. (SELECT PRS_ID

5. FROM   S_PRS.T_PERSONNE_PHYSIQUE_PSP

6. INTERSECT

7. SELECT PRS_ID

8. FROM   S_PRS.T_PERSONNE_MORALE_PSM);

La sous-requête SELECT de la clause CHECK qui forme le prédicat de l'assertion, met en correspondance les tables S_PRS.T_PERSONNE_PHYSIQUE_PSP et S_PRS.T_PERSONNE_MORALE_PSM en faisant l’intersection des ensembles formés de la clé de chacune des tables. Dans le cas présent, afin d'assurer l'unicité de la clé à travers les deux tables, il ne faut pas qu'une personne physique porte le même numéro de clé qu'une personne morale. En aucun cas, la requête ne doit retourner un résultat. Cela est assuré à l'aide de l'expression EXISTS qui renvoite vrai au cas où un résultat est retourné et faux dans le cas où la requête ne retourne aucune ligne.

L'assertion est vérifiée chaque fois que l’une des deux tables fait l’objet d’une mise à jour (INSERT, UPDATE, DELETE…) et la contrainte valide la commande si le prédicat CHECK vaut vrai, donc si la requête d'extraction ne produit aucun résultat. Dans le cas contraire le viol de la contrainte intervient et l'ordre SQL d'insertion ou de modification est empêché qu'il porte sur l'une ou l'autre table.

Dans le cas de l’exemple 3.29, toute suppression de ligne d’une des deux tables filles ne violera jamais cette contrainte, mais par sa nature, l’assertion impose que cette vérification soit faite même en cas de suppression, ce qui est un travail inutile…

Une contrainte de table ne suffit pas à exprimer une telle règle de validation. Seul l'usage de l'assertion (contrainte propre à la base) le permet.

NOTE
Compte tenu de la complexité de mise en œuvre de la vérification des assertions et de leur coût exorbitant, la plupart des éditeurs de SGBD relationnel ont préféré ne pas implémenter à la lettre ce genre de contraintes, mais de leur substituer la notion de déclencheur (trigger) que nous verrons au chapitre 7.

III-6 - La déférabilité des contraintes

Qu'il s'agisse de contraintes de domaine, de table ou encore d'assertions, toutes les contraintes peuvent inclure une clause de déferrabilité, c'est à dire que leur application peut être différée à un moment plus opportun.

Jusqu'ici, nous avons supposé implicitement que la contrainte opérait dès que le fait générateur la déclenchait (insertion, modification ou suppression dans une table par exemple). Or SQL admet une grande souplesse en ce sens qu'il propose différents outils pour différer la validation de la contrainte. Ces outils sont la clause de déferrabilité propre à chaque contrainte, et le paramétrage de la déferrabilité des contraintes au sein du schéma.

La déférabilité d'une contrainte est une opération nécessaire dès que différentes contraintes interagissent créant ainsi ce que l'on appelle une référence croisée ou circulaire...

Pour comprendre ce mécanisme et les modes d'utilisation qu'il offre, il est nécessaire de prendre en compte la notion de transaction, c'est à dire une combinaison de différents ordres SQL que l'on veut exécuter en séquence et d'un seul bloc, c'est à dire en tout ou rien.

Ainsi lorsqu'une table T1 fait référence à une table T2 par une intégrité référentielle, se pose le problème de la mise en place d'une intégrité référentielle inverse de T2 vers T1 (intégrité référentielle circulaire) ... Nous voici confrontés au problème de l'œuf et de la poule... C'est pour trancher ce dilemme que les outils de déferrabilité de contrainte ont été définis par SQL.

Par exemple, dans ce cas de figure, on ne peut insérer dans T1 que si l'on a inséré dans T2 et inversement. Les opérations doivent donc être successives et combinées et si l'une d'entre elle échoue, il faut les défaire toutes, ce qui se fait au travers d'une transaction SQL.

La syntaxe de la clause de déferrabilité de la contrainte est la suivante :

[ NOT ] DEFERRABLE | DEFERRABLE { INITIALLY DEFERRED | INITIALY IMMEDIATE }

NOT DEFERRABLE :

La contrainte opère dès que l'ordre SQL qui la met en cause est appliqué. Elle ne pourra jamais être différée. C'est l'option par défaut en l'absence de spécification.

DEFERRABLE :

La contrainte peut être différée. À défaut de spécification complémentaire, elle opère dès que l'ordre SQL qui la met en en cause est appliqué.

INITIALLY DEFERRED :

À défaut de spécification contraire au niveau de la session, la contrainte sera différée au début de chaque transaction. En d'autres termes elle est vérifiée en fin de transaction. Cette option n'est pas compatible avec NOT DEFERRABLE.

INITIALLY IMMEDIATE :

Une contrainte différable, INITIALLY IMMEDIATE sera appliquée immédiatement au début de la transaction. En d'autres termes elle est vérifiée au cours de la transaction pour chaque ordre SQL qui l'appelle, sauf si le paramétrage de session en a décidé autrement.

Le paramètre SET CONSTRAINTS permet de définir le fonctionnement global ou particulier des contraintes au sein de la session. Il se définit à l'aide de la syntaxe suivante :

SET CONSTRAINTS { <liste_de_contraintes> | [ ALL ] }
[ DEFERRED | IMMEDIATE ]

Il n'opère que pour les contraintes qui n'ont pas été définies en tant que NOT DEFFERABLE.

Imaginons que nous voulons modéliser un client et ses commandes et placer dans la table du client la dernière commande servie, cela afin d'éviter de placer des clients sans commande dans la table des clients (les clients sans commandes étant placés naturellement dans la table des prospects...). Le problème se pose ainsi : comment insérer un nouveau client qui, par définition, n'a pas encore de commande, alors que l'on exige dans la table client de faire référence à la dernière commande ?

Nous pouvons concevoir cela à l'aide des descriptions de table suivante :

Exemple 3.29* – Tables avec contrainte d’intégrité en référence croisée

1. CREATE TABLE S_COM.T_CLIENT_CLI

2. (CLI_ID      INTEGER NOT NULL PRIMARY KEY,

3. CLI_NOM     CHAR(32),

4. CDE_ID      INTEGER NOT NULL,

5. CONSTRAINT  FK_CLI_CDE FOREIGN KEY (CDE_ID)

6. REFERENCES S_COM.T_COMMANDE (CDE_ID))

8. CREATE TABLE S_COM.T_COMMANDE_CDE

9. (CDE_ID      INTEGER NOT NULL PRIMARY KEY,

10. CDE_DATE    DATE,

11. CLI_ID      INTEGER NOT NULL,

12. CONSTRAINT  FK_CDE_CLI FOREIGN KEY (CLI_ID)

13. REFERENCES S_COM.T_CLIENT (CLIK_ID));

Or, si le lancement de ce script SQL de création des tables permettait d'instancier les objets, il serait en revanche impossible d'y rentrer la moindre donnée.

Dès lors, deux moyens sont possibles pour résoudre ce dilemme : modifier l'une des contraintes en la différant en fin de transaction, ou bien paramétrer le SGBDR pour qu'il valide une partie ou la totalité des contraintes à la fin de la transaction.

Deux solutions, toutes deux à base de contraintes différables sont possibles…

Exemple 3.30* – Résolution d’une référence d’intégrité croisée lors de la création des objets du schéma :

Les objets doivent avoir été créés comme suit :

1. CREATE TABLE S_COM.T_CLIENT_CLI

2. (CLI_ID      INTEGER NOT NULL PRIMARY KEY,

3. CLI_NOM     CHAR(32),

4. CDE_ID      INTEGER NOT NULL,

5. CONSTRAINT  FK_CLI_CDE FOREIGN KEY (CDE_ID)

6. REFERENCES S_COM.T_COMMANDE (CDE_ID))

8. CREATE TABLE S_COM.T_COMMANDE_CDE

9. (CDE_ID      INTEGER NOT NULL PRIMARY KEY,

10. CDE_DATE    DATE,

11. CLI_ID      INTEGER NOT NULL,

12. CONSTRAINT  FK_CDE_CLI FOREIGN KEY (CLI_ID)

13. REFERENCES S_COM.T_CLIENT (CLIK_ID)

14. DEFERRABLE INITIALLY DEFFERED);

Dès lors, la transaction d’insertion combinée réussie :

1. BEGIN TRANSACTION;

3. INSERT INTO S_COM.T_CLIENT_CLI VALUES (101, 'DUPONT');

5. INSERT INTO T_COMMANDE_CDE VALUE (4589, '2005-09-01', 101);

7. UPDATE S_COM.T_CLIENT_CLI SET CDE_ID = 4589 WHERE CLI_ID = 101;

9. COMMIT;

Exemple 3.31* – Résolution d’une référence d’intégrité croisée par forçage de la session :

Les objets doivent avoir été créés comme suit :

1. CREATE TABLE S_COM.T_CLIENT_CLI

2. (CLI_ID      INTEGER NOT NULL PRIMARY KEY,

3. CLI_NOM     CHAR(32),

4. CDE_ID      INTEGER NOT NULL,

5. CONSTRAINT  FK_CLI_CDE FOREIGN KEY (CDE_ID)

6. REFERENCES S_COM.T_COMMANDE (CDE_ID));

8. CREATE TABLE S_COM.T_COMMANDE_CDE

9. (CDE_ID      INTEGER NOT NULL PRIMARY KEY,

10. CDE_DATE    DATE,

11. CLI_ID      INTEGER NOT NULL,

12. CONSTRAINT  FK_CDE_CLI FOREIGN KEY (CLI_ID)

13. REFERENCES S_COM.T_CLIENT (CLIK_ID)

14. DEFERRABLE);

Là aussi, la transaction d’insertion combinée réussie avec le paramétrage de différabilité au niveau de la session :

1. SET CONSTRAINTS FK_CDE_CLI DEFERRED;

3. BEGIN TRANSACTION;

5. INSERT INTO S_COM.T_CLIENT_CLI VALUES (101, 'DUPONT');

7. INSERT INTO S_COM.T_COMMANDE_CDE VALUE (4589, '2005-09-01', 101);

9. UPDATE S_COM.T_CLIENT_CLI SET CDE_ID = 4589 WHERE CLI_ID = 101;

11. COMMIT;

NOTE

- La déférabilité d'une contrainte est un des deux seuls éléments du langage capable de créer un « auto rollback », c'est à dire une invalidation automatique des ordres SQL passés.

- La déférabilité s'applique à toute contrainte qu'elle soit de domaine, de table ou même une assertion.

III-7 - En guise de conclusion sur les contraintes

Si les contraintes sont des mécanismes séduisants, notamment pour garantir l’intégrité et la qualité des données, il faut les utiliser à bon escient.

Précisons tout de suite, que la vérification des contraintes prend un temps généralement infime en regard à l’ensemble des opérations qu’une requête doit effectuer pour s’exécuter…

Dans les SGBDR évolués, l’optimiseur, qui décide du plan d’exécution de la requête, utilise les contraintes pour simplifier les commandes SQL et gagner du temps d’exécution…

III-7-1 - Limites de la contrainte CHECK

En ce qui concerne les contraintes de validation, il est extrêmement dangereux et très contre-performant de faire référence à des données externes à la table. De plus, dans un tel cas, il serait possible d’utiliser la contrainte CHECK comme substitut à la contrainte FOREIGN KEY (ce que nous avons vu dans l’exemple 3.21 avec la contrainte CHECK de nom CK_SGN_SI_MEDECIN).

Dangereux, car pour des raisons de performance, certains SGBDR exécutent en parallèle les commandes SQL. Dès lors, des effets de bords pourraient se faire sentir sur les jointures résultant des opérations de vérification des contraintes de validation. À titre d’exemple, un calcul parallélisé comptant les occurrences de données externes à la table pourrait comptabiliser plusieurs fois les mêmes lignes de la table externe (recoupement de threads), pour différentes occurrences de la table interne. Cela entraînerait de facto l’interdiction du parallélisme et donc des problèmes de performance.

Pour ces mêmes raisons de performance, il faudrait poser de nombreux verrous afin d’interdire aux utilisateurs concurrents toute modification, le temps de vérifier la règle de validation de la contrainte CHECK, sur les lignes de la table externe, ce qui pose des problèmes de ressources et des blocages pour les autres utilisateurs.

Pire, il faudrait que les tables externes à la règle de validation soient dotées d’un mécanisme qui, lors de chaque mise à jour (INSERT, UPDATE, DELETE…) vérifient « à l’envers » que la contrainte dans la table distante est toujours validée…

Bien que la norme SQL n’interdise pas l’accès à des tables externes pour vérifier la validation de la règle d’une contrainte CHECK, rares sont les SGBDR professionnels qui le permettent.

Rares aussi sont les SGBDR qui permettent une contrainte CHECK portant sur plusieurs lignes de la table dans laquelle figure cette contrainte de validation, toujours pour des raisons de parallélisme, de concurrence et donc de performances.

Nous dirons donc que, même si votre SGBDR permet de créer des contraintes portant sur des tables externes ou vérifiant un lot de ligne de la table interne, il est plus que préférable de s’en abstenir.

III-7-2 - Désintérêt de la notion d’ASSERTION

Les contraintes généralisées de type « assertions » sont des contraintes portant sur différentes tables de la base et doivent être vérifiées à chaque mise à jour des tables mentionnées dans la règle de validation.

Dans l’exemple 3.29 Ceci suppose de vérifier l’assertion pour :

- La table S_PRS.T_PERSONNE_PHYSIQUE_PSP lors des commandes INSERT, UPDATE et DELETE

- La table S_PRS.T_PERSONNE_MORALE_PMR lors des commandes INSERT, UPDATE et DELETE

Cela fait donc 6 événements à surveiller repartis en deux objets.

Or on s’aperçoit que dans le lot d’exemples 3.28 et 3.29 la vérification lors des suppressions est inutile.

De même, la vérification lors des modifications n’a aucun intérêt à s’exécuter si la valeur de la clé PRS_ID est inchangée.

Enfin, dans tous les cas, cette vérification devrait n’être entreprise que sur les valeurs modifiées :

- nouvelles lignes ;

- valeurs modifiées des colonnes ;

- lignes supprimées ;

…et non sur l’ensemble de toutes les lignes des tables invoquées.

De fait, les assertions posent donc des problèmes de performances en demandant un travail trop important, inutile, mobilisant trop de ressources et bloquant la concurrence. C’est pourquoi l'éditeur Sybase avec son SGBDR SQL Server (l'ancêtre de Microsoft SQL Server) a riposté en proposant la notion de déclencheur (trigger) rapidement adoptée par les autres SGBDR, dans laquelle on peut choisir les actions à circonscrire et limiter la règle de vérification aux lignes portant les valeurs nouvelles ou anciennes. Ce concept de déclencheur inventé par l’éditeur Sybase dans son SGBDR SQL Server en 1987 a finalement été accepté dans la norme SQL.

Heureusement, rares sont les SGBDR ayant mis en œuvre les assertions.

De manière systématique, abstenez vous de créer des contraintes portant sur des jeux de données, même à travers des fonctions « utilisateur » mais recourez aux déclencheurs pour ce faire. Ils ont performants, à condition de fonctionner en mode ensembliste (per statement). Voir le chapitre 7 à ce sujet.

III-7-3 - La dangereuse Déferrabilité des contraintes

Là encore, si la chose semble séduisante, de nombreux auteurs, parmi lesquels Franck Edgar Codd lui-même et Chris Date, déconseillent la notion de déferrabilité des contraintes, car elle permet de violer à l’évidence quelques uns des principes mêmes du fondement des SGBD Relationnels comme la notion d’exécution atomique ou le fonctionnement ensembliste…

La solution de contournement passe par l’utilisation systématique des vues, associées à des contraintes simples, des procédures stockées et des déclencheurs. Nous étudierons ces deux derniers éléments au chapitre 7.

III-7-4 - Optimisation par les contraintes

Nous avons vu au chapitre 1, § 1.3.1 que les contraintes pouvaient jouer un rôle important pour l’optimiseur, l’aidant à simplifier une requête. Cela porte sur tous les types de contrainte… PRIMARY KEY, UNIQUE, FOREIGN KEY, CHECK

Quelques exemples théoriques simples permettent de comprendre la chose…

Si une requête recherche un produit dont le prix est inférieur à une valeur négative, la présence d’une contrainte limitant les prix à des valeurs positive ou 0, entraînera un résultat immédiat et vide, car l’optimiseur constatant cette impossibilité n’ira même pas scruter les valeurs de la table.

Dans le cas d’une interrogation multi-tabulaire, les tables peuvent être référencées par des valeurs de clé à d’autres tables comme nous l’avons vu au § 3.3.2.2 consacré à la contrainte FOREIGN KEY entre autres. On parle alors de table mère possédant une clé primaire et d’une table fille possédant une clé étrangère référençant la clé primaire de la table mère. En l’absence de mécanisme garantissant l’intégrité d’une telle correspondance, les requêtes sont moins bien optimisées que si l’intégrité référentielle est activée.

Voici un exemple modélisant un club de sport dans lequel nous trouvons une table des adhérents (ils sont nombreux) une table des sports pratiqués (plus d’une centaine) et une table associant les adhérents aux sports qu’ils pratiquent. Pour connaître les sports réellement pratiqués, c’est-à-dire, ceux sur lesquels il y a encore des adhérents, la requête proposée est la suivante :

Figure 3.3 – Plan de requête en l’absence de contraintes d’intégrité référentielle (MS SQL Server)

Le plan d’exécution montre que l’on accède aux trois tables et le coût de la requête (juste un indice de comparaison établi par l’optimiseur) est évalué à 0,264.

En ajoutant les contraintes d’intégrité référentielles et en rejouant la requête :

Figure 3.4 – Plan de requête avec présence de contraintes d’intégrité référentielle (MS SQL Server)

Le coût de la requête est maintenant évalué à 0,0376… soit un gain de 7 fois !

La raison en est simple : la jointure entre la table des pratiques et la table des adhérents n’est plus nécessaire car l’intégrité référentielle garantit que toute clé d’adhérent (ADR_ID) dans la table des pratiques existe bien dans la table des adhérents. Bien qu’exprimée dans la requête, cette jointure est totalement éliminée pour des raisons de performance !

La volumétrie des données d’une base induit des temps de réponse qu’il est facile de combattre avec de bonne pratique comme la nécessité absolue de mettre en place le plus possible de contraintes simples. L’optimiseur en tiendra compte pour simplifier certains plans de requête.

III-7-5 - Indexation des contraintes

Certaines contraintes sont créées avec un index qui permet d’en accélérer l’exécution.

À bien y regarder, aucun index n’est logiquement utile à une base de données (c’est pourquoi la norme SQL n’y fait aucune référence), mais physiquement nécessaire à l’obtention de bonnes performances.

Par exemple le fait de contrôler l’unicité d’une série de valeurs n’a pas besoin d’un index, censé accélérer la recherche de l’information. Pour garantir lors de l’insertion que l’on ajoute une valeur unique à la table, il suffit alors de verrouiller l’intégralité des lignes de la table de manière exclusive et les passer toutes en revue pour savoir si cette valeur existe déjà. Si oui, on rejette l’insertion, si non, on l’accepte. Mais il est évident que bloquer la totalité des accès concurrents à une table pour assurer une telle fonctionnalité n’est pas jouable, sauf si l’on est l’utilisateur unique de la base (dans ce cas, mieux vaut sans doute utiliser un tableur).

C’est pourquoi lorsque des contraintes FOREIGN KEY et UNIQUE sont posées, un index est créé de manière sous-jacente et cet index porte généralement le nom de la contrainte.

Certains SGBDR peu professionnels, en particulier MySQL et MariaDB ont décidé de créer des index sous les clés étrangères. Mais indexer systématiquement toutes les contraintes FOREIGN KEY est une absurdité que nous démonterons au chapitre 14.

III-8 - Les vues

Dans le langage SQL les vues ne sont autres que des requêtes instanciées, c'est à dire un ordre d'extraction de données SELECT dont le résultat est vu comme une table, d'ou le nom de « vue ». L’ordre SQL CREATE VIEW permet de créer une vue.

On peut les considérer comme des tables virtuelles dans le sens ou l’on peut y extraire les données des tables sous-jacentes, comme effectuer des mises à jour qui seront répercutées dans les tables finales, directement sous certaines conditions, ou indirectement via des déclencheurs de type INSTEAD OF. D’ailleurs la norme SQL considère les vues comme des tables du type « VIEW » tandis que les tables qui stockent les données sont considérées comme des « BASE TABLE ».

La vue normalisée de métadonnées « INFORMATION_SCHEMA » donnant la liste des tables, présente cette distinction, comme le montre la figure 3.5 :

Figure 3.5 – Liste des tables selon la norme SQL

Elles sont nécessaires pour gérer finement les privilèges et elles sont utiles pour masquer la complexité de certains modèles relationnels afin de les rendre plus présentables, plus compréhensibles, ne serait-ce qu’aux développeurs. Elles sont indispensables au développement qui ne devrait jamais être bâti en accès direct aux tables, mais toujours par l’intermédiaire de vues.

Voici la syntaxe SQL pour définir une vue :

1. CREATE VIEW nom_vue [ ( <liste_colonne> ) ]

2. AS

3. <requête_select>

4. [ WITH [ CASCADED | LOCAL ] CHECK OPTION ]]

Bien entendu, la liste des colonnes, si elle est spécifiée, doit comporter le même nombre d'items qu'il y a de colonnes dans la clause de sélection de la requête SELECT (les requêtes d'extraction de l'information SELECT sont vues aux chapitres 4 et 5).

Nous étudierons la clause CHECK OPTION au paragraphe 3.8.2.

ATTENTION
La liste des colonnes de la vue, construite dans la clause SELECT doit comporter des colonnes dont tous les noms sont distincts, comme dans une table.

En reprenant les tables de l’exemple 3.27, nous pouvons former les vues suivantes présentant les personnes morales, les personnes physiques et une concaténation des deux types de personnes :

Exemple 3.32* – Création de différentes vues basées sur les tables de l’exemple 3.28 :

1. CREATE VIEW S_PRS.V_PERSONNE_PHYSIQUE

2. AS

3. SELECT PG.*, PSP_NOM , PSP_PRENOM, PSP_DATE_NAISSANCE

4. FROM   S_PRS.T_PERSONNE_GENERIQUE_PSG AS PG

5. INNER JOIN S_PRS.T_PERSONNE_PHYSIQUE_PSP AS PP

6. ON PG.PSG_ID = PP.PSG_ID;

8. CREATE VIEW S_PRS.V_PERSONNE_MORALE

9. AS

10. SELECT PG.*,PSM_RAISON_SOCIALE, PSM_ENSEIGNE, PSM_SIRET

11. FROM   S_PRS.T_PERSONNE_GENERIQUE_PSG AS PG

12. INNER JOIN S_PRS.T_PERSONNE_MORALE_PSM AS PM

13. ON PG.PSG_ID = PM.PSG_ID;

15. CREATE VIEW S_PRS.V_PERSONNE

16. AS

17. SELECT PG.*, CASE

18. WHEN PP.PSG_ID IS NULL

19. THEN 'Morale'

20. ELSE 'Physique'

21. END AS TYPE_PERSONNE,

22. COALESCE(PSP_NOM, PSM_RAISON_SOCIALE) AS NOM,

23. COALESCE(PSP_PRENOM, PSM_ENSEIGNE) AS PRENOM_OU_ENSEIGNE,

24. PSP_DATE_NAISSANCE AS DATE_NAISSANCE_PERSONNE_PHYSIQUE,

25. PSM_SIRET AS SNUMERO_SIRET_PERSONNE_MORALE

26. FROM   S_PRS.T_PERSONNE_GENERIQUE_PSG AS PG

27. LEFT OUTER JOIN S_PRS.T_PERSONNE_PHYSIQUE_PSP AS PP

28. ON PG.PSG_ID = PP.PSG_ID

29. LEFT OUTER JOIN S_PRS.T_PERSONNE_MORALE_PSM AS PM

30. ON PG.PSG_ID = PM.PSG_ID;

Ces vues, présentent des données plus complètes concernant les personnes.

Les vues servent aussi à simplifier l'accès de certains utilisateurs aux informations communes situées dans une même table. Considérant par exemple la table des employés d'une entreprise comme définie à l’exemple 3.33 :

Exemple 3.33* – Création d’une table pour les employés :

1. CREATE TABLE S_RH.T_EMPLOYE_EMP

2. (EMP_ID                 INT PRIMARY KEY,

3. EMP_NOM                CHAR(32) NOT NULL,

4. EMP_PRENOM             VARCHAR(25),

5. EMP_DATE_NAISSANCE     DATE,

6. EMP_NNI_SEXE           CHAR(1) NOT NULL,

7. EMP_NNI_DATE_NAISS     CHAR(4) NOT NULL,

8. EMP_NNI_COMMUNE_NAISS  CHAR(5) NOT NULL,

9. EMP_NNI_RANG_NAISS     CHAR(3) NOT NULL,

10. EMP_DATE_ENTREE        DATE NOT NULL,

11. EMP_SALAIRE            DECIMAL(16, 2) NOT NULL,

12. EMP_FONCTION           VARCHAR(16),

13. EMP_SERVICE            VARCHAR(16),

14. EMP_MEDECIN_TRAITANT   VARCHAR(32));

Différents acteurs vont devoir accéder aux informations des employés. En particulier :

- les comptables pour calculer la paye ;

- les délégués du personnel pour les revendications syndicales ;

- la médecine du travail pour les contrôles annuels.

Mais tous ces acteurs bénéficient-ils du même accès à toutes les informations de cette table d’employés ? Assurément non ! D’où l’intérêt des vues que présente l’exemple 3.34 :

Exemple 3.34* – Trois vues des employés pour trois catégories d’utilisateurs :

--> pour les ressources humaines, tout sauf le médecin traitant

1. CREATE VIEW S_RH.V_EMP_RH

2. AS

3. SELECT EMP_ID, EMP_NOM, EMP_PRENOM, EMP_DATE_NAISSANCE,

4. EMP_NNI_SEXE, EMP_NNI_DATE_NAISS,

5. EMP_NNI_COMMUNE_NAISS, EMP_NNI_RANG_NAISS,

6. EMP_DATE_ENTREE, EMP_SALAIRE, EMP_FONCTION, EMP_SERVICE

7. FROM   S_RH.T_EMPLOYE_EMP;

--> pour les syndicats, pas de NNI ni salaire, ni médecin traitant

1. CREATE VIEW S_RH.V_EMP_SYNDICAT

2. AS

3. SELECT EMP_ID, EMP_NOM, EMP_PRENOM, EMP_DATE_NAISSANCE,

4. EMP_DATE_ENTREE, EMP_FONCTION, EMP_SERVICE

5. FROM   S_RH.T_EMPLOYE_EMP

6. WHERE  EMP_FONCTION <> 'PDG';

--> pour la médecine du travail, tout sauf le salaire

1. CREATE VIEW S_RH.V_EMP_MEDECIN

2. AS

3. SELECT EMP_ID, EMP_NOM, EMP_PRENOM, EMP_DATE_NAISSANCE,

4. EMP_NNI_SEXE, EMP_NNI_DATE_NAISS,

5. EMP_NNI_COMMUNE_NAISS, EMP_NNI_RANG_NAISS,

6. EMP_DATE_ENTREE, EMP_FONCTION, EMP_SERVICE, EMP_MEDECIN_TRAITANT

7. FROM   S_RH.T_EMPLOYE_EMP;

Chacun des acteurs ayant droit aux informations de l’employé ne verra pas les mêmes rubriques dans son interface, d’où le recours aux vues :

- les ressources humaines ont besoin de toutes les informations sauf le médecin traitant ;

- les syndicats ne doivent pas accéder aux informations confidentielles (NNI, médecin traitant) ni aux salaires et bien entendu ignorent le PDG ! ;

- le médecin du travail ne doit pas connaître le salaire mais il lui est indispensable d’accéder au NNI (Numéro National d’Identité – càd. N° de sécu) et au nom du médecin traitant.

Un dernier exemple (3.35) nous montre les tables des taux de TVA avec l’historique de l’évolution des taux. Cette dernière n’est pas facile à exploiter car y figure la date de début d’application du taux de TVA, mais pas la date de fin, ceci pour éviter une redondance qui, en cas de modification, pourrait conduire à un problème d’anomalie de mise à jour :

Exemple 3.35* – Tables présentant l’historique de l’évolution du taux de TVA de 1968 à 2023 :

1. CREATE TABLE S_TAXE.T_TVA_NATURE_TVN

2. (TVN_ID                 INT PRIMARY KEY,

3. TVN_CODE               CHAR(16) NOT NULL UNIQUE,

4. TVN_DATE_SUPPRESSION   DATE);

6. CREATE TABLE S_TAXE.T_TVA_HISTORIQUE_TVH

7. (TVH_ID              INT PRIMARY KEY,

8. TVN_ID              INT NOT NULL REFERENCES S_TAXE.T_TVA_NATURE_TVN (TVN_ID),

9. TVH_D_APPLICATION   DATE NOT NULL,

10. TVH_TAUX            FLOAT NOT NULL)

On ne saurait fournir directement un accès à ce jeu de tables tant elles sont difficiles d’exploitation. Le recours à une vue de synthèse apparaît nécessaire :

Figure 3.6 – Tables des natures et taux de TVA

La vue suivante synthétise l’information de ces deux tables :

1. CREATE VIEW S_TAXE.V_TVA

2. AS

3. SELECT N.TVN_ID, TVN_CODE, TVH_ID, TVH_D_APPLICATION AS DEBUT,

4. COALESCE(LEAD(TVH_D_APPLICATION)

5. OVER(PARTITION BY H.TVN_ID

6. ORDER BY TVH_D_APPLICATION),

7. TVN_DATE_SUPPRESSION, '9999-12-31') AS FIN,

8. H.TVH_TAUX

9. FROM   S_TAXE.T_TVA_NATURE_TVN AS N

10. INNER JOIN S_TAXE.T_TVA_HISTORIQUE_TVH AS H

11. ON N.TVN_ID = H.TVN_ID;

La présentation du contenu de cette vue (extrait) est bien plus pratique :

Figure 3.7 – Vue de synthèse de l’historique des taux de TVA (extrait)

La présence des dates de début et fin d’application de chacun des types de taux avec leur nom facilite la lecture et l’exploitation.

Nous pouvons d’ailleurs faire une autre vue, à partir de cette vue, afin de filtrer sur les taux de TVA encore actuels, c’est-à-dire ceux dont la date de fin est au 31 décembre 9999 (date théorique de la fin des temps en SQL). L’exemple 3.36 nous montre la construction de cette vue :

Exemple 3.36* – Tables présentant l’historique de l’évolution du taux de TVA de 1968 à 2023 :

1. CREATE VIEW S_TAXE.V_TVA_ACTUELLE

2. AS

3. SELECT TVN_ID, TVN_CODE, TVH_ID, TVH_TAUX

4. FROM   S_TAXE.V_TVA

5. WHERE  FIN = '9999-12-31';

On peut difficilement faire plus simple !

Figure 3.8 – Vue des taux de TVA actuels

Au final, une vue s’exploite comme une table. On peut en extraire les informations par une commande SELECT comme on le fait sur une table. On peut mettre à jour les données par les commandes INSERT, UPDATE, DELETE comme une table, à condition de respecter certaines conditions… Voyons les quelles !

III-8-1 - Mise à jour des données à travers les vues

Comme lorsqu'il s'agit d'une table, on peut mettre à jour directement les données des vues (insertion, suppression, modification), mais à condition que certaines règles soient respectées :

- ne porter que sur un ensemble de tables ou de vues modifiables : pas de sous requête sauf dans la clause WHERE, ni de requête ensembliste EXCEPT, UNION ou INTERSECT). Les vues contenant des tables jointes ou l'opérateur ensembliste UNION ALL peuvent être mises à jour dans certains cas, notamment si la vue publie la clé primaire ;

- ne pas contenir de données transformées (pas de concaténation, addition de colonne, fonction...) ni calcul d'agrégat (COUNT, SUM, AVG, MIN, MAX…) ;

- ne pas contenir l’opérateur DISTINCT ni de clause GROUP BY ou HAVING ;

- contenir toutes les colonnes obligatoires dans le cas de l’ajout de ligne (INSERT) ;

Cependant, et comme l'indique la première condition, une vue peut porter sur une autre vue et pour que la nouvelle vue construite à partir d'une autre vue puisse être modifiée, il faut qu'à chaque niveau d'imbrication des vues l'ensemble de ces règles soit respecté.

Les contraintes de la table étant respectées lors des mises à jour de vue, on ne peut insérer des données à travers une vue que si ces dernières respectent les contraintes. En particulier une vue qui ne contiendrait pas une colonne rendue obligatoire par une contrainte NOT NULL ne pourrait faire l'objet d'une insertion, alors qu'elle peut faire l'objet d'une modification ou d'une suppression de ligne.

En fait on peut dire que toute vues encapsulant un sous ensemble des lignes d'une unique table, dont aucune colonne n’est le résultat d’une transformation ou d’une d’un calcul, peuvent toujours être mises à jour.

Dans tous les cas il est possible de contourner ces limitations en ayant recours aux déclencheurs INSTEAD OF.

III-8-1-1 - Mise à jour directe dans une vue mono-tabulaire

En repartant de la table des employés de l’exemple 3.34, formons la vue suivante :

Exemple 3.37* – Vue mono-tabulaire avec filtre WHERE :

1. CREATE VIEW S_RH.V_EMP_SALARIE

2. AS

3. SELECT EMP_ID, EMP_NOM, EMP_PRENOM, EMP_DATE_NAISSANCE,

4. EMP_NNI_SEXE, EMP_NNI_DATE_NAISS, EMP_NNI_COMMUNE_NAISS,

5. EMP_NNI_RANG_NAISS, EMP_DATE_ENTREE, EMP_SALAIRE, EMP_FONCTION,

6. EMP_SERVICE, EMP_MEDECIN_TRAITANT

7. FROM   S_RH.T_EMPLOYE_EMP

8. WHERE  EMP_SERVICE <> 'Direction';

L’insertion d’un salarié est possible, même s’il est membre de la direction :

1. INSERT INTO S_RH.V_EMP_SALARIE VALUES

2. (1, 'DUPONT', 'Marc', '1987-12-21', '1', '8712', '54336', '028',

3. '2010-03-11', 1250, 'Découpeur', 'Production', NULL),

4. (2, 'LAMBERT', 'Luc', '1976-06-30', '1', '7606', '2A285', '001',

5. '2018-05-20', 18500, 'Président', 'Direction', NULL);

Alors que Luc Lambert, de par son statut de PDG sera invisible de la vue, mais aura bien été stocké dans la table… Nous verrons au § 3.8.2 comment dans un tel cas interdire les modifications des lignes non qualifiées avec la clause WITH CHECK OPTION.

En revanche, dès que notre vue ne montre pas les données brutes de la table :

- les insertions ne sont plus possibles si :

- les colonnes originelles d’une transformation sont obligatoires ;

- l’insertion porte sur une colonne transformée lorsqu’elle n’est pas obligatoire ;

- les modifications ne sont pas possibles pour les colonnes transformées.

Ainsi dans la vue de l’exemple 3.38, toute insertion échoue, car le numéro de sécurité sociale a été présenté de manière concaténée :

Exemple 3.38*– Vue mono-tabulaire avec transformation des données :

1. CREATE VIEW S_RH.V_EMP_SALARIE_NNI

2. AS

3. SELECT EMP_ID, EMP_NOM, EMP_PRENOM, EMP_DATE_NAISSANCE,

4. CONCAT(EMP_NNI_SEXE, EMP_NNI_DATE_NAISS, EMP_NNI_COMMUNE_NAISS,

5. EMP_NNI_RANG_NAISS) AS NNI, EMP_DATE_ENTREE, EMP_SALAIRE,

6. EMP_FONCTION, EMP_SERVICE

7. FROM   S_RH.T_EMPLOYE_EMP;

Les commandes SQL suivantes qui tentent d’ajouter des lignes partent en erreur :

1. INSERT INTO S_RH.V_EMP_SALARIE_NNI VALUES

2. (3, 'SCHMIDT', 'Albert', '1982-11-21', '1821154220030',

3. '2010-03-11', 1450, 'Manoeuvre', 'Logistique'),

4. (4, 'MOULIN', 'Marc', '1969-11-18', '1691125321007',

5. '2018-05-20', 3200, 'Vendeur', 'Commercial');

Néanmoins, il sera possible de contourner cette difficulté à l’aide d’un déclencheur INSTEAD OFF.

III-8-1-2 - Mise à jour directe dans une vue multi-tabulaire

Reprenons les vues créées à l’exemple 3.32 où il est question de personnes génériques, personnes physiques et personnes morales. Pouvons nous faire des mises à jour ? Par exemple insérer ? La réponse est oui, à condition que ces insertions concernent toujours une seule table à la fois, sinon le moteur SQL ne peut pas savoir, par nature – une seule table doit être visée dans une mise à jour – et par logique : combien de lignes (cardinalité) devront être insérées dans chacune des tables ?

Exemple 3.39* – Insertion dans une table à travers la vue des personnes physiques

1. INSERT INTO S_PRS.V_PERSONNE_PHYSIQUE

2. (PSG_ID, PSG_DATE_CREATION)

3. VALUES (987,    CURRENT_DATE);

5. INSERT INTO S_PRS.V_PERSONNE_PHYSIQUE

6. (PSP_NOM,   PSP_PRENOM, PSP_DATE_NAISSANCE)

7. VALUES ('BONNARD', 'Frédéric', '1963-02-18');

La première insertion est réussie, mais la seconde échoue en indiquant qu’il est impossible d'insérer la « valeur » NULL dans la colonne PSG_ID, table S_PRS.T_PERSONNE_PHYSIQUE_PSP, et pour cause, cette colonne ne figure pas dans la liste d’insertion (PSP_NOM, PSP_PRENOM, PSP_DATE_NAISSANCE), car elle ne figure pas dans la définition de la vue. Si nous tentons de l’ajouter comme ceci :

1. INSERT INTO S_PRS.V_PERSONNE_PHYSIQUE

2. (PSG_ID, PSP_NOM,   PSP_PRENOM, PSP_DATE_NAISSANCE)

3. VALUES (987,    'BONNARD', 'Frédéric', '1963-02-18');

Là encore impossible de d’insérer cette ligne car la colonne PSG_ID fait partie de la table des personnes génériques tandis que les colonnes PSP_NOM, PSP_PRENOM, PSP_DATE_NAISSANCE font partie de la table des personnes physiques. Le message d’erreur est généralement assez explicite, du genre : la vue ou la fonction S_PRS.V_PERSONNE_PHYSIQUE ne peut pas être mise à jour car la modification porte sur plusieurs tables de base. Dans ce cas, une solution serait de rajouter dans la vue la colonne PSG_ID de la table des personnes physiques en lui donnant un autre nom. Voici comment modifier la vue pour que l’insertion réussisse :

1. ALTER VIEW S_PRS.V_PERSONNE_PHYSIQUE

2. AS

3. SELECT PG.*, PP.PSG_ID AS PSG_ID_PSP, PSP_NOM , PSP_PRENOM, PSP_DATE_NAISSANCE

4. FROM   S_PRS.T_PERSONNE_GENERIQUE_PSG AS PG

5. INNER JOIN S_PRS.T_PERSONNE_PHYSIQUE_PSP AS PP

6. ON PG.PSG_ID = PP.PSG_ID;

Du coup, l’insertion ci-après, réussit :

1. INSERT INTO S_PRS.V_PERSONNE_PHYSIQUE

2. (PSG_ID_PSP, PSP_NOM,   PSP_PRENOM, PSP_DATE_NAISSANCE)

3. VALUES (987,        'BONNARD', 'Frédéric', '1963-02-18');

III-8-1-3 - Mise à jour d’une vue calculée

Qu’en est-il si les informations publiées par une vue ont été transformées ? La mise à jour directe est possible lorsque les informations sont extraites de manière brute des tables. Mais si une colonne fait l’objet de calculs ou de modifications, comment permettre quand même les INSERT, UPDATE et DELETE ?

C’est ce que nous allons voir avec un exemple simple, basé sur la vue de l’exemple 3.38 où toute insertion semblait impossible du fait de la concaténation des éléments du numéro national d’identité (n° de « sécu ») …

Dans un tel cas, if faut recourir à un déclencheur INSTEAD OF (nous verrons les déclencheurs plus en détail au chapitre 7). L’exemple 3.40 montre le code d’un tel déclencheur pour la commande INSERT :

Exemple 3.40* – Déclencheur INSTEAD OF pour permettre l’INSERT sur une vue calculée

1. CREATE TRIGGER E_I_EMP_SALAIRE_NNI

2. ON S_RH.V_EMP_SALARIE_NNI

3. INSTEAD OF INSERT

4. FOR EACH STATEMENT

5. BEGIN ATOMIC

6. INSERT INTO S_RH.T_EMPLOYE_EMP

7. SELECT EMP_ID, EMP_NOM, EMP_PRENOM, EMP_DATE_NAISSANCE,

8. SUBSTRING(NNI, 1, 1),

9. SUBSTRING(NNI, 2, 4),

10. SUBSTRING(NNI, 6, 5),

11. SUBSTRING(NNI, 10, 3),,

12. EMP_DATE_ENTREE, EMP_SALAIRE, EMP_FONCTION,

13. EMP_SERVICE, NULL

14. FROM   new;

15. END;

Le code de ce trigger intercepte l’insertion de la ligne pour le rerouter sur la table en extrayant bribe par bribe les 4 éléments du numéro de sécurité sociale de la pseudo table « new ». Dès lors les insertions que nous avions tentées sont couronnées de succès :

1. INSERT INTO S_RH.V_EMP_SALARIE_NNI VALUES

2. (3, 'SCHMIDT', 'Albert', '1982-11-21', '1821154220030',

3. '2010-03-11', 1450, 'Manoeuvre', 'Logistique'),

4. (4, 'MOULIN', 'Marc', '1969-11-18', '1691125321007',

5. '2018-05-20', 3200, 'Vendeur', 'Commercial');

Enfin, par le même biais d’un déclencheur INSTEAD OF nous pouvons contourner le problème d’insertion multitable… Dans l’exemple 3.39 nous avions modifié la vue S_PRS.V_PERSONNE_PHYSIQUE pour qu’elle expose les colonnes des deux tables. Voici un déclencheur qui permet une insertion simultanée dans les deux tables :

Exemple 3.41* – Déclencheur INSTEAD OF pour insertion dans plusieurs tables à travers une vue

1. CREATE TRIGGER E_I_V_PERSONNE_PHYSIQUE

2. ON S_PRS.V_PERSONNE_PHYSIQUE

3. INSTEAD OF INSERT

4. FOR EACH STATEMENT

5. BEGIN ATOMIC

6. INSERT INTO S_PRS.T_PERSONNE_GENERIQUE_PSG

7. SELECT PSG_ID, COALESCE(PSG_DATE_CREATION, CURRENT_DATE)

8. FROM   new;

9. INSERT INTO S_PRS.T_PERSONNE_PHYSIQUE_PSP

10. SELECT PSG_ID, PSP_NOM, PSP_PRENOM, PSP_DATE_NAISSANCE

11. FROM   new;

12. END;

Dans l’exemple 3.41 nous alimentons les tables de la vue en deux temps, par rapport à la pseudo table « new ». La première insertion dirige les valeurs des colonnes PSG_ID et PSG_DATE_CREATION vers la table des personnes génériques et la seconde le reste des informations vers la table des personnes physiques.

Notez le recours à la fonction COALESCE que nous avons déjà évoquée au chapitre 2 § 2.6 et qui permet de remplacer un marqueur NULL par une valeur, ici la date système.

III-8-2 - Restriction de mise à jour des vues modifiables

La clause WITH CHECK OPTION implique que, si la vue peut être mise à jour, alors les valeurs modifiées, insérées ou supprimées doivent répondre à la validation de la clause WHERE comme s'il s'agissait d'une contrainte.

Dans la vue de l’exemple 3.37, nous avions une clause WHERE indiquant que la vue était restreinte à tous les employés sauf ceux de la direction (en fait les PDG ne sont pas des salariés…) avec la clause WHERE suivante :

WHERE EMP_SERVICE <> 'Direction'

Lorsque nous avions tenté d’insérer Luc LAMBERT en tant que président à la direction, cette commande avait bien fonctionné mais cette personne n’était plus visible dans la vue… Dans un tel cas, il n’est alors plus possible de modifier (par un UPDATE) les informations de Luc LAMBERT, ni même de le supprimer (par un DELETE) à travers cette vue. Aucun message d’erreur, mais simplement le fait qu’aucune ligne n’est modifiée ou supprimée En revanche il est possible de promouvoir tout autre employé ne faisant pas partie de la direction en tant que membre de la direction.

C’est ce que montre l’exemple 3.42 :

Exemple 3.42* – Mise à jour d’une vue pour des données qualifiées ou non sans CHECK OPTION

1. UPDATE S_RH.V_EMP_SALARIE

2. SET    EMP_DATE_NAISSANCE = '1978-06-30'

3. WHERE  EMP_ID = 2; --> Luc Lambert;

5. DELETE FROM S_RH.V_EMP_SALARIE

6. WHERE  EMP_ID = 2;  --> Luc Lambert;

8. UPDATE S_RH.V_EMP_SALARIE

9. SET    EMP_SERVICE = 'Direction',

10. EMP_FONCTION = 'Directeur'

11. WHERE  EMP_ID = 1; --> Marc Dupont;

En modifiant la vue pour y ajouter la qualification des mises à jour qui doivent respecter le filtre WHERE, comme le montre l’exemple 3.42 :

Exemple 3.43* – Modification d’une vue pour y ajouter la clause CHECK OPTION

1. CREATE VIEW S_RH.V_EMP_SALARIE

2. AS

3. SELECT EMP_ID, EMP_NOM, EMP_PRENOM, EMP_DATE_NAISSANCE,

4. EMP_NNI_SEXE, EMP_NNI_DATE_NAISS, EMP_NNI_COMMUNE_NAISS, EMP_NNI_RANG_NAISS,

5. EMP_DATE_ENTREE, EMP_SALAIRE, EMP_FONCTION, EMP_SERVICE

6. FROM   S_RH.T_EMPLOYE_EMP

7. WHERE  EMP_SERVICE <> 'Direction'

8. WITH CHECK OPTION;

Les mises à jour de l’exemple 3.42 vont toutes échouer avec l'apparition d'un message d'erreur comme celui-ci : « L'ordre UPDATE/DELETE a échoué parce qu'une ou plusieurs lignes résultant de l'opération n'étaient pas qualifiées sous la contrainte CHECK OPTION »

Les options CASCADED et LOCAL (CASCADE par défaut) font que la clause CHECK OPTION porte uniquement sur la vue (LOCAL) ou bien sur toutes les vues dérivées qui en dépendent (CASCADED), c’est-à-dire sur les vues construites sur d’autres vues. À défaut de le spécifier l'option CASCADED est prise en compte.

III-8-3 - De l’intérêt des vues

Les vues SQL sont essentielles et on devrait toujours passer par ces objets qui constituent le modèle externe de Données (ou MED), grand oublié de la plupart des cours et tutoriels de la modélisation des bases de données. En voici les raisons…

III-8-3-1 - Simplification de la présentation des données

Nous avons constaté avec les tables présentant l’historique de la TVA combien il était difficile de savoir la TVA applicable à telle date. La vue de l’exemple 3.35 présente des données faciles à comprendre par tout utilisateur et sans que les données de la base aient une forme quelconque de redondance… En effet, la tentative est grande de créer une table de l’historique de la TVA qui ait exactement les caractéristiques de la vue, mais ce serait au détriment des performances et de la facilité de mise à jour. En effet une telle table redonderait de manière directe le code de la TVA et de manière indirecte car calculée la date de fin qui n’est autre que la date suivante d’application du même code TVA ou si la TVA est actuelle la date de la fin des temps… Or toute redondance se traduit par des octets supplémentaires et tout octet supplémentaire par un allongement des temps de réponse, ainsi que qu’un allongement de la durée des différentes tâches de la maintenance d’une base de données (défragmentation, sauvegarde, vérification d’intégrité physique…).

III-8-3-2 - Facilitation de la gestion de la sécurité

L’exemple 3.34 nous a montré que les données d’une table ne doivent pas toutes être présentées aux différentes catégories d’utilisateurs. Nous avons distingué le personnel des ressources humaines (RH), qui doit pouvoir accéder à tout sauf le médecin traitant, les délégués syndicaux ne devant voir aucune des données de numéro de sécurité sociale, ni de salaire, ni le médecin traitant et enfin les acteurs de la médecine du travail qui doivent pouvoir accéder à tout sauf le salaire. Donc trois vues pour trois catégories d’acteurs différents.

Figure 3.9 – Une même table trois vues différentes

Il devient alors extrêmement facile de donner accès en lecture aux différents acteurs et pour les écritures en restreindre encore les possibilités :

Figure 3.10 – Une même table trois vues différentes et ses différents niveaux d’accès

III-8-3-3 - Abstraction des tables

Les vues constituent une abstraction des tables. C’est un concept similaire à la notion d’objet entre une instance de l’objet et son « moule » constitué par la classe. Mais nous ne sommes pas dans le même univers, car la persistance de l’information doit être prépondérante à toute considération de modification de la structure de l’objet. Dans le monde des objets, aucun objet n’est persistant. Dans le monde des bases de données, tout ce qui porte des données doit persister sous peine de perdre les informations.

Or il arrive que l’on soit dans l’obligation de restructurer la base par exemple en découpant une table devenue obèse (on parle de table obèse, lorsqu’il y figure trop de colonnes par rapport ce que devrait être une modélisation appliquant strictement les règles de l’art…). Une telle restructuration, par exemple couper une table horizontalement en deux parties impacterait la plupart des requêtes déjà écrites et le code de traitement… Mais en utilisant des vues qui assurent une interface entre les données des tables et la couche applicative, il n’y a plus rien à modifier dans le code applicatif, même si l’on découpe la table en plusieurs parties qui seront toutes rassemblées par une vue que l’on rendra modifiable directement ou par le biais de déclencheurs INSTEAD OF.

III-8-3-4 - Simulation du comportement objet (CRUD)

Finalement, les tables pourvues d’une intégrité référentielle savamment pesée, de la technique des vues, combinées, si besoin est, à des déclencheurs INSTEAD OF et des procédures stockées, fournissent l’aspect CRUD (Create, Read, Update, Delete) dont le monde de la programmation objet revendique la paternité sans jamais avoir vu qu’il existait déjà au sein des SGBDR ! En effet, rappelons que l’acte de naissance les SGBD Relationnels date de 1970 sous la houlette de Franck Edgar Codd, alors que les premiers langages objet réellement opérationnels (smalltalk) datent de 1972…

Au début des années 2000, les connaissances du monde SQL et des SGBD Relationnels étaient assez bien maîtrisées par les développeurs, mais l’arrivée de nouveaux acteurs à la piètre qualité (PostGreSQL, MySQL…), mais aux coûts apparemment moindres, a fait pencher la balance de la connaissance vers le tout programmatique au détriment de l’intelligence des SGBD Relationnels. Et l’on a malheureusement vu l’arrivée d’ORM (Object Retational Mapping) et de « frameworks » censés combler le fossé séparant les deux mondes en offrant malheureusement des performances souvent lamentables, ce que Ted Neward qualifia à l’époque de Viêtnam de l’informatique…

En l’occurrence, CRUD est implémenté dans tous les SGBDR de bonne qualité (Oracle Database, IBM DB2, Microsoft SQL Server), même si l’objet représenté par la vue est composé d’accès à plusieurs tables, par le biais de :

- Create : INSERT INTO par le biais de déclencheurs ou de procédures stockées

- Read : SELECT par le biais de déclencheurs ou de procédures stockées

- Update : UPDATE par le biais de déclencheurs ou de procédures stockées

- Delete : DELETE impactant différentes tables en mode CASCADE par le biais de l’intégrité référentielle

III-9 - Colonne calculées et auto incréments

Certaines colonnes de table peuvent contenir autre chose que des données brutes. SQL distingue les colonnes calculées et les auto-incréments.

III-9-1 - Colonnes calculées

La norme SQL:2003 a formalisé un mécanisme déjà bien implanté dans certains SGBDR : les colonnes calculées, que SQL appelle GENERATED COLUMNS. Il s'agit tout simplement de définir dans la table une colonne dont le type sera remplacé par une expression scalaire pouvant être bâtie à partir d'autres colonnes de la table. Une colonne calculée doit être introduite par la clause :

GENERATED { ALWAYS | BY DEFAULT } AS ( <expression_de_calcul> )

Exemple 3.44 : tables avec des colonnes calculées

1. CREATE TABLE S_COM.T_FACTURE_ENTETE_FCE

2. (FCE_ID                  INTEGER PRIMARY KEY,

3. CLI_ID                  INTEGER NOT NULL

4. REFERENCES S_COM.T_CLIENT_CLI (CLI_ID),

5. FCE_DATE                DATE NOT NULL DEFAULT CURRENT_DATE,

6. FCE_REMISE_POURCENT     FLOAT NOT NULL DEFAULT 0

7. CHECK (VALUE BETWEEN 0 AND 100),

8. FCE_TOTAL_HT            GENERATED ALWAYS AS

9. ((SELECT SUM(FCD_PRIX_VENTE_HT)

10. FROM   S_COM.T_FACTURE_ETAIL_FCD AS T

11. WHERE  T.FCE_ID = FCE_ID) * (1 - FCE_REMISE_POURCENT / 100.0)),

12. FCE_TOTAL_TVA           GENERATED ALWAYS AS

13. ((SELECT SUM(FCD_TVA)

14. FROM   S_COM.T_FACTURE_ETAIL_FCD AS T

15. WHERE  T.FCE_ID = FCE_ID) * (1 - FCE_REMISE_POURCENT / 100.0)));

17. CREATE TABLE S_COM.T_FACTURE_ETAIL_FCD

18. (FCD_ID                  INTEGER PRIMARY KEY,

19. FCE_ID                  INTEGER NOT NULL

20. REFERENCES S_COM.T_FACTURE_ENTETE_FCE (FCE_ID) ON DELETE CASCADE,

21. PRD_ID                  INTEGER NOT NULL

22. REFERENCES S_COM.T_PRODUIT_PRD (PRS_ID),

23. FCD_PRIX_UNITAIRE_HT    DECIMAL(16, 2) NOT NULL

24. CHECK (VALUE >= 0),

25. FCD_QUANTITE            FLOAT NOT NULL DEFAULT 1

26. CHECK (VALUE > 0),

27. FCD_REMISE_POURCENT     FLOAT NOT NULL DEFAULT 0

28. CHECK (VALUE BETWEEN 0 AND 100),

29. TVA_ID                  INTEGER

30. REFERENCES S_REF.T_TVA (TVA_ID),

31. FCD_PRIX_VENTE_HT       GENERATED ALWAYS AS

32. (FCD_PRIX_UNITAIRE_HT * FCD_QUANTITE * (1 - FCD_REMISE_POURCENT / 100.0)),

33. FCD_TVA                 GENERATED ALWAYS AS

34. (FCD_PRIX_VENTE_HT

35. * (1 + COALESCE((SELECT TVA_TAUX

36. FROM   S_REF.T_TVA AS T

37. WHERE  T.TVA_ID = TVA_ID), 0))));

Cependant, et pour les mêmes raisons que cellesinvoquées concernant les contraintes de validation CHECK, la plupart des SGBDR refusent les expressions de calcul portant sur d’autres choses que la ligne courante de la table. Est donc généralement interdit dans l’expression d’une colonne calculée :

- un accès à plus d’une ligne de la table hôte ;

- un accès à une table externe à la table hôte.

Nous verrons au chapitre 7 que les vues matérialisées peuvent correspondre, sous certaines conditions à l’obtention d’un résultat similaire.

En l’occurrence, seule la colonne calculée FCD_PRIX_VENTE_HT est prise en compte dans la plupart des SGBDR.

III-9-2 - Auto incréments

Il existe deux méthodes bien distinctes. L’usage de la propriété IDENTITY qui s’applique à une colonne d’une table, et l’objet SEQUENCE indépendant qui fournit des valeurs par le biais d’un appel intégré soit dans la définition de la colonne d’une table soit dans un déclencheur BEFORE.

ATTENTION
Quel que soit l’outil choisit pour générer un auto incrément, ce dernier n’offre aucune garantie de séquencement (les paramètres peuvent être changés à tout moment et on peut forcer des valeurs) et n’offre pas non plus de numérotation continue (il peut y avoir des « trous » ou des chevauchements).

III-9-2-1 - Propriété IDENTITY

La propriété IDENTITY se définit dans la table comme colonne calculée à la place de la contrainte par défaut après la définition du type SQL de la colonne comme suit :

GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY
[ ( <option_autoincrément> ) ]

Les arguments de cette propriété sont les suivants :

- START WITH <valeur> : initialisation de l'incrément

- INCREMENT BY <valeur> : initialisation du pas d'incrément

- MAXVALUE <valeur> | NO MAXVALUE : spécification ou suppression de la valeur maximale de l'auto incrément

- MINVALUE <valeur> | NO MINVALUE : spécification ou suppression de la valeur minimale de l'auto incrément

- CYCLE | NO CYCLE : spécification de cycle ou non.

- CACHE n | NO CACHE : indique si des valeurs précalculées doivent être mises en mémoire

Il ne peut y avoir qu’une seule colonne de la table pourvue de ce type d’auto-incrément.

Par défaut les options sont de démarrer à 1 par pas de 1 sans aucune autre option.

Il doit être utilisé sur une colonne de type entier.

Exemple 3.45* – Table pourvu de l’auto incrément IDENTITY

1. CREATE TABLE S_ADM.T_UTILISATEUR_USR

2. (USR_ID             INTEGER

3. GENERATED ALWAYS AS IDENTITY

4. (START WITH -2147483648

5. INCREMENT BY 1

6. NO CYCLE

7. CACHE 100)

8. CONSTRAINT PK_USR PRIMARY KEY,

9. USR_LOGIN          VARCHAR(256) NOT NULL);

ATTENTION
Ne confondez pas auto incrément et clé. Un auto incrément ne garantit pas l’unicité et ne propose pas les mêmes services qu’une clé primaire. Une clé est systématiquement indexée et garantit l’unicité. Ce n’est pas le cas de l’auto incrément dont les valeurs sont arbitraires et peuvent avoir des doublons par forçage ou modification des paramètres de calcul.

III-9-2-2 - Objet SEQUENCE

C’est un objet indépendant de toute table dont la syntaxe est :

1. CREATE SEQUENCE [ nom_schema. ] nom_sequence

2. AS { <type_SQL_entier> | nom_domaine_entier }

3. [ START WITH <valeur> ]

4. [ INCREMENT BY <valeur> ]

5. [ { MINVALUE [ <valeur> ] } | { NO MINVALUE } ]

6. [ { MAXVALUE [ <valeur> ] } | { NO MAXVALUE } ]

7. [ CYCLE | { NO CYCLE } ]

8. [ { CACHE [ <valeur> ] } | { NO CACHE } ]

Observez qu’il possède les mêmes caractéristiques que la propriété IDENTITY.

Pour en obtenir une valeur il faut faire appel à la commande :

NEXT VALUE FOR [ nom_schema. ] nom_sequence
[ OVER ( <clause_orde_by_over> ) ]

Pour opérer comme valeur d’une colonne de table, il doit être placé dans la contrainte par défaut de la colonne visée.

La clause OVER du séquenceur ne peut être invoquée que dans une commande de lecture SELECT ou un ordre UPDATE.

Exemple 3.46* – Définition d’une séquence et utilisation

1. CREATE SEQUENCE S_SYSTEM.SQC_TOUTES_TABLES

2. AS BIGINT

3. START WITH 1

4. INCREMENT BY 255

5. NO CYCLE

6. CACHE 10000;

8. -- utilisation dans la définition d'une table

9. CREATE TABLE S_ADM.T_UTILISATEUR_USR

10. (USR_ID             INTEGER NOT NULL

11. DEFAULT NEXT VALUE FOR S_SYSTEM.SQC_TOUTES_TABLES

12. CONSTRAINT PK_USR PRIMARY KEY,

13. USR_LOGIN          VARCHAR(256) NOT NULL);

15. -- utilisation dans une requêtes d'extraction SELECT

16. SELECT NEXT VALUE FOR S_SYSTEM.SQC_TOUTES_TABLES

17. OVER(ORDER BY  EMP_DATE_NAISSANCE ),

18. *

19. FROM S_RH.T_EMPLOYE_EMP;

III-10 - Modification et suppression des objets

SQL permet de modifier les objets créés à l'aide de l'ordre ALTER et de les supprimer à l'aide de la commande DROP.

Le principe général est le suivant : on peut modifier les caractéristiques d'un objet, à condition de ne pas entraîner de pertes de données. On peut supprimer un objet, mais si cet objet dépend d'autres objets (par exemple pour une table mère d'une intégrité référentielle ou une table d'une vue) il faut spécifier que l'on veut aussi entraîner la suppression en cascade des objets dépendants.

III-10-1 - Suppression des objets

La syntaxe est simple :

DROP <type_objet> <nom_objet> [ { RESTRICT | CASCADE } ]

Avec :

<type_objet> ::=
{ DATABASE | SCHEMA | DOMAINE | SEQUENCE | TABLE | ASSERTION | VIEW }
<nom_objet> ::= [ nom_schema.]nom_objet

RESTRICT est par défaut, sauf dans le cas de DATABASE (la base de données) dont la suppression entraîne la perte de tout le contenu de la base.

CASCADE n’est pas disponible pour le domaine mais la suppression est réussie dans tous les cas car le domaine se propage par copie de sa définition.

ATTENTION
Même en mode cascade, la suppression d’une vue n’implique pas la suppression des tables, mais juste des vues qui en dépendent.

La suppression d’un schéma en mode CASCADE peut échouer s’il existe des intégrités référentielles entre tables de différents schémas.

La suppression d’une table en mode CASCADE supprimera toutes les tables filles liées par l’intégrité référentielle mais échouera si la table elle-même est une fille d’une intégrité référentielle à un niveau supérieur.

Exemple 3.47 – Suppression de divers objets

1. DROP SCHEMA S_COM WITH CASCADE;

2. DROP TABLE S_ADM.T_UTILISATEUR_USR;

3. DROP SEQUENCE S_SYSTEM.SQC_TOUTES_TABLES;

4. DROP VIEW S_TAXE.V_TVA WITH CASCADE;

NOTE
Certains SGBDR ne supportent pas la notion de DROP … CADCADE essentiellement pour des raisons de sécurité. En effet il n’existe pas de filet de sécurité comme la corbeille Windows, pour se rattraper en cas de mauvaise cascade… Il faudra donc supprimer les objets un à un dans l’ordre inverse d’imbrication.

III-10-2 - Modifier une base de données ou la supprimer

La norme SQL ne propose pas de commande pour modifier une base de données. Notons cependant que tous les éditeurs permettent de modifier des éléments d’une base de données, comme par exemple la collation par défaut, le stockage, différents paramétrages, etc.

La syntaxe générale pour ce faire est :

ALTER DATABASE nom_base <commande_modifiant_la_base>;

Pour supprimer une base de données, la commande est :

DROP DATABASE nom_base;

ATTENTION
Toute commande DROP supprime de manière définitive une base de données Rappelons que SQL ne dispose pas d’une corbeille à la Windows…

III-10-3 - Modifier un schéma SQL

Bien que la norme SQL ne propose aucune manière de modifier un SCHEMA la plupart des éditeurs de SGBDR proposent une commande ALTER SCHEMA, par exemple pour en changer le propriétaire du schéma (nous verrons cette notion au chapitre 8 consacré à la sécurité), ou encore, renommer le schéma ou transférer des objets d’un schéma dans l’autre, etc.

La syntaxe générale pour ce faire est :

ALTER SCHEMA nom_schema <option_modification_schema>;

Pour supprimer un schéma SQL, la commande est :

DROP SCHEMA nom_schema [ { RESTRICT | CASCADE } ];

III-10-4 - Modifier un DOMAIN

La syntaxe de l’ordre SQL ALTER DOMAIN est la suivante :

1. ALTER DOMAIN nom_domaine

2. { SET DEFAULT <valeur_par_défaut> |

3. DROP DEFAULT |

4. ADD <contrainte_de_domaine> |

5. DROP CONSTRAINT <nom_contrainte> }

ATTENTION

- ALTER DOMAIN ne permet pas de changer le type de données ;

- Les changements de contraintes et de la valeur par défaut se répercutent pour toutes les colonnes des tables et des vues qui utilisent cette définition de domaine.

Exemple 3.48 – Modification d’un domaine SQL

1. CREATE DOMAIN S_TYPE.D_JOUR_SEMAINE

2. AS CHAR(8)

3. DEFAULT 'Dimanche'

4. CONSTRAINT CK_D_JOUR CHECK (VALUE IN ('Lundi', 'Mardi', 'Mercredi',

5. 'Jeudi', 'Vendredi', 'Samedi', 'Dimanche'));

On peut changer la contrainte listant les valeurs comme ceci :

1. ALTER DOMAIN S_TYPE.D_JOUR_SEMAINE

2. DROP DEFAULT

3. DROP CONSTRAINT CK_D_JOUR;

5. ALTER DOMAIN S_TYPE.D_JOUR_SEMAINE

6. SET DEFAULT 'DIM';

7. ADD CONSTRAINT CK_D_JOUR CHECK (VALUE IN ('LUN', 'MAR', 'MER',

8. 'JEU', 'VEN', 'SAM', 'DIM'));

S’il existe des informations conformes à l’ancienne définition du domaine, une violation de contrainte de domaine se produira.

III-10-5 - Modifier une séquence ou un IDENTITY

On peut modifier les caractéristiques d’une séquence par le biais de la commande :

1. ALTER SEQUENCE [ nom_schema.] nom_sequence

2. AS { <type_SQL_entier> | nom_domaine_entier }

3. [ START WITH <valeur> ]

4. [ INCREMENT BY <valeur> ]

5. [ { MINVALUE [ <valeur> ] } | { NO MINVALUE } ]

6. [ { MAXVALUE [ <valeur> ] } | { NO MAXVALUE } ]

7. [ CYCLE | { NO CYCLE } ]

8. [ { CACHE [ <valeur> ] } | { NO CACHE } ];

Pour un IDENTITY, les paramètres modifiables sont les mêmes, mais doivent être modifiés via la commande ALTER TABLE en précisant le nom de la colonne.

Syntaxe de l’ajout d’un IDENTITY à une colonne existante :

1. ALTER TABLE [ nom_schéma.] nom_table

2. ALTER [ COLUMN ] nom_colonne

3. ADD GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY

4. [ ( <options_identity> ) ];

Syntaxe de la modification des propriétés d’un IDENTITY lié à une colonne existante :

1. ALTER TABLE[ nom_schéma.] nom_table

2. ALTER [ COLUMN ] nom_colonne

3. { SET GENERATED { ALWAYS| BY DEFAULT }

4. | SET <options_identity>

5. | RESTART [ [ WITH ] graine ] }

Les options identity sont les mêmes que celle de la séquence. Graine est la nouvelle valeur initiale de l’identity.

Syntaxe de la suppression d’un IDENTITY à une table :

ALTER TABLE[ nom_schéma.] nom_table DROP IDENTITY

Exemple 3.49 – Divers modification d’IDENTITY

1. ALTER TABLE S_ADM.T_UTILISATEUR_USR

2. ALTER COLUMN USR_ID

3. RESTART WITH 1;

4. ALTER TABLE S_ADM.T_UTILISATEUR_USR

5. ALTER COLUMN USR_ID

6. SET GENERATED BY DEFAULT;

7. ALTER TABLE S_ADM.T_UTILISATEUR_USR

8. ALTER COLUMN USR_ID

9. SET MAXVALUE 32765, CYCLE;

III-10-6 - Supprimer une table ou en modifier la structure

La commande SQL DROP TABLE, permet de supprimer une table. Comme dans le cadre du DROP SCHEMA, elle prend elle aussi un éventuel paramètre RESTRICT ou CASCADE permettant de définir si une exception est levée et l'ordre annulé dans le cas ou la table est référencée par une vue, une assertion, un déclencheur ou une routine (RESTRICT), ou encore s'il faut aussi détruire en chaîne les objets ainsi référencés (CASCADE).

Plus intéressant est l'ordre SQL ALTER TABLE de modification d'une table, dont la syntaxe est la suivante :

1. ALTER TABLE nom_table

2. ADD [ COLUMN ] nom_colonne <type_ou_domaine> [ nom_collation ]

3. [ [ NOT ] NULL] [ <liste_constrainte>

4. | DROP [ COLUMN ] nom_colonne { RESTRICT | CASCADE }

5. | ALTER [ COLUMN ] nom_colonne SET DEFAULT <valeur_par_defaut>

6. | ALTER [ COLUMN ] nom_colonne DROP DEFAULT

7. | ALTER [ COLUMN ] nom_colonne <identity_option>

8. | ADD CONSTRAINT <contrainte_de_table>

9. | DROP CONSTRAINT nom_contrainte { RESTRICT | CASCADE }

On note donc qu'il est possible :

- d’jouter ou supprimer une colonne ou une contrainte de table ;

- d’ajouter ou supprimer une valeur par défaut à une colonne.

On en déduit qu'il n'est pas possible :

- de modifier le type de données ou le nom de la colonne;

- de supprimer ou ajouter une contrainte de colonne et en particulier jamais pour la contrainte de type NULL / NOT NULL;

- de modifier la collation d'une colonne existante.

Dans le cas de la suppression de la colonne avec l'option CASCADE, alors tous les objets (vues, assertions, déclencheurs, routines...), à commencer par les contraintes internes à la table et référençant cette colonne, sont supprimés.

Exemple 3.50 – Modification de la structure d’une table

En partant de la table ainsi définie :

1. CREATE TABLE S_ADM.T_UTILISATEUR_USR

2. (USR_NOM            CHAR(32)     NOT NULL,

3. USR_PRENOM         VARCHAR(16)  NOT NULL,

4. USR_MAIL           VARCHAR(256) NOT NULL,

5. USR_ORG            VARCHAR(128),

6. CONSTRAINT PK_USR_NOMPRE PRIMARY KEY (USR_NOM, USR_PRENOM),

7. CONSTRAINT UK_USR_MAIL   UNIQUE (USR_MAIL),

8. CONSTRAINT UK_USR_NOMORG UNIQUE (USR_NOM, USR_ORG));

La séquence des ordres suivants :

1. ALTER TABLE S_ADM.T_UTILISATEUR_USR

2. DROP COLUMN USR_ORG

3. CASCADE;

5. ALTER TABLE S_ADM.T_UTILISATEUR_USR

6. ADD COLUMN USR_ID INTEGER NOT NULL;

8. ALTER TABLE S_ADM.T_UTILISATEUR_USR

9. DROP CONSTRAINT PK_USR_NOMPRE;

11. ALTER TABLE S_ADM.T_UTILISATEUR_USR

12. ADD CONSTRAINT PK_USR PRIMARY KEY (USR_ID);

…a pour effet d'obtenir la structure de table suivante :

1. CREATE TABLE S_ADM.T_UTILISATEUR_USR

2. (USR_NOM            CHAR(32)     NOT NULL,

3. USR_PRENOM         VARCHAR(16)  NOT NULL,

4. USR_MAIL           VARCHAR(256) NOT NULL,

5. USR_ID             INTEGER      NOT NULL,

6. CONSTRAINT UK_USR_MAIL UNIQUE (USR_MAIL),

7. CONSTRAINT PK_USR      PRIMARY KEY (USR_ID));

Bien que SQL n'ait pas prévu la possibilité de modifier directement le type ou le nom d'une colonne, on peut utiliser un script SQL transactionnel en plusieurs étapes pour y parvenir.

En partant de la structure de la table S_ADM.T_UTILISATEUR_USR obtenue dans l’exemple 3.49, voici un exemple de script SQL qui change le type et le nom de la colonne USR_MAIL en USR_USER_EMAIL NVARCHAR(128), ajoute une colonne référençant le DNS depuis la table S_ADM.T_DNS, coupe l’email en deux parties : la partie nom d’utilisateur allant dans la colonne USR_USER_EMAIL et le DNS faisant référence à la table des DNS dont la description est la suivante :

1. CREATE TABLE S_ADM.T_DNS

2. (DNS_ID             INTEGER PRIMARY KEY,

3. DNS_NAME           VARCHAR(128) NOT NULL UNIQUE);

Le tout en préservant les données…

Exemple 3.51 – Modification de table incluant un changement de nom de colonne

1. ALTER TABLE S_ADM.T_UTILISATEUR_USR

2. ADD USR_USER_EMAIL NVARCHAR(128) NOT NULL,

3. DNS_ID INTEGER NOT NULL REFERENCES S_ADM.T_DNS(DNS_ID);

5. UPDATE S_ADM.T_UTILISATEUR_USR

6. SET USR_USER_EMAIL =

7. SUBSTRING(USR_MAIL FROM 1

8. FOR CHARACTER_LENGTH(USR_MAIL)

9. -POSITION('@' IN USR_MAIL)-1),

10. DNS_ID = (SELECT DNS_ID

11. FROM S_ADM.T_DNS

12. WHERE DNS_NAME =

13. SUBSTRING(USR_MAIL FROM

14. POSITION('@' IN USR_MAIL) + 1 FOR

15. CHARACTER_LENGTH(USR_MAIL)

16. - POSITION('@' IN USR_MAIL)));

18. ALTER TABLE S_ADM.T_UTILISATEUR_USR

19. ADD CONSTRAINT UK_USR_MAIL UNIQUE (USR_USER_EMAIL, DNS_ID);

21. ALTER TABLE S_ADM.T_UTILISATEUR_USR

22. DROP COLUMN USR_MAIL CASCADE;

Qui donne au final la table suivante :

1. CREATE TABLE S_ADM.T_UTILISATEUR_USR

2. (USR_NOM            CHAR(32)     NOT NULL,

3. USR_PRENOM         VARCHAR(16)  NOT NULL,

4. USR_ID             INTEGER      NOT NULL,

5. USR_USER_EMAIL     NVARCHAR(128) NOT NULL,

6. DNS_ID             INTEGER NOT NULL REFERENCES S_ADM.T_DNS(DNS_ID)

7. CONSTRAINT PK_USR      PRIMARY KEY (USR_ID)

8. CONSTRAINT UK_USR_MAIL UNIQUE (USR_USER_EMAIL, DNS_ID));

On peut être surpris de voir que la première commande ALTER TABLE fonctionne alors que l’on a spécifié NOT NULL pour la colonne DNS_ID sans préciser de valeur par défaut. Mais lors d’une modification de table avec ajout de colonne, les lignes déjà présentes ne sont pas vérifiées pour les valeurs de la nouvelle colonne. On en conclut donc que l’obligation de valeurs ne porte que sur les opérations futures de mise à jour et non sur le passé. Mais le langage SQL a prévu que l’on puisse forcer les valeurs par défaut dans les lignes actuelles à condition que l’option WITH VALUES soit mentionnée juste après la valeur par défaut. À titre d’exemple on aurait pu lancer la commande suivante :

1. ALTER TABLE S_ADM.T_UTILISATEUR_USR

2. ADD USR_USER_EMAIL NVARCHAR(128) NOT NULL,

3. DNS_ID INTEGER NOT NULL DEFAULT 1 WITH VALUES

4. REFERENCES S_ADM.T_DNS(DNS_ID);

On conçoit facilement qu'il est préférable d'utiliser un outil de modélisation de données capable d'automatiser de tels script SQL de modification de table.

III-10-7 - modifier une ASSERTION

Il n'est pas possible de modifier une assertion, mais il est possible de la supprimer à l'aide de l'ordre SQL DROP ASSERTION puis de la recréer, le tout dans une transaction explicite.

III-10-8 - Renommer un objet

La norme SQL n’a pas prévu la possibilité de changer le nom d’un objet et propose de le supprimer puis le recréer. Pour une table, il est plus facile de la recopier par création à la volée et détruire la table source (voire paragraphe suivant).

Certains SGBDR proposent une option de de changement de nom intégrée à la commande ALTER.

C’est le cas de PostGreSQL qui permet la commande :

ALTER <type_objet> [ nom_schema. ] nom_objet RENAME TO nouveau_nom;

…pour un objet de premier niveau (table, vue, …) et :

1. ALTER TABLE [ nom_schema. ] nom_table

2. { RENAME [ COLUMN ] nom_colonne TO nouveau_nom_colonne

3. |  RENAME CONSTRAINT nom_constrainte TO nouveau_nom_constrainte

…pour un objet de second niveau (colonnes, contraintes, …).

Ces commandes spécifiques à PostGreSQL ne permettent pas de changer le schéma de l’objet. Pour déplacer un objet d’un schéma à l’autre il faut utiliser la commande PostGreSQL :

ALTER <type_objet> [ nom_schema. ] nom_objet SET SCHEMA autre_nom_schéma;

Dans les deux syntaxes ci-avant :

<type_objet> ::= { SCHEMA | TABLE | VIEW | DOMAIN }

Exemple 3.52 – Renommage d’une table et déplacement dans un autre schéma (PostGreSQL).

1. ALTER TABLE S_ADM.T_DNS RENAME TO T_NOM_SERVEUR_WEB_DNS;

2. ALTER TABLE S_ADM.T_NOM_SERVEUR_WEB_DNS SET SCHEMA S_INTERNET;

Dans Microsoft SQL Server ceci se fait, pour le changement de nom, à l’aide de la procédure stockée sp_rename et pour le changement de schéma avec ALTER SCHEMA :

EXEC sp_rename '[ nom_schema. ] nom_objet' , 'nouveau_nom_objet', '<type_classe>';

Dans lequel :

<type_classe ::= { COLUMN | DATABASE | <object_name> }

La sous-classe object_name devant être le nom d’une contrainte (CHECK, FOREIGN KEY, PRIMARY KEY, UNIQUE…) par exemple.

ATTENTION
Microsoft SQL Server n’autorise pas de changer le nom d’un schéma SQL. Créez un nouveau schéma, transférez les objets dedans puis supprimez l’ancien.

Enfin, la commande :

ALTER SCHEMA nom_schéma TRANSFERT [ nom_schéma. ] nom_objet;

…permet de transférer tout objet dans son nouveau schéma.

Exemple 3.53 – Renommage d’une table et déplacement dans un autre schéma (SQL Server).

1. EXEC sp_rename 'S_ADM.T_DNS', 'T_NOM_SERVEUR_WEB_DNS', 'OBJECT';

2. ALTER SCHEMA S_INTERNET TRANSFER S_ADM.T_NOM_SERVEUR_WEB_DNS;

III-10-9 - Création ou modification

Certains SGBDR permettent de modifier un objet, ou si l’objet n’existe pas de le créer. C’est la commande CREATE OR ALTER, disponible uniquement pour certains objets comme des domaines, des vues… mais pas des tables.

La syntaxe générale est :

CREATE OR ALTER [ nom_schéma.]nom_objet <definition_objet>

III-10-10 - Suppression si présence

Certains SGBDR permettent de supprimer un objet uniquement au cas où ce dernier existe déjà.

La syntaxe générale est :

IF EXISTS DROP <classe_objet> [nom_schéma.]nom_objet [;]

Comme dans le cas de la création ou modification, cela évite une erreur si l'objet n'existe pas dans le cadre de l'ALTER ou du DROP.

III-11 - Création d'une table à la volée

SQL permet de créer une table à la volée en partant, soit d'une expression de requête SELECT, soit d'une autre table.

III-11-1 - Création d'une table basée sur une autre table

Pour créer une table en partant d'une autre table, il suffit de préciser, n'importe où à la place d'une définition de colonne, le mot clé LIKE et de l'accompagner du nom de la table. On peut ajouter des colonnes supplémentaires ou bien exclure certaines colonnes comme le montre la syntaxe suivante :

1. CREATE TABLE [ nom_schéma.]nom_table

2. (LIKE [ nom_schéma.]nom_autre_table <options_like>

4. <options_like> ::= option1 [, option2 [, option3 ] ]

6. <optionN> ::= { INCLUDING | EXCLUDING } IDENTITY

7. | { INCLUDING | EXCLUDING } DEFAULTS

8. | { INCLUDING | EXCLUDING } GENERATED

Exemple 3.52 – Création d’une table vide empruntant la structure d’une table existante :

1. CREATE TABLE S_ADM.T_LISTE_UTILISATEUR_LUT

2. (LIKE S_ADM.T_UTILISATEUR_USR);

III-11-2 - Création d'une table basée sur une requête

Il est possible de créer une table à partir d'une expression de requête d'extraction de l'information (les requêtes SELECT d'extraction de l'information SELECT seront vues aux chapitres 4 et 5). Une syntaxe simplifiée est la suivante :

1. CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY } ]

2. TABLE  [nom_schéma.]nom_table

3. [ ON COMMIT { PRESERVE ROWS | DELETE ROWS } ]

4. AS ( <requête_SELECT> )

5. [ WITH [ NO ] DATA ]

La clause ON COMMIT n’est valable que si la table est temporaire et permet de conserver ou supprimer les lignes ayant été mises à jour (INSERT, UPDATE, DELETE) pendant la transaction explicite. Nous expliquerons ce point plus en détail au chapitre 7.

DATA : signifie que les données émanent de la requête seront insérées à la suite de la création de la table. Dans le cas contraire (NO DATA) la table sera créée vide. Par défaut la table est copiée avec ses données

Exemple 3.53 – Création d’une table à partir d’une requête :

1. CREATE TEMPORARY LOCAL TABLE T_USR

2. AS ( SELECT USR_PRENOM, USR_NOM,

3. USR_USER_EMAIL || '@' || DNS_NAME AS DEMAIL

4. FROM   S_ADM.T_UTILISATEUR_USR AS U

5. JOIN S_ADM.T_DNS AS D ON U.DNS_ID = D.DNS_ID)

6. WITH DATA;

Créé une table temporaire locale avec les noms et prénoms des utilisateurs et leur email reconstitué.

IV - Chapitre 4 - Recherche de l'information dans une table

V - Chapitre 5 - Les jointures

VI - Chapitre 6 - Opérations d’ensembles

VII - Chapitre 7 -

Annexe A : glossaire

VIII - Chapitre 8 - Mise à jour (INSERT, UPDATE, DELETE, MERGE, TRUNCATE)

IX - Chapitre 9 - Fonction utilisateur

X - Chapitre 10 - Exploitation des vues

XI - Chapitre 11 – Transactions

XII - Chapitre 12 – Déclencheurs

XIII - Chapitre 13 – Procédures

XIV - Chapitre 14 - : XML

XV - Chapitre 15 – JSON

XVI - Chapitre 16 - Système d’information géographique

XVII - Chapitre 17 - Tables temporelles

XVIII - Chapitre 18 - table de graphe

XIX - Chapitre 19 - Tables « in memory »

XX - Chapitre 20 - Médonnées (information_schema et sys)

XXI - Chapitre 21 - La sécuritité (roles et privilèges)

XXII - Chapitre 22 - Sauvegardes et restaurations

XXIII - Chapitre 23 – L’indexation

XXIV - Chapitre 24 - Maintenance basique

XXV - Remerciements Developpez.com

(1) Object Relational Mapping : outil informatique d’interfaçage entre code applicatif et base de données relationnelle pour simuler une base de données orientée objet.

(2)En retirant le mot « synchrone » de ce théorème, les GAFA ont fait croire qu’aucun SGBD Relationnel n’était capable d’assurer les trois fonctionnalités que sont la cohérence, la haute disponibilité et la répartition des données sur différents nœuds.

(3) Cet article est disponible dans l’ouvrage « Date on Database – Writings 2000-2006 » Apress 2007.

(4)Molière avait démontré cela quelques siècles auparavant avec sa leçon de français dans le Bourgois Gentilhomme. « Belle marquise vos beaux yeux me font mourir d’amour… »

(5)C’est ma partie « visible » du résultat.

(6)C’est la partie masquée, destinée à interagir avec les applications.

(7)Toutes les normes sur le langage SQL sont disponibles auprès de l’ISO, moyennant paiement : https://www.iso.org/fr/home.html

(8)BI (Business Intelligence) : informatique décisionnelle construite à partir de données d’autres bases de données et structurées en « cubes » multidimensionnels afin d’analyser les données dans différentes dimensions et métriques.

(9) Parfois cette restriction est opérée malencontreusement dans un prédicat de jointure interne ce qui est à éviter

(10) Il en existe de nombreux gratuits parmi lesquels DBeaver, SQuirell, HeidiSQL…

(11) Nous faisons ici référence à un PC (Personal Computer) de type « serveur ».

(12) Pour une raison que nous ignorons, le terme d’instance n’a pas été retenue par PostGreSQL qui parle de « cluster »…

(13) Hélas sauf dans Oracle Database

(14) Hélas sauf dans PostGreSQL

(15) C’est le principe de Shanon, théoricien de l’information décrit dans « A Mathematical Theory of Communication » publié dans The Bell System Technical Journal, octobre 1948

(16) OS (Operating System) : système d’exploitation.

(17) Pas d’accent, cédille ou ligature (œ, æ…).

(18) Il est déconseillé d’utiliser comme identifiant SQL un mot réservé du SQL, y compris les mots à venir… Ils sont listés dans la norme SQL et chez certains éditeurs de SGBDR https://learn.microsoft.com/fr-fr/sql/t-sql/language-elements/reserved-keywords-transact-sql.
https://learn.microsoft.com/fr-fr/sql/t-sql/language-elements/reserved-keywords-transact-sql

(19) Dans la littérature consacrée on parle de multibase