Compte rendu du T.P. base de données SQL (R0) Liste de noms de tous les hotels, avec leur numéros de station. Vérif. : On doit obtenir 78 réponses. Solution 0 SELECT nomh, ns FROM hotels (R1) En faisant suivre la requête précédente de la commande ORDER BY nomh, on s’aperçoit que des hotels situés dans des stations différentes portent le même nom : essayez ! On se demande alors combien de noms distincts d’hotels, il y a dans la base. Pour cela, une commande est utile : SELECT DISTINCT. Vérif. On ne trouve plus que 22 réponses, ce qui montre que l’imagination des hôteliers est ici limitée ! Solution 1 En suivant l’énoncé, pour voir seulement les noms d’hotels distincts : SELECT DISTINCT nomh FROM hotels -- Facultatif, on peut rajouter ORDER BY nomh Ce qu’ll faut comprendre : avec SELECT nomh FROM hotels ORDER BY nomh les noms d’hotels seront répétés autant de fois qu’ils apparaissent dans les lignes de la table hotels. C’est le comportement normal lors d’une projection sur une colonne, ici la colonne nomh. Ici , on lit avec SQLite Manager le nombre de réponses en bas. Mais sinon la commande SQL pour compter serait COUNT(*) (qui compte le nombre total de lignes) sauf qu’il faut compter le nombre de lignes dans la table renvoyée par le SELECT DISTINCT précédent d’où la requête déjà plus compliquée : SELECT COUNT(*) FROM (SELECT DISTINCT nomh FROM hotels) (R2) Liste des numéros et noms des stations de montagne avec leur capacité en chambres. Vérif. : On doit obtenir six réponses. Solution 2 SELECT ns, noms, capch FROM resorts WHERE types=’montagne’. (R3) Liste des noms des hôtels trois étoiles et quatre étoiles, avec leur numéros de station. Vérif. : On doit obtenir 31 réponses. Solution 3 SELECT nomh,ns FROM hotels WHERE cath=3 OR cath=4 (R4) Liste des noms et des numéros des hôtels avec leur adresse, catégorie, se trouvant dans une station balnéaire. Indication : Attention, il y a deux tables à croiser.... car le caractère balnéaire d’une station ne se voit pas dans la table hôtel. Vérif. : On doit obtenir 37 réponses. Solution 4 SELECT h.nomh,h.adrh,h.telh,h.cath FROM resorts r, hotels h WHERE r.ns=h.ns AND r.types=’mer’ Variante avec la commande JOIN : SELECT h.nomh,h.adrh,h.telh,h.cath FROM resorts r JOIN hotels h ON r.ns=h.ns WHERE r.types=’mer’ La jointure se fait à l’aide de la clé primaire de resorts à savoir r.ns qui apparaı̂t dans hotels sous la forme h.ns 1 (R5) Liste des noms des stations au bord de la mer ayant au moins un hôtel 4 étoiles. Vérif. : Vous obtiendrez trois réponses. Solution 5 SELECT DISTINCT r.noms FROM resorts r, hotels h WHERE r.ns=h.ns AND r.types=’mer’ AND cath=4 ou encore SELECT DISTINCT r.noms FROM resorts r JOIN hotels h ON r.ns=h.ns WHERE r.types=’mer’ AND cath=4 En SQL, les conditions de type ≪ au moins ≫ sont facile à traduire. En effet, dès qu’il y aura une ligne de la table qui vérifie la condition (ici les deux conditions r.types=’mer’ AND cath=4), elle sera sélectionnée, et la valeur de l’attribut r.noms de cette ligne apparaı̂tra dans le résultat. (R6) Liste des noms et adresses des clients dont l’adresse est un hôtel. Vérif. : 2 réponses. Solution 6 SELECT DISTINCT FROM Guests g, Hotels h WHERE g.adrcl=h.adrh g.nomcl, g.adrcl # On remarque qu’ici la jointure g.adrcl=h.adrh ne se fait pas sur une clé primaire. Il y a plusieurs hôtels qui ont la même adresse. (Ce n’est d’ailleurs pas normal, la table est mal faite, car il s’agit d’adresse emails, qui devraient normalement être distinctes dans la vraie vie ! ). D’autre part, la projection sur g.nomcl peut aussi laisser des ambiguı̈tés : la clé primaire serait ici g.ncl. Cela signifie qu’éventuellement il peut y avoir plusieurs clients du même nom. " ! Voyons cela de plus près : ● si on enlève le DISTINCT, on obtient 4 réponses : chacun des deux noms est répété deux fois. Qu’est-ce que cela signifie ? ● rajoutons l’attribut g.ncl qui est une clé du guests : on voit que les 4 réponses sont formées avec deux fois le même numéro de clients. Il n’y a donc bien que deux vrais clients qui sont solutions. ● Alors pourquoi sont-ils répétés deux fois si on enlève le distinct ? (R7) Liste des noms et adresses des clients ayant réservé à la montagne. Vérif. : 889 réponses. Solution 7 SELECT DISTINCT g.nomcl,g.adrcl FROM resorts r, bookings b, guests g WHERE r.ns=b.ns AND g.ncl=b.ncl AND r.types=’montagne’ Ici, il n’y pas le choix pour les jointures : le type r.types="montagne" n’est que dans resorts, qu’on ne peut joindre qu’à bookings via r.ns=b.ns, et la jointure entre bookings et guests se fait aussi sur une clé primaire : la clé g.ncl. Avec la syntaxe JOIN on peut mettre en premier la table sur laquelle vont être faites les deux jointures : 2 SELECT DISTINCT g.nomcl,g.adrcl FROM bookings b JOIN resorts r ON r.ns=b.ns JOIN guests g ON g.ncl=b.ncl WHERE r.types=’montagne’ (R8) Nom et numéro de station de l’hotel ayant la plus grande capacité en chambre : attention, on veut que ce nom soit une valeur de retour de SQL, il ne s’agit pas de faire simplement ORDER BY nbch pour lire le résultat dans la liste. Solution 8 SELECT nomh, ns FROM hotels WHERE nbch=(SELECT MAX(nbch) FROM hotels) Vérif. : Sea Hotel, 1. C’est notre première requête ≪ imbriquée ≫. ● Ici, la requête imbriquée retourne un nombre : le résultat de SELECT MAX(nbch) FROM hotels ● Les sous-requêtes doivent toujours être entre parenthèses.. Exercice supplémentaire : comment faire pour faire apparaı̂tre plutôt le nom de la station que le numéro de la station ? (R9) Liste des numéros nh des hôtels avec leur numéro de station ayant au moins une chambre dont le prix est supérieur strictement à 40 euros. En déduire la liste des noms des hôtels avec leur numéro de station ayant toutes leurs chambres de prix inférieur ou égal à 40 euros. (On pourra utiliser la commande EXCEPT de différence ensembliste entre tables.) Vérif. 31 réponses pour la deuxième question. Solution 9 Pour la première requête, on peut n’utiliser que la table rooms. SELECT DISTINCT ns,nh FROM rooms WHERE prix >40 Si en plus, pour la même requête, on voulait le vrai nom des hotels, on a besoin de la jointure : SELECT DISTINCT r.ns, h.nomh FROM rooms r, hotels h WHERE r.prix>40 AND r.ns=h.ns AND r.nh=h.nh Pour la deuxième requête demandée : il suffit de prendre le complémentaire dans la liste précédente, avec EXCEPT. SELECT DISTINCT r.ns, h.nomh FROM rooms r, hotels h WHERE r.ns=h.ns AND r.nh=h.nh EXCEPT SELECT DISTINCT r.ns, h.nomh FROM rooms r, hotels h WHERE r.prix>40 AND r.ns=h.ns AND r.nh=h.nh ' $ ● Retenir que la condition tout(e) en SQL s’obtient plus facilement par passage au complémentaire à partir de la condition au moins un(e). ● L’opérateur EXCEPT fait la différence entre deux tables qui doivent avoir les mêmes attributs ! ● En SQLIte L’opérateur NON IN ne peut pas s’appliquer car IN et NOT IN doivent précéder une sous-requête dont le résultat est une seule colonne. Ici on en a deux (d’où l’intérêt d’avoir une seul attribut de clé primaire...). Il semble que ce soit une différence entre SQLite et d’autres versions de SQL, comme MySQL. & % (R10) Prix de la chambre la moins chère située dans un hôtel trois étoiles d’une station balnéaire ? Vérif. 65 euros. Solution 10 SELECT MIN(ro.prix) AS pluspetitprix FROM Resorts re, Hotels h, Rooms ro WHERE re.ns=h.ns AND h.ns=ro.ns AND h.nh=ro.nh AND h.cath=3 AND re.types=’mer’ 3 ● La commande MIN(ro.prix) est notre première fonction dans ce TP. Elle s’applique ici directement sur la colonne ro.prix. Elle va renvoyer un nombre (une table 1 × 1). ● Le résultat de MIN(ro.prix) est ici renvoyé dans une colonne renommée pluspetitprix. C’est le sens du AS. En réalité : $ ' En SQL il y a deux façons de renommer un objet que ce soit un attribut ou une table : ● Soit faire suivre le nom de l’objet du nouveau nom avec juste une espace de séparation : c’est ce qu’on a fait systématiquement pour les tables pour les jointures hotels h, rooms r. On peut aussi le faire pour un attribut, dans le code précédent on aurait pu écrire SELECT MIN(ro.prix) pluspetitprix. ● Ou bien utiliser AS comme on voit de le faire. C’est la solution conseillée pour plus de iisibilité, mais pour les tables nous préférerons quand même la première solution plus rapide. hotels AS h est plus long que hotels h. & % (R11) Liste des numéros de chambres d’hotels avec leur noms d’hotels et leur numéro de station, ayant un prix inférieur au prix moyen de toutes les chambres d’hotels. Vérif. : 1726 réponses. Solution 11 SELECT h.nomh, h.ns, r.nch FROM rooms r, hotels h WHERE r.ns=h.ns AND r.nh=h.nh AND r.prix < (SELECT avg(prix) FROM rooms) Attention : il est essentiel de faire correctement la jointure entre hotels et rooms. Ici la clé primaire de hotels est formée des deux attributs h.nh et h.ns. Le seul numéro d’hotel nh ne suffit pas à identifier correctement un hotel, puisqu’il y a un hotel numéro 1 dans la station 1, dans la station 2 etc.. Puis la liste des hotels (noms, ns), avec leur nombre de chambres vérifiant la condition de prix précédente. Vérif. : 47 réponses. SELECT h.nomh, h.ns, COUNT(*) AS NbChambre AND r.prix < (SELECT avg(prix) FROM rooms) FROM rooms r, hotels h WHERE r.ns=h.ns AND r. GROUP BY h.nomh, h.ns ' $ ● On a repris la requête précédente, en rajoutant une colonne supplémentaire où l’on compte le nombres de lignes du tableaux par groupes où h.nomh et h.ns sont identiques : c’est le sens du GROUP BY. ● La fonction d’agrégation COUNT() avec l’argument * compte toutes les lignes de chaque groupe. ● Si on utilise COUNT avec un nom d’attribut particulier, attention, on ne comptera que les lignes où l’attribut prend une valeur non nulle ! Par exemple s’il y avait une chambre qui a le numéro 0 et qu’on faisait COUNT(r.nch) elle ne serait pas comptée, ce qui n’est pas ce qu’on veut ici. & % (R12) Obtenir la liste des numéro d’hotels avec leur numéro de station des hôtels 4 étoiles n’ayant que des chambres avec salles de bain. Vérif. 11 réponses. Solution 12 SELECT DISTINCT ns, nh FROM hotels WHERE cath=4 EXCEPT SELECT DISTINCT ns,nh FROM rooms WHERE typch<>"SDB" 4 Remarque : comment ferait-on, pour la requête précédente, si on voulait faire apparaı̂tre le nom de l’hôtel, sachant qu’il n’apparaı̂t pas dans la table rooms et que le EXCEPT doit faire la différence de tables ayant les mêmes attributs ? Il faudrait faire une jointure supplémentaire de ce résultat avec la table hôtel. (R13) Nom de clients ayant réservé au moins trois jours consécutifs une même chambre dans le même hôtel de la même station. Vérif. 949 réponses. Solution 13 SELECT DISTINCT g.nomcl FROM guests g, bookings b1, bookings b2, bookings b3 WHERE g.ncl=b1.ncl AND g.ncl=b2.ncl AND g.ncl=b3.ncl AND b1.ns=b2.ns AND b1.ns=b3.ns AND b1.nh=b2.nh AND b1.nh=b3.nh AND b1.nch=b2.nch AND b1.nch=b3.nch AND b1.Jour=b2.Jour-1 AND b2.Jour=b3.Jour-1 C’est un exemple de jointure d’une table avec elle-même. On parle d’autojointure ou encore de jointure réflexive. L’important est de donner deux noms à la table pour la joindre avec elle-même. Ici, on fait même cela deux fois. 5 (R14) Listes des hôtels avec leur nom, adresse et leur nombre de réservations dans l’année. Vérif. 78 réponses : c’est le nombre total d’hôtel mais : Pour compter les réservations : dans la table bookings, chaque réservation compte pour une ligne. Lorsqu’on fait la jointure entre la table bookings et la table hotels, chaque hotel (identité par sa clé primaire h.ns, h.nh) apparaı̂tra autant de fois qu’il aura de réservations. Avec un COUNT(*) suivi d’un GROUP BY h.ns,h.nh, on comptera bien les réservations par hôtel sauf pour les hôtels qui n’ont pas de réservation qui n’apparaı̂tront pas dans cette jointure. C’est ce que l’on aurait avec la requête : SELECT h.nomh, h.adrh, COUNT(*) FROM hotels h, bookings b WHERE h.ns=b.ns AND h.nh=b.nh GROUP BY h.ns,h.nh Il n’y a que 77 hotels qui apparaissent. Pour traiter les cas d’exception, on rajoute la réunion suivante : SELECT h.nomh, h.adrh, COUNT(*) AS NbResa FROM hotels h, bookings b WHERE h.ns=b.ns AND h.nh=b.nh GROUP BY h.ns,h.nh UNION SELECT h.nomh, h.adrh, 0 AS NbResa FROM hotels h WHERE (h.ns,h.nh) NOT IN (SELECT ns,nh FROM bookings) (R15) Nom et adresse de l’hôtel de la station ≪ Chamonix ≫ ayant eu le plus de réservations dans l’année. Indication On peut bien sûr utiliser des requêtes imbriquées, mais cela en fait pas mal, les tester au fur et à mesure. Une autre méthode peut-être plus agréable (requête moins lourde) est d’utiliser un HAVING. Vérification On trouvera l’Hôtel de la Gentiane. Solution 15 On reprend le code précédent qui comptait le nombre de réservations dans chaque hôtel, en se restreignant à la station chamonix. SELECT h.nomh, h.adrh, COUNT(*) AS NbResa FROM hotels h, bookings b, resorts r WHERE h.ns=b.ns AND h.nh=b.nh AND r.noms="Chamonix" GROUP BY h.ns,h.nh Puis de ce code on va extraire le uplet réalisant le MAX de la réservation, en faisant précéder la requête précédente de SELECT MAX(NbResa) FROM ( ) : on trouve 921. Et enfin on trouve l’hotel dont le nombre de réservations est égale à ce MAX (ouf) : ce qui donne la requête complète suivante : SELECT h.nomh,NbResa FROM (SELECT h.nomh, h.adrh, COUNT(*) AS NbResa FROM hotels h, bookings b, resorts r WHERE h.ns=b.ns AND h.nh=b.nh AND r.noms="Chamonix" GROUP BY h.ns,h.nh) WHERE NbResa = SELECT MAX(NbResa) FROM (SELECT h.nomh, h.adrh, COUNT(*) AS NbResa FROM hotels h, bookings b, resorts r WHERE h.ns=b.ns AND h.nh=b.nh AND r.noms="Chamonix" GROUP BY h.ns,h.nh) Il y a plus simple avec l’opérateur HAVING : SELECT h.nomh,h.adrh FROM resorts r, hotels h, bookings b WHERE r.ns=h.ns AND h.ns=b.ns AND h.nh=b.nh AND r.noms="Chamonix" GROUP BY h.nh, h.adrh HAVING COUNT(b.ncl)=(SELECT Count(ncl)) FROM bookings b2, resorts r2 WHERE b2.ns=r2.ns AND r2.noms="Chamonix" GROUP BY b2.ns, b2.nh) (R16) Jour de l’année où l’hôtel ≪ Bon séjour ≫ de la station ≪ Chamonix ≫ a eu le plus de réservations. Vérification On trouvera deux jours : le jour 99 et le jour 100. 6 (R17) Noms des clients ayant réservé dans tous les hôtels 2 étoiles de la station ≪ Chamonix ≫. Vérifications 5 réponses. 7