Uploaded by Marouane Misdak

crtpsql-tdalgebrerel

advertisement
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
Download