aggregation
4
Home.md
4
Home.md
@@ -3,8 +3,8 @@
|
||||
- [Fonctions d'agrégation](aggregation.md)
|
||||
- [Jointure](Jointures.md)
|
||||
- [Sous requêtes](sousrequete.md)
|
||||
- [Arbres](Arbres.md)
|
||||
- [Fonctions de fenêtrage](window.md)
|
||||
- [Arbres](Arbres.md)
|
||||
|
||||
## Exercices
|
||||
|
||||
@@ -33,5 +33,3 @@ Arrondir les montant à deux chiffres après la virgule.
|
||||
- 12b : Quels sont les articles qui apparaissent qu'une seule fois dans les lignes de ticket (Ananas séché 100g, ...)
|
||||
- 13 : Lister les articles dont la famille est absente. (MENMA)
|
||||
- 14 : Lister les tickets qui contiennent l’article le plus cher du catalogue.
|
||||
|
||||
- 20 : Quel adhérent est en double (même nom, prénom, date de naissance)
|
||||
|
||||
13
Jointure.md
13
Jointure.md
@@ -230,3 +230,16 @@ WHERE G.id <> D.id;
|
||||
C'est une représentation purement théorique il n'y a pas de mot clé particulier, la différence est seulement que la même table apparaît plusieurs fois.
|
||||
|
||||
Ce type de requête est utilisées pour comparer des lignes entres elles ou détecter des doublons.
|
||||
|
||||
Quel adhérent est en double (même nom, prénom et date de naissance)
|
||||
|
||||
```sql
|
||||
select a.nom, a.prenom
|
||||
from adherent a
|
||||
join adherent b on a.nom = b.nom
|
||||
and a.prenom = b.prenom
|
||||
and a.naissance = b.naissance
|
||||
and a.id <> b.id
|
||||
```
|
||||
|
||||
## Lateral
|
||||
|
||||
@@ -135,3 +135,270 @@ join article a on f.code = a.famille_code
|
||||
join ligne l on a.code = l.article_code
|
||||
where f.code = '03FRAISE'
|
||||
group by f.code
|
||||
|
||||
Très bonne question 👍 ! Ici on veut, **pour chaque adhérent**, identifier **le ticket avec le montant total le plus élevé**.
|
||||
|
||||
On peut le résoudre **proprement avec les fonctions OLAP (fenêtrage)**.
|
||||
|
||||
---
|
||||
|
||||
## 🟢 Solution avec `ROW_NUMBER()`
|
||||
|
||||
```sql
|
||||
SELECT adherent_id, id AS ticket_id, total
|
||||
FROM (
|
||||
SELECT
|
||||
t.adherent_id,
|
||||
t.id,
|
||||
SUM(l.prix_unitaire * l.quantite) AS total,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY t.adherent_id
|
||||
ORDER BY SUM(l.prix_unitaire * l.quantite) DESC
|
||||
) AS rn
|
||||
FROM Ticket t
|
||||
JOIN Ligne l ON l.ticket_id = t.id
|
||||
GROUP BY t.adherent_id, t.id
|
||||
) sub
|
||||
WHERE rn = 1;
|
||||
```
|
||||
|
||||
### ✅ Explications
|
||||
|
||||
1. On calcule le **total par ticket** avec `SUM(l.prix_unitaire * l.quantite)`.
|
||||
2. Avec `ROW_NUMBER() OVER (PARTITION BY t.adherent_id ORDER BY total DESC)` :
|
||||
|
||||
* On numérote les tickets **par adhérent**.
|
||||
* Le ticket avec le total le plus élevé prend `rn = 1`.
|
||||
3. La requête finale ne garde que `rn = 1`, donc le **ticket max par adhérent**.
|
||||
|
||||
---
|
||||
|
||||
## 🟡 Variante avec `RANK()` (pour gérer les ex æquo)
|
||||
|
||||
Si un adhérent a **plusieurs tickets ex æquo** avec le même montant maximum, `ROW_NUMBER()` n’en garde qu’un seul.
|
||||
Si vous voulez les **tous**, utilisez `RANK()` :
|
||||
|
||||
```sql
|
||||
SELECT adherent_id, id AS ticket_id, total
|
||||
FROM (
|
||||
SELECT
|
||||
t.adherent_id,
|
||||
t.id,
|
||||
SUM(l.prix_unitaire * l.quantite) AS total,
|
||||
RANK() OVER (
|
||||
PARTITION BY t.adherent_id
|
||||
ORDER BY SUM(l.prix_unitaire * l.quantite) DESC
|
||||
) AS rk
|
||||
FROM Ticket t
|
||||
JOIN Ligne l ON l.ticket_id = t.id
|
||||
GROUP BY t.adherent_id, t.id
|
||||
) sub
|
||||
WHERE rk = 1;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🟣 Variante simple avec sous-requêtes corrélées
|
||||
|
||||
Moins élégante, mais sans fonctions OLAP :
|
||||
|
||||
```sql
|
||||
SELECT t.adherent_id, t.id AS ticket_id,
|
||||
SUM(l.prix_unitaire * l.quantite) AS total
|
||||
FROM Ticket t
|
||||
JOIN Ligne l ON l.ticket_id = t.id
|
||||
GROUP BY t.adherent_id, t.id
|
||||
HAVING SUM(l.prix_unitaire * l.quantite) = (
|
||||
SELECT MAX(total)
|
||||
FROM (
|
||||
SELECT SUM(l2.prix_unitaire * l2.quantite) AS total
|
||||
FROM Ticket t2
|
||||
JOIN Ligne l2 ON l2.ticket_id = t2.id
|
||||
WHERE t2.adherent_id = t.adherent_id
|
||||
GROUP BY t2.id
|
||||
) s
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
👉 Préférez la version **fenêtrage (`ROW_NUMBER` ou `RANK`)**, bien plus claire et efficace.
|
||||
|
||||
Voulez-vous que je vous propose ensuite **la même logique mais pour les 3 tickets les plus chers par adhérent** (classement des top-N) ?
|
||||
Très bonne question 👍 ! Ici on veut, **pour chaque adhérent**, identifier **le ticket avec le montant total le plus élevé**.
|
||||
|
||||
On peut le résoudre **proprement avec les fonctions OLAP (fenêtrage)**.
|
||||
|
||||
---
|
||||
|
||||
## 🟢 Solution avec `ROW_NUMBER()`
|
||||
|
||||
```sql
|
||||
SELECT adherent_id, id AS ticket_id, total
|
||||
FROM (
|
||||
SELECT
|
||||
t.adherent_id,
|
||||
t.id,
|
||||
SUM(l.prix_unitaire * l.quantite) AS total,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY t.adherent_id
|
||||
ORDER BY SUM(l.prix_unitaire * l.quantite) DESC
|
||||
) AS rn
|
||||
FROM Ticket t
|
||||
JOIN Ligne l ON l.ticket_id = t.id
|
||||
GROUP BY t.adherent_id, t.id
|
||||
) sub
|
||||
WHERE rn = 1;
|
||||
```
|
||||
|
||||
### ✅ Explications
|
||||
|
||||
1. On calcule le **total par ticket** avec `SUM(l.prix_unitaire * l.quantite)`.
|
||||
2. Avec `ROW_NUMBER() OVER (PARTITION BY t.adherent_id ORDER BY total DESC)` :
|
||||
|
||||
* On numérote les tickets **par adhérent**.
|
||||
* Le ticket avec le total le plus élevé prend `rn = 1`.
|
||||
3. La requête finale ne garde que `rn = 1`, donc le **ticket max par adhérent**.
|
||||
|
||||
---
|
||||
|
||||
## 🟡 Variante avec `RANK()` (pour gérer les ex æquo)
|
||||
|
||||
Si un adhérent a **plusieurs tickets ex æquo** avec le même montant maximum, `ROW_NUMBER()` n’en garde qu’un seul.
|
||||
Si vous voulez les **tous**, utilisez `RANK()` :
|
||||
|
||||
```sql
|
||||
SELECT adherent_id, id AS ticket_id, total
|
||||
FROM (
|
||||
SELECT
|
||||
t.adherent_id,
|
||||
t.id,
|
||||
SUM(l.prix_unitaire * l.quantite) AS total,
|
||||
RANK() OVER (
|
||||
PARTITION BY t.adherent_id
|
||||
ORDER BY SUM(l.prix_unitaire * l.quantite) DESC
|
||||
) AS rk
|
||||
FROM Ticket t
|
||||
JOIN Ligne l ON l.ticket_id = t.id
|
||||
GROUP BY t.adherent_id, t.id
|
||||
) sub
|
||||
WHERE rk = 1;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🟣 Variante simple avec sous-requêtes corrélées
|
||||
|
||||
Moins élégante, mais sans fonctions OLAP :
|
||||
|
||||
```sql
|
||||
SELECT t.adherent_id, t.id AS ticket_id,
|
||||
SUM(l.prix_unitaire * l.quantite) AS total
|
||||
FROM Ticket t
|
||||
JOIN Ligne l ON l.ticket_id = t.id
|
||||
GROUP BY t.adherent_id, t.id
|
||||
HAVING SUM(l.prix_unitaire * l.quantite) = (
|
||||
SELECT MAX(total)
|
||||
FROM (
|
||||
SELECT SUM(l2.prix_unitaire * l2.quantite) AS total
|
||||
FROM Ticket t2
|
||||
JOIN Ligne l2 ON l2.ticket_id = t2.id
|
||||
WHERE t2.adherent_id = t.adherent_id
|
||||
GROUP BY t2.id
|
||||
) s
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
👉 Préférez la version **fenêtrage (`ROW_NUMBER` ou `RANK`)**, bien plus claire et efficace.
|
||||
|
||||
Voulez-vous que je vous propose ensuite **la même logique mais pour les 3 tickets les plus chers par adhérent** (classement des top-N) ?
|
||||
|
||||
|
||||
Exactement ✅ !
|
||||
Les **fonctions de fenêtrage** (aussi appelées **window functions**) sont parfaites pour ce genre de problème car elles permettent de classer ou de filtrer à l’intérieur d’un groupe sans avoir à écrire des sous-requêtes imbriquées lourdes.
|
||||
|
||||
---
|
||||
|
||||
## 🔹 Exemple : Le ticket le plus élevé par adhérent
|
||||
|
||||
Avec `ROW_NUMBER()` :
|
||||
|
||||
```sql
|
||||
SELECT adherent_id, ticket_id, total
|
||||
FROM (
|
||||
SELECT
|
||||
t.adherent_id,
|
||||
t.id AS ticket_id,
|
||||
SUM(l.prix_unitaire * l.quantite) AS total,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY t.adherent_id
|
||||
ORDER BY SUM(l.prix_unitaire * l.quantite) DESC
|
||||
) AS rn
|
||||
FROM Ticket t
|
||||
JOIN Ligne l ON l.ticket_id = t.id
|
||||
GROUP BY t.adherent_id, t.id
|
||||
) sub
|
||||
WHERE rn = 1;
|
||||
```
|
||||
|
||||
* `PARTITION BY t.adherent_id` → remet le compteur à zéro pour chaque adhérent.
|
||||
* `ORDER BY total DESC` → donne `rn = 1` au ticket le plus élevé.
|
||||
* La clause externe `WHERE rn = 1` isole le meilleur ticket.
|
||||
|
||||
---
|
||||
|
||||
## 🔹 Variante : gérer les ex-æquo
|
||||
|
||||
Si deux tickets d’un même adhérent ont exactement le même montant maximum, on peut utiliser `RANK()` (ou `DENSE_RANK()`) au lieu de `ROW_NUMBER()` :
|
||||
|
||||
```sql
|
||||
SELECT adherent_id, ticket_id, total
|
||||
FROM (
|
||||
SELECT
|
||||
t.adherent_id,
|
||||
t.id AS ticket_id,
|
||||
SUM(l.prix_unitaire * l.quantite) AS total,
|
||||
RANK() OVER (
|
||||
PARTITION BY t.adherent_id
|
||||
ORDER BY SUM(l.prix_unitaire * l.quantite) DESC
|
||||
) AS rk
|
||||
FROM Ticket t
|
||||
JOIN Ligne l ON l.ticket_id = t.id
|
||||
GROUP BY t.adherent_id, t.id
|
||||
) sub
|
||||
WHERE rk = 1;
|
||||
```
|
||||
|
||||
* `RANK()` → donne le même rang aux ex-æquo (par exemple 1 et 1).
|
||||
* `DENSE_RANK()` → fait la même chose mais sans "sauter" de rang.
|
||||
|
||||
---
|
||||
|
||||
## 🔹 Variante bonus : les **3 meilleurs tickets** par adhérent
|
||||
|
||||
Très souvent demandé en pratique (Top-N) :
|
||||
|
||||
```sql
|
||||
SELECT adherent_id, ticket_id, total
|
||||
FROM (
|
||||
SELECT
|
||||
t.adherent_id,
|
||||
t.id AS ticket_id,
|
||||
SUM(l.prix_unitaire * l.quantite) AS total,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY t.adherent_id
|
||||
ORDER BY SUM(l.prix_unitaire * l.quantite) DESC
|
||||
) AS rn
|
||||
FROM Ticket t
|
||||
JOIN Ligne l ON l.ticket_id = t.id
|
||||
GROUP BY t.adherent_id, t.id
|
||||
) sub
|
||||
WHERE rn <= 3
|
||||
ORDER BY adherent_id, total DESC;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
👉 Voulez-vous que je prépare un **petit tableau récapitulatif** (avec exemples) qui compare `ROW_NUMBER()`, `RANK()` et `DENSE_RANK()` ? Cela permettrait de bien visualiser les différences pour les cas d’ex-æquo.
|
||||
|
||||
@@ -7,33 +7,22 @@
|
||||
Compte le nombre d'enregistrements dans un ensemble de données.
|
||||
|
||||
```sql
|
||||
select count(*) from table;
|
||||
select count(*) from adherent;
|
||||
```
|
||||
|
||||
On peut compter sur n'importe quelle colonne, dans ce cas, pour ne pas avoir à choisir on utilise le caractère joker *.
|
||||
|
||||
- 1 : Quel est le nombre total de ticket ? (101 616)
|
||||
En ajoutant le mot clé distinct l'opération s'effectue sur les valeurs uniques d'une ou de plusieurs colonnes.
|
||||
|
||||
```sql
|
||||
select count(*) as nb_ticket from ticket;
|
||||
select count(distinct codepostal) from adherent;
|
||||
```
|
||||
|
||||
- 2 : Quel est le nombre total d'adhérents ? (2439)
|
||||
Il est également possible d'ajouter une condition WHERE à cette colonne uniquement. C’est souvent plus lisible et performant que de faire un CASE WHEN imbriqué.
|
||||
|
||||
```sql
|
||||
select count(*) as nb_adherent from adherent;
|
||||
```
|
||||
|
||||
- 3 : Quel est le nombre total d'articles ? (1099)
|
||||
|
||||
```sql
|
||||
select count(*) as nb_articles from article;
|
||||
```
|
||||
|
||||
- 4 : Quel est le nombre total de familles ? (138)
|
||||
|
||||
```sql
|
||||
select count(*) as nb_familles from famille;
|
||||
select count(distinct client) filter (where produit = 'pomme')
|
||||
from vente;
|
||||
```
|
||||
|
||||
### Somme
|
||||
@@ -41,13 +30,14 @@ select count(*) as nb_familles from famille;
|
||||
Calcule la somme des valeurs d'une colonne numérique.
|
||||
|
||||
```sql
|
||||
SELECT SUM(colonne) FROM table;
|
||||
SELECT SUM(vente) FROM ticket;
|
||||
```
|
||||
|
||||
- 2 : Calculer le chiffre d’affaires global. (1 914 792.40)
|
||||
|
||||
```sql
|
||||
select round(sum(quantite * prix_unitaire), 2) as chiffre_affaire from ligne;
|
||||
|
||||
select sum(round(quantite * prix_unitaire, 2)) as chiffre_affaire from ligne;
|
||||
```
|
||||
|
||||
@@ -56,7 +46,7 @@ select sum(round(quantite * prix_unitaire, 2)) as chiffre_affaire from ligne;
|
||||
Calcule la moyenne des valeurs d'une colonne numérique.
|
||||
|
||||
```sql
|
||||
SELECT AVG(salaire) FROM employes;
|
||||
SELECT AVG(prix) FROM article;
|
||||
```
|
||||
|
||||
### Maximum
|
||||
@@ -64,7 +54,7 @@ SELECT AVG(salaire) FROM employes;
|
||||
Retourne la valeur maximale dans une colonne.
|
||||
|
||||
```sql
|
||||
SELECT MAX(age) FROM utilisateurs;
|
||||
SELECT MAX(age) FROM adherent;
|
||||
```
|
||||
|
||||
Une autre solution consiste à trier la colonne est de limiter le résultat à une seule ligne
|
||||
@@ -81,7 +71,7 @@ order by prix desc limit 1;
|
||||
Retourne la valeur minimale dans une colonne.
|
||||
|
||||
```sql
|
||||
SELECT MIN(age) FROM utilisateurs;
|
||||
SELECT MIN(age) FROM adherent;
|
||||
```
|
||||
|
||||
Une autre solution consiste à trier la colonne est de limiter le résultat à une seule ligne
|
||||
@@ -93,6 +83,19 @@ select article, prix from article
|
||||
order by prix asc limit 1;
|
||||
```
|
||||
|
||||
### Concaténation en chaine
|
||||
|
||||
`STRING_AGG` construit une chaine de caractères séparés par un délimiteur
|
||||
|
||||
```sql
|
||||
select string_agg(distinct famille_code, ', ')
|
||||
from article;
|
||||
```
|
||||
|
||||
### Concaténation en tableau
|
||||
|
||||
`ARRAY_AGG` construit une tableau à partir des éléments. Très utilisé lors d'une sérialisation en JSON par exemple.
|
||||
|
||||
### Filtre après agrégation
|
||||
|
||||
`HAVING` est emblable à `WHERE`, mais utilisé pour filtrer les résultats des fonctions d'agrégation **après** un `GROUP BY`.
|
||||
@@ -107,3 +110,5 @@ join ticket t on a.id = t.adherent_id
|
||||
group by a.id
|
||||
having count(t.id) = 72;
|
||||
```
|
||||
|
||||
Avec postgreSQL et Microsoft SQL Server il n'est pas possible d'utiliser l'alias dans la condition having. La clause having intervient en amont du select dans le moteur d'exécution, l'alias n'est pas encore connu. Il faut réécrire la formule.
|
||||
|
||||
@@ -9,12 +9,25 @@ Elle est généralement entourée de parenthèses avec un nom d'alias et peut ap
|
||||
- parfois dans HAVING
|
||||
|
||||
Il existe deux types de sous requêtes :
|
||||
- La sous-requête corrélée qui dépend de la requête principale. Elle est donc réévaluée pour chaque ligne.
|
||||
- La sous-requête non corréléé qui est indépendante et exécuté une seule fois.
|
||||
- La sous-requête **corrélée** qui dépend de la requête principale. Elle est donc réévaluée pour chaque ligne.
|
||||
- La sous-requête **non corréléé** qui est indépendante et exécuté une seule fois.
|
||||
|
||||
## Valeur
|
||||
> Dans la clause FROM, une sous-requête est **toujours non corrélée**.
|
||||
Parce qu’elle doit produire une table (un jeu de résultats) indépendante **avant** d’être "reliée" au reste de la requête principale.
|
||||
|
||||
Une sous requête renvoie un ensemble de valeurs. Il est possible de renvoyer une seule valeur en limitant les résultats.
|
||||
- Dans le SELECT, une sous-requête peut être corrélée (elle utilise des colonnes de la requête principale) ou non.
|
||||
- Dans le WHERE, idem : elle peut être corrélée (EXISTS, IN dépendant de la ligne en cours) ou non (test fixe).
|
||||
|
||||
## Sous-requête scalaire
|
||||
|
||||
une sous-requête scalaire est une sous-requête qui retourne une seule valeur (un scalaire, c’est-à-dire une seule ligne et une seule colonne).
|
||||
|
||||
Elle peut être utilisée partout où une valeur simple est attendue, par exemple :
|
||||
|
||||
- dans la liste de sélection (SELECT),
|
||||
- dans une clause WHERE,
|
||||
- dans une clause HAVING,
|
||||
- parfois même dans la clause ORDER BY.
|
||||
|
||||
```sql
|
||||
SELECT *
|
||||
@@ -84,3 +97,7 @@ WHERE prix > ALL (
|
||||
SELECT prix_promo FROM promotions
|
||||
);
|
||||
```
|
||||
|
||||
## Lateral
|
||||
|
||||
Une sous-requête LATERAL (ou sous-requête latérale) permet à une sous-requête dans la clause FROM d’accéder aux colonnes déjà définies dans les relations qui la précèdent.
|
||||
|
||||
Reference in New Issue
Block a user