- L3 MIASHS/Ingémath
- Université Paris Cité
- Année 2023-2024
- Course Homepage
- Moodle
Toutes les questions portent sur le schéma world
rappelé ci-dessous.
world
Les territoires qui sont inscrits dans la table country
ne sont pas tous des pays, et pour certaines lignes, population_country
, gnp
, governmentform
, … ne sont pas renseignés.
Pour chaque question, proposer une requête écrite en algèbre relationnelle ou en SQL
.
- Donner pour chaque pays, chaque district, la population totale qui réside dans des villes répertoriées dans
city
.
Un district
est une unité territoriale incluse dans un territoire. Pour la France, les district
correspondent aux régions, pour l’Allemagne aux Laender, …
SELECT DISTINCT ci.district
FROM world.city ci
WHERE ci.countrycode='FRA' ;
Comme on doit sommer les populations des villes situées dans un district (et donc dans un pays), il faut partitionner city
par district et pays.
2SELECT ci.countrycode, ci.district,
3SUM(ci.population) as pop
FROM world.city ci
1GROUP BY ci.countrycode, ci.district
- 1
-
Deux colonnes pour le partitionnement (même si
countrycode
est peut-être déterminé pardistrict
) - 2
- On ne peut projeter que sur les colonnes utilisées pour partitionner et …
- 3
- Sur des colonnes agrégées.
Après partition/aggrégation, la table résultat contient une ligne pour chaque combinaison des attributs/colonnes servant au partitionnement. Sur chaque ligne du résultat, on peut faire figurer des aggrégations.
Si on veut disposer des totaux par pays en plus, on peut utiliser la construction GROUP BY ROLLUP(...)
SELECT ci.countrycode, ci.district,
SUM(ci.population) as pop
FROM world.city ci
GROUP BY ROLLUP(ci.countrycode, ci.district)
ORDER BY ci.countrycode, ci.district
LIMIT 20 ;
On peut chercher à imiter le résultat de GROUP BY ROLLUP
avec les constructions vues en cours.
WITH r AS (
SELECT ci.countrycode, ci.district,
SUM(ci.population) as pop
FROM world.city ci
GROUP BY ci.countrycode, ci.district
AS (
), s SELECT r.countrycode, NULL AS district, sum(r.pop_district) AS pop
FROM r
GROUP BY r.countrycode
)
SELECT * FROM s)
(UNION
SELECT * FROM r)
(ORDER BY countrycode, district ;
La construction GROUP BY ROLLUP (...)
ne permet pas définir des requêtes qui seraient hors de portée de GROUP BY ...
, elle permet une écriture plus concise et plus claire.
- Donner pour chaque pays le nombre d’habitants qui ne parlent pas une langue officielle. On suppose ici que chaque habitant ne parle qu’une seule langue.
1WITH cc_pop_not_official AS (
SELECT countrycode, SUM(percentage) as percentage
FROM world.countrylanguage
WHERE NOT(isofficial)
GROUP BY countrycode
)
SELECT name_country, (population_country * percentage / 100)::INTEGER
2FROM world.country NATURAL JOIN cc_pop_not_official;
- 1
-
La requête intermédiaire
cc_pop_not_official
nous donne pour chaque pays le pourcentage de locuteurs des langues non-officielles du pays - 2
-
Grâce à une jounture avec
country
, on peut combiner ce pourcentage avec la population du pays.
On peut chercher à vérifier la convention avancée dans l’énoncé:
SELECT cl.countrycode, SUM(cl.percentage) AS total_percent
FROM world.countrylanguage cl
GROUP BY cl.countrycode
HAVING SUM(cl.percentage) > 100
ORDER BY total_percent DESC
LIMIT 10 ;
- Donner, pour chaque région ayant au moins 50 millions d’habitants, le ou les pays les moins peuplés de la région.
WITH r_minpop_g50M AS (
SELECT region, MIN(population_country) AS population_country
FROM world.country
GROUP BY region
HAVING SUM(population_country) >= 50000000)
SELECT co.region, co.name_country
FROM world.country co NATURAL JOIN r_minpop_g50M ;
Ne pas confondre les régions ayant au moins 50 millions d’habitants et les régions où au moins un pays a plus de 50 millions d’habitants.
- Liste des pays où la langue la plus parlée n’est pas officielle
WITH r AS (
SELECT cl.*,
co.name_country, rank() OVER w AS rang
FROM world.countrylanguage cl NATURAL JOIN
world.country coAS (PARTITION BY cl.countrycode ORDER BY cl.percentage DESC)
WINDOW w
)
SELECT DISTINCT r.name_country
FROM r
WHERE r.rang=1 AND NOT r.isofficial ;
L’usage d’une fonction fenêtre est commode, mais c’est un marteau-pilon pour casser un œuf.
Alternatives sans fonction fenêtre :
WITH langmax AS (
SELECT countrycode,
MAX(percentage) AS percentage
FROM world.countrylanguage
GROUP BY countrycode)
SELECT DISTINCT name_country
FROM world.country NATURAL JOIN
NATURAL JOIN
world.countrylanguage
langmaxWHERE NOT(isofficial);
SELECT DISTINCT cl.countrycode
FROM world.countrylanguage cl
WHERE NOT cl.isofficial AND
NOT EXISTS (
SELECT *
FROM world.countrylanguage cl1
WHERE cl1.isofficial AND
= cl.countrycode
cl1.countrycode > cl.percentage
cl1.percentage ) ;
Une erreur fréquente consiste à lister les pays où il existe une langue officielle qui est moins parlée qu’une langue non-officelle. Ce n’est pas la même chose que la liste des pays où toutes les langues officielles sont moins parlées qu’une certaine langue non-officiele
\[\begin{array}{rl}\left\{ t : \texttt{country}(t) \right. & \wedge \exists s, s \in \texttt{countrylanguage} \wedge \neg s.\texttt{isofficial} \wedge \\ & \left. \phantom{\wedge}\forall u \neg \left(\texttt{countrylanguage}(u) \wedge u.\texttt{isofficial} \wedge u.\texttt{percentage} > s.\texttt{percentage} \right) \right\} \end{array}\]
Une autre erreur fréquente consiste à ne lister que les pays où il existe une langue non-officielle parlée par au moins la moitié de la population:
SELECT cl.countrycode
FROM world.countrylanguage cl
WHERE cl.percentage > 50 AND NOT c.isofficial ;
Il est très possible que dans un pays, aucune langue ne soit parlée par la moitié de la population, que toutes les langues soient minoritaires, et que la langue la plus parlée ne soit pas officielle.
- Liste des pays où aucune ville ne compte plus \(3\) millions d’habitants.
WITH r AS (
SELECT ci.countrycode,
SUM(CAST (ci.population > 3000000 AS INTEGER)) AS nb
FROM world.city ci
GROUP BY ci.countrycode
1
)
SELECT co.name_country, co.continent
FROM world.country co LEFT JOIN
ON (co.countrycode = r.countrycode)
r WHERE r.nb IS NULL OR r.nb = 0
2ORDER BY co.continent, co.name_country;
- 1
-
La requête
r
collecte les codes de pays et pour chaque pays le nombre de villes de plus de \(3000000\) d’habitants. - 2
-
En effectuant la jointure externe entre
country
etr
, on récupère les territoires sans villes et les territoires où toutes les villes comptent moins de \(3000000\) d’habitants.
On a ajouté continent
parmi les colonnes de projection pour rendre plus lisibles les résultats.
Alternative utilisant EXCEPT
:
WITH cc_no_big_city AS (
SELECT countrycode
(FROM world.country)
EXCEPT
SELECT countrycode
(FROM world.city
WHERE population > 3000000))
SELECT name_country, continent
FROM world.country NATURAL JOIN cc_no_big_city
ORDER BY continent, name_country;
Cette requête donne le même résultat que la précédente.
Alternative utilisant NOT IN
:
SELECT name_country, continent
FROM world.country
WHERE countrycode NOT IN (
SELECT countrycode
FROM world.city
WHERE population > 3000000)
ORDER BY continent, name_country;
Cette requête donne encore le même résultat que la précédente.
Traduction quasi-litérale de la question en formule du calcul des tuples: \[\left\{ t :\texttt{country}(t) \wedge \left(\not \exists s \quad \texttt{city}(s) \wedge s.\texttt{countrycode}=t.\texttt{countrycode} \wedge s.\texttt{population} > 3000000 \right)\right\}\]. Cette formule se traduit presque mécaniquement avec une requête imbriquée
SELECT co.*
FROM world.country co
WHERE NOT EXISTS (
SELECT *
FROM world.city ci
WHERE ci.countrycode=co.countrycode AND ci.population > 3000000) ;
on peut aussi réécrire la formule en \[\texttt{country} \setminus \left\{ t : \texttt{country}(t) \wedge \left( \exists s \quad \texttt{city}(s) \wedge s.\texttt{countrycode}=t.\texttt{countrycode} \wedge s.\texttt{population} > 3000000 \right)\right\}\]
- Liste des formes de gouvernement (governmentform) pour lesquelles dans tous les pays possédant cette forme de gouvernement, aucune langue n’est officielle.
WITH r AS (
SELECT DISTINCT cl.countrycode
FROM world.countrylanguage cl
WHERE cl.isofficial
AS (
), s
SELECT DISTINCT co.governmentform
FROM world.country co
WHERE co.countrycode IN (
SELECT r.countrycode FROM r)
)
SELECT DISTINCT co.governmentform
FROM world.country co
EXCEPT
SELECT s.governmentform
FROM s ;
Alternative utilisant EXCEPT
:
SELECT governmentform
(FROM world.country)
EXCEPT
SELECT governmentform
(FROM world.country NATURAL JOIN world.countrylanguage
WHERE isofficial);
Alternative avec partition, agrégation
SELECT co.governmentform
FROM world.country co LEFT join
ON (co.countrycode=cl.countrycode)
world.countrylanguage cl GROUP BY co.governmentform
HAVING SUM(CAST(cl.isofficial AS INTEGER))=0 OR
SUM(CAST(cl.isofficial AS INTEGER)) IS NULL;
Si on oublie la condition SUM(CAST(cl.isofficial AS INTEGER)) IS NULL
, on ne retrouve pas Co-administrated
qui concerne des territoires qui ne sont pas mentionnés dans la table countrylanguage
.
- Donner pour chaque région, le minimum du PIB par habitant (c’est-à-dire le résultat de la division
1000000 * gnp / population_country
, puisque le PIB est donné en millions) dans la région, un des pays (soncountrycode
) de la région où ce minimum est réalisé, le maximum du PIB par habitant et un des pays (soncountrycode
) où ce maximum est réalisé.
WITH r AS (
SELECT co.region, co.countrycode, co.name_country,
1000000*co.gnp/co.population_country AS gnp_per_cap,
row_number() OVER w_max AS rang_max,
row_number() OVER w_min AS rang_min
FROM world.country co
WHERE co.population_country IS NOT NULL AND co.population_country >0 AND co.gnp IS NOT NULL
WINDOW AS (PARTITION BY co.region ORDER BY co.gnp/co.population_country DESC),
w_max AS (PARTITION BY co.region ORDER BY co.gnp/co.population_country)
w_min AS (
), r1 SELECT r.*
FROM r
WHERE r.rang_max=1
AS (
), r2 SELECT r.*
FROM r
WHERE r.rang_min=1
)
SELECT r1.region, r1.countrycode, r1.gnp_per_cap,
r2.countrycode, r2.gnp_per_capFROM r1 JOIN r2 ON (r1.region=r2.region) ;
Alternative sans fonction fenêtre :
WITH cc_gpc AS (
SELECT countrycode, 1000000 * gnp/population_country AS gnp_per_cap
FROM world.country
WHERE population_country > 0),
AS (
region_mingpc_maxgpc SELECT region, MIN(gnp_per_cap) AS mingpc, MAX(gnp_per_cap) AS maxgpc
FROM world.country NATURAL JOIN cc_gpc
GROUP BY region),
AS (
region_cc_mingpc SELECT region, MIN(c1.countrycode) AS countrycode, c1.gnp_per_cap
FROM world.country c NATURAL JOIN region_mingpc_maxgpc r
JOIN cc_gpc c1 ON c.countrycode = c1.countrycode
AND r.mingpc = c1.gnp_per_cap
GROUP BY region, c1.gnp_per_cap),
AS (
region_cc_maxgpc SELECT region, MIN(c1.countrycode) AS countrycode, c1.gnp_per_cap
FROM world.country c NATURAL JOIN region_mingpc_maxgpc r
JOIN cc_gpc c1 ON c.countrycode = c1.countrycode
AND r.maxgpc = c1.gnp_per_cap
GROUP BY region, c1.gnp_per_cap)
SELECT *
FROM region_cc_mingpc JOIN region_cc_maxgpc
USING (region);
Avec une table foo
de schéma (col1, col2, col3, col4)
, la requête suivante n’est pas correcte :
SELECT col1, col2, SUM(col4) as bar
FROM foo
GROUP BY col1, col3 ;
Les colonnes qui figurent dans la clause de projection SELECT ...
doivent
- figurer dans la clause de partitionnement
GROUP BY ...
, icicol1
etcol3
- représenter des agrégations, comme ici
SUM(col4)
La colonne col2
ne peut pas figurer dans la clause de projection !
Dans une clause WHERE
ou une condition de jointure, lorsqu’on compare deux colonnes, les types des deux colonnes doivent être compatibles (en général identiques).
Des conditions comme language=isofficial
ou governmentform=countrycode
n’ont pas de sens.