Vous êtes sur la page 1sur 9

Correction TD1 et TP1 (question 5)

Exercice 2

Le sujet de cet exercice est l'oeuvre de Cécile CAPPONI, tout comme la majorité de cette correction.
Certaines questions vous ont été épargnées dans l'énoncé du TD, vous les trouverez toutefois
dans cette correction.

Pour chaque question on donne sa réponse sous forme de langage algébrique puis en SQL en
police "courier". Pour la plupart des questions, il peut exister plusieurs solutions algébriques
(puisque certains opérateurs s'expriment en fonction d'autres), et plusieurs solutions SQL. Nous
donnons ici une solution pour chaque question, et parfois deux solutions pour montrer des
différences importantes.

Pour bien comprendre les requêtes que vous jugez difficiles, un bon moyen est de spécifier, à
chaque étape de l'expression algébrique, les relations mises en jeu (avec leurs attributs).

1) Lister les noms d'hôtels et leur ville


Il s'agit ici d'une simple projection, puisque toutes les informations sont présentes dans la
relation hotel, et puisqu'il n'y a aucune restriction.

RESULTAT = PROJECT (hotel, nom, ville)


SELECT nom, ville
FROM hotel;

*) Combien y-a-t-il de clients enregistrés ?


L'attribut numclient est unique dans la relation client : il suffit donc d'en compter les occurrences
(aucun groupement n'est nécessaire).

RESULTAT = AGREGAT(client ; ; COUNT(numclient))


SELECT COUNT(numclient)
FROM client;

2) Lister les noms d'hôtels ayant 3 étoiles ou plus.


On commence par restreindre les tuples d'hôtels ayant strictement plus de 2 étoiles, et on
projette sur le nom des hôtels retenus.

R1 = RESTRICT(hotel, etoiles > 2)


RESULTAT = PROJECT(R1, nom)
SELECT nom
FROM hotel
WHERE etoiles > 2;

*) Lister les clients dont le nom commence par 'H' (la fonction START(string, char) renvoie TRUE si la
chaîne string commence par le caractère char).

On applique une restriction sur le nom des clients, en appliquant à chaque nom une fonction pour
vérifier la condition de restriction.
RESULTAT = RESTRICT(client, START(nom, 'H'))

En SQLite, la fonction START n'est pas implémenté : on s'en sors avec LIKE :
SELECT *
FROM client
WHERE nom LIKE 'H%';

3) Lister les hôtels sur lesquels portent une réservation.


On réalise une semi-jointure entre les relations hotel et reservation, puisque c'est la relation reservation
qui compile les réservations effectuées, et que c'est la relation hotel qui détient les attributs que l'on
veut “en sortie" (en projection). Ceci est équivalent à une jointure naturelle suivie d'une projection
sur les attributs de hotel.

RESULTAT = NATURAL SEMI-JOIN(hotel, reservation)


SELECT hotel.numhotel, nom, ville, etoiles
FROM hotel
JOIN reservation ON hotel.numhotel = reservation.numhotel;

Pour des raisons historiques et d'efficacité, certains développeurs SQL n'utilisent pas explicitement la
primitive JOIN en SQLite. A la place ils font un produit cartésien qu'ils restreignent par la suite :
SELECT hotel.numhotel, nom, ville, etoiles
FROM hotel, reservation --produit catésien
WHERE hotel.numhotel = reservation.numhotel;

A l'exécution de cette requête, on s'aperçoit que le même hôtel peut revenir plusieurs fois en
résultat : ceci est dû au fait qu'il peut faire l'objet de plusieurs réservations, et comme le produit
cartésien (donc la jointure) est effectué avant la projection, la table intermédiaire obtenue par la
jointure contient autant de fois un même hôtel qu'il y a de réservations dans cet hôtel. Ensuite, la
projection de cette table intermédiaire est faite sur les attributs de la table hotel, mais tous les
tuples, non identiques dans la table intermédiaire (car associant un hôtel à une réservation),
peuvent devenir identiques dans la table résultat puisque ne sont gardés que les attributs de la
table hotel (donc plus de trace des réservations). Pour vous en persuader, ajoutez l'attribut numresa
dans la projection : on s'aperçoit bien que tous les tuples de la table intermédiaire sont distincts les
uns des autres.
Pour éviter ce petit problème, il existe la clause DISTINCT qui permet, après projection, d'éliminer
tous les tuples identiques pour n'en garder qu'un exemplaire de chacun. Notre requête devient
alors :

SELECT DISTINCT hotel.numhotel, nom, ville, etoiles


FROM hotel
JOIN reservation ON hotel.numhotel = reservation.numhotel;

4) Lister les clients ayant déjà occupé (ou en cours d'occupation d') une chambre.

RESULTAT = NATURAL SEMI-JOIN(client, occupation)


SELECT client.numclient, nom, prenom
FROM client
JOIN occupation ON occupation.numclient = client.numclient;

Tout comme dans la précédente nous pouvons rajouter la directive DISTINCT pour ne garder en
résultat qu'un seul exemplaire de chaque client, sinon chaque client revient autant de fois qu'il a
occupé de chambres.

*) Lister les clients n'ayant pas occupé (ou pas en cours d'occupation d') une chambre.

Soit R1 le résultat la question précédente. Il suffit ici de considérer tous les clients de la base, et
d'enlever ceux qui ont déjà occupé une chambre : il reste alors ceux qui n'en ont jamais occupé.

RESULTAT = MINUS(client, R1)


SELECT * FROM client
EXCEPT
SELECT client.numclient, nom, prenom
FROM client
JOIN occupation ON occupation.numclient=client.numclient;

Attention, SQLite ne gère pas les parenthèses de façon optimal, en particulier on ne peut pas avoir
de parenthèses entre les tables des opérateurs ensemblistes (UNION, EXCEPT, INTERSECT).
5) Pour chaque hôtel, indiquer le nombre total de chambres

On groupe le résultat de la jointure entre hotel et chambre par hôtel (numéro, nom et ville). On obtient
alors, pour chaque hôtel, l'ensemble des chambres qui y sont : il suffit alors de compter le nombre
de numchambre, unique à l'intérieur de chaque hôtel.

R1 = NATURAL JOIN(hotel, chambre)


RESULTAT = AGREGAT(R1 ; R1.numhotel, R1.nom, R1.ville ; COUNT(R1.numchambre) )
SELECT hotel.numhotel, nom, ville, COUNT(chambre.numchambre)
FROM hotel
JOIN chambre ON chambre.numhotel = hotel.numhotel
GROUP BY hotel.numhotel, nom, ville;

*) Pour chaque hôtel, quel est le prix moyen de ses chambres ? Le prix maximum ? Le prix
minimum ?

On retrouve pour cette question la relation R1 de la question 9 ; mais la fonction d'agrégat diffère,
et s'applique non pas sur le numéro de la chambre, mais sur le prix de la chambre.

R1 = NATURAL JOIN(hotel, chambre)


RESULTAT = AGREGAT(R1 ; R1.numhotel, R1.nom, R1.ville ; AVG(R1.prixnuitht) )

Pour avoir le minimum (resp. le maximum) du prix, on remplace la fonction de calcul de l'agrégat
par MIN (resp. MAX). Même chose dans la requête SQL.

SELECT hotel.numhotel, nom, ville, AVG(chambre.prixnuitht)


FROM hotel
JOIN chambre ON chambre.numhotel = hotel.numhotel
GROUP BY hotel.numhotel, nom, ville;

6) Quels sont tous ceux qui ont été, ou sont encore, clients de l'hôtel des voyageurs à Nice ?

Pour la première question, il s'agit de repérer les clients dont le numéro est référencé dans une
occupation de chambre d'un hôtel précis. Pour cela, on commence par ne garder que l'hôtel en
question (son numéro suffit: R2) puis on doit faire le lien entre cet hôtel et les occupations qui le
concerne (R3), et à partir de là, on peut faire une jointure pour mettre en relation chaque client
avec la chambre occupée.

R1 = RESTRICT(hotel, nom = 'Hotel des voyageurs' AND ville = 'Nice')


R2 = PROJECT (R1.numhotel)
R3 = NATURAL JOIN(R1, occupation)
R4 = JOIN(client, R3, client.numclient = R3.numclient)
RESULTAT = PROJECT(R4, numclient, nom, prenom)

Il existe, comme souvent, plusieurs façons de traduire cette combinaison d'opérations


algébriques en SQL : on préfère souvent faire les restrictions avant les jointures (car l'ensemble de
tuples à joindre est alors plus petit), ce que nous faisons dans la première solution donnée ci-
dessous. Nous pouvons aussi privilégier la facilité d'écriture, aux dépens de la rapidité d'exécution,
comme le montre la seconde solution donnée (où la restriction est effectuée après la jointure).
Remarquons ici que lorsque l'on utilise le résultat d'une requête comme paramètre d'une jointure, il
est nécessaire de donner un nom à cette sous-requête : ce nom peut ensuite être utilisé pour
lever les ambiguïtés sur les attributs. Plus généralement, chaque fois que le résultat d'une requête
est utilisé comme argument d'une requête englobante, la sous-requête doit être renommée pour
lever toute ambiguïté ; cela se fait en donnant un nom à la suite de le sous-requête.

SELECT client.numclient, nom, prenom


FROM occupation
JOIN ( SELECT hotel.numhotel
FROM hotel
WHERE nom = 'Hotel des voyageurs'
AND ville = 'Nice' ) AS hvoy ON occupation.numhotel = hvoy.numhotel
JOIN client ON client.numclient = occupation.numclient;

L'autre solution (moins rapide à l'exécution) est la suivante :


SELECT client.numclient, client.nom, client.prenom
FROM occupation
JOIN client ON client.numclient = occupation.numclient
JOIN hotel ON hotel.numhotel = occupation.numhotel
WHERE hotel.nom = 'Hotel des voyageurs' AND ville = 'Nice';

Comme dans la question 3, on remarque, à la vue du résultat, qu'un même client peut
apparaître plusieurs fois, et c'est encore pour la même raison. Afin d'éliminer les doublons, il suffit
de spécifier un DISTINCT sur la projection englobante.

7) Combien de chambres triples possède l'Hôtel des ambassadeurs à Grenoble ?

R1 = RESTRICT( hotel, nom = 'Hotel des ambassadeurs' AND ville = 'Grenoble')


R2 = RESTRICT( chambre, type = 'triple')
R3 = JOIN(R1, R2, R1.numhotel = R2.numhotel)
RESULTAT = AGREGAT( R3 ; ; COUNT(R3.numchambre) )
Remarquons que la relation R3 est obtenue par jointure : il s'agit plus exactement d'une jointure
naturelle puisque le seul attribut qu'ont en commun R1 et R2 est numhotel. dans ce cas, il est
préférable de spécifier directement (en algèbre relationnelle) cette jointure naturelle, pour un
soucis de lisibilité. Il est donc préférable d'écrire (même s'il faut ensuite éviter la clause NATURAL
en SQLite, et donc y expliciter la condition de jointure...) :
R3 = NATURAL JOIN(R1, R2)

SELECT COUNT(triple.numchambre)
FROM ( SELECT *
FROM chambre
WHERE type = 'triple' ) AS triple
JOIN ( SELECT *
FROM hotel
WHERE nom = 'Hotel des ambassadeurs'
AND ville = 'Grenoble') AS ambassadeur
ON triple.numhotel = ambassadeur.numhotel;

Pour info, cet hôtel ne dispose que d'une chambre triple.

*) Combien y-a-t-il d'hôtels enregistrés à Bordeaux ?

On groupe selon chaque hôtel restreint à Bordeaux, et on compte dans chaque groupe, le nombre
de numéros d'hôtel trouvés dans ce groupe.

R1 = RESTRICT(hotel, ville = 'Bordeaux')


RESULTAT = AGREGAT(R1 ;; COUNT(numhotel) )
SELECT COUNT(numhotel)
FROM hotel
WHERE ville = 'Bordeaux';

*) Pour chaque chambre de chaque hôtel, compter le nombre de clients y ayant séjourné, ou y
séjournant encore.

On groupe par chambre d'hôtel, on récupère les clients de chaque groupe par jointure, et on
compte le numéro de client dans chaque groupe (un groupe étant une chambre).

R1 = NATURAL JOIN(hotel, chambre)


R2 = NATURAL JOIN(client, occupation)
R3 = PROJECT(R2, numclient, numhotel, numchambre)
R4 = JOIN(R1, R3, R1.numhotel = R3.numhotel)
RESULTAT = AGREGAT( R4 ; nom, ville, numchambre ; COUNT(numclient) )
SELECT nom, ville, numchambre, COUNT(numclient)
FROM hotel
JOIN (SELECT client.numclient, numhotel, numchambre
FROM occupation
JOIN client ON occupation.numclient = client.numclient) AS sejour
ON hotel.numhotel = sejour.numhotel
GROUP BY nom, ville, numchambre;

8) Pour chaque client, lister le nom des hôtels dans lesquels il a séjourné, ou séjourne encore,
avec les dates d'arrivée et de départ correspondant à chaque séjour.

Il faut ici faire attention aux jointures naturelles car deux attributs ont le même identificateur mais
n'ont rien à voir : le nom de l'hôtel et le nom du client. Pour lever l'ambiguïté, nous précisons d'où
vient l'attribut en le préfixant par une variable liée à la table correspondante. Dans la requête SQL,
lever cette ambiguïté est plus aisé, puisque nous devons renommer les tables intermédiaires qui
sont arguments de jointure.

R1 = NATURAL JOIN(occupation, client C)


R2 = JOIN( R1, hotel H, R1.numhotel = H.numhotel)
-- ici deux colonnes de même nom existent, d'où les renommages : C.nom et H.nom
RESULTAT = PROJECT(R2, C.nom, prenom, H.nom, ville, datearrivee, datedepart)

SELECT sejour.nom, prenom, hotel.nom, hotel.ville, datearrivee, datedepart


FROM hotel
JOIN ( SELECT *
FROM occupation
JOIN client ON occupation.numclient=client.numclient ) AS sejour
ON hotel.numhotel = sejour.numhotel ;

*) Existe-t-il plusieurs hôtels portant le même nom, et si oui, lesquels ?

On doit faire une jointure entre deux exemplaires de la même relation.

R1 = JOIN(hotel H1, hotel H2, H1.nom = H2.nom)


R2 = RESTRICT(R1, H1.numhotel <> H2.numhotel)
RESULTAT = PROJECT(R2, H1.nom, H1.ville, H2.ville)
Ici, nous ne faisons pas une jointure naturelle, puisque les deux relations de la jointure ont les
mêmes attributs : on spécifie donc la condition de jointure (sur le nom de l'hôtel).
SELECT h1.nom, h1.ville, h2.ville
FROM hotel h1
JOIN hotel h2 ON h1.nom = h2.nom
WHERE h1.numhotel!= h2.numhotel;

9) Lister les numéros de chambres disponibles dans l'hôtel de la gare, à Bordeaux, pour les nuits
du 12 avril 2014 au 17 avril 2014 .

Nous devons ici répertorier les chambres qui ne sont ni occupées ni réservées pendant une
période considérée. On répertorie donc toutes les chambres occupées -- ou réservées -- pendant
cette période, et on retire ces dernières de la liste de toutes les chambres de l'hôtel de la base de
données.
-- ci-dessous, on se restreint à l'hôtel de la gare de Bordeaux
R1 = RESTRICT( hotel, nom = 'Hotel de la gare' AND ville = 'Bordeaux' )
-- ci-dessous, on répertorie toutes les occupations de cet hôtel
R2 = NATURAL JOIN( R1, occupation)
-- ci-dessous, on répertorie toutes les réservations de cet hôtel
R'2 = NATURAL JOIN( R1, reservation)
-- ci-dessous, on sélectionne toutes les occupations de l'hôtel pendant la période considérée
R3 = RESTRICT ( R2, (17/04/2014 >= datedepart > 12/04/2014) OR (17/04/2014 > datearrivee >= 12/04/2014)
OR ( datearrivee <= 12/04/2014 AND (datedepart >= 17/04/2014 OR datedepart IS NULL ) ) )
R'3 = PROJECT (R3, numhotel, numchambre)
-- ci-dessous, on sélectionne toutes les réservations de l'hôtel pendant la période considérée
R4 = RESTRICT ( R'2, (17/04/2014 >= datedepart > 12/04/2014) OR (17/04/2014 > datearrivee >= 12/04/2014)
OR ( datearrivee <= 12/04/2014 AND (datedepart >= 17/04/2014 OR datedepart IS NULL ) ) )
R'4 = PROJECT (R4, numhotel, numchambre)
-- ci dessous, on fait de l'union de touotes les chambres occupées ou réservées pendant la période
R5 = UNION(R'3, R'4) -- on s'assure que les schémas de R'3 et R'4 sont identiques
-- ci-dessous, on garde toutes les chambres de l'hôtel sans critère de date
R6 = NATURAL JOIN( R1, chambre )
R'6 = PROJECT ( R6, numhotel, numchambre )
-- ci-dessous, on retire de toutes les occupations et résa celles qui sont pendant la période considérée
R7 = MINUS (R'6, R5) -- on s'assure que les schémas de R'6 et R5 sont identiques
-- On projette sur le numéro de la chambre
RESULTAT = PROJECT( R7, numchambre )

SELECT hgare.numhotel, chambre.numchambre -- R'6


FROM ( SELECT hotel.numhotel -- R1
FROM hotel
WHERE nom='Hotel de la gare' AND ville = 'Bordeaux' ) AS hgare
JOIN chambre ON hgare.numhotel = chambre.numhotel
EXCEPT -- R7
SELECT * FROM
(SELECT hgare.numhotel, chambre.numchambre -- R'3
FROM ( SELECT hotel.numhotel -- R1
FROM hotel
WHERE nom='Hotel de la gare' AND ville = 'Bordeaux' ) AS hgare
JOIN chambre ON hgare.numhotel = chambre.numhotel
JOIN ( SELECT occupation.numhotel, occupation.numchambre
FROM occupation
WHERE ( datearrivee >= DATE('2014-04-12')
AND datearrivee < DATE('2014-04-17') )
OR ( datedepart > DATE('2014-04-12')
AND datedepart <= DATE('2014-04-17') )
OR ( ( datedepart >= DATE('2014-04-17')
OR datedepart IS NULL )
AND datearrivee <= DATE('2014-04-12') ) )
AS occupees
ON hgare.numhotel = occupees.numhotel
AND chambre.numchambre = occupees.numchambre
UNION
SELECT hgare.numhotel, chambre.numchambre -- R'4
FROM ( SELECT hotel.numhotel
FROM hotel
WHERE nom='Hotel de la gare' AND ville = 'Bordeaux' ) AS hgare
JOIN chambre ON hgare.numhotel = chambre.numhotel
JOIN ( SELECT reservation.numhotel, reservation.numchambre
FROM reservation
WHERE ( datearrivee >= DATE('2014-04-12')
AND datearrivee < DATE('2014-04-17') )
OR ( datedepart > DATE('2014-04-12')
AND datedepart <= DATE('2014-04-17') )
OR ( ( datedepart >= DATE('2014-04-17')
OR datedepart IS NULL )
AND datearrivee <= DATE('2014-04-12') ) )
AS reservees
ON ( hgare.numhotel = reservees.numhotel
AND chambre.numchambre = reservees.numchambre)
);
10) Si la TVA est de 19.6%, quel sera le montant de chaque séjour que le client Jean Némarre a
eu à régler à la date du 02/04/2014 (on ne compte donc pas un éventuel séjour actuel non
terminé) ?

Il faut répertorier les séjour du client précisé. Nous commençons par repérer le client en question
(dans R1). puis nous passons par la relation occupation pour récupérer toutes les chambres que ce
client a occupées avant le 02/04/2014 (R2 puis R3), et nous passons enfin par la relation chambre
pour obtenir le prix de la chambre occupée dans le séjour concerné (jointure R4), et enfin nous
projetons le résultat obtenu par hôtel, chambre, client, dates d'arrivée et de départ (tout cela
constitue un "séjour"), et nous faisons un calcul sur ces attributs pour calculer le montant de la
facture TTC (le prix des chambres est HT).

R1 = PROJECT ( RESTRICT(client, nom='Némarre' AND prenom = 'Jean'), numclient )


R2 = NATURAL JOIN ( R1, occupation )
R3 = RESTRICT ( R2, datedepart IS NOT NULL AND datedepart <= 02/04/2014 )
R4 = NATURAL JOIN ( R3, chambre )
RESULTAT = PROJECT (R4, R4.numhotel, R4.numchambre, R4.numclient,
R4.datearrivee, R4.datedepart,
(R4.prixnuitht + R4.prixnuitht*0,196)*(R4.datedepart-R4.datearrivee))

SELECT sejour.numhotel, sejour.numchambre, nemarre.numclient,


sejour.datearrivee, sejour.datedepart,
chambre.prixnuitht*1.196*(julianday(datedepart)-julianday(datearrivee))
AS prix
FROM ( SELECT numclient
FROM client
WHERE nom='Nemarre' AND prenom='Jean' )AS nemarre
JOIN ( SELECT *
FROM occupation
WHERE datedepart IS NOT NULL
AND datedepart <= DATE('2014-04-02')
) AS sejour
ON sejour.numclient = nemarre.numclient
JOIN chambre ON ( sejour.numchambre = chambre.numchambre
AND sejour.numhotel = chambre.numhotel );

11) Quelles sont les chambres (hôtel et numéro) qui ont été occupées plus de 40 jours ? Quels
sont les clients qui ont occupé toutes ces chambres ?

Il suffit de compter, pour chaque chambre jointe avec les occupations de cette chambre, le nombre
de jours d'occupation en sommant sur la différence départ-arrivée de chaque séjour. On ne garde
alors que ces chambres dont le nombre de jours calculés en R2 dépasse 40.

R1 = PROJECT ( occupation, numchambre, numhotel, (datedepart - datearrivee) nbj )


R2 = AGREGAT( R1 ; numchambre, numhotel ; SUM(nbj) totaljours )
RESULTAT = RESTRICT( R2, totaljours >=40 )

SELECT numhotel, numchambre, totaljours


FROM ( SELECT numhotel, numchambre, SUM(nbj) AS totaljours
FROM ( SELECT numhotel, numchambre,
julianday(datedepart)-julianday(datearrivee) AS nbj
FROM occupation ) AS sejour
GROUP BY numhotel, numchambre
) AS sejourtotal
WHERE totaljours >= 40;

Pour la seconde question, il suffit de réaliser une jointure entre le résultat de la requête précédente
et les clients, via les occupations enregistrées. On utilise ainsi deux fois la même relation
occupation, mais pour des objectifs différents (pour repérer les chambres très utilisées d'une part, et,
ensuite, pour repérer les clients qui ont séjourné dans ces chambres, même pour une très courte
durée.
R1 = PROJECT ( occupation, numchambre, numhotel, (datedepart - datearrivee) nbj )
R2 = AGREGAT( R1 ; numchambre, numhotel, numclient ; SUM(nbj) totaljours )
R3 = RESTRICT( R2, totaljours >=40 )
R4 = NATURAL JOIN( client, occupation)
R5 = NATURAL JOIN(R4, R3)
RESULTAT = PROJECT(R5, numclient, nom, prenom)

Ce qui donne, en SQL (et en reprenant la requête donnée précédente pour répondre à la première
partie de la question) :
SELECT cli.numclient, cli.nom, cli.prenom
FROM ( SELECT numhotel, numchambre, SUM(nbj) AS totaljours
FROM ( SELECT numhotel, numchambre,
julianday(datedepart)-julianday(datearrivee) AS nbj
FROM occupation
) AS sejour
GROUP BY numhotel, numchambre
) AS sejourtotal
JOIN ( SELECT client.numclient, nom, prenom, numchambre, numhotel
FROM client
JOIN occupation ON occupation.numclient = client.numclient) AS cli
ON (cli.numchambre = sejourtotal.numchambre
AND cli.numhotel = sejourtotal.numhotel )
WHERE totaljours >= 40;

*) Quels sont les clients (nom et prénom) qui ont toujours séjourné au premier étage ?

Pour répondre à ce type de question où apparaît un « toujours » (et donc, un « jamais » sur une
condition opposée), l'opérateur de différence est le plus pratique (comme dans la question 8) : on
répond à la question « quels sont les clients qui ont occupé une chambre ailleurs qu'au premier
étage », et on enlève ceux-là de la liste de tous les clients ayant déjà occupé une chambre.

R1 = NATURAL JOIN (client, occupation)


R2 = RESTRICT (chambre, etage <> 1)
R3 = NATURAL JOIN (R1, R2)
R4 = PROJECT (R3, numclient, nom, prenom)
R5 = PROJECT (R1, numclient, nom, prenom)
RESULTAT = MINUS( R5, R4 )
SELECT client.numclient, nom, prenom
FROM client
JOIN occupation ON occupation.numclient = client.numclient
EXCEPT
SELECT client.numclient, nom, prenom
FROM ( SELECT numclient
FROM occupation
JOIN ( SELECT numchambre, numhotel
FROM chambre
WHERE etage!= 1 ) AS etageeleve
ON (occupation.numchambre = etageeleve.numchambre
AND occupation.numhotel = etageeleve.numhotel )
) AS clientsoccupants
JOIN client ON client.numclient = clientsoccupants.numclient;

Concernant cette question comme pour beaucoup d'autres, il est possible d'y répondre sans
passer par des requêtes imbriquées, mais c'est alors moins efficace à l'exécution (même si,
dans le TP, cela n'est pas trop visible car la base de données GestionHotel n'est pas grande). Par
exemple, pour cette dernière question, nous pouvons écrire (c'est le second terme de la différence
qui change) :
SELECT client.numclient, nom, prenom
FROM client
JOIN occupation ON occupation.numclient = client.numclient
EXCEPT
SELECT client.numclient, nom, prenom
FROM occupation
JOIN chambre ON ( occupation.numchambre = chambre.numchambre
AND occupation.numhotel = chambre.numhotel )
JOIN client ON client.numclient = occupation.numclient
WHERE etage!= 1;

Cette expression de requête est peut-être plus simple à lire, est correcte, mais encore une fois,
dans le cadre de grandes bases de données, elle ne s'avère guère efficace car la restriction sur
l'étage de la chambre est faite après la jointure, alors que la faire avant (comme dans l'expression
précédente) restreint considérablement le nombre de tuples à joindre. Mais pour faire les
restrictions le plus en aval possible, il faut imbriquer des requêtes.
Faisons une remarque générale sur les ambiguïtés d'attributs, à partir de l'exemple de cette
requête. Travaillons pour cela sur la première des requêtes SQL précédentes pour répondre à
cette question. Par exemple dans le premier SELECT, nous projetons typiquement la relation
obtenue sur les attributs de la relation client. Mais, dans le cas de l'attribut numclient, si nous
omettons de le préfixer par la relation client (ou par la relation occupation...), l'interpréteur SQL nous
gratifie d'une erreur peu ambigüe : ERROR: column reference "numclient" is
ambiguous. Or, nous pourrions penser que, dans la mesure où nous faisons une jointure sur
l'égalité des colonnes numclient de client et d'occupation, il n'y a plus d'ambiguïté sur la provenance de
cet attribut. En réalité, l'interpréteur ne va pas si loin dans l'unification des noms d'attributs : il est
donc nécessaire, même en cas de jointure naturelle, de préciser de quelle relation vient l'attribut à
partir du moment où plusieurs relations de la même expression algébrique, dans le même espace
de noms, possèdent un attribut de même nom.

Vous aimerez peut-être aussi