Chapitre 9
Transcription
Chapitre 9
9 janvier 2010 Chapitre 9 Bases de données 9.1 Introduction Les bases de données sont nécessaires au fonctionnement d'innombrables applications sur la Toile. Chaque fois que vous commandez un billet pour un spectacle ou un voyage, passez en revue des catalogues, recherchez des informations, vous utilisez des bases de données. Une base de données est composée d'un ensemble de tables telles que celle qui est présentée ci-dessous. +----+------------+----------+----------+---------+-------+ | id | name | quantity | category | weight | price | +----+------------+----------+----------+---------+-------+ | 1 | roue | 76 | aa,c | 145.000 | 12.45 | | 2 | pneu | 88 | b | 319.600 | 62.60 | | 3 | pare-brise | 44 | | 59.100 | 52.20 | +----+------------+----------+----------+---------+-------+ Une table comprend des lignes et des colonnes. Chaque colonne a un nom et contient des valeurs du type (entier chaîne de caractères) attribué à la colonne. Une ligne est aussi appelée enregistrement ou tuple. Il y a plusieurs variantes du langage SQL, qui dièrent par de petits détails, mais nous nous tiendrons à la variante comprise par la base de données MySQL, un environnement largement utilisé dans l'industrie et qui existe en version gratuite. Les instructions que nous présentons ne représentent qu'un sous-ensemble, mais il sura pour donner une bonne idée des concepts sousjacents et faire des applications intéressantes. Le langage SQL permet de dénir la forme des tables, comme celle qui est présentée ci-dessus, de les remplir, de rechercher des informations, de modi137 138 CHAPITRE 9. BASES DE DONNÉES er ces informations et nalement de détruire les tables ou des informations qu'elles contiennent, quand elles sont périmées. Dans les prochaines sections, nous présentons les commandes de SQL (Structured Query Langage), puis nous verrons comment les mettre en ÷uvre pour gérer des aspects plus globaux. Ces commandes peuvent être écrites sur une ou plusieurs lignes et indentées comme bon vous semble. 9.2 Création d'une table (create) La commande ci-dessous crée une table vide qui pourra contenir les informations apparaissant dans la table du paragraphe précédent. create table produits ( id long auto_increment primary key, nom varchar(20), quantite int default 0, categorie set('aa',"b",'c'), poids double (10,3), prix decimal (10,2) ); Les deux premiers symboles ci-dessus, create table décrivent la commande. Le troisième symbole est le nom de la table choisi par le développeur. La suite consiste en une liste de types entre accolades et séparés par des virgules. Le premier symbole de chaque ligne correspond à un nom choisi par le développeur. Il ne doit évidemment pas comprendre de lettre accentuée, ni d'espace. Le système ne fait pas de diérence entre une lettre majuscule et la minuscule correspondante. Le premier type est particulier. On met en général une colonne de ce type dans chaque table. Il correspond à un numéro qui identie les lignes. Ces numéros ne sont pas forcément dans l'ordre, mais chacun d'eux est unique, c'est ce qui est assuré par les symboles primary key. auto_increment indique que le numéro est géré par la base de données et que chaque fois qu'on entre une nouvelle ligne dans la table (voir 9.5) la base de données lui attribue automatiquement un nouveau numéro, de 1 plus élevé que le précédent. La quatrième ligne montre comment dénir une valeur par défaut. Le symbole default est valable pour toutes les lignes et permet de dénir une valeur qui est utilisée si le programme entre une ligne sans dénir cette colonne. 9.2. CREATE) CRÉATION D'UNE TABLE ( 139 9.2.1 Types et propriétés des colonnes Voici les types de données principaux et quelques propriétés qu'on peut attribuer aux colonnes. Les lignes montrent les 6 types principaux : int, long, integer Représente un entier qui peut contenir des valeurs plus ou moins étendues. Aujourd'hui, ces types permettent de mémoriser des nombres jusqu'à 263 . varchar Ce type correspond à une chaîne de caractères. Le nombre entre parenthèse indique le nombre maximal de caractères qui vont être mémorisés. Si l'on essaie de déposer une chaîne plus longue, elle sera coupée. double Ce type mémorise un nombre réel, possédant donc des chires après la virgule. decimal(10,2) Ce type permet de mémoriser des prix ou des valeurs ayant un nombre déni de chires après la virgule. Le nombre de caractères après la virgule est indiqué dans les paramètres. Le nombre ci-dessus possède 10 chires, dont 2 après la virgule. Ce type est utilisé pour représenter des sommes d'argent. set ('aa', "b", 'c') (ensemble) Ce type permet de créer un certain nombre de symboles représentant des caractéristiques ou propriétés attribuées à un objet représenté dans la ligne d'une table. Par exemple on pourraît décider que les symboles aa, b et c 1 représentent trois qualités : jante alu, sans chambre à air et enjoliveurs plastiques. En lui attribuant ('aa,c'), la roue mémorisée ci-dessus aurait la première et la dernière de ces qualités. Pour l'insertion dans une base de données, voir paragraphe 9.5, attention, ne pas mettre d'espace dans la chaîne insérée. 1. les symboles ont été choisis pour que la table ne dépasse pas les bords de la page, mais dans une application, on choisirait des symboles plus explicites, évidemment. 140 CHAPITRE 9. BASES DE DONNÉES time, date, datetime Les champs time, date et datetime contiennent des dates et/ou des heures sous forme de chaînes de caractères. Une durée ou une heure est mise sous la forme "J HH :MM :SS", "HH :MM :SS", "J HH", "HH :MM :SS", "HH :MM" ou "SS", où chaque lettre représente un chire. La lettre J représente un nombre de jours. Une date est représentée par la forme "AAAA-MM-JJ" où les lettres représentent les années, jours et mois. La concaténation des deux séparés par un espace, forme un tout mémorisé dans un champ datetime. Un certain nombre de fonctions permettent de manipuler des dates, voir now() à date_add() page 149. Pour l'insertion dans une base de données, voir paragraphe 9.12.4. default Le mot-clé placé sur la quatrième ligne de la commande de création cidessus indique la valeur que l'on veut voir introduite si l'on ne connaît pas sa valeur au moment de l'introduction d'une nouvelle ligne. 9.3 Achage de la structure d'une table (describe) La commande describe produits ; permet de visualiser la structure d'une table, soit pour la vérier, soit pour générer des applications qui découvrent toutes seules ce qu'il y a dans une table. La commande ci-dessus, appliquée à la table précédente produit la description suivante, formatée en forme de table. describe producis; +-----------+------------------+------+-----+----------------+ | Field | Type | Null | Key | Extra | +-----------+------------------+------+-----+----------------+ | id | int(11) | | PRI | auto_increment | | nom | varchar(20) | YES | | | | quantite | int(11) | | | | | categorie | set('aa','b','c')| YES | | | | poids | double(10,3) | YES | | | | prix | decimal(10,2) | YES | | | +-----------+------------------+------+-----+----------------+ 9.4. DESTRUCTION D'UNE TABLE (DROP) 141 On peut vérier que la table a une clé auto-incrementée et que la plupart des colonnes peuvent être indénies (indiqué par la colonne Null). 9.4 Destruction d'une table (drop) Une table peut être éliminée de la base au moyen de cette commande. On élimine une table si on veut en construire une avec une autre structure, ce qui arrive souvent au moment où l'on développe une nouvelle application et que l'on a mal jugé les éléments dont on a besoin. drop table produits; 9.5 Insertion de données (insert) Quand une table a été créée, elle peut recevoir des lignes de données grâce aux commandes suivantes. La première commande ajoute une ligne complète dans la table produits. Comme le premier paramètre est une primary key, la base de données vérie avant l'insertion que la valeur introduite pour cette clé, dans la première position de values, n'est pas déjà présente dans une autre ligne. Dans la deuxième commande on a un 0 dans cette position. Comme la première colonne contient également auto_increment, la base de données introduit automatiquement le prochain index disponible. insert into produits values(7, 'porte', 93, "aa,cc", 67.200, 73.40); insert into produits values(0, 'roue', 76, "aa,cc", 145.0, 12.45) (0, 'porte', default, "xx", 85.300, 136.20); Cette deuxième forme montre également comment insérer simultanément plusieurs lignes dans la base de données. Elle illustre nalement l'emploi de default. Dans la dernière ligne, dans la colonne correspondant à quantite, on a placé ce mot-clé. La base de données va donc charger la valeur dénie pour ces cas dans la commande de création (section 9.2). Notez que les valeurs numériques sont écrites sans délimiteurs et que les chaînes de caractères sont encadrées par des guillemets ou des apostrophes. La prochaine commande indique, après le nom de la table, les colonnes qui vont apparaître dans la liste de valeurs. Comme l'identicateur n'est pas inclus dans cette liste, la base de données introduit le prochain index, pour 142 CHAPITRE 9. BASES DE DONNÉES les raisons déjà invoquées. Les valeurs non dénies sont laissées vides dans la base à moins qu'on ait déni des valeurs par défaut à la création. insert into produits (nom, quantite, categorie, poids, prix) values ('roue', 76, "", 145.0,12.45); La commande ci-dessous introduit une troisième forme pour dénire l'insertion d'une ligne. Le paramètre de la clé primaire n'étant pas déni, il est à nouveau généré automatiquement. insert into produits set name='pneu', quantite=88,categorie="bb", weight=319.600, price=62.60; 9.6 Extraction d'information (select) Les buts principaux d'une base de données sont l'extraction et le tri des informations. Ainsi il y a de nombreuses formes de la commande aectée à ces actions. 9.6.1 Sélection simple Voici quelques exemples de la commande select suivis des tables qu'ils peuvent produire. Entre select et from, on place l'indication des colonnes dont on veut retourner les valeurs. Dans le premier exemple, on a placé une étoile à cet endroit. Cela signie que l'on désire voir toutes les colonnes. select * from table1; +----+------+------+------+ | id | aaa | bbb | ccc | +----+------+------+------+ | 1 | x1 | x1 | x1 | | 2 | x2 | x2 | x2 | +----+------+------+------+ Dans la commande suivante, 9.6. EXTRACTION D'INFORMATION ( SELECT) 143 select table2.* from table2; +----+------+------+------+ | id | aa | bb | cc | +----+------+------+------+ | 1 | y1 | y1 | y1 | | 2 | y2 | y2 | y1 | | 3 | y2 | y2 | y2 | +----+------+------+------+ table2.* signie toutes les colonnes dénies dans table2. Dans la com- mande ci-dessous, les colonnes à retourner sont identiées. 144 CHAPITRE 9. BASES DE DONNÉES select id, aa, bb, cc from table3; +----+------+------+------+ | id | aa | bb | cc | +----+------+------+------+ | 1 | y1 | y1 | y1 | | 2 | y2 | y2 | y1 | +----+------+------+------+ Aussi longtemps que les noms des colonnes ne sont pas ambigus, et donc que soit il n'y a qu'un nom de table après from, soit les noms de colonnes des tables dénies après from sont diérents, ils peuvent être inscrits sans nom de table. Par contre, si l'on veut indiquer des colonnes de même nom dans deux tables, il faut faire précéder le nom de colonne d'un nom de table et d'un point (deux noms de table, voir paragraph 9.6.2). select id, cc, table3.bb from table3; +----+------+------+ | id | cc | bb | +----+------+------+ | 1 | y1 | y1 | | 2 | y1 | y2 | +----+------+------+ 9.6.2 Jointures L'indication from peut être suivie par un nom seul ou par une liste de noms de tables. Dans ce cas, il faut imaginer que cette liste crée une table 2 contenant le produit carthésien des lignes des tables listées, appelé jointure. L'exemple ci-dessous illustre le produit des tables table1 et table2 apparaissant ci-dessus. La table résultante contiendra toutes les colonnes de table1 suivies des colonnes de table2. Chaque ligne de la pemière table apparaît en combinaison avec chacune des lignes de la deuxième table. Comme ces tables ont respectivement 2 et 3 lignes, la table résultante aura donc 6 lignes, ce qu'on peut suivre en examinant les id des deux tables. 2. La plupart du temps, on extrait des éléments de cette table et le système ne la construit pas en entier, mais crée directement l'extraction 9.6. EXTRACTION D'INFORMATION ( SELECT) 145 select * from table1, table2; +----+------+------+------+----+------+------+------+ | id | aaa | bbb | ccc | id | aa | bb | cc | +----+------+------+------+----+------+------+------+ | 1 | x1 | x1 | x1 | 1 | y1 | y1 | y1 | | 2 | x2 | x2 | x2 | 1 | y1 | y1 | y1 | | 1 | x1 | x1 | x1 | 2 | y2 | y2 | y1 | | 2 | x2 | x2 | x2 | 2 | y2 | y2 | y1 | | 1 | x1 | x1 | x1 | 3 | y2 | y2 | y2 | | 2 | x2 | x2 | x2 | 3 | y2 | y2 | y2 | +----+------+------+------+----+------+------+------+ Si l'on ne dénit pas toutes les colonnes dans le select, il peut arriver que des colonnes qui font la diérence entre deux lignes soient éliminées, auquel cas des lignes risquent d'apparaître en doubles ou multiples exemplaires, comme la table ci-dessous qui est extraite de la table précédente.. select aa,bb,bbb,ccc from table1, table2 ; +------+------+------+------+ | aa | bb | bbb | ccc | +------+------+------+------+ | y1 | y1 | x1 | x1 | | y1 | y1 | x2 | x2 | | y2 | y2 | x1 | x1 | | y2 | y2 | x2 | x2 | | y2 | y2 | x1 | x1 | | y2 | y2 | x2 | x2 | +------+------+------+------+ Les doublons peuvent être éliminés en utilisant le symbole DISTINCT, comme montré ci-dessous. select distinct aa,bb,bbb,ccc from table1,table2 ; +------+------+------+------+ | aa | bb | bbb | ccc | +------+------+------+------+ | y1 | y1 | x1 | x1 | | y1 | y1 | x2 | x2 | | y2 | y2 | x1 | x1 | | y2 | y2 | x2 | x2 | +------+------+------+------+ 146 CHAPITRE 9. BASES DE DONNÉES 9.6.3 Critères de sélection (where) La clause where dénit les lignes qui sont sélectionnées dans la ou les tables accédées. Cette clause contient une expression Booléenne qui peut être vraie ou fausse pour chaque ligne. Seules les lignes pour lesquelles l'expression est vraie sont retournées par la commande. Par exemple : select * from table2 where id>1 and id<3; +----+------+------+------+ | id | aa | bb | cc | +----+------+------+------+ | 2 | y2 | y2 | y1 | +----+------+------+------+ select table2.*,table3.* from table2, table3 where table2.aa=table3.aa and table2.bb=table3.bb and table2.cc=table3.cc; +----+------+------+------+----+------+------+------+ | id | aa | bb | cc | id | aa | bb | cc | +----+------+------+------+----+------+------+------+ | 1 | y1 | y1 | y1 | 1 | y1 | y1 | y1 | | 2 | y2 | y2 | y1 | 2 | y2 | y2 | y1 | +----+------+------+------+----+------+------+------+ Dans la requête ci-dessous, les lignes qui sont prises en compte sont celles dont la valeur de la colonne est contenue dans la liste retournée par le select emboîté. select * from table2 where id in (1, 2) +----+------+------+------+ | id | aa | bb | cc | +----+------+------+------+ | 1 | y1 | y1 | y1 | | 2 | y2 | y2 | y1 | +----+------+------+------+ L'ensemble placé à côté de IN peut également contenir des chaînes de caractères. Cet ensemble peut être construit par un select qui retourne une liste de valeurs. On appelle un tel select emboîté. 9.6. EXTRACTION D'INFORMATION ( SELECT) 147 select * from table1 where id in (select id from table1) +----+------+------+------+ | id | aa | bb | cc | +----+------+------+------+ | 1 | y1 | y1 | y1 | | 2 | y2 | y2 | y1 | +----+------+------+------+ 9.6.4 Fonction utilisables dans where Le partie where d'une commande select peut utiliser les fonctions et opérateurs listés ci-dessous. On peut utiliser les parenthèses pour composer des expressions. = and > or < <> (or !=) not is null <= >= is not null Pour illustrer de façon simple les résultats de certaines comparaisons, elles sont introduites dans des sélections sans tables. Il est en eet possible d'envoyer ces lignes à la base de données, pour vérier des résultats, mais elles auraient évidemment le même eet si elles étaient placées entre des noms de colonnes qui retournent les valeurs comparées. Le résultat d'une expression Booléenne est soit 0 (false), soit 1 (true), soit null. select 3<7,234+22 ; // produit une valeur Booléenne et un nombre select 0=NULL ; // le résultat est NULL, pas false. Quand un des membres risque d'être NULL il faut utiliser le comparateur suivant select 1<=>NULL ; // le résultat est 0 (false). <=> a sinon la même signication que = select 4="4aa" ; // une chaîne de caractères est transformée en un nombre (jusqu'à la première lettre, comme en Javascript)) select 0="aa" ; // Si la chaîne ne commence par aucun chire, le système la prend pour la valeur 0 select CURDATE()< DATE_ADD(CURDATE(), INTERVAL 1 DAY) ; // comparaison de dates 9.6.5 Quelques-unes des fonctions dénies par MySQL MySQL dénit une longue liste de fonctions qui peuvent être utilisées après les select pour certaines d'entre elles et dans le corps des commandes pour d'autres, en concordance avec les fonctions. Voici les plus utilisées. 148 CHAPITRE 9. BASES DE DONNÉES SUM (expr), AVG (expr), MIN (expr), MAX (expr) Ces fonctions retournent la somme, la moyenne, le minimum et le maximum calculé sur toutes les lignes des colonnes désignées dans les expressions. Un exemple est donné ci-dessous. select avg(weight), sum(weight), min(weight) from table; Il est possible d'utiliser une clause group by (voir paragraphe 9.6.7) pour créer une liste de moyennes, par exemple pour chacun des étudiants. count (*), count (expr), count (distinct expr1 . . .) La première forme retourne le nombre de lignes sélectionnées par les paramètres qui suivent select. select count(*) from table1; La seconde forme retourne le nombre de fois que l'expression est non nulle. select count(table1.bb) from table1; La dernière forme retourne le nombre de lignes distinctes dont aucun des champs placés dans les arguments n'est nul. select count(distinct table1.bb, table1.cc) from table1; LAST_insert_ID( ) Cette fonction retourne la dernière valeur créé par la clause auto_increment. Cela permet d'introduire cette valeur dans une autre ligne, par exemple pour créer une relation (paragraphe 9.9) insert insert insert insert into into into into products values( null, 'roue', 44, "", 59.1, 52.20); reservations SET id={last_insert_id()}; products values( 0, 'door', 12, "xx", 85.3, 136.20); reservations values ({ last_insert_id()}, default); SUBSTRING(str, pos, len) Cette fonction retourne la sous-chaîne partant de pos et de longueur len. Attention, le premier caractère vaut 1. 9.6. EXTRACTION D'INFORMATION ( SELECT) 149 strExpression like `xxx%yyy_zzz' L'opérateur like retourne true si deux chaînes sont équivalentes. Le signe % à droite de LIKE représente n'importe quelle séquence de caractères. Le signe _ représente un seul caractère. Si un de ces caractères ou un \ doit être déni, ils doivent être précédés par un \ : \%, \_ ou \\. now() Cette fonction retourne la date et l'heure dans une chaîne, telle que : 2003-06-1917:31:19 curdate() Cette retourne la date courante. curtime() Cette fonction retourne l'heure courante. date_add(date, interval 3 minute) Cette fonction retourne une date augmentée de 3 minutes. Par exemple : now() date\_add(now(),interval 3 minute) // 2003-06-19 17:35:32 // 2003-06-19 17:38:32 D'autres exemples de valeurs possibles sont données ci-dessous : 12 second 30 minute 2 hour 5 day 2 month 10 year "1:30" minute\_second "1:45" hour\_minute "2:02:10" hour\_second "1 12" day\_hour "1 11:30" day\_minute "1 11:30:10" day\_second "1-6" year\_month 150 CHAPITRE 9. BASES DE DONNÉES date_sub(date, interval 3 minute) Comme ci-dessus, mais pour la soustraction. 9.6.6 order by grouping_columns Si l'on ajoute cette commande après un select, la base de données trie les lignes selon les valeurs déposées dans les colonnes indiquées. On donne ci-dessous quelques exemples. Les nombres correspondent aux colonnes spéciées à la suite du select. La première colonne a le numéro 1. Une colonne suivie de desc est triée par ordre décroissant. select table1.ccc, table2.cc from table1, table2 order by 1 desc, 2 desc; 9.6.7 group by Dans le cas simple, group by a le même eet qu'order. Dans le cas où un select inclut des fonctions telles que AVG, count ou SUM, et fait référence à plusieurs tables, group BY est obligatoire, et ces fonctions calculent alors plusieurs valeurs, c'est-à-dire une pour chaque groupe indiqué après group BY. select table1.ccc, table2.cc from table1, table2 group by table1.ccc, table2.cc desc; 9.7 Mise à jour des lignes d'une table Les lignes peuvent être mises à jour au moyen des commandes suivantes. update products set weight=33.33 where id=2; La commande suivante ajoute 2 au poids et ensuite 33.33, c'est-à-dire 35.33 au total. update products set weight=weight+2, weight=weight+33.33 where id=2; 9.8. ELIMINATION D'UNE LIGNE D'UNE TABLE 9.8 Elimination d'une ligne d'une table 151 Les lignes peuvent être éliminées d'une table par la commande suivante. delete from products where products.weight<80; La commande précédente eace les lignes dans lesquelles weight vaut moins que 80. La commande retourne le nombre de lignes éliminées. S'il n'y a pas de clause where toutes les lignes sont éliminées. delete from products,reservations where products.id=1 and reservations.id=products.id; Une commande qui a plusieurs noms de table après le symbole from élimine des lignes dans plusieurs tables. Rappelez-vous que from products, reservations représente le produit carthésien des lignes des deux tables, c'est-à-dire une table dans laquelle les lignes sont faites d'une ligne de la première table et d'une ligne de la seconde table. 9.9 Relations entre tables Dans la plupart des applications des bases de données, une partie des colonnes des tables font références à des lignes d'autres tables. On a, par exemple, une table de clients, une table de factures une table de produits, etc. Chaque facture est liée à un client et contient une liste de références à des produits. Les connexions entre tables peuvent être très complexes, mais nous resterons dans des cas relativement élémentaires. La première table ci-dessous contient une liste de vins avec quelques caractéristiques et un numéro indiquant le marchand qui le livre. On pourrait mettre directement le nom du marchand dans la table des vins, mais le marchand a d'autres caractéristiques : adresse, liste de livraisons en cours, etc. et on ne peut pas mettre toutes ces caractristiques dans la table des vins. De plus elles seront vraisemblablement utilisées dans d'autres tables. Dans les tables ci-dessous, on sait par exemple que Jean livre du Bourgogne en lisant la colonne marchand de la table des vins. Le numéro mémorisé dans la ligne Bourgogne est celui qui est attribué à Jean dans la colonne idm de la table des marchands. idv 1 2 3 region Vins cepage Lavaux chasselas Chianti sangiovese Bourgogne pinot noir annee marchand 2005 2002 2000 3 3 1 Marchands idm 1 3 nom Jean Luc Table marchands refV refM relationVM Table vins 152 CHAPITRE 9. Table vins 0..n Å livre 0..n est livré par Æ BASES DE DONNÉES Table marchands Figure 9.1 Relations entre vins et les marchands Table produits La relation entre ces tables est schématisée sur la gure 9.1. Notez que dans une application réelle d'un peu d'importance, on a facilement une dizaine à plusieurs dizaines de tables. no_p En examinant les tables des vins et des marchands, onTable pourrait savoir qui fournisseurs no_u no_f Table usines livre le chasselas en utilisant la première instruction ci-dessous qui retourne le livraisons numéro du marchand et en exécutant la deuxième instruction avec le numéro obtenu dans la première. select marchand from vins where cepage="chasselas" On obtient NUMERO. Le nom du marchand est donc obtenu par : select nom from marchands where idm=NUMERO Cependant il est possible de ne faire qu'une seule requête, ce qui est fait normalement. Une première possibilité est donnée ci-dessous. select nom from vins,marchands where marchand=idm and cepage="chasselas" Cette requête crée virtuellement le produit cartésien qui contient les lignes suivantes : idv 1 2 3 1 2 3 region Lavaux Chianti Bourgogne Lavaux Chianti Bourgogne cepage chasselas sangiovese pinot noir chasselas sangiovese pinot noir annee marchand idm nom 2005 2002 2000 2005 2002 2000 3 3 1 3 3 1 1 1 1 3 3 3 Jean Jean Jean Luc Luc Luc Parmi ces lignes, les lignes dans lesquelles les numéros des colonnes marchand et idm ne sont pas identiques ne représentent rien, alors que les autres contiennent des informations cohérentes. Ces lignes sont retenues par la première partie du where ci-dessus (marchand=idm). Lorsque la requête précise ensuite cepage="chasselas", on obtient alors la ligne recherchée et l'on peut extraire la colonne qui nous intéresse, nom placé entre select et from. Une autre façon de faire se base sur le select emboîté (9.6.3). 9.10. 153 RELATIONS N X M refV refM relationVM Table vins Table marchands Figure 9.2 Relation entre les marchands et les vins Table vins Å livre 0..n est livré par Æ marchands 0..n Table marchands select nom from where idm in (select marchand from vins where cepage="chasselas") Table produits Le select emboîté retourne la liste des numéros de marchands qui livrent du chasselas. Le select principal retourne les noms pour les lignes dont la colonne idm est contenue dans cette liste. no_p Table usines no_u no_f livraisons 9.10 Relations n × m Table fournisseurs Dans la section pécédente, on ne peut pas exprimer le fait qu'un marchand peut fournir plusieurs vins et que chaque vin puisse être fourni par plusieurs marchands. Pour cela, il faut construire une table supplémentaire qui établit une telle relation entre deux autres tables. idv 1 2 3 region Vins cepage Lavaux chasselas Chianti sangiovese Bourgogne pinot noir annee 2005 2002 2000 Marchands idm 1 3 nom Jean Luc relationVM refV refM 2 3 3 1 1 1 3 3 La table ci-dessus nous indique que Jean (1) livre du Chianti (2) et du Bourgogne (3). Luc (3) livre du Lavaux (1) et du Bourgogne (3). Cette relation s'exprime au moyen du schéma de la gure 9.2. refV refM relationVM Table vins 154 0..n Table vins Table marchands Å livre 0..n Table marchands est livré par Æ CHAPITRE 9. BASES DE DONNÉES Table produits Table usines no_p no_u no_f livraisons Table fournisseurs Figure 9.3 Relations entre les 4 tables 9.11 Exercices 9.11.1 ex - Questions sur une structure existante Le chier http://siteLivre/exoBD.txt contient une liste de commandes qui créent les quatre tables ci-dessous, reliées entre elles selon la gure 9.3, et qui y insère des données. Copiez le contenu de ce chier dans la fenêtre du moniteur de SQL et exécutez-les. Usines No_u, Nom_u, Ville Produits No_p, Nom_p, Couleur, Poids Fournisseurs No_f, Nom_f, Statut Ville Livraisons No_p, No_u, No_f, Quantité Déterminez ensuite les commandes qui répondent aux questions posées ci-dessous. 1. Trouver le numéro, le nom et la ville de toutes les usines. 2. Trouver le numéro, le nom et la ville de toutes les usines de Lausanne. 3. Trouver les numéros des fournisseurs qui approvisionnent l'usine n◦ 1 en produit n◦ 1. 4. Trouver le nom et la couleur des produits livrés par le fournisseur n◦ 1. Ecrire les requêtes SQL permettant de répondre aux questions ci-dessous 5. Trouver les numéros des fournisseurs qui approvisionnent l'usine n◦ 1 en un produit rouge. 6. Trouver les noms des fournisseurs qui approvisionnent soit une usine de Lausanne soit une de Genève en un produit rouge. 7. Trouver les numéros des produits livrés à une usine par un fournisseur de la même ville que l'usine. 9.12. ACCÈS À LA BASE DE DONNÉES PAR JAVASCRIPT 155 8. Trouver les numéros des produits livrés à une usine de Lausanne par un fournisseur de Lausanne. 9. Supprimer tous les produits de couleur rouge. 10. Changer la ville du fournisseur n◦ 1 : il a déménagé à Genève. 11. Trouver les numéros des usines qui ont au moins un fournisseur qui n'est pas de la même ville que l'usine. 12. Trouver les numéros des fournisseurs qui approvisionnent à la fois les usines n◦ 1 et n◦ 2. 13. Trouver les numéros des usines qui utilisent au moins un produit disponible chez le fournisseur n◦ 3 (c'est-à-dire un produit qu'il est capable de livrer, mais que l'usine n'obtient pas forcément de ce fournisseur). 14. Trouver le numéro du produit le plus léger (les numéros, si plusieurs produits ont ce même poids). 15. Trouver les numéros des usines qui ne reçoivent aucun produit rouge d'un fournisseur de Lausanne. 16. Trouver les numéros des fournisseurs qui fournissent au moins un produit fourni par au moins un fournisseur qui fournit au moins un produit rouge. 17. Trouver tous les triplets (VilleF, No_P, VilleU) tels qu'un fournisseur de la première ville approvisionne une usine de la deuxième ville avec un produit NP. 18. Même question qu'en 15, mais sans les triplets où les deux villes sont identiques. 9.12 Accès à la base de données par Javascript Les applications qui gérent des données placées dans une base, accèdent à cette base au moyen de SQL. Or les réponses aux requêtes SQL sont adaptées à la vision d'un humain, pas d'un programme. Pour aborder ce problème, on utilise des librairies et des astuces présentés dans cette section. Le système LemanOS permet à chaque utilisateur (pour lequel on a déni un compte) d'accéder à une base de données et de faire des requêtes SQL et de traiter les réponses de façon simple. 9.12.1 Librairies Les librairies d'interface au système sont disponibles dans les chiers suivants, qu'il faut donc les importer dans les applications qui doivent gérer des bases de données sur LemanOS. 156 CHAPITRE 9. BASES DE DONNÉES <script src='/LemanOS/dwr/engine.js'> </script> <script src='/LemanOS/database.js'> </script> 9.12.2 Requêtes SQL en Javascript Les requêtes SQL sont simplement placées dans des chaînes de caractères et envoyées au moyen de l'instruction suivante : var result = database.query("select * from vins") Au retour des requêtes select, le résultat contient un tableau d'objets. Pour les autres requêtes, le système retourne le nombre de lignes modiées ou -1 s'il y a une erreur, comme le moniteur. Par exemple la requête précédente eectuée sur la table ci-dessous : idv 1 2 3 Vins region cepage Lavaux Bourgogne chasselas sangiovese annee 2005 2002 retournerait les résultats qui suivent : [ {'idv':1,'cepage':'chasselas','region':'Lavaux','annee':2005}, {'idv':2,'cepage':'sangiovese','annee':2002}, {'idv':3, 'region':'Bourgogne'} ] Les champs nuls n'apparaissent pas dans les objets. Supposant que le résultat soit déposé dans result comme indiqué dans la requête, on obtient les diérents champs au moyen de result[0].idv result[0].cepage result[0].region result[0].annee result[1].idv result[1].cepage result[1].annee Pour déterminer si un champ n'a pas été déni, il sut de faire le test : if (!result[1].region) { alert("Pas défini") } 9.12. ACCÈS À LA BASE DE DONNÉES PAR JAVASCRIPT 157 Le nombre de lignes retournées est évidemment obtenu par result.length. Attention, même si le système ne retourne qu'une ligne, elle est placée dans un tableau. Pour détecter d'éventuelles erreurs d'exécution, on utilise la séquence d'instructions ci-dessous : var result try { result = database.query("select * from vins") } catch(e) { alert("Erreur: "+e+" <<"+database.readyQuery+">>") return } // continue ici, en cas de succès if (result.length==0) { // pas d'erreur mais table vide } L'attribut database.readyQuery placé dans l'alerte contient la chaîne SQL envoyée à la base de données après remplacement des ? . Cela aide certaines fois à comprendre les erreurs. 9.12.3 Introduction de variables dans la requête SQL Lorsque l'on veut introduire des données entrées par un utilisateur dans la base de données, il faut bien adapter la commande SQL pour qu'elle contienne les données actuelles. De même pour les sélections. Il y a plusieurs possibilités de faire cela, ce qui permet d'entrer des données selon les diérentes formes de SQL. Ces diérentes formes peuvent être combinées. Concaténation Dans la forme la plus simple la chaîne est formée par concaténation : var laRegion = "Lavaux" var result = database.query("select * from vins " +"where region='"+laRegion+"'") Attention, il faut introduire soi-même les apostrophes qui encadreront Lavaux après la synthèse de la chaîne, comme sur le moniteur. 158 CHAPITRE 9. BASES DE DONNÉES Passage de paramètres Pour simplier les instructions, on a introduit la possibilité de placer un ou plusieurs points d'interrogations, qui seront remplacés par le système par les paramètres qui suivent la chaîne SQL. Le système détecte lui-même s'il s'agit de chaînes ou de nombres et ajoute lui-même les apostrophes. var laRegion = "Lavaux" var result = database.query("select * from vins " +"where region=?", laRegion) Tableau de valeurs Dans la première forme de l'insert, il faut introduire une liste de valeurs. Cela correspond exactement à un tableau, dans lequel l'ordre est respecté. Dans l'exemple ci-dessous, le ? est remplacé par le tableau en deuxième paramètre. var data = [0, 'Barolo', 'nebbiolo', null] var result = database.query( 'insert into vins values (?)' , data ) data étant un tableau, le ? est remplacé par les valeurs de data séparées par des virgules. Les chaînes sont automatiquement encadrées par des apostrophes. Comme cette requête est une action, result reçoit le nombre de lignes modiées : 1 Objet en paramètre De même qu'on peut recevoir un objet, on peut transmettre un objet. Ce dernier s'intègre parfaitement dans la deuxième forme d'insertion. var vin = {'region':'Bordeaux', 'cepage':'merlot'} var result = database.query( 'insert into vins set ?' , vin ) vin étant un objet, le ? est remplacé par les couples nom-valeur de la variable vin séparés par des virgules. De nouveau, les chaînes sont encadrées par des apostrophes. 9.12. ACCÈS À LA BASE DE DONNÉES PAR JAVASCRIPT 159 9.12.4 Insertion d'une date Le code ci-dessous crée la date courante et la stocke dans un champ datetime. date = new Date() dateSQL = date.getFullYear() + "-" + (date.getMonth()+1) + "-" + date.getDate() + " " + date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds() . . . database.query("insert into unite set jourHeure=?",dateSQL) Dans le code ci-dessous, on a complété l'objet Date de Javascript pour faciliter la construction des dates en format SQL. Date.prototype.sql = function () { return this.getFullYear() + "-" + (this.getMonth()+1) + "-" + this.getDate() + " " + this.getHours() + ":" + this.getMinutes() + ":" + this.getSeconds() } . . . database.query("insert into timeTable set jourHeure=?", new Date().sql()) 9.12.5 Obtention de la clé primaire Lorsque la clé primaire est générée automatiquement, et qu'on veut l'introduire dans une autre table pour créer une relation, on peut l'obtenir en appelant idNb = database.last_insert_id() juste après la requête qui a provoqué la création automatique d'une nouvelle clé primaire. 9.12.6 Lecture de toutes les colonnes Il est possible d'acher toutes les colonnes d'une réponse en les parcourant automatiquement. Notez que les champs sont dans n'importe quel ordre et que cette façon de faire n'est utile que pour faire des essais. Dans une application réelle, on ache les champs de façon spécique. 160 CHAPITRE 9. BASES DE DONNÉES Couche métier Couche affichage handleVins() { lit les champs Æ objet database.query("insert into Vins set ?", objet) } une ligne Couche BD tables de l’application exec() { var x = database.query("select * from Vins") displayVinsM(x) } n lignes Figure 9.4 Architecture 3 tiers var result = [ {'a':2, 'b':3} ] for ( key in result [0] ) { document.write( result [0] [key] ) } document.close() 9.13 Exercice 9.13.1 ex - Marchand de vins On doit créer une application qui pourra gérer des vins, les marchands qui les livrent et la quantité de bouteilles de chaque sorte de vin livrée par chaque marchand. Toute application de ce type comprend trois couches (voir gure 9.4) : une couche d'achage, une couche de gestion des données et une couche intermédiaire dit couche métier, qui va chercher les données dans la base de données et les ache, ou lit ce que l'utilisateur a entré dans les tables HTML d'achage et les insère dans la base de données. La couche d'achage est contituée de pages HTML. Chaque page permet de faire une ensemble cohérent d'opérations (gestion des marchands, des vins gestion du stock, etc) en présentant deux sortes de tables HTML (gure 9.5 : des tables qui ache les données d'une ligne d'une table SQL, les champs étant placés les uns sous les autres et des tables HTML dont les lignes et les colonnes reètent celles des tables SQL. Des liens permettent de passer d'une page d'accueil aux diérentes pages 9.13. 161 EXERCICE Figure 9.5 Tables HTML d'opération, de revenir sur la page d'accueil, voire de sauter d'une page à l'autre. La couche de la base de données est accédée principalement au moyen de SQL. Le moniteur SQL (menus d'outils) est utile dans cette couche pour initialiser les tables et pour tester les commandes SQL avant de les intégrer dans la couche métier. Les diérentes opérations de la couche métier sont déclenchées au chargement des pages, lorsque l'utilisateur clique un bouton ou l'autre, ou lors qu'il sélectionne les lignes des tables. Cette couche contient donc la logique qui gère l'application. Créez dans a base de données une table contenant des vins avec quelques caractéristiques, une table de marchands avec leur nom ainsi qu'une table dont chaque ligne représente un vin en stock, dont on connaît les caractéristiques (référence dans la table des vins), le marchand qui l'a livré et le nombre de bouteilles. Créez une page HTML qui permet d'entrer des vins et des marchands. Le système LemanOS permet de générer les tables HTML et des fonctions générales achant des lignes ou des groupes de lignes. Voir les détails dans le générateur d'interfaces parmi les outils de ce système. Créez une page qui permet d'acher tous les vins dans une première table et tous les marchands dans une deuxième table. Ces tables peuvent être chargées par l'attribut onload dans body, en faisant une requête SQL sur les tables et utilisant les méthodes d'achages produites par 162 CHAPITRE 9. BASES DE DONNÉES le générateur. Quand on clique sur une ligne d'une table à plusieurs lignes, une fonction est appelée. Cette fonction dépose le numéro de la ligne dans une variable globale. Créer un bouton qui permet d'entrer un nombre de bouteilles (champ d'input ) dans le stock en utilisant les lignes sélectionnées dans la table de vins et celle de marchands. Si le programme a gardé les tableaux de lignes qui ont servi à charger les tables au point précédent, on peut lire les identicateurs dans ces tables : vins[ligneSelectionnee].idv. Créez un deuxième bouton qui permet de sortir du stock un nombre de bouteilles correspondant au nombre dans le champ d'input. Modiez la page précédente pour qu'on puisse sélectionner un vin et ne voir dans la deuxième table que les marchands qui livrent ce vin. Créez une page d'accueil qui permet d'appeler l'une des deux pages cidessus au moyen d'un lien indiquant les opérations disponibles. Mettez des liens sur les deux pages pour revenir sur la page d'accueil. 9.13.2 ex - Application de gestion de fournisseurs Dans cet exercice, on va gérer une base de données contenant des descriptions de fournisseurs, de produits et de commandes. Une table auxiliaire sera utilisée pour dénir les quantités de produits en stock ou dans les commandes. Une marche à suivre est disponible dans les résultats à obtenir liés aux exercices de ce manuel. 9.13.3 ex- Organisation d'un musée Dénir un diagramme entité-association représentant les faits suivants, relatifs à un musée : toute oeuvre du musée a un titre, un ou plusieurs auteurs, une date d'acquisition et un numéro de catalogue (identiant) ; une oeuvre est exposée dans l'une des salles du musée (qui est caractérisée par un numéro, son nom, le nombre d'oeuvres exposables, sol, éclairage), ou est en prêt dans un autre musée (nom et adresse de ce musée, début et durée du prêt) ; certaines oeuvres exposées dans le musée peuvent avoir été empruntées par le musée, soit à un autre musée, soit à un particulier (nom et adresse). Dans ce cas, on connaît son titre, son (ou ses) auteur(s), la date de début et la durée de l'emprunt. De plus, l'oeuvre doit alors être assurée. On veut savoir le montant de la prime d'assurance, la valeur 9.13. EXERCICE 163 pour laquelle l'oeuvre est assurée, le nom et l'adresse de la compagnie qui l'assure ; le conservateur garde le chier des musées et des particuliers qui ont prêté ou qui sont susceptibles de prêter des oeuvres. Pour chacun (musée ou particulier), il garde le nom et l'adresse et la liste des collections qui l'intéressent (art déco, art contemporain, antiquités, ...).