SQL9. Les erreurs les plus fréquentes
Tous les débutants commetent les mêmes erreurs.
Confondant par exemple la gestion de lignes de fichiers avec une table,
la logique procédurale avec la logique ensembliste... 1. Les noms d'objets SQL 2. Terminologie SQL 3. NULL, n'est pas une valeur ! 4. CASE SENSITIVE 5. Le dernier... 6. Position... 7. Format de Date... 8. Dédoublonnage 8.1. DISTINCTROW ou l'exemple parfait de l'absurdité d'Access ! 8.2. Dédoublonnage partiel 9. Cosmétique... 10. Insertion multiple 1. Les noms d'objets SQLJ'ai donné des noms explicites à mes tables et colonnes, et je me heurte à des problèmes... Pourquoi ? SQL est un langage qui repose sur une norme. Les noms des objets SQL ont donc une construction normative spécifique. En particulier on ne peut faire usage que des 26 lettres de l'alphabet, des dix chiffres et du caractère "blanc souligné" (underscore). Tout autre caractère est interdit pour nommer un objet SQL (table, colonne, vue, utilisateur, contrainte...). Certains SGBDR autorisent n'importe quoi comme caractères dans le nom d'un objet... hélas ! Le nombre maximal de caractères est de 128, mais certains SGBDR ont des limites plus petites comme Oracle. Sont donc interdits le caractère "blanc", les caractères diacritiques (accents, cédille, etc.). En principe, la casse (c'est à dire majuscule et minuscule) n'a pas d'importance
dans la nomination des objets. Cependant, certains SGBDR s'avèrent
sensibles à la casse pour le nom des objets SQL dans certaines
conditions. C'est la cas en particulier de SQL Server, si on le
paramètre à l'installation en CASE SENSITIVE. Il est très dangereux de donner des noms d'objets avec des blancs ou des accents.
Pourquoi ? Parce que certains modules de programme ne travaillent pas
sur les mêmes codes de pages. A l'heure où les bases de données sont de
plus en plus intégrées à Internet, la multiplicité des interfaces de
traitement, rend difficile, voire impossible l'interaction du code si
les noms des objets SQL n'ont pas été définis correctement. De plus si
le nom comporte des caractères illicites (ce que certains SGBDR
permettent comme Access ou SQL Server) alors il faut ruser en utilisant
des expressions spécifiques, et les traitements qui en découlent
prennent plus de temps que si les noms avaient été définis proprement. Bien écrire... Voici quelques règles pour bien écrire votre code SQL... Tout d'abord, toujours écrire votre code SQL en majuscule. Ainsi les noms des objets seront par exemple écrit :
Ouvrez n'importe quel bouquin
d'informatique traitant de SQL. Vous y verrez toujours le code SQL en
majuscule. C'est une habitude acquise depuis très longtemps. Elle est
devenue un véritable standard de fait. Pourquoi ? Parce que lorsque l'on
mélange du code client (Cobol, C, VB, Delphi, PHP, C#, Java...) et du
code SQL, il est difficile de s'y retrouver si l'on ne peut facilement
distinguer l'un de l'autre. Ainsi l'habitude a été prise d'écrire le
code "client" en minuscule et le code "server" en majuscule. Ceci permet
une distinction efficace et immédiate des éléments de code s'exécutant
sur le seveur et ceux s'exécutant localement. Exemple : With dmSys.QAdoGenericUpdate do begin if active then close; SQL.clear; SQL.add('UPDATE TS_PARAMETRES_BASE_PRB SET PRB_VALEUR = '+QuotedStr(ParaVal) +' WHERE PRB_NOM = '+QuotedStr(ParaName)); execSQL; end; Ensuite, indentez votre code SQL !
En effet SQL peut devenir difficile à lire s'il n'est pas proprement
présenté. Voici pour comparaison une même requête écrite malproprement
et sa jumelle écrite avec indentation... Laquelle sauriez vous relire et
comprendre plus rapidement ?
La règle de base en la matière est :
Exemple :
Ici l'arbre de jointure est : Table T_ITEM_ITM => racine de l'arbre Table T_PARAGRAPHE_PRG => branche depuis table racine T_ITEM_ITM Table TR_TYPE_PARAGRAPHE_TPG => branche depuis table T_PARAGRAPHE_PRG (donc double indentation) Table TR_STYLE_ITEM_STI => branche depuis table racine T_ITEM_ITM Les noms des objets sont-ils sensibles à la casse ? La norme SQL dit que par principe, les noms
des objets SQL (nom de table, de vue, de colonne...) ne sont pas
sensible à la casse. Ainsi T_Client et T_CLIENT sont un seul et même
objet. Mais cette règle possède une exception. En effet, lorsque l'on
utilise un identifiant de SQL comme nom, alors le nom devient sensible à
la casse. Par exemple, si je veux utiliser le mot clef TYPE comme nom
de colonne d'une table, il me faut l'entourer de guillemets car ce mot
est réservé. Dès lors, ce nom de colonne devient sensible à la casse. Exemple :
Constitue un ordre de création de table SQL tout à fait valide ! ATTENTION : comme les noms des objets sont
stockés dans des tables de la base dites "table de schéma" ou
"dictionnaire" ou encore table "système", certains SGBDR dans certaines
configurations sont sensibles à la casse pour les noms des objets. C'est
le cas par exemple de SQL Server qui, lors de son installation peut
être configuré pour être sensible à la casse... 2. Terminologie SQLIdée reçue : "champ" et "enregistrement" n'existent pas dans les bases de données ! Pas plus que "droits"... CHAMP: cette notion n'existe pas
dans les bases de données car un champ est un élément visuel (champ
opératoire du chirurgien, champ visuel du pilote, champs de saisie dans
une interface de saisie...) En base de données on parle de "colonne", car les tables sont visualisables sous forme de tableaux dans lesquels existent des lignes et des colonnes... ENREGISTREMENT: cette notion
n'existe pas dans les bases de données car un enregistrement suppose un
fichier lu ligne par ligne, alors que (sauf exception pour les bases de
type fichier) toutes les tables d'une même base sont stockées dans un
seule et même fichier par des granules de stockage unitaires appelée
"pages". On parle alors de ligne qui est une notion virtuelle. DROITS: les droits n'existent pas en
SQL. En effet les droits supposent un mode d'accès sur un fichier. Or
les bases de données et SQL gèrent différents modes qui n'ont pas
forcément quelque chose à voir avec les lectures et écritures de
données. Ainsi SQL permet de définir des privilèges d'utilisation sur
des objets de bases de données qui ne sont ni des tables, ni des vues,
ni des colonnes, ni des données, mais des éléments de conception de la
base de données... C'est pourquoi parler de droits est ... absurde et
réservé aux notions "systèmes" basées sur des fichiers et des
ressources. Dans SQL on parle donc de privilèges que l'on octroie ou révoque. 3. NULL, n'est pas une valeur !Je n'arrive pas à retrouver mes valeurs nulles. Je fais SELECT ... WHERE MaColonne = NULL et cela ne marche pas ! NULL : cet élément du SQL n'est pas une valeur, puisque c'est justement l'absence de valeur. En fait NULL est un marqueur comme NIL en programmation qui indique qu'au bout du pointeur il n'y a pas de ressource. Par exemple comparer une colonne à NULL n'a aucun sens. Pour traiter les marqueurs NULL il faut utiliser des prédicats spéciaux comme IS NULL, IS NOT NULL ou des fonctions particulières comme COALESCE ou NULLIF ou encore en utilisant la structure CASE. 4. CASE SENSITIVEUne fausse bonne idée consiste à définir une base de données (ou certaines tables, colonnes) avec une collation qui rends les chaînes de caractères insensible à la casse
(plus de différences entre majuscules et minuscules). C'est une hérésie
que d'utiliser un tel comportement par défaut. En effet autant il est
facile dans les requêtes de s'affranchir de la casse dans les
comparaisons, autant il est très difficile voire impossible de rendre
des éléments sensible à la casse lorsque ces derniers ont été rendus
CASE INSENSITIVE ! Pour rendre vos comparaisons insensibles à la casse, il suffit de jouer avec la fonction UPPER ou LOWER présente dans tous les SGBDR. Exemple :
Mais comment faire la distinction entre "Dupont" et "DUPONT" si votre SGBDR a été rendu insensible à la casse ? La solution consiste, si votre SGBDR le supporte, à transtyper les colonnes en chaînes binaires : Exemple :
Ce code est évidemment incommensurablement plus long à exécuter que si votre SGBDR avait été rendu sensible à la casse ! Au fait, savez-vous pourquoi on parle de
casse pour distinguer les majuscules et minuscules ? Cette terminologie
existe depuis des temps anciens. Des temps ou les imprimeurs avaient à
leur disposition pour fabriquer les matrices d'impression, un grand
"bac" divisé en de multiples cases dans lesquelles on trouvait les
majuscules dans les cases des rangées supérieures et les minuscules dans
les cases des rangées inférieurs. Comme l'on utilisait plus fréquemment
les minuscules, on les avaient rangés en bas de manière à ce quelle
soient plus accessibles à l'utilisateur. Ce grand bac, s'apelle une
casse, et cette technique de rangement relève de l'ergonomie. ! Donc, en
bas les minuscules en haut les majuscules, d'où les expressions bas de
casse (minuscules) et haut de casse (majuscules). Bref, l'informatique
n'a rien inventé en la matière... 5. Le dernier...Une question qui revient souvent dans les discussions est de retrouver la dernière ligne insérée dans une table.
Cette question n'a aucun sens dans l'univers des bases de données, car
ces dernières ne possèdent pas d'ordre établie. Une table est un sac de
bille. Si vous recevez mon sac de bille, avez-vous un moyen quelconque
de savoir quelle a été la dernière bille que j'y ai fourrée ? Les SGBDR
ont l'habitude de minimiser les coûts de gestion des entrées/sorties de
disque. Autrement dit, lorsque je supprime une ligne, celle ci n'est pas
effacée, ni compactée. Elle reste un espace mort. Si une nouvelle
insertion arrive, il y a fort à parier que votre SGBDR y placera la
nouvelle ligne à insérer ! Exemple :
Vous vous attendiez sans doute que la ligne
"jkl" soit la dernière ! Erreur, le SGBDR a récupéré la place de la
ligne "def" pour y mettre jkl ! Il n'y a aucune notion d'ordre dans les
SGBDR, ni ordre des lignes, ni ordre des colonnes, sauf ceux que
l'utilisateur veut y voir figurer dans les données en ajoutant une
colonne spécifique pour le tri. Tout au plus, si une colonne possède une donnée dont la valeur est toujours supérieure a celles déjà insérées (par exemple une colonne auto incrémentée) alors on peut retrouver la valeur maximale (donc la dernière ligne insérée) par une requête de calcul d'agrégat comme SELECT MAX(... Mais s'il faut avoir l'assurance de la séquence dans le temps, il convient d'ajouter à la table une colonne contenant une défintion de type TIMESTAMP (combiné date heure) avec la valeur par défaut du serveur par une fonction comme CURRENT_TIMESTAMP et cela dans la défintion de la table. Exemple :
Mais cette pratique est dangereuse, car
lors d'insertion massives la précision de ce combiné date heure peut
générer des données indentiques... C'est pourquoi certains SGBDR ont ajouté a leur définition un type particulier tel que l'horodatage (TIMESTAMP par exemple pour MS SQL Server) assurant d'avoir toujours une colonne dont toutes les valeurs sont distinctes et croissante en fonction du moment de l'insertion. Enfin, dans la cas de calculs de clefs auto incrémentées, l'utilisation de SELLECT MAX(...) +1 est une abération qui ne peut que conduire la base à des violations de contraintes de clefs primaires du fait de la concurrence. 6. Position...Autre problématique, ajouter une colonne à
la position n, par exemple entre la colonne x et la colonne y de la
table... Autre utopie bien entendu car il n'existe pas d'ordre dans les
bases de données (voir ci dessus). Par d'ordre concernant les tables, ni
d'ordre concernant les colonnes au sein de la table. Quoique... ! Tout bien considéré la norme SQL présente dans les informations de schéma une colonne intitulé "ORDINAL_POSITION" qui précise la place de la colonne au sein de la liste des colonnes de la table et cette place correspondant généralement à celle définie lors de la création. Toute nouvelle colonne étant rajoutée a la position la plus haute incrémentée d'une unité. Cet ordre ordinal est utilisé notamment lors de l'insertion de données (INSERT) lorsque l'on ne précise pas la liste des colonnes. Autre particularité : la possibilité de définir les colonnes dans la clause de tri (ORDER BY) par leur ordre positionnel dans la clause SELECT et non par leur nom... La question bête qui revient sans cesse est : comment faire pour insérer une colonne Z exactement entre la colonne X et la colonne Y ? Simplement en recréant la table. Mais cette méthode, qui n'offre aucune certitude est lourde à utiliser car il faut préserver les données dans une table temporaire avant de détruire la table pour la recréer dans son architecture nouvelle. D'ailleurs SQL n'offre aucune certitude quand à l'ordre de restitution des colonnes lorsque, au lieu de spécifier les colonnes explicitement on utiliser le joker * ! 7. Format de Date...La notion de format de date n'existe pas en
SQL ni dans les SGBDR. La date est stockée sous un type DATE, qui, la
plupart du temps est un entier et pour TIMESTAMP (DATETIME) un réel. La
partie entière étant le nombre de jours écoulés depuis une certaine
date. Ainsi dans SQL Server la date "origine" est le 1er janvier 1900 :
La partie fractionnaire compte le temps écoulé sur 24 heures. Par exemple 0,5 signifie 1er janvier 1900 à midi pour SQL Server. Mais comment spécifier une date avec une expression littérale comme 21/01/2003 16h18m ? Il convient d'écrire la date en littéral et de la transtyper en DATE ou DATETIME. Le trsantypage peut être implicite ou explicite : Exemples :
Mais sur quel format la conversion va t-elle opérer ? La plupart des SGBDR reposent sur le format normatif ISO de date qui spécifie : AAAA-MM-JJ hh:mm:ss.xxx Exemple :
Mais certains SGBDR proposent de pouvoir
spécifier librement le format avec lequel on veut travailler. Par
exemple pour SQL Server il faut positionner le flag DATEFORMAT avec le
paramétrage souhaité. Ce flag peut prendre les valeurs : YMD, YDM, MDY,
MYD, DYM, DMY. Voici comment sous SQL Server on précise l'utilisation des dates ISO :
8. Dédoublonnage8.1. DISTINCTROW ou l'exemple parfait de l'absurdité d'Access !Les quidams qui passent d'Access à un
SGBDR qui respecte la norme SQL (car SQL est un langage fortement
normalisé : 1986, 1992, 1999, 2003 !) ne comprennent pas que l'opérateur
DISTINCTROW n'existe pas dans SQL... Rapellons à quoi sert ce mot clef
loufoque : "DISTINCTROW" concerne les requêtes qui utilisent au moins
une jointure. Il arrive que les tuples résultant apportent des données
doublonnées dans le sous ensemble des colonnes de la table mère. Pour
empêcher cela Access nous invite à utiliser le mot DISTINCTROW. Un
exemple étant toujours plus compréhensible voici les élements de notre
jeu d'essai : Soit une table T_PERSONNE_PRS contenant des "parents" : PRS_ID PRS_NOM PRS_DATE_NAISSANCE --------- -------------- -------------------- 1 DUPONT 21/05/1960 2 DUPONT 12/07/1972 3 DUVAL 16/11/1980 et une table des enfants T_ENFANT_ENF : ENF_ID PRS_ID ENF_PRENOM --------- -------------- -------------------- 48 1 Camille 57 1 Alain 63 2 Marcel 78 3 Raoul Voici ce que donne la requete qui "met à plat" les informations :
Voici maintenant une requête qui donne un aperçu partiel des données jointes :
Comme on le voit, le nom DUPONT apparait
trois fois, ce qui n'a pas d'intérêt car il est impossible de savoir
quelle ligne contenant le nom DUPONT appartient à la personne de clef 1
ou 2 ou de l'enfant de clef 48, 57 ou 63... Avec le mot clef DISTINCT (celui de la norme SQL) , nous obtenons :
Les lignes redondantes ont été éliminées, et c'est tant mieux ! Mais avec le mot Access DISTINCTROW, une surprise nous attend...
Mais comment cette ineptie est-elle possible ??? Je vous le demande ! Simplement parce qu'il y a deux DUPONT, l'un d'identifiant 1 et l'autre d'identifiant 2 et c'est pourquoi vous vous retrouvez avec deux fois DUPONT, sans savoir à la lecture du résultat à quel DUPONT correspond l'identifiant 1 ou 2 !!! A quoi ça sert ? A rien, sinon à
embrouiller le développeur car cet opérateur viole les bases mêmes de la
logique ensembliste sur laquelle repose les fondements des bases de
données. 8.2. Dédoublonnage partielEn revanche, une question qui revient
souvent est de pouvoir dédoublonner partiellement un ensemble de
données. Cette demande cache souvent une incompréhension globale de la
logique ensembliste des bases de données et une méconnaissance de SQL en
particulier. Pour mieux comprendre cette demande, nous allons créer notre jeu de données : Soit la table T_MACHINE_MCH et la table T_INCIDENT_ICD qui historise les incidents sur les différentes machines :
Notre quidam voudrait récupérer le dernier
incident ayant eût lieu (dans l'ordre chronologique) sur chacune des
machines, mais surtout, il voudrait récupérer l'identifiant de ce
dernier incident, afin de pouvoir parfaitement identifier les données
concernant ces incidents. Voici la jointure des données entre les deux tables :
Ce que notre quidam veut obtenir, c'est la réponse suivante : MCH_ID MCH_NOM ICD_ID ICD_NATURE ICD_DATEHEURE ----------- ---------------- ----------- ---------------- ----------------------- 1 Avion 78 Panne APU 2002-11-17 22:03:21.000 2 Vélo 82 Casse chaine 2003-03-04 11:22:33.000 Autrement dit il voudrait dédoublonner sur
les colonnes MCH_ID et MCH_NOM en obtennant le MAX de ICD_DATEHEURE et
les données de ICD_ID / ICD_NATURE... Quelque chose comme :
Or DISTINCT n'est pas une fonction
applicable à une colonne ou un groupe de colonne particulier. DISTINCT
concerne la totalité des données de la ligne résultante. Il faut donc
procéder par étape : ÉTAPE N°1 : trouver les incidents ayant le MAX de la dateHeure pour chaque machine :
ÉTAPE N°2 : relier ce résultat avec l'incident visé à l'aide d'une sous requête corrélée sur l'identifiant de machine :
ÉTAPE N°3 : étendre ce résultat par jointure pour obtenir les informations sur les machines :
Et voilà notre problème résolu. Difficile de faire cela sans sous requêtes... 9. Cosmétique...Je voudrais présenter mes données de telle manière. Comment faire cela avec SQL ? Un SGBDR n'est surtout pas fait pour faire du "cosmétique" ! La cosmétique (nom commun) signifie "qui est propre aux soins de beauté"... Non, un SGBDR ne sert pas, et ne doit jamais servir, à traiter la présentation des données. Une fois que les données sont là dans le résultat d'une requête, alors il suffit de piocher ou l'on veut pour construire telle ou telle "facture" de présentation : en ligne, en colonne, en tableau, en cube, a onglet, en relief, en hébreu et en couleur, si cela vous fait plaisir, mais pas avec un SGBDR. L'ensemble de ces traitements doivent être effectués par la couche client du serveur, c'est à dire, le programme exécutable ou un objet d'un serveur d'application par exemple. MAIS PAS EN CODE SQL !!! Le langage SQL n'est pas fait pour cela, et le SGBDR se révélera TOUJOURS un veau si vous lui demandez de faire ce travail et tout le monde sera mécontent : vous d'abord, car a la longue le temps de traitement sera désatsreux, lui ensuite (le SGBDR) parce qu'il s'emmerdera a faire de la peinture alors que dans votre orchestre lui, c'est le piano, et vous lui demander de jouer le triangle ! L'exemple le plus classique est le
suivant... Je voudrais que mes données en colonnes soient remise en
ligne et tout cela dans une jolie requête ! Ben voyons... Alors puisqu'il faut démontrer l'absurdité d'une telle chose, démontrons là... Comme exemple nous allons nous intéresser au calcul du chiffre d'affaire par trimestre en fonction des clients. Voici une table des commandes :
CLI_ID étant la clef étrangère référençant le client. Notre direction commerciale cherche à obtenir les chiffres des ventes par client et par trimestre concernant l'année 2001... La requête peut s'écrire :
Bien entendu, notre service commercial préférerait avoir les résultats sous cette forme : CLI_ID CA_TRIMESTRE_1 CA_TRIMESTRE_2 CA_TRIMESTRE_3 CA_TRIMESTRE_4 ------ -------------- -------------- -------------- -------------- 1 19328.37 3587.0 2 123185.22 82462.05 61458.0 962.28 Cela permet de mieux se rendre compte que
certains trimestres sont à zéro pour certains clients, ce qui n'était
pas évident dans la présentation venant de la réponse à notre première
requête. Déterminer le chiffre d'affaires par client pour un trimestre déterminé n'est pas bien difficile en SQL. Par exemple calculons-le pour le premier trimestre 2001 :
Il n'est pas possible, du fait des
groupages nécessaire à l'agrégation de caculer les autres trimestres
dans la même requête. Mais il est possible de modifier la présentation
pour la faire correspondre au souhait de notre service commercial :
Rien ne nous empêche maintenant de répéter
cette requête pour chacun des trimestres et d'effectuer une union pour
aboutir à une table plus proche de la demande :
Dès lors la solution saute aux yeux : il
suffit de faire la somme de ces différents chiffres d'affaire
trimestriels par client. Cela est possible en imbriquant la précédente
requête dans une clause FROM d'un SELECT :
C'est à dire le résultat attendu, et comme
les NULL sont ignorés par les calculs d'agrégation, nous avons même de
jolis zéros plus explicites que l'absence d'information ! Comme on le voit, la première requête donne
les même résultats que la dernière... Seule la présentation à
changée.... Mais cette dernière compte en fait 4 sous requêtes plus la
requête regroupant les sous requêtes soit 5 SELECT. Quel est à votre
avis la plus coûteuse des deux ? Si vous utilisez un programme procédural pour faire cette présentation à partir de la première requête, vous divisez le temps de traitement par un facteur énorme. Je parierais entre 100 et 1000 fois plus rapide... Alors, à vous de juger ! Bémol Toutefois il existe un bémol à une telle
façon de faire. En effet, certains SGBDR (comme certains outils de
développement, Delphi par exemple) proposent un ensemble de technique
(ou de composant utilisant de telles techniques) dites ROLAP (Relational
On Line Analytical Process). Dans ce cas, de telle requêtes sont facile à écrire en utilisant un agrégat spécial (GROUPING) et les mots clef CUBE et ROLLUP. Mais là il ne s'agit plus de présentation mais d'analyse multidimensionnelle ! Et votre base de données doit avoir été conçue pour (étoile, flocon, nuage...). 10. Insertion multipleJe n'arrive pas à insérer dans plusieurs tables à la fois ! Comment faire ? Les ordres de manipulation des données
(INSERT, UPDATE, DELETE) ne sont pas fait pour travailler sur plus d'une
table. Pourquoi ? Parce que dans la très grande majorité des cas de
relations entre entité, la cardinalité n'étant pas strictement 1:1, le
SGBDR ne peut pas retrouver ses petits ! D'ailleurs, même ce cas de
relation 1:1 est... impossible, sauf à se résumer à une seule et même
table. En effet, considérons une table des hommes et une des femmes et
la relation obligatoire mariage avec cardinalité 1:1. Cela signifie que
tout homme est marié à une femme et une seule et que, de même, toute
femme est marié à un homme et un seul. Exit donc les célibataires et
vive le mariage dès la naissance... Dès lors comment faire pour remplir notre table ? Si nous insérons un homme, la contrainte d'intégrité référentielle nous impose de préciser l'épouse de cet homme. D'un autre côté si nous tentons d'insérer une femme, alors il faut préciser son mari, ce qui supposerait que son mari soit déjà inséré dans la table des hommes, sans liens avec cette femme ce qui est impossible du fait de l'intégrité référentielle ! Donc, il est impossible de saisir une quelconque donné dans ce modèle. Alors nous devons le casser et par exemple
définir une relation en 0:1. Des lors nous pouvons insérer des données
dans une des tables ou dans l'autre sans pour cela être freiné par la
contrainte d'intégrité référentielle. Mais cette insertion ne peut se
faire dans une seule table à la fois ! Le cas est encore plus frappant dans le
cadre des relations 1:n ou n:m. En effet comment insérer dans une table
une seule ligne et dans les autres plusieurs lignes en un seul ordre
SQL ? Cela n'a pas de sens ! Alors comment faire pour insérer dans plusieurs tables à la fois en étant sûr que toutes les insertions soient faites ?
La solution nous est fournie par SQL : cela s'apelle une transaction !
C'est pour cela qu'existe les transactions : jouer plusieurs ordres SQL,
les valider tous d'un coup ou les annuler tous d'un coup. |