jointures

2025-08-30 08:22:46 +02:00
parent 354cd0c939
commit 739837a806
11 changed files with 656 additions and 191 deletions

162
Jointure.md Normal file

@@ -0,0 +1,162 @@
# Jointures
Les jointures (joins en anglais) permettent de combiner des enregistrements de plusieurs tables d'une base de données en fonction d'une condition définie. Elles sont utilisées lorsque l'on veut extraire des données provenant de plusieurs sources qui partagent un lien commun, souvent une clé étrangère (foreign key).
Le type de jointure utilisé dépend du résultat attendu et de la structure des données.
## Jointure interne
`INNER JOIN` est le type de jointure le plus courant. Il ne renvoie que les lignes qui ont des correspondances exactes dans les deux tables gauche et droite.
```sql
SELECT gauche.colonne, droite.colonne
FROM gauche
INNER JOIN droite ON gauche.id = droite.id;
```
Dans cet exemple, seules les lignes qui ont le même id dans gauche et droite seront incluses dans le résultat.
![](jointures/innerjoin.svg)
## Jointure externe gauche
La jointure `LEFT JOIN` ou `LEFT OUTER JOIN` renvoie **toutes** les lignes de la table de gauche, même si elles n'ont pas de correspondance dans la table de droite. Si aucune correspondance n'est trouvée, les colonnes de la table de droite contiendront des valeurs NULL.
```sql
SELECT gauche.colonne, droite.colonne
FROM gauche
LEFT JOIN droite
ON gauche.id = droite.id;
```
![](jointures/leftjoin.svg)
## Jointure externe droite
`RIGHT JOIN` ou `RIGHT OUTER JOIN` est l'inverse de la jointure externe gauche. Elle renvoie **toutes** les lignes de la table de droite et les lignes correspondantes de la table de gauche. Si une ligne de la table de droite n'a pas de correspondance dans la table de gauche, les colonnes de la table de gauche auront des valeurs NULL.
```sql
SELECT gauche.colonne, droite.colonne
FROM gauche
RIGHT JOIN droite
ON gauche.id = droite.id;
```
![](jointures/rightjoin.svg)
## Jointure externe complète
`FULL JOIN` ou `FULL OUTER JOIN` combine les fonctionnalités des jointures gauche et droite. Elle renvoie toutes les lignes des deux tables, avec des valeurs NULL là où il n'y a pas de correspondance dans l'autre table.
```sql
SELECT gauche.colonne, droite.colonne
FROM gauche
FULL JOIN droite
ON gauche.id = droite.id;
```
![](jointures/fullouterjoin.svg)
## Jointure d'exclusion
Un jointure d'exclusion à gauche (left anti-join) garde uniquement les lignes pour lesquelles il ny a **pas** de correspondance (cest-à-dire que les colonnes de la table de droite sont NULL).
```sql
SELECT gauche.colonne, droite.colonne
FROM gauche
LEFT JOIN droite
ON gauche.id = droite.id
WHERE droite.id IS NULL;
```
![](jointures/leftantijoin.svg)
Cela permet de trouver les lignes de gauche sans correspondance dans la table de droite.
Un jointure d'exclusion à droite (right anti-join) garde uniquement les lignes pour lesquelles il ny a **pas** de correspondance (cest-à-dire que les colonnes de la table de gauche sont NULL).
```sql
SELECT gauche.colonne, droite.colonne
FROM gauche
RIGHT JOIN droite
ON gauche.id = droite.id
WHERE gauche IS NULL;
```
![](jointures/rightantijoin.svg)
## Jointure croisée ou produit cartésien
La jointure croisée renvoie le produit cartésien des deux tables. Chaque ligne de la première table est combinée avec chaque ligne de la deuxième table. Cela produit un très grand nombre de lignes de résultat.
```sql
SELECT gauche.colonne, droite.colonne
FROM gauche
CROSS JOIN droite;
```
Cela peut être utile dans des situations spécifiques mais doit être utilisé avec prudence car cela peut générer des résultats volumineux.
## Ancienne syntaxe
L'ancienne syntaxe qui date de 1992 n'utilise pas les mots clés `JOIN` mais liste uniquement les tables dans la clause `FROM`.
```sql
SELECT *
FROM A, B;
```
Le résultat de cette requête est strictement équivalent au produit cartésien `CROSS JOIN`. Si une condition `WHERE` est ajoutée, l'équivalent est une jointure `INNER`.
```sql
SELECT *
FROM A, B
WHERE A.id = B.a_id;
```
## EXISTS
Les jointures d'exclusion peuvent aussi s'écrire avec une sous requête et une condition `EXISTS`.
```sql
SELECT gauxhe.*
FROM gauche g
WHERE NOT EXISTS (
SELECT 1
FROM droite d
WHERE d.g_id = g.id
);
```
`SELECT 1` car nous ne voulons pas récupérer de valeur de la table mais uniquement savoir si la ligne existe ou pas.
Avantages :
- Robuste face aux valeurs NULL (pas dambiguïté).
- Souvent optimisé par le moteur en anti-semi-join (cest-à-dire que le moteur ne lit pas plus de lignes que nécessaire dans la table de droite). Les jointures qui passent par des produits cartésiens sont consommatrices en ressources.
Il existe plusieurs syntaxes permettant dobtenir le même résultat. Toutefois, certains points méritent dêtre pris en compte :
- **Consommation de ressources** : un même résultat peut être produit par des requêtes dont le coût en temps dexécution ou en mémoire diffère sensiblement.
- **Lisibilité** : les syntaxes modernes apportent plus de clarté et facilitent la compréhension de lintention de la requête.
```sql
SELECT g.*
FROM gauche g
WHERE g.id NOT IN (
SELECT d.g_id
FROM droite d
);
```
Cette requête est elle aussi équivalente cependant il existe un risque de mauvaise interprétation. Si le sous-select contient au moins un NULL, alors toutes les lignes sont rejetées (car la comparaison avec la valeur NULL dans id NOT IN ( …, NULL, … ) est indéterminée.)
## Self Join (auto-jointure)
C'est une jointure dans laquelle une table est jointe avec elle-même. Cela peut être utile pour comparer les lignes d'une même table. Des alias sont utilisés pour identifier la partie gauche de la partie droite
```sql
SELECT G.colonne, D.colonne
FROM table G, table D
WHERE G.nom = D.nom;
```
![SQL Jointure](jointures/SQL_Joins.svg)
## lateral

@@ -1,7 +1,7 @@
-- Réponses
-- 1a : Quel est le nombre total de ticket ?
-- 101 615
-- 101 616
select count(*) as nb_ticket from ticket;
-- 1b : Quel est le nombre total d'adhérents ?
@@ -9,34 +9,34 @@ select count(*) as nb_ticket from ticket;
select count(*) as nb_adherent from adherent;
-- 2 : Calculer le chiffre daffaires global.
-- 1 881 766.53085
-- 1 914 792.39585
select sum(quantite * prix_unitaire) as chiffre_affaire from ligne;
-- 3a : Pour chaque adhérent, afficher son nom et son nombre de ticket.
-- 3a : Pour chaque adhérent unique, afficher son nom et son nombre de ticket.
select a.nom,
count(t.id) as nb_ticket
from adherent a
join ticket t on a.id = t.client_id
join ticket t on a.id = t.adherent_id
group by a.id;
-- 3b : Pour chaque adhérent, afficher son nom et son nombre de ticket.
select a.nom,
-- 3b : Quel est le détail du nombre de ticket pour ceux dont le nom de famille est Lavergne ?
select a.nom, a.prenom,
count(t.id) as nb_ticket
from adherent a
join ticket t on a.id = t.client_id
join ticket t on a.id = t.adherent_id
where a.nom = 'Lavergne'
group by a.id;
-- 3c : Pour chaque adhérent, afficher son nom et son nombre de ticket.
select a.nom,
-- 3c : Quels sont les personnes qu iont exactement 72 tickets ?
select a.nom, a.prenom,
count(t.id) as nb_ticket
from adherent a
join ticket t on a.id = t.client_id
join ticket t on a.id = t.adherent_id
group by a.id
having count(t.id) = 72;
-- 4 : Calculer le montant moyen dun ticket.
-- 18.52
-- 4a : Calculer le montant moyen dun ticket.
-- 18.84
select avg(total_ticket) as panier_moyen
from (
select t.id, sum(l.quantite * l.prix_unitaire) as total_ticket
@@ -45,21 +45,59 @@ from (
group by t.id
) sous_requete;
-- 4b : Calculer le nombre moyen d'article distinct d'un ticket.
-- 6.69
SELECT avg(nb_articles_distincts) AS moyenne_articles_distincts
FROM (
SELECT t.id, COUNT(DISTINCT l.article_code) AS nb_articles_distincts
FROM ticket t
JOIN ligne l ON l.ticket_id = t.id
GROUP BY t.id
) sub;
-- 4c Calculer le nombre moyen d'article d'un ticket. Les articles facturés au poids comptent pour 1.
-- 7.21
SELECT AVG(nb_articles) AS moyenne_articles_par_ticket
FROM (
SELECT t.id,
SUM(
CASE
WHEN a.factpoids THEN 1 -- articles à la pièce
ELSE l.quantite -- articles au poids : 1 unité
END
) AS nb_articles
FROM ticket t
JOIN ligne l ON l.ticket_id = t.id
JOIN article a ON a.code = l.article_code
GROUP BY t.id
) sub;
-- 5 : Afficher le produit le plus cher et le produit le moins cher (avec leur prix).
-- Pistache 63
select article.article, prix
from article
order by prix desc
limit 1;
-- Courgette Mini Fleur
select article.article, prix
from article
order by prix asc
limit 1;
limit 10;
-- 6a : Pour chaque famille, afficher le nom de la famille et le nombre d'articles associés.
select f.famille, count(a.code) as nb_produits
from famille f
left join article a on f.code = a.famille
left join article a on f.code = a.famille_code
group by f.famille;
-- 6b
-- 29
select f.famille, count(a.code) as nb_produits
from famille f
left join article a on f.code = a.famille_code
where f.code = '02CHOU'
group by f.famille;
-- 7 : Afficher, pour chaque mois, le chiffre daffaires réalisé.
@@ -71,18 +109,19 @@ group by date_trunc('month', t.date_ticket)
order by mois;
-- 8 : Trouver les 3 adhérents qui ont dépensé le plus en montant total.
select c.nom, sum(l.quantite * l.prix_unitaire) as total_depense
from adherent c
join ticket t on c.id = t.client_id
select a.nom, sum(l.quantite * l.prix_unitaire) as total_depense
from adherent a
join ticket t on a.id = t.adherent_id
join ligne l on t.id = l.ticket_id
group by c.nom
group by a.nom
order by total_depense desc
limit 3;
-- 9 : Afficher l'article ayant généré le plus de ventes en quantité totale vendue.
select a.article, sum(l.quantite) as total_vendu
from article a
join ligne l on a.code = l.article
join ligne l on a.code = l.article_code
where a.factpoids = false
group by a.article
order by total_vendu desc
limit 1
@@ -91,166 +130,7 @@ limit 1
select f.famille,
sum(l.quantite * l.prix_unitaire) as total_famille
from famille f
join article a on f.famille = a.famille
join ligne l on a.code = l.article
join article a on f.code = a.famille_code
join ligne l on a.code = l.article_code
group by f.famille
order by total_famille desc;
-- ----------
-- 1 : Intersection (INNER JOIN)
SELECT t.id_ticket, t.date_vente, c.nom AS client
FROM ticket t
INNER JOIN client c ON c.id_client = t.id_client;
-- 2 : LEFT JOIN (client sans ticket)
SELECT c.id_client, c.nom, COUNT(t.id_ticket) AS nb_ticket
FROM client c
LEFT JOIN ticket t ON t.id_client = c.id_client
GROUP BY c.id_client, c.nom
ORDER BY c.nom;
-- 3 : RIGHT JOIN (ticket sans client)
SELECT t.id_ticket, t.date_vente, c.nom AS client
FROM client c
RIGHT JOIN ticket t ON c.id_client = t.id_client;
-- 4 : FULL OUTER JOIN (union)
SELECT
c.id_client, c.nom,
t.id_ticket, t.date_vente,
CASE
WHEN t.id_ticket IS NULL THEN 'client_sans_ticket'
WHEN c.id_client IS NULL THEN 'ticket_sans_client'
ELSE 'apparié'
END AS statut
FROM client c
FULL OUTER JOIN ticket t ON c.id_client = t.id_client
ORDER BY statut, c.nom NULLS LAST, t.id_ticket;
-- 5 : SEMI-JOIN (existence)
--Lister uniquement les client qui ont acheté au moins un produit. en SQL standard, cela se fait avec EXISTS.
--Lister tous les produits et indiquer sils ont é vendus au moins une fois (utiliser un CASE ou COALESCE).
SELECT
p.id_produit,
p.nom_produit,
CASE WHEN EXISTS (
SELECT 1
FROM ligne lt
WHERE lt.id_produit = p.id_produit
) THEN TRUE ELSE FALSE END AS deja_vendu
FROM Produits p
ORDER BY p.nom_produit;
SELECT
p.id_produit,
p.nom_produit,
COALESCE(SUM(CASE WHEN lt.id_ligne IS NOT NULL THEN 1 ELSE 0 END), 0) > 0 AS deja_vendu
FROM Produits p
LEFT JOIN ligne lt ON lt.id_produit = p.id_produit
GROUP BY p.id_produit, p.nom_produit;
-- 6 : ANTI-JOIN (différence)
Lister les client qui nont jamais acheté de produit.
utiliser NOT EXISTS ou une jointure + WHERE.
-- 7 : Jointure avec condition multiple
Lister les produits avec leur famille, même si certains produits nont pas de famille renseignée.
-- 8 : Auto-join (self join)
SELECT DISTINCT
c1.nom AS client_1,
c2.nom AS client_2
FROM client c1
JOIN ticket t1 ON t1.id_client = c1.id_client
JOIN ligne lt1 ON lt1.id_ticket = t1.id_ticket
JOIN ligne lt2 ON lt2.id_produit = lt1.id_produit
JOIN ticket t2 ON t2.id_ticket = lt2.id_ticket
JOIN client c2 ON c2.id_client = t2.id_client
WHERE c1.id_client < c2.id_client;
-- 9 : Produit cartésien
Lister toutes les combinaisons possibles de familles et de client (sans condition de jointure).
Attention : nombre de lignes = nb_familles × nb_client.
-- 10 : Jointure filtrée
Lister les ticket contenant au moins un produit de la famille "Boissons".
SELECT DISTINCT c.id_client, c.nom
FROM client c
JOIN ticket t ON t.id_client = c.id_client
JOIN ligne lt ON lt.id_ticket = t.id_ticket
JOIN Produits p ON p.id_produit = lt.id_produit
JOIN Familles f ON f.id_famille = p.id_famille
WHERE f.nom_famille = 'Boissons'
ORDER BY c.nom;
-- 11
SELECT c.id_client, c.nom
FROM client c
WHERE NOT EXISTS (
SELECT 1
FROM ticket t
JOIN ligne lt ON lt.id_ticket = t.id_ticket
JOIN Produits p ON p.id_produit = lt.id_produit
JOIN Familles f ON f.id_famille = p.id_famille
WHERE t.id_client = c.id_client
AND f.nom_famille = 'Boissons'
)
ORDER BY c.nom;
SELECT c.id_client, c.nom
FROM client c
LEFT JOIN (
SELECT DISTINCT t.id_client
FROM ticket t
JOIN ligne lt ON lt.id_ticket = t.id_ticket
JOIN Produits p ON p.id_produit = lt.id_produit
JOIN Familles f ON f.id_famille = p.id_famille
WHERE f.nom_famille = 'Boissons'
) acheteurs_boissons
ON acheteurs_boissons.id_client = c.id_client
WHERE acheteurs_boissons.id_client IS NULL
ORDER BY c.nom;
-- total des ventes par famille
SELECT
f.nom_famille,
SUM(lt.quantite * lt.prix_unitaire) AS total_ventes
FROM Familles f
JOIN Produits p ON p.id_famille = f.id_famille
JOIN ligne lt ON lt.id_produit = p.id_produit
GROUP BY f.nom_famille
ORDER BY total_ventes DESC;
--
select f.code, f.famille, count(a.code) as nb_produits
from famille f
left join article a on f.code = a.famille
group by f.code having count(a.code) = 0 order by f.code;
select distinct a.famille from article a
left join famille f on f.code = a.famille
where f.code is null
order by a.famille
SELECT DISTINCT ON (f.code)
f.code AS famille_id,
f.famille AS famille_nom,
a.article AS article_exemple
FROM famille f
LEFT JOIN article a ON a.famille = f.code
where f.famille is null
ORDER BY f.code, a.code;

261
Réponses/jointure.sql Normal file

@@ -0,0 +1,261 @@
-- ----------
-- Combien d'adhérents nont jamais acheté d'article ?
SELECT COUNT(*) AS nb_adherents_sans_achat
FROM adherent a
LEFT JOIN ticket t ON t.adherent_id = a.id
LEFT JOIN ligne l ON l.ticket_id = t.id
WHERE l.id IS NULL;
SELECT COUNT(*) AS nb_adherents_sans_achat
FROM adherent a
WHERE NOT EXISTS (
SELECT 1
FROM ticket t
JOIN ligne l ON l.ticket_id = t.id
WHERE t.adherent_id = a.id
);
-- Combien d'adhérents nont aucun ticket ?
SELECT COUNT(*) AS nb_adherents_sans_ticket
FROM adherent a
LEFT JOIN ticket t ON t.adherent_id = a.id
WHERE t.id IS NULL;
SELECT COUNT(*) AS nb_adherents_sans_ticket
FROM adherent a
WHERE NOT EXISTS (
SELECT 1
FROM ticket t
WHERE t.adherent_id = a.id
);
-- Version spécifique PostgreSQL Ne fontionne pas
SELECT
COUNT(*) FILTER (
WHERE NOT EXISTS (
SELECT 1 FROM ticket t WHERE t.adherent_id = a.id
)
) AS nb_sans_ticket,
COUNT(*) FILTER (
WHERE EXISTS (
SELECT 1 FROM ticket t WHERE t.adherent_id = a.id
)
AND NOT EXISTS (
SELECT 1
FROM ticket t
JOIN ligne l ON l.ticket_id = t.id
WHERE t.adherent_id = a.id
)
) AS nb_tickets_vides,
COUNT(*) FILTER (
WHERE EXISTS (
SELECT 1
FROM ticket t
JOIN ligne l ON l.ticket_id = t.id
WHERE t.adherent_id = a.id
)
) AS nb_acheteurs
FROM adherent a;
--
SELECT
COUNT(CASE
WHEN NOT EXISTS (SELECT 1 FROM ticket t WHERE t.adherent_id = a.id)
THEN 1 END) AS nb_sans_ticket,
COUNT(CASE
WHEN EXISTS (SELECT 1 FROM ticket t WHERE t.adherent_id = a.id)
AND NOT EXISTS (
SELECT 1
FROM ticket t
JOIN ligne l ON l.ticket_id = t.id
WHERE t.adherent_id = a.id
)
THEN 1 END) AS nb_tickets_vides,
COUNT(CASE
WHEN EXISTS (
SELECT 1
FROM ticket t
JOIN ligne l ON l.ticket_id = t.id
WHERE t.adherent_id = a.id
)
THEN 1 END) AS nb_acheteurs
FROM adherent a;
-- 13 :
SELECT COUNT(*) AS nb_adherents_un_seul_article
FROM (
SELECT a.id
FROM adherent a
JOIN ticket t ON t.adherent_id = a.id
JOIN ligne l ON l.ticket_id = t.id
GROUP BY a.id
HAVING COUNT(DISTINCT l.article_code) = 1
) sub;
-- 1 : Intersection (INNER JOIN)
SELECT t.id_ticket, t.date_vente, c.nom AS client
FROM ticket t
INNER JOIN client c ON c.id_client = t.id_client;
-- 2 : LEFT JOIN (client sans ticket)
SELECT c.id_client, c.nom, COUNT(t.id_ticket) AS nb_ticket
FROM client c
LEFT JOIN ticket t ON t.id_client = c.id_client
GROUP BY c.id_client, c.nom
ORDER BY c.nom;
-- 3 : RIGHT JOIN (ticket sans client)
SELECT t.id_ticket, t.date_vente, c.nom AS client
FROM client c
RIGHT JOIN ticket t ON c.id_client = t.id_client;
-- 4 : FULL OUTER JOIN (union)
SELECT
c.id_client, c.nom,
t.id_ticket, t.date_vente,
CASE
WHEN t.id_ticket IS NULL THEN 'client_sans_ticket'
WHEN c.id_client IS NULL THEN 'ticket_sans_client'
ELSE 'apparié'
END AS statut
FROM client c
FULL OUTER JOIN ticket t ON c.id_client = t.id_client
ORDER BY statut, c.nom NULLS LAST, t.id_ticket;
-- 5 : SEMI-JOIN (existence)
--Lister uniquement les client qui ont acheté au moins un produit. en SQL standard, cela se fait avec EXISTS.
--Lister tous les produits et indiquer sils ont é vendus au moins une fois (utiliser un CASE ou COALESCE).
SELECT
p.id_produit,
p.nom_produit,
CASE WHEN EXISTS (
SELECT 1
FROM ligne lt
WHERE lt.id_produit = p.id_produit
) THEN TRUE ELSE FALSE END AS deja_vendu
FROM Produits p
ORDER BY p.nom_produit;
SELECT
p.id_produit,
p.nom_produit,
COALESCE(SUM(CASE WHEN lt.id_ligne IS NOT NULL THEN 1 ELSE 0 END), 0) > 0 AS deja_vendu
FROM Produits p
LEFT JOIN ligne lt ON lt.id_produit = p.id_produit
GROUP BY p.id_produit, p.nom_produit;
-- 6 : ANTI-JOIN (différence)
Lister les client qui nont jamais acheté de produit.
utiliser NOT EXISTS ou une jointure + WHERE.
-- 7 : Jointure avec condition multiple
Lister les produits avec leur famille, même si certains produits nont pas de famille renseignée.
-- 8 : Auto-join (self join)
SELECT DISTINCT
c1.nom AS client_1,
c2.nom AS client_2
FROM client c1
JOIN ticket t1 ON t1.id_client = c1.id_client
JOIN ligne lt1 ON lt1.id_ticket = t1.id_ticket
JOIN ligne lt2 ON lt2.id_produit = lt1.id_produit
JOIN ticket t2 ON t2.id_ticket = lt2.id_ticket
JOIN client c2 ON c2.id_client = t2.id_client
WHERE c1.id_client < c2.id_client;
-- 9 : Produit cartésien
Lister toutes les combinaisons possibles de familles et de client (sans condition de jointure).
Attention : nombre de lignes = nb_familles × nb_client.
-- 10 : Jointure filtrée
Lister les ticket contenant au moins un produit de la famille "Boissons".
SELECT DISTINCT c.id_client, c.nom
FROM client c
JOIN ticket t ON t.id_client = c.id_client
JOIN ligne lt ON lt.id_ticket = t.id_ticket
JOIN Produits p ON p.id_produit = lt.id_produit
JOIN Familles f ON f.id_famille = p.id_famille
WHERE f.nom_famille = 'Boissons'
ORDER BY c.nom;
-- 11
SELECT c.id_client, c.nom
FROM client c
WHERE NOT EXISTS (
SELECT 1
FROM ticket t
JOIN ligne lt ON lt.id_ticket = t.id_ticket
JOIN Produits p ON p.id_produit = lt.id_produit
JOIN Familles f ON f.id_famille = p.id_famille
WHERE t.id_client = c.id_client
AND f.nom_famille = 'Boissons'
)
ORDER BY c.nom;
SELECT c.id_client, c.nom
FROM client c
LEFT JOIN (
SELECT DISTINCT t.id_client
FROM ticket t
JOIN ligne lt ON lt.id_ticket = t.id_ticket
JOIN Produits p ON p.id_produit = lt.id_produit
JOIN Familles f ON f.id_famille = p.id_famille
WHERE f.nom_famille = 'Boissons'
) acheteurs_boissons
ON acheteurs_boissons.id_client = c.id_client
WHERE acheteurs_boissons.id_client IS NULL
ORDER BY c.nom;
-- total des ventes par famille
SELECT
f.nom_famille,
SUM(lt.quantite * lt.prix_unitaire) AS total_ventes
FROM Familles f
JOIN Produits p ON p.id_famille = f.id_famille
JOIN ligne lt ON lt.id_produit = p.id_produit
GROUP BY f.nom_famille
ORDER BY total_ventes DESC;
--
select f.code, f.famille, count(a.code) as nb_produits
from famille f
left join article a on f.code = a.famille
group by f.code having count(a.code) = 0 order by f.code;
select distinct a.famille from article a
left join famille f on f.code = a.famille
where f.code is null
order by a.famille
SELECT DISTINCT ON (f.code)
f.code AS famille_id,
f.famille AS famille_nom,
a.article AS article_exemple
FROM famille f
LEFT JOIN article a ON a.famille = f.code
where f.famille is null
ORDER BY f.code, a.code;

@@ -1,34 +1,41 @@
# Exercices
## Série 1
## 1. Fonctions d'agrégation
- 1a : Quel est le nombre total de ticket ?
- 1b : Quel est le nombre total d'adhérents ?
- 2 : Calculer le chiffre daffaires global.
- 3a : Pour chaque adhérent, afficher son nom et son nombre de ticket.
- 3a : Pour chaque adhérent unique, afficher son nom et son nombre de ticket.
- 3b : Quel est le détail du nombre de ticket pour ceux dont le nom de famille est Lavergne ?
- 3c : Quels sont les personnes qu iont exactement 72 tickets ?
- 4 : Calculer le montant moyen dun ticket.
- 4a : Calculer le montant moyen dun ticket.
- 4b : Calculer le nombre moyen d'article distinct d'un ticket.
- 4c : Calculer le nombre moyen d'article d'un ticket. Les articles facturés au poids comptent pour 1.
- 5 : Afficher l'article le plus cher et l'article le moins cher (avec leur prix).
- 6a : Pour chaque famille, afficher le nom de la famille et le nombre d'articles associés.
- 6b : Quel est le nombre d'article de la famille
- 6b : Quel est le nombre d'article de la famille 02CHOU
- 7 : Afficher, pour chaque mois, le chiffre daffaires réalisé.
- 8 : Trouver l'adhérent qui a dépensé le plus en montant total.
- 9 : Afficher l'article ayant généré le plus de ventes en quantité totale vendue.
- 8 : Trouver les 3 adhérents qui ont dépensé le plus en montant total.
- 9a : Afficher l'article facturé au poids ayant généré le plus de ventes en quantité totale vendue.
- 9b : Afficher l'article facturé à l'unité ayant généré le plus de ventes en quantité totale vendue.
- 10 : Pour chaque famille, afficher le montant total des ventes.
## Série 2
## 2. Jointures
- 1a : Combien d'adhérents nont jamais acheté d'article ?
- 1b : Combien d'adhérents nont aucun ticket ?
- 1c : Combien de tickets sont vides (sans ligne)
- 2a : Quel est l'article qui n'a jamais été commandé ?
- 2b : Quels sont les articles qui apparaissent qu'une seule fois
- 3 : Lister les articles dont la famille est absente
- 1 : Lister tous les tickets avec le nom du client. Afficher uniquement les tickets qui ont un client existant.
- 2 : Lister tous les clients et le nombre de tickets associés, même les clients qui nont pas encore de ticket doivent apparaître (avec 0).
- 3 : Lister tous les tickets, avec le nom du client sil existe. Si un ticket a perdu sa référence client (valeur orpheline), il doit tout de même apparaître.
- 4 : Lister tous les clients et tous les tickets, même si la correspondance nexiste pas. Union des clients sans tickets et des tickets sans clients.
- 5 : Lister uniquement les clients qui ont acheté au moins un produit.
- 6 : Lister les clients qui nont jamais acheté de produit.
- 7 : Lister les produits avec leur famille, même si certains produits nont pas de famille renseignée.
- 8 : Lister tous les couples de clients qui habitent dans la même ville.
- 9 : Lister toutes les combinaisons possibles de familles et de clients (sans condition de jointure).
- 10 : Lister les tickets contenant au moins un produit de la famille "Boissons".
- Lister

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" width="800" height="600" version="1.1">
<g style="stroke:#000000;stroke-width:4">
<path style="fill:#f00;" d="m 348.5301,292.16164 c 0,-54.25306 21.33532,-103.52299 56.13833,-139.99391 34.93487,36.25996 56.36165,85.62469 56.36165,139.99391 0,54.20576 -21.29814,103.43708 -56.02381,140.01839 C 370.01352,396.02597 348.5301,346.60266 348.5301,292.16164 z"/>
<path style="fill:#f00;" d="m 550.44376,494.66164 c 111.83766,0 202.5,-90.66234 202.5,-202.5 0,-111.83766 -90.66234,-202.500004 -202.5,-202.500004 -57.5846,0 -109.55528,24.036074 -146.36166,62.506094 34.93488,36.25996 56.36166,85.62469 56.36166,139.99391 0,54.20576 -21.29814,103.43708 -56.02382,140.01839 36.80788,38.6022 88.62718,62.48161 146.02382,62.48161 z"/>
<path style="fill:#f00;" d="m 258.5821,494.66164 c -111.83766,0 -202.5,-90.66234 -202.5,-202.5 0,-111.83766 90.66234,-202.500004 202.5,-202.500004 57.5846,0 109.55528,24.036074 146.36166,62.506094 -34.93488,36.25996 -56.36166,85.62469 -56.36166,139.99391 0,54.20576 21.29814,103.43708 56.02382,140.01839 -36.80788,38.6022 -88.62718,62.48161 -146.02382,62.48161 z" />
</g>
<g>
<text y="312.31482" x="235.67825" style="font-size:120px;font-family:Sans"><tspan y="312" x="210">G</tspan></text>
<text y="312.31482" x="531.66656" style="font-size:120px;font-family:Sans"><tspan y="312" x="530">D</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

12
jointures/innerjoin.svg Normal file

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" width="800" height="600" version="1.1">
<g style="stroke:#000000;stroke-width:4">
<path style="fill:#f00;" d="m 348.5301,292.16164 c 0,-54.25306 21.33532,-103.52299 56.13833,-139.99391 34.93487,36.25996 56.36165,85.62469 56.36165,139.99391 0,54.20576 -21.29814,103.43708 -56.02381,140.01839 C 370.01352,396.02597 348.5301,346.60266 348.5301,292.16164 z"/>
<path style="fill:#fff;" d="m 550.44376,494.66164 c 111.83766,0 202.5,-90.66234 202.5,-202.5 0,-111.83766 -90.66234,-202.500004 -202.5,-202.500004 -57.5846,0 -109.55528,24.036074 -146.36166,62.506094 34.93488,36.25996 56.36166,85.62469 56.36166,139.99391 0,54.20576 -21.29814,103.43708 -56.02382,140.01839 36.80788,38.6022 88.62718,62.48161 146.02382,62.48161 z"/>
<path style="fill:#fff;" d="m 258.5821,494.66164 c -111.83766,0 -202.5,-90.66234 -202.5,-202.5 0,-111.83766 90.66234,-202.500004 202.5,-202.500004 57.5846,0 109.55528,24.036074 146.36166,62.506094 -34.93488,36.25996 -56.36166,85.62469 -56.36166,139.99391 0,54.20576 21.29814,103.43708 56.02382,140.01839 -36.80788,38.6022 -88.62718,62.48161 -146.02382,62.48161 z" />
</g>
<g>
<text y="312.31482" x="235.67825" style="font-size:120px;font-family:Sans"><tspan y="312" x="210">G</tspan></text>
<text y="312.31482" x="531.66656" style="font-size:120px;font-family:Sans"><tspan y="312" x="530">D</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" width="800" height="600" version="1.1">
<g style="stroke:#000000;stroke-width:4">
<path style="fill:#fff;" d="m 348.5301,292.16164 c 0,-54.25306 21.33532,-103.52299 56.13833,-139.99391 34.93487,36.25996 56.36165,85.62469 56.36165,139.99391 0,54.20576 -21.29814,103.43708 -56.02381,140.01839 C 370.01352,396.02597 348.5301,346.60266 348.5301,292.16164 z"/>
<path style="fill:#fff;" d="m 550.44376,494.66164 c 111.83766,0 202.5,-90.66234 202.5,-202.5 0,-111.83766 -90.66234,-202.500004 -202.5,-202.500004 -57.5846,0 -109.55528,24.036074 -146.36166,62.506094 34.93488,36.25996 56.36166,85.62469 56.36166,139.99391 0,54.20576 -21.29814,103.43708 -56.02382,140.01839 36.80788,38.6022 88.62718,62.48161 146.02382,62.48161 z"/>
<path style="fill:#f00;" d="m 258.5821,494.66164 c -111.83766,0 -202.5,-90.66234 -202.5,-202.5 0,-111.83766 90.66234,-202.500004 202.5,-202.500004 57.5846,0 109.55528,24.036074 146.36166,62.506094 -34.93488,36.25996 -56.36166,85.62469 -56.36166,139.99391 0,54.20576 21.29814,103.43708 56.02382,140.01839 -36.80788,38.6022 -88.62718,62.48161 -146.02382,62.48161 z" />
</g>
<g>
<text y="312.31482" x="235.67825" style="font-size:120px;font-family:Sans"><tspan y="312" x="210">G</tspan></text>
<text y="312.31482" x="531.66656" style="font-size:120px;font-family:Sans"><tspan y="312" x="530">D</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

12
jointures/leftjoin.svg Normal file

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" width="800" height="600" version="1.1">
<g style="stroke:#000000;stroke-width:4">
<path style="fill:#f00;" d="m 348.5301,292.16164 c 0,-54.25306 21.33532,-103.52299 56.13833,-139.99391 34.93487,36.25996 56.36165,85.62469 56.36165,139.99391 0,54.20576 -21.29814,103.43708 -56.02381,140.01839 C 370.01352,396.02597 348.5301,346.60266 348.5301,292.16164 z"/>
<path style="fill:#fff;" d="m 550.44376,494.66164 c 111.83766,0 202.5,-90.66234 202.5,-202.5 0,-111.83766 -90.66234,-202.500004 -202.5,-202.500004 -57.5846,0 -109.55528,24.036074 -146.36166,62.506094 34.93488,36.25996 56.36166,85.62469 56.36166,139.99391 0,54.20576 -21.29814,103.43708 -56.02382,140.01839 36.80788,38.6022 88.62718,62.48161 146.02382,62.48161 z"/>
<path style="fill:#f00;" d="m 258.5821,494.66164 c -111.83766,0 -202.5,-90.66234 -202.5,-202.5 0,-111.83766 90.66234,-202.500004 202.5,-202.500004 57.5846,0 109.55528,24.036074 146.36166,62.506094 -34.93488,36.25996 -56.36166,85.62469 -56.36166,139.99391 0,54.20576 21.29814,103.43708 56.02382,140.01839 -36.80788,38.6022 -88.62718,62.48161 -146.02382,62.48161 z" />
</g>
<g>
<text y="312.31482" x="235.67825" style="font-size:120px;font-family:Sans"><tspan y="312" x="210">G</tspan></text>
<text y="312.31482" x="531.66656" style="font-size:120px;font-family:Sans"><tspan y="312" x="530">D</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" width="800" height="600" version="1.1">
<g style="stroke:#000000;stroke-width:4">
<path style="fill:#fff;" d="m 348.5301,292.16164 c 0,-54.25306 21.33532,-103.52299 56.13833,-139.99391 34.93487,36.25996 56.36165,85.62469 56.36165,139.99391 0,54.20576 -21.29814,103.43708 -56.02381,140.01839 C 370.01352,396.02597 348.5301,346.60266 348.5301,292.16164 z"/>
<path style="fill:#f00;" d="m 550.44376,494.66164 c 111.83766,0 202.5,-90.66234 202.5,-202.5 0,-111.83766 -90.66234,-202.500004 -202.5,-202.500004 -57.5846,0 -109.55528,24.036074 -146.36166,62.506094 34.93488,36.25996 56.36166,85.62469 56.36166,139.99391 0,54.20576 -21.29814,103.43708 -56.02382,140.01839 36.80788,38.6022 88.62718,62.48161 146.02382,62.48161 z"/>
<path style="fill:#fff;" d="m 258.5821,494.66164 c -111.83766,0 -202.5,-90.66234 -202.5,-202.5 0,-111.83766 90.66234,-202.500004 202.5,-202.500004 57.5846,0 109.55528,24.036074 146.36166,62.506094 -34.93488,36.25996 -56.36166,85.62469 -56.36166,139.99391 0,54.20576 21.29814,103.43708 56.02382,140.01839 -36.80788,38.6022 -88.62718,62.48161 -146.02382,62.48161 z" />
</g>
<g>
<text y="312.31482" x="235.67825" style="font-size:120px;font-family:Sans"><tspan y="312" x="210">G</tspan></text>
<text y="312.31482" x="531.66656" style="font-size:120px;font-family:Sans"><tspan y="312" x="530">D</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

12
jointures/rightjoin.svg Normal file

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" width="800" height="600" version="1.1">
<g style="stroke:#000000;stroke-width:4">
<path style="fill:#f00;" d="m 348.5301,292.16164 c 0,-54.25306 21.33532,-103.52299 56.13833,-139.99391 34.93487,36.25996 56.36165,85.62469 56.36165,139.99391 0,54.20576 -21.29814,103.43708 -56.02381,140.01839 C 370.01352,396.02597 348.5301,346.60266 348.5301,292.16164 z"/>
<path style="fill:#f00;" d="m 550.44376,494.66164 c 111.83766,0 202.5,-90.66234 202.5,-202.5 0,-111.83766 -90.66234,-202.500004 -202.5,-202.500004 -57.5846,0 -109.55528,24.036074 -146.36166,62.506094 34.93488,36.25996 56.36166,85.62469 56.36166,139.99391 0,54.20576 -21.29814,103.43708 -56.02382,140.01839 36.80788,38.6022 88.62718,62.48161 146.02382,62.48161 z"/>
<path style="fill:#fff;" d="m 258.5821,494.66164 c -111.83766,0 -202.5,-90.66234 -202.5,-202.5 0,-111.83766 90.66234,-202.500004 202.5,-202.500004 57.5846,0 109.55528,24.036074 146.36166,62.506094 -34.93488,36.25996 -56.36166,85.62469 -56.36166,139.99391 0,54.20576 21.29814,103.43708 56.02382,140.01839 -36.80788,38.6022 -88.62718,62.48161 -146.02382,62.48161 z" />
</g>
<g>
<text y="312.31482" x="235.67825" style="font-size:120px;font-family:Sans"><tspan y="312" x="210">G</tspan></text>
<text y="312.31482" x="531.66656" style="font-size:120px;font-family:Sans"><tspan y="312" x="530">D</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

83
jointures/td.md Normal file

@@ -0,0 +1,83 @@
Le nombre de produit par catégories
```sql
SELECT c.CategoryName, COUNT(p.ProductID) AS ProductCount
FROM categories c
JOIN products p ON c.category_id = p.category_id
GROUP BY c.CategoryName;
```
Les produits jamais commandés
```sql
select * from products p
left join order_details od on od.ProductID = p.ProductID
where od.OrderDetailID is null;
```
Les ventes mois par mois de Dodsworth
```sql
select year(OrderDate), month(OrderDate), d.unit_price * d.quantity
from order_details d
join orders o on o.OrderID = d.OrderID
join employees y on y.employee_id = o.employee_id
where LastName = 'Dodsworth'
group by year(OrderDate), month(OrderDate)
```
```sql
CREATE TABLE IF NOT EXISTS calendrier (
DateValue DATE PRIMARY KEY,
YearValue INT,
MonthValue INT,
DayValue INT,
WeekDayName VARCHAR(10),
WeekDayNumber INT,
WeekNumber INT,
QuarterValue INT
);
```
```sql
SET SESSION max_recursive_iterations = 100000;
INSERT INTO calendrier
WITH RECURSIVE CalendarCTE AS (
SELECT
'2020-01-01' AS DateValue,
YEAR('2020-01-01') AS YearValue,
MONTH('2020-01-01') AS MonthValue,
DAY('2020-01-01') AS DayValue,
DAYNAME('2020-01-01') AS WeekDayName,
WEEKDAY('2020-01-01') + 1 AS WeekDayNumber, -- WEEKDAY retourne de 0 à 6, on ajuste pour avoir 1 à 7
WEEK('2020-01-01', 3) AS WeekNumber, -- Mode 3 : semaines commencent le lundi
QUARTER('2020-01-01') AS QuarterValue
UNION ALL
SELECT
DATE_ADD(DateValue, INTERVAL 1 DAY),
YEAR(DATE_ADD(DateValue, INTERVAL 1 DAY)),
MONTH(DATE_ADD(DateValue, INTERVAL 1 DAY)),
DAY(DATE_ADD(DateValue, INTERVAL 1 DAY)),
DAYNAME(DATE_ADD(DateValue, INTERVAL 1 DAY)),
WEEKDAY(DATE_ADD(DateValue, INTERVAL 1 DAY)) + 1,
WEEK(DATE_ADD(DateValue, INTERVAL 1 DAY), 3),
QUARTER(DATE_ADD(DateValue, INTERVAL 1 DAY))
FROM CalendarCTE
WHERE DATE_ADD(DateValue, INTERVAL 1 DAY) <= '2030-12-31'
)
SELECT * FROM CalendarCTE;
```
```sql
select c.YearValue , c.MonthValue, sum(montant)
from calendrier c
left join
(select
o.OrderDate, d.unit_price * d.quantity as montant
from order_details d
left join orders o on o.OrderID = d.OrderID
left join employees y on y.employee_id = o.employee_id
where LastName = 'Dodsworth') k on k.OrderDate = c.DateValue
group by c.YearValue , c.MonthValue
```