TD 4: SQL
Aggrégations, Partitions, Fenêtres
L3 MIASHS/Ingémath |
Année 2024 |
Documentation Postgres en Français
Fonctions d’agrégation
Les fonctions d’agrégation permettent d’effectuer des opérations avancées sur les solutions d’une requête (sur une table) comme : compter les lignes, sélectionner le maximum dans une colonne, etc.
Une des opérations les plus courantes est de compter. COUNT(col)
permet de compter les résultats d’une requête.
Count
Pour compter les pays en Europe, on écrira :
SELECT COUNT(countrycode)
FROM world.country
WHERE continent='Europe';
Cette requête renvoie une table ayant une ligne et une colonne contenant le nombre de lignes dans le résultat de la requête.
GROUP BY
Admettons qu’on veuille compter les pays par continent. On serait tenté d’écrire :
SELECT
COUNT(countrycode)
continent, FROM
world.country;
Cependant, cette requête ne fonctionnera pas en SQL. SQL est incapable de deviner comment regrouper les différentes lignes pour compter. On doit lui spécifier clairement cela avec la clause GROUP BY
:
SELECT
COUNT(countrycode)
continent, FROM
world.countryGROUP BY
continent;
Cette requête regroupe les lignes de la table country
par modalité de la colonne continent
et pour chaque groupe compte le nombre de countrycode
y apparaissant. Lorsque plusieurs lignes sont susceptibles d’avoir la même valeur, on peut compter seulement le nombre d’occurences distinctes avec COUNT(DISTINCT col)
.
Écrire une requête qui compte le nombre de langues parlées dans chaque pays.
SELECT
COUNT(language)
countrycode, FROM
world.countrylanguageGROUP BY
countrycode;
Écrire une requête qui compte le nombre de langues parlées dans le monde.
SELECT
COUNT(DISTINCT language)
FROM
world.countrylanguage;
Écrire une requête qui compte le nombre de langues officielles par pays.
Une solution presque bonne :
SELECT countrycode, COUNT(language)
FROM world.countrylanguage
WHERE isofficial
GROUP BY countrycode;
Cependant, on rate les pays qui ne possèdent pas de langue officielle. On va utiliser une superbe jointure extérieure:
SELECT P.countrycode, COUNT(language)
FROM
as P
world.country LEFT JOIN
as L
world.countrylanguage ON (P.countrycode = L.countrycode and L.isofficial)
GROUP BY P.countrycode;
Sum, Max, Min, Avg
Une autre fonction importante est la fonction SUM(col)
qui effectue la somme des valeurs (numériques) d’une colonne :
SELECT SUM(population_country)
FROM world.country;
renvoie la population mondiale.
On peut de même utiliser GROUP BY
pour faire des paquets :
SELECT
SUM(population_country)
continent, FROM
world.countryGROUP BY continent;
renvoie la population de chaque continent.
On peut même faire des opérations sur la colonne à l’intérieur de SUM
. Par exemple: SUM(percentage/100)
.
Écrire une requête qui renvoie le nombre de langues officielles par pays
WITH s AS(
SELECT L.countrycode, sum(CAST (isofficial AS INTEGER)) AS n_official
FROM world.countrylanguage as L
GROUP BY L.countrycode)
SELECT s.*, c.name_country
FROM world.country c NATURAL JOIN s
ORDER BY s.n_official DESC, s.countrycode;
Requêtes (I)
Écrire une requête qui renvoie la surface de chaque région.
SELECT region, SUM(surfacearea)
FROM world.country
GROUP BY region;
Écrire une requête qui compte le nombre de francophones dans le monde.
SELECT
SUM((percentage/100)*population_country)
FROM
world.country NATURAL JOIN
world.countrylanguageWHERE
= 'French'; language
On peut utiliser de la même façon la fonction MIN
(resp. MAX
) qui renvoie la plus petite (resp. grande) valeur ou AVG
qui renvoie la moyenne.
Combien de personnes vivent dans une capitale européenne ?
SELECT
SUM(ci.population)
FROM
world.country co JOIN
ON co.capital=ci.id
world.city ci WHERE
='Europe'; co.continent
Quelle est la capitale européenne la moins peuplée ?
WITH r as (
select ci.name, ci.population as s
FROM
world.city ci JOIN
ON co.capital = ci.id
world.country co WHERE
='Europe'
co.continent
)SELECT
r.nameFROM
r WHERE r.s = (
SELECT min(r.s)
FROM r)
;
Quelle est la langue la plus parlée dans le monde ?
WITH R AS (
SELECT language, SUM((percentage/100)*population_country) as s
FROM
world.countryNATURAL JOIN
world.countrylanguageGROUP BY language
)
SELECT
language FROM
R WHERE s = (
SELECT MAX(s)
FROM R
);
ou plus simplement
SELECT
SUM((percentage/100)*population_country) as s
language, FROM
world.countryNATURAL JOIN
world.countrylanguageGROUP BY language
ORDER BY s DESC
LIMIT 1 ;
Having
Parfois, on veut filtrer les requêtes en fonction du résultat d’une fonction d’agrégation.
Par exemple, pour connaître les langues officielles dans plus de 10 pays, on serait tenté d’écrire :
SELECT
language FROM
world.countrylanguageWHERE
COUNT(countrycode) > 10 AND isofficial
GROUP BY language;
Cela ne fonctionne pas. WHERE
applique une condition sur chaque ligne de la table pour les filtrer, par exemple, garder seulement les langues officielles. Ici, on veut ensuite sélectionner les lignes après avoir regroupé par langue et compté.
On utilisera alors HAVING
, après la clause GROUP BY
:
SELECT
5
languageFROM
1
world.countrylanguageWHERE
2
isofficial3GROUP BY language
HAVING
4COUNT(countrycode) > 10;
- 1
-
La requête concerne la table
world.countrylanguage
- 2
- On filtre les lignes qui correspondent à des langues officielles
- 3
- On groupe/partitionne la table filtrée selon la langue
- 4
- On ne garde que les groupes comportant au moins 10 tuples
- 5
-
On projette le résultat sur la colonne
language
Requêtes (II)
- Écrire une requête qui renvoie le nombre de pays par régime.
SELECT
AS regime, COUNT(countrycode) AS nombre
governmentform FROM
world.countryGROUP BY governmentform
ORDER BY governmentform ;
Écrire une requête calculant le nombre de personnes vivant dans des villes de plus d’un million d’habitants.
SELECT
SUM(population) AS pop
FROM
world.cityWHERE
>= 1000000 ; population
Écrire une requête qui calcule le nombre total de personnes vivants dans des villes qui ne sont pas listées dans la table city
. (Indice : comparer la population du pays avec la somme sur les villes).
WITH
AS (
pop_villes SELECT
SUM(population) AS pop
FROM
world.city),AS (
pop_totale SELECT
SUM(population_country) AS pop
FROM
world.country)
SELECT
- pop_villes.pop
pop_totale.pop FROM
pop_totale, pop_villes ;
Réponse : (4,649,189,566)
Écrire une requête qui compte le nombre moyen de langues parlées par pays dans chaque région.
WITH nb_langues_parlees AS (
SELECT
COUNT(language) nb
countrycode, FROM
world.countrylanguageGROUP BY countrycode
)
SELECT
SUM(nb)/COUNT(DISTINCT countrycode)
region, FROM
world.country coNATURAL JOIN
nb_langues_parleesGROUP BY region ;
Avec la fonction AVG()
WITH nb_langues_parlees AS (
SELECT
COUNT(language) nb
countrycode, FROM
world.countrylanguageGROUP BY countrycode
)SELECT
AVG(nb)
region, FROM
world.country co NATURAL JOIN
nb_langues_parleesGROUP BY region ;
Écrire une requête qui donne la liste des pays ayant deux langues officielles parlées par plus de la moitié de la population.
Pas besoin d’agrégation à cet endroit là.
WITH R AS (
SELECT *
FROM
countrylanguageWHERE
AND percentage>=25
isofficial
)
SELECT
R1.countrycode, R1.language, R2.languageFROM
AS R1
R JOIN
AS R2 ON (
R =R2.countrycode AND
R1.countrycode< R2.language
R1.language ) ;
Écrire une fonction plus_peuplee(p_name_country text)
qui, étant donné le nom d’un pays, renvoie le nom de la ville la plus peuplée de ce pays. (schéma : world
)
CREATE FUNCTION public.plus_peuplee(p_name_country text)
RETURNS text AS $$
LANGUAGE sql WITH
AS (
R SELECT
co.countrycodeFROM
world.country coWHERE
=p_name_country
co.name_country
), AS (
M SELECT
MAX(population) AS m_pop
FROM
world.city ciWHERE
IN (
ci.countrycode SELECT
countrycodeFROM
R
)
)
SELECT
ci.name FROM
world.city ciWHERE
IN (
ci.countrycode SELECT
countrycodeFROM
RAND
) >= (
ci.population SELECT
m_popFROM
M
) ; $$ ;
Écrire une fonction langues_region(p_continent text)
qui étant donné le nom d’un continent, renvoie le nombre moyen de langues parlées par pays dans chaque région (schéma : world
). L’entête de cette fonction doit être :
FUNCTION langues_region(p_continent TEXT)
TABLE(region TEXT, nbmoy NUMERIC) RETURNS
CREATE FUNCTION public.langues_region(p_continent TEXT)
TABLE(region TEXT, nbmoy NUMERIC)
RETURNS AS $$
LANGUAGE SQL WITH S AS (
SELECT
COUNT(cl.language) AS n_l
R.region, cl.countrycode, FROM
(SELECT
co.region, co.countrycodeFROM
world.country coWHERE
= 'Asia'
co.continent AS R
) JOIN
USING (countrycode)
world.countrylanguage cl GROUP BY
R.region, cl.countrycode
)
SELECT
AVG(n_l) AS nbmoy
region, FROM
SGROUP BY
region ; $$ ;
Ecrire une fonction actor_category(p_nom text, p_prenom text)
qui prend en argument le nom et le prénom d’un acteur (d’une actrice) et renvoie la liste des noms des catégories de films dans lesquels il/elle a joué (schéma : pagila
).
CREATE FUNCTION public.actor_category(p_nom text, p_prenom text)
RETURNS text AS
LANGUAGE SQL
$$SELECT
DISTINCT pc.name, ', ')
string_agg(FROM
SELECT
(
actor_idFROM
pagila.actor WHERE
= p_nom AND
last_name = p_prenom
first_name as pa
) JOIN
as pfa USING(actor_id)
pagila.film_actor JOIN
as pfc USING(film_id)
pagila.film_category JOIN
category as pc USING(category_id) ;
pagila. $$ ;
-24=# SElECT public.actor_category('LOLLOBRIGIDA', 'JOHNNY') ;
bd_2023
actor_category ------------------------------------------------------------------------------------------------------------
New, Sci-Fi, Sports, Travel
Action, Animation, Children, Comedy, Documentary, Drama, Games, Horror, Music, 1 row) (
Ecrire une fonction co_actors(p_nom text, p_prenom text)
qui renvoie les noms et prénoms des acteurs qui jouent dans un film où apparaît un acteur ou une actrice dont le nom et le prénom sont donnés en argument (schéma : pagila
).
CREATE OR REPLACE FUNCTION public.co_actors(p_nom text, p_prenom text)
TABLE(nom text, prenom text)
RETURNS AS
LANGUAGE SQL
$$SELECT
DISTINCT pa2.last_name as nom, pa2.first_name as prenom
FROM
SELECT
(
actor_idFROM
pagila.actor WHERE
= p_nom AND
last_name = p_prenom
first_name as pa
) JOIN
as pfa1 USING(actor_id)
pagila.film_actor JOIN
as pfa2 ON (
pagila.film_actor =pfa2.film_id AND
pfa1.film_id<>pfa2.actor_id
pfa1.actor_id
)JOIN
as pa2 ON (pa2.actor_id=pfa2.actor_id)
pagila.actor ORDER BY nom, prenom;
$$ ;
Écrire une vue qui contient une ligne pour chaque pays où on parle français, présente les pays par population croissante, et contient trois colonnes :
name_country
(même type que dansworld.country
) ;cumul_loc
(de typefloat4
) qui donne le nombre cumulé de locuteurs du français dans les pays où on parle français, pas plus peuplés que le pays courant ;cum_pop
(de typefloat4
) qui donne la population cumulée des pays où on parle français, pas plus peuplés que le pays courant.
Utilisez une fenêtre (WINDOW
) avec une clause RANGE ...
.
Pour trouver les pays où on parle français, utilisez l’expression language like '%French%'
. Vous remarquerez que dans certains pays, il existe plusieurs variétés de ‘French’. Veillez à compter tous les locuteurs, et à ne compter les habitants qu’une seule fois.
WITH f AS (
SELECT
SUM(cl.percentage) AS percentage
cl.countrycode, FROM
world.countrylanguage clWHERE
LIKE '%French%'
cl.language GROUP BY
cl.countrycode
)
SELECT
co.name_country, SUM(f.percentage * co.population_country::float4/100) OVER w AS cumul_loc,
SUM(co.population_country::float4) OVER w AS cum_pop #<1>
FROM
f NATURAL JOIN
world.country coAS (
WINDOW w ORDER BY co.population_country
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
);
- Pose problème !!! Si plusieurs formes de français sont utilisées dans un pays, la population de ce pays est comptabilisée plusieurs fois.