ajout de tous les cours et TP préparés cet été

This commit is contained in:
2026-01-17 23:10:49 +01:00
parent ed9415bc81
commit 301cf5a98f
125 changed files with 21614 additions and 542 deletions

464
SQL/CORRIGE.md Normal file
View File

@@ -0,0 +1,464 @@
# Corrigé des exercices — SQL
---
## Exercice 1 : Requêtes SELECT simples
1. **Tous les livres de la table LIVRES.**
```sql
SELECT * FROM LIVRES;
```
2. **Les titres et années de publication de tous les livres.**
```sql
SELECT titre, ann_publi FROM LIVRES;
```
3. **Les noms et prénoms de tous les auteurs.**
```sql
SELECT nom, prenom FROM AUTEURS;
```
4. **Les titres des livres publiés après 1950.**
```sql
SELECT titre FROM LIVRES WHERE ann_publi > 1950;
```
5. **Les titres des livres du genre "dystopie".**
```sql
SELECT titre FROM LIVRES WHERE genre = 'dystopie';
```
6. **Les auteurs de nationalité française.**
```sql
SELECT * FROM AUTEURS WHERE nationalite = 'français';
```
---
## Exercice 2 : Filtres et conditions
1. **Les livres publiés entre 1940 et 1960 (inclus).**
```sql
SELECT * FROM LIVRES WHERE ann_publi BETWEEN 1940 AND 1960;
-- ou
SELECT * FROM LIVRES WHERE ann_publi >= 1940 AND ann_publi <= 1960;
```
2. **Les livres ayant plus de 300 pages.**
```sql
SELECT * FROM LIVRES WHERE nb_pages > 300;
```
3. **Les auteurs nés après 1900 et de nationalité américaine.**
```sql
SELECT * FROM AUTEURS
WHERE ann_naissance > 1900 AND nationalite = 'américain';
```
4. **Les livres dont le titre contient le mot "Terre".**
```sql
SELECT * FROM LIVRES WHERE titre LIKE '%Terre%';
```
5. **Les livres qui ne sont pas du genre "SF".**
```sql
SELECT * FROM LIVRES WHERE genre <> 'SF';
-- ou
SELECT * FROM LIVRES WHERE genre != 'SF';
-- ou
SELECT * FROM LIVRES WHERE NOT genre = 'SF';
```
6. **Les auteurs dont le prénom commence par la lettre "R".**
```sql
SELECT * FROM AUTEURS WHERE prenom LIKE 'R%';
```
---
## Exercice 3 : Tri et limitation
1. **Tous les livres triés par année de publication (du plus ancien au plus récent).**
```sql
SELECT * FROM LIVRES ORDER BY ann_publi ASC;
-- ou simplement
SELECT * FROM LIVRES ORDER BY ann_publi;
```
2. **Les 5 livres les plus récents.**
```sql
SELECT * FROM LIVRES ORDER BY ann_publi DESC LIMIT 5;
```
3. **Les auteurs triés par année de naissance décroissante.**
```sql
SELECT * FROM AUTEURS ORDER BY ann_naissance DESC;
```
4. **Les 3 livres ayant le plus de pages.**
```sql
SELECT * FROM LIVRES ORDER BY nb_pages DESC LIMIT 3;
```
5. **Les livres triés par genre, puis par titre alphabétique.**
```sql
SELECT * FROM LIVRES ORDER BY genre ASC, titre ASC;
```
6. **La liste des genres sans doublons.**
```sql
SELECT DISTINCT genre FROM LIVRES;
```
---
## Exercice 4 : Fonctions d'agrégation
1. **Le nombre total de livres dans la base.**
```sql
SELECT COUNT(*) FROM LIVRES;
-- Résultat : 12
```
2. **Le nombre de pages du livre le plus long.**
```sql
SELECT MAX(nb_pages) FROM LIVRES;
-- Résultat : 896 (Dune)
```
3. **L'année de publication du livre le plus ancien.**
```sql
SELECT MIN(ann_publi) FROM LIVRES;
-- Résultat : 1865 (De la Terre à la Lune)
```
4. **Le nombre moyen de pages des livres.**
```sql
SELECT AVG(nb_pages) FROM LIVRES;
-- Résultat : environ 333.25
```
5. **Le nombre de livres par genre.**
```sql
SELECT genre, COUNT(*) AS nb_livres
FROM LIVRES
GROUP BY genre;
```
Résultat :
| genre | nb_livres |
|-------|-----------|
| aventure | 2 |
| dystopie | 3 |
| post-apo | 1 |
| SF | 4 |
| space opera | 2 |
6. **Le nombre total de pages de tous les livres de Jules Verne (id_auteur = 8).**
```sql
SELECT SUM(nb_pages) FROM LIVRES WHERE id_auteur = 8;
-- Résultat : 672 (192 + 480)
```
---
## Exercice 5 : GROUP BY et HAVING
1. **Le nombre de livres écrits par chaque auteur.**
```sql
SELECT id_auteur, COUNT(*) AS nb_livres
FROM LIVRES
GROUP BY id_auteur;
```
2. **Le nombre moyen de pages par genre.**
```sql
SELECT genre, AVG(nb_pages) AS moy_pages
FROM LIVRES
GROUP BY genre;
```
3. **Les genres ayant plus de 2 livres.**
```sql
SELECT genre, COUNT(*) AS nb_livres
FROM LIVRES
GROUP BY genre
HAVING COUNT(*) > 2;
```
Résultat : dystopie (3), SF (4)
4. **Les auteurs ayant écrit au moins 2 livres.**
```sql
SELECT id_auteur, COUNT(*) AS nb_livres
FROM LIVRES
GROUP BY id_auteur
HAVING COUNT(*) >= 2;
```
Résultat : id_auteur 3, 5, 7, 8 (Asimov, Bradbury, Barjavel, Verne)
5. **L'année de publication du premier livre de chaque auteur.**
```sql
SELECT id_auteur, MIN(ann_publi) AS premier_livre
FROM LIVRES
GROUP BY id_auteur;
```
6. **Les genres dont la moyenne de pages dépasse 250.**
```sql
SELECT genre, AVG(nb_pages) AS moy_pages
FROM LIVRES
GROUP BY genre
HAVING AVG(nb_pages) > 250;
```
---
## Exercice 6 : Jointures
1. **Les titres des livres avec le nom et prénom de leur auteur.**
```sql
SELECT L.titre, A.nom, A.prenom
FROM LIVRES L
INNER JOIN AUTEURS A ON L.id_auteur = A.id;
```
2. **Les titres des livres écrits par des auteurs français.**
```sql
SELECT L.titre
FROM LIVRES L
INNER JOIN AUTEURS A ON L.id_auteur = A.id
WHERE A.nationalite = 'français';
```
Résultat : La nuit des temps, Ravage, De la Terre à la Lune, Vingt mille lieues sous les mers
3. **Les titres et auteurs des livres du genre "dystopie", triés par année.**
```sql
SELECT L.titre, A.nom, A.prenom, L.ann_publi
FROM LIVRES L
INNER JOIN AUTEURS A ON L.id_auteur = A.id
WHERE L.genre = 'dystopie'
ORDER BY L.ann_publi;
```
4. **Le nom des auteurs ayant écrit des livres de plus de 400 pages.**
```sql
SELECT DISTINCT A.nom, A.prenom
FROM AUTEURS A
INNER JOIN LIVRES L ON A.id = L.id_auteur
WHERE L.nb_pages > 400;
```
Résultat : Herbert Frank, Verne Jules
5. **Les livres avec le nom de l'auteur, pour les auteurs américains nés après 1910.**
```sql
SELECT L.titre, A.nom
FROM LIVRES L
INNER JOIN AUTEURS A ON L.id_auteur = A.id
WHERE A.nationalite = 'américain' AND A.ann_naissance > 1910;
```
6. **Pour chaque nationalité, le nombre de livres écrits.**
```sql
SELECT A.nationalite, COUNT(*) AS nb_livres
FROM AUTEURS A
INNER JOIN LIVRES L ON A.id = L.id_auteur
GROUP BY A.nationalite;
```
Résultat :
| nationalite | nb_livres |
|-------------|-----------|
| américain | 6 |
| britannique | 2 |
| français | 4 |
---
## Exercice 7 : Requêtes complexes
1. **Le titre du livre le plus long de chaque auteur, avec le nom de l'auteur.**
```sql
SELECT A.nom, A.prenom, L.titre, L.nb_pages
FROM LIVRES L
INNER JOIN AUTEURS A ON L.id_auteur = A.id
WHERE L.nb_pages = (
SELECT MAX(nb_pages)
FROM LIVRES
WHERE id_auteur = L.id_auteur
);
```
2. **Les auteurs n'ayant écrit qu'un seul livre.**
```sql
SELECT A.nom, A.prenom, L.titre
FROM AUTEURS A
INNER JOIN LIVRES L ON A.id = L.id_auteur
WHERE A.id IN (
SELECT id_auteur
FROM LIVRES
GROUP BY id_auteur
HAVING COUNT(*) = 1
);
```
3. **Le nombre moyen de pages des livres par nationalité d'auteur.**
```sql
SELECT A.nationalite, AVG(L.nb_pages) AS moy_pages
FROM AUTEURS A
INNER JOIN LIVRES L ON A.id = L.id_auteur
GROUP BY A.nationalite;
```
4. **Les genres pour lesquels tous les livres ont été publiés après 1940.**
```sql
SELECT genre
FROM LIVRES
GROUP BY genre
HAVING MIN(ann_publi) > 1940;
```
Résultat : dystopie (min 1931... non!), SF (min 1950), space opera (min 1951)
Note : En vérifiant les données, "Le meilleur des mondes" (dystopie) est de 1931, donc dystopie ne correspond pas. Les genres valides sont : SF, space opera, post-apo.
5. **Le nom complet des auteurs et le nombre total de pages qu'ils ont écrites.**
```sql
SELECT A.prenom || ' ' || A.nom AS nom_complet, SUM(L.nb_pages) AS total_pages
FROM AUTEURS A
INNER JOIN LIVRES L ON A.id = L.id_auteur
GROUP BY A.id
ORDER BY total_pages DESC;
```
---
## Exercice 8 : INSERT, UPDATE, DELETE
1. **Ajouter l'auteur Ursula K. Le Guin.**
```sql
INSERT INTO AUTEURS (id, nom, prenom, nationalite, ann_naissance)
VALUES (9, 'Le Guin', 'Ursula K.', 'américain', 1929);
```
2. **Ajouter le livre "La Main gauche de la nuit".**
```sql
INSERT INTO LIVRES (id, titre, id_auteur, ann_publi, genre, nb_pages)
VALUES (13, 'La Main gauche de la nuit', 9, 1969, 'SF', 304);
```
3. **Changer le genre de "Dune" en "SF".**
```sql
UPDATE LIVRES
SET genre = 'SF'
WHERE titre = 'Dune';
```
4. **Augmenter de 10% le nombre de pages de tous les livres de Jules Verne.**
```sql
UPDATE LIVRES
SET nb_pages = nb_pages * 1.1
WHERE id_auteur = 8;
```
5. **Supprimer tous les livres publiés avant 1900.**
```sql
DELETE FROM LIVRES
WHERE ann_publi < 1900;
```
Note : Cela supprimera "De la Terre à la Lune" (1865) et "Vingt mille lieues sous les mers" (1870).
---
## Exercice 9 : Création de tables
1. **Créer la table EMPRUNTS.**
```sql
CREATE TABLE EMPRUNTS (
id INTEGER PRIMARY KEY,
id_livre INTEGER,
id_adherent INTEGER,
date_emprunt DATE,
date_retour DATE,
FOREIGN KEY (id_livre) REFERENCES LIVRES(id)
);
```
2. **Créer la table EDITEURS.**
```sql
CREATE TABLE EDITEURS (
id INTEGER PRIMARY KEY,
nom TEXT,
pays TEXT,
ann_creation INTEGER
);
```
3. **Ajouter une colonne id_editeur à la table LIVRES.**
```sql
ALTER TABLE LIVRES ADD COLUMN id_editeur INTEGER REFERENCES EDITEURS(id);
```
---
## Exercice 10 : Analyse de requêtes
1. **Requête avec sous-requête MIN :**
```sql
SELECT titre FROM LIVRES WHERE ann_publi = (SELECT MIN(ann_publi) FROM LIVRES);
```
**Explication :** Cette requête retourne le titre du livre le plus ancien de la base. La sous-requête trouve l'année minimale, puis la requête principale sélectionne le(s) livre(s) publié(s) cette année-là.
**Résultat :** "De la Terre à la Lune" (1865)
2. **Requête complexe avec double sous-requête :**
```sql
SELECT A.nom, COUNT(*)
FROM AUTEURS A
INNER JOIN LIVRES L ON A.id = L.id_auteur
GROUP BY A.id
HAVING COUNT(*) = (SELECT MAX(cnt) FROM (SELECT COUNT(*) as cnt FROM LIVRES GROUP BY id_auteur));
```
**Explication :** Cette requête retourne le(s) auteur(s) ayant écrit le plus grand nombre de livres. La sous-requête interne compte les livres par auteur, la sous-requête externe trouve le maximum, et la requête principale affiche les auteurs atteignant ce maximum.
**Résultat :** Les auteurs avec 2 livres chacun (Asimov, Bradbury, Barjavel, Verne)
3. **Requête avec NOT IN :**
```sql
SELECT DISTINCT genre FROM LIVRES WHERE genre NOT IN (SELECT genre FROM LIVRES WHERE ann_publi < 1950);
```
**Explication :** Cette requête retourne les genres pour lesquels AUCUN livre n'a été publié avant 1950. La sous-requête trouve les genres ayant au moins un livre avant 1950, puis la requête principale exclut ces genres.
**Résultat :** Les genres dont tous les livres sont de 1950 ou après (SF, space opera, potentiellement post-apo selon les données)
---
## Exercice 11 : QCM
1. **Quelle clause permet de filtrer les résultats d'un GROUP BY ?**
- **Réponse : c) HAVING**
2. **Quelle est la différence entre WHERE et HAVING ?**
- **Réponse : b) WHERE filtre avant GROUP BY, HAVING filtre après**
Explication : WHERE filtre les lignes individuelles avant le regroupement. HAVING filtre les groupes après le regroupement et permet d'utiliser des fonctions d'agrégation.
3. **Que retourne `SELECT COUNT(*) FROM LIVRES WHERE 1=0;` ?**
- **Réponse : c) 0**
Explication : La condition `1=0` est toujours fausse, donc aucune ligne n'est sélectionnée. COUNT(*) sur un ensemble vide retourne 0 (pas NULL, pas d'erreur).
4. **Quel type de jointure retourne tous les enregistrements de la table de gauche ?**
- **Réponse : b) LEFT JOIN**
Explication : LEFT JOIN retourne toutes les lignes de la table de gauche, même si elles n'ont pas de correspondance dans la table de droite (avec NULL pour les colonnes de droite).
5. **Quelle fonction d'agrégation ignore les valeurs NULL ?**
- **Réponse : b) COUNT(colonne)**
Explication : `COUNT(*)` compte toutes les lignes, y compris celles avec des NULL. `COUNT(colonne)` ne compte que les valeurs non NULL de cette colonne.
---
Auteur : Florian Mathieu
Licence CC BY NC
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.

View File

@@ -0,0 +1,407 @@
# Corrigé du TP : Base de données d'un service de streaming musical
---
## Partie 1 : Schéma relationnel
```
ARTISTES ALBUMS
+----+---------+ +----+--------+-----------+
| id | nom | | id | titre | id_artiste|
| PK | ... |<─────────────────| PK | ... | FK |
+----+---------+ +----+--------+-----------+
TITRES
+----+------+----------+
| id | nom | id_album |
| PK | ... | FK |
+----+------+----------+
UTILISATEURS │
+----+--------+ │
| id | pseudo | │
| PK | ... | │
+----+--------+ │
│ │
│ ECOUTES │
│ +----+--------------+-----+
└────────>| id | id_utilisateur| id_titre |
| PK | FK | FK |
+----+--------------+----------+
```
---
## Partie 2 : Requêtes de base
**1. Tous les artistes**
```sql
SELECT * FROM ARTISTES;
```
**2. Artistes français**
```sql
SELECT nom, pays FROM ARTISTES WHERE pays = 'France';
```
Résultat : Daft Punk, PNL, Aya Nakamura
**3. Albums sortis après 2015**
```sql
SELECT titre FROM ALBUMS WHERE ann_sortie > 2015;
```
**4. Utilisateurs premium**
```sql
SELECT pseudo FROM UTILISATEURS WHERE type_abo = 'premium';
```
Résultat : music_lover_33, kevin_hiphop, thomas_electro, jp_kpop
**5. Titres de plus de 4 minutes**
```sql
SELECT nom, duree_sec FROM TITRES WHERE duree_sec > 240;
```
**6. Artistes Hip-Hop ou Pop**
```sql
SELECT * FROM ARTISTES WHERE genre_principal IN ('Hip-Hop', 'Pop');
-- ou
SELECT * FROM ARTISTES WHERE genre_principal = 'Hip-Hop' OR genre_principal = 'Pop';
```
---
## Partie 3 : Requêtes avec jointures
**7. Titres avec nom d'album**
```sql
SELECT T.nom AS titre, A.titre AS album
FROM TITRES T
INNER JOIN ALBUMS A ON T.id_album = A.id;
```
**8. Titres avec nom d'artiste**
```sql
SELECT T.nom AS titre, AR.nom AS artiste
FROM TITRES T
INNER JOIN ALBUMS AL ON T.id_album = AL.id
INNER JOIN ARTISTES AR ON AL.id_artiste = AR.id;
```
**9. Albums de Daft Punk**
```sql
SELECT AL.titre, AL.ann_sortie
FROM ALBUMS AL
INNER JOIN ARTISTES AR ON AL.id_artiste = AR.id
WHERE AR.nom = 'Daft Punk';
```
Résultat : Random Access Memories (2013), Discovery (2001)
**10. Écoutes avec pseudo et titre**
```sql
SELECT U.pseudo, T.nom AS titre, E.date_ecoute
FROM ECOUTES E
INNER JOIN UTILISATEURS U ON E.id_utilisateur = U.id
INNER JOIN TITRES T ON E.id_titre = T.id;
```
**11. Titres des artistes français**
```sql
SELECT T.nom AS titre, AR.nom AS artiste
FROM TITRES T
INNER JOIN ALBUMS AL ON T.id_album = AL.id
INNER JOIN ARTISTES AR ON AL.id_artiste = AR.id
WHERE AR.pays = 'France';
```
**12. Écoutes complètes**
```sql
SELECT U.pseudo, T.nom AS titre, AL.titre AS album, AR.nom AS artiste
FROM ECOUTES E
INNER JOIN UTILISATEURS U ON E.id_utilisateur = U.id
INNER JOIN TITRES T ON E.id_titre = T.id
INNER JOIN ALBUMS AL ON T.id_album = AL.id
INNER JOIN ARTISTES AR ON AL.id_artiste = AR.id;
```
---
## Partie 4 : Agrégations
**13. Nombre de titres**
```sql
SELECT COUNT(*) AS nb_titres FROM TITRES;
```
Résultat : 43
**14. Durée moyenne des titres**
```sql
SELECT AVG(duree_sec) AS duree_moyenne FROM TITRES;
```
Résultat : environ 227 secondes
**15. Titre le plus long et le plus court**
```sql
-- Le plus long
SELECT nom, duree_sec FROM TITRES WHERE duree_sec = (SELECT MAX(duree_sec) FROM TITRES);
-- Résultat : Get Lucky (369 sec)
-- Le plus court
SELECT nom, duree_sec FROM TITRES WHERE duree_sec = (SELECT MIN(duree_sec) FROM TITRES);
-- Résultat : SMS (164 sec)
```
**16. Nombre d'albums par artiste**
```sql
SELECT AR.nom, COUNT(*) AS nb_albums
FROM ARTISTES AR
INNER JOIN ALBUMS AL ON AR.id = AL.id_artiste
GROUP BY AR.id
ORDER BY nb_albums DESC;
```
**17. Nombre d'écoutes par utilisateur**
```sql
SELECT U.pseudo, COUNT(*) AS nb_ecoutes
FROM UTILISATEURS U
INNER JOIN ECOUTES E ON U.id = E.id_utilisateur
GROUP BY U.id
ORDER BY nb_ecoutes DESC;
```
**18. Nombre de titres par genre**
```sql
SELECT AR.genre_principal, COUNT(*) AS nb_titres
FROM ARTISTES AR
INNER JOIN ALBUMS AL ON AR.id = AL.id_artiste
INNER JOIN TITRES T ON AL.id = T.id_album
GROUP BY AR.genre_principal
ORDER BY nb_titres DESC;
```
---
## Partie 5 : Requêtes avancées
**19. Artistes avec plus d'un album**
```sql
SELECT AR.nom, COUNT(*) AS nb_albums
FROM ARTISTES AR
INNER JOIN ALBUMS AL ON AR.id = AL.id_artiste
GROUP BY AR.id
HAVING COUNT(*) > 1;
```
**20. Top 5 des titres les plus écoutés**
```sql
SELECT T.nom, COUNT(*) AS nb_ecoutes
FROM TITRES T
INNER JOIN ECOUTES E ON T.id = E.id_titre
GROUP BY T.id
ORDER BY nb_ecoutes DESC
LIMIT 5;
```
**21. Utilisateurs ayant écouté Stromae**
```sql
SELECT DISTINCT U.pseudo
FROM UTILISATEURS U
INNER JOIN ECOUTES E ON U.id = E.id_utilisateur
INNER JOIN TITRES T ON E.id_titre = T.id
INNER JOIN ALBUMS AL ON T.id_album = AL.id
INNER JOIN ARTISTES AR ON AL.id_artiste = AR.id
WHERE AR.nom = 'Stromae';
```
**22. Durée totale d'écoute par utilisateur (en minutes)**
```sql
SELECT U.pseudo, SUM(E.duree_ecoute_sec) / 60.0 AS duree_totale_min
FROM UTILISATEURS U
INNER JOIN ECOUTES E ON U.id = E.id_utilisateur
GROUP BY U.id
ORDER BY duree_totale_min DESC;
```
**23. Artistes sans titres explicites**
```sql
SELECT DISTINCT AR.nom
FROM ARTISTES AR
WHERE AR.id NOT IN (
SELECT DISTINCT AL.id_artiste
FROM ALBUMS AL
INNER JOIN TITRES T ON AL.id = T.id_album
WHERE T.explicite = TRUE
);
```
**24. Écoutes par pays d'utilisateur**
```sql
SELECT U.pays, COUNT(*) AS nb_ecoutes, SUM(E.duree_ecoute_sec) / 60.0 AS duree_totale_min
FROM UTILISATEURS U
INNER JOIN ECOUTES E ON U.id = E.id_utilisateur
GROUP BY U.pays
ORDER BY nb_ecoutes DESC;
```
---
## Partie 6 : Analyse business
**25. Taux de conversion**
```sql
SELECT type_abo, COUNT(*) AS nb_utilisateurs
FROM UTILISATEURS
GROUP BY type_abo;
-- Ou en pourcentage
SELECT
type_abo,
COUNT(*) AS nb,
ROUND(COUNT(*) * 100.0 / (SELECT COUNT(*) FROM UTILISATEURS), 1) AS pourcentage
FROM UTILISATEURS
GROUP BY type_abo;
```
**26. Artistes populaires auprès des Français**
```sql
SELECT AR.nom, COUNT(*) AS nb_ecoutes
FROM ARTISTES AR
INNER JOIN ALBUMS AL ON AR.id = AL.id_artiste
INNER JOIN TITRES T ON AL.id = T.id_album
INNER JOIN ECOUTES E ON T.id = E.id_titre
INNER JOIN UTILISATEURS U ON E.id_utilisateur = U.id
WHERE U.pays = 'France'
GROUP BY AR.id
ORDER BY nb_ecoutes DESC;
```
**27. Utilisateurs Daft Punk mais pas Stromae**
```sql
SELECT DISTINCT U.pseudo
FROM UTILISATEURS U
INNER JOIN ECOUTES E ON U.id = E.id_utilisateur
INNER JOIN TITRES T ON E.id_titre = T.id
INNER JOIN ALBUMS AL ON T.id_album = AL.id
INNER JOIN ARTISTES AR ON AL.id_artiste = AR.id
WHERE AR.nom = 'Daft Punk'
AND U.id NOT IN (
SELECT DISTINCT U2.id
FROM UTILISATEURS U2
INNER JOIN ECOUTES E2 ON U2.id = E2.id_utilisateur
INNER JOIN TITRES T2 ON E2.id_titre = T2.id
INNER JOIN ALBUMS AL2 ON T2.id_album = AL2.id
INNER JOIN ARTISTES AR2 ON AL2.id_artiste = AR2.id
WHERE AR2.nom = 'Stromae'
);
```
**28. Pourcentage de contenu explicite**
```sql
SELECT
ROUND(SUM(CASE WHEN explicite = TRUE THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 1) AS pct_explicite
FROM TITRES;
```
**29. Utilisateurs anciens actifs**
```sql
SELECT DISTINCT U.pseudo, U.date_inscription
FROM UTILISATEURS U
INNER JOIN ECOUTES E ON U.id = E.id_utilisateur
WHERE U.date_inscription < '2021-01-01'
AND E.date_ecoute BETWEEN '2024-01-01' AND '2024-01-31';
```
**30. Vue synthétique par artiste**
```sql
SELECT
AR.nom AS artiste,
AR.pays,
AR.genre_principal AS genre,
COUNT(DISTINCT AL.id) AS nb_albums,
COUNT(DISTINCT T.id) AS nb_titres,
COUNT(E.id) AS nb_ecoutes
FROM ARTISTES AR
LEFT JOIN ALBUMS AL ON AR.id = AL.id_artiste
LEFT JOIN TITRES T ON AL.id = T.id_album
LEFT JOIN ECOUTES E ON T.id = E.id_titre
GROUP BY AR.id
ORDER BY nb_ecoutes DESC;
```
---
## Partie 7 : Modifications de données
**31. Ajouter Angèle**
```sql
INSERT INTO ARTISTES (id, nom, pays, genre_principal, ann_debut)
VALUES (11, 'Angèle', 'Belgique', 'Pop', 2014);
```
**32. Ajouter l'album Brol**
```sql
INSERT INTO ALBUMS (id, titre, id_artiste, ann_sortie, nb_titres)
VALUES (19, 'Brol', 11, 2018, 15);
```
**33. Changer l'abonnement d'alex_beats**
```sql
UPDATE UTILISATEURS
SET type_abo = 'etudiant'
WHERE pseudo = 'alex_beats';
```
**34. Supprimer les écoutes de jp_kpop**
```sql
DELETE FROM ECOUTES
WHERE id_utilisateur = (SELECT id FROM UTILISATEURS WHERE pseudo = 'jp_kpop');
```
---
## Questions de réflexion
**1. Pourquoi séparer artistes et albums ?**
- Éviter la redondance : les infos de l'artiste ne sont stockées qu'une fois
- Faciliter les mises à jour : changer le pays d'un artiste se fait en un seul endroit
- Garantir la cohérence : pas de risque d'avoir "Daft Punk" et "DaftPunk"
- C'est le principe de **normalisation** des bases de données
**2. Problème de suppression d'un artiste**
- **Violation de contrainte d'intégrité référentielle** : les albums font référence à cet artiste via la clé étrangère
- Solutions possibles :
- Empêcher la suppression (comportement par défaut)
- Supprimer en cascade tous les albums et titres associés (`ON DELETE CASCADE`)
- Mettre la référence à NULL (`ON DELETE SET NULL`)
**3. Gérer les collaborations**
- Créer une **table de liaison** COLLABORATIONS :
```sql
CREATE TABLE COLLABORATIONS (
id_titre INTEGER,
id_artiste INTEGER,
role TEXT, -- 'principal', 'featuring', 'producteur'
PRIMARY KEY (id_titre, id_artiste)
);
```
- Ou modifier la table TITRES pour permettre plusieurs artistes
**4. Informations supplémentaires pour un vrai service**
- **Playlists** : table PLAYLISTS avec les titres associés
- **Historique complet** : horodatage précis des écoutes
- **Préférences** : genres favoris, artistes suivis
- **Social** : amis, partages, playlists collaboratives
- **Paiements** : historique des abonnements, factures
- **Métadonnées audio** : tempo, tonalité, énergie (pour les recommandations)
- **Paroles** : table LYRICS liée aux titres
- **Podcasts** : extension du modèle pour d'autres types de contenu
---
Auteur : Florian Mathieu
Licence CC BY NC
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.

554
SQL/Cours_SQL.md Normal file
View File

@@ -0,0 +1,554 @@
# Cours : Le langage SQL
---
## 1. Introduction
### 1.1. Historique
**SQL** (*Structured Query Language*) est un langage de requêtes créé en 1974 par IBM, puis normalisé en 1986. Il permet d'interagir avec les **bases de données relationnelles**.
SQL est aujourd'hui le standard universel pour :
- Créer et modifier la structure des bases de données
- Insérer, modifier et supprimer des données
- Interroger les données (requêtes)
### 1.2. Les SGBD
Un **SGBD** (*Système de Gestion de Base de Données*) est un logiciel qui permet de stocker, organiser et manipuler des données. Exemples :
| SGBD | Type | Utilisation |
|------|------|-------------|
| SQLite | Léger, fichier local | Applications mobiles, embarqué |
| MySQL / MariaDB | Serveur | Sites web, applications |
| PostgreSQL | Serveur avancé | Applications complexes |
| Oracle | Entreprise | Grandes entreprises |
| Microsoft SQL Server | Entreprise | Environnement Windows |
En NSI, nous utilisons principalement **SQLite** avec l'outil **DB Browser for SQLite**.
---
## 2. Structure d'une base de données
### 2.1. Tables, attributs et enregistrements
Une base de données relationnelle est composée de **tables**. Chaque table contient :
- Des **colonnes** (ou **attributs**) : les caractéristiques des données
- Des **lignes** (ou **enregistrements**) : les données elles-mêmes
**Exemple : Table LIVRES**
| id | titre | auteur | ann_publi | note |
|----|-------|--------|-----------|------|
| 1 | 1984 | Orwell | 1949 | 10 |
| 2 | Dune | Herbert | 1965 | 8 |
| 3 | Fondation | Asimov | 1951 | 9 |
### 2.2. Types de données
| Type SQL | Description | Exemple |
|----------|-------------|---------|
| `INT` ou `INTEGER` | Nombre entier | 42, -7, 2024 |
| `REAL` ou `FLOAT` | Nombre décimal | 3.14, -0.5 |
| `TEXT` ou `VARCHAR` | Chaîne de caractères | 'Bonjour', 'Alice' |
| `DATE` | Date | '2024-01-15' |
| `BOOLEAN` | Booléen | TRUE, FALSE |
### 2.3. Clés primaires et étrangères
- **Clé primaire** (`PRIMARY KEY`) : Identifiant unique de chaque enregistrement
- **Clé étrangère** (`FOREIGN KEY`) : Référence vers la clé primaire d'une autre table
```
Table AUTEURS Table LIVRES
+----+--------+ +----+--------+-----------+
| id | nom | | id | titre | id_auteur |
+----+--------+ +----+--------+-----------+
| 1 | Orwell |<─────────────────| 1 | 1984 | 1 |
| 2 | Asimov |<─────────────────| 2 | Robots | 2 |
+----+--------+ +----+--------+-----------+
PK PK FK
```
---
## 3. Création et modification de tables
### 3.1. Créer une table : CREATE TABLE
```sql
CREATE TABLE LIVRES (
id INTEGER PRIMARY KEY,
titre TEXT,
auteur TEXT,
ann_publi INTEGER,
note INTEGER
);
```
### 3.2. Créer une table avec clé étrangère
```sql
CREATE TABLE LIVRES (
id INTEGER PRIMARY KEY,
titre TEXT,
id_auteur INTEGER,
ann_publi INTEGER,
FOREIGN KEY (id_auteur) REFERENCES AUTEURS(id)
);
```
### 3.3. Supprimer une table : DROP TABLE
```sql
DROP TABLE LIVRES;
```
⚠️ **Attention** : Cette opération est irréversible !
### 3.4. Modifier une table : ALTER TABLE
```sql
-- Ajouter une colonne
ALTER TABLE LIVRES ADD COLUMN editeur TEXT;
-- Supprimer une colonne (selon le SGBD)
ALTER TABLE LIVRES DROP COLUMN editeur;
```
---
## 4. Manipulation des données
### 4.1. Insérer des données : INSERT INTO
```sql
-- Insérer un enregistrement
INSERT INTO LIVRES (id, titre, auteur, ann_publi, note)
VALUES (1, '1984', 'Orwell', 1949, 10);
-- Insérer plusieurs enregistrements
INSERT INTO LIVRES (id, titre, auteur, ann_publi, note)
VALUES
(2, 'Dune', 'Herbert', 1965, 8),
(3, 'Fondation', 'Asimov', 1951, 9);
```
### 4.2. Modifier des données : UPDATE
```sql
-- Modifier tous les enregistrements correspondant à la condition
UPDATE LIVRES
SET note = 10
WHERE auteur = 'Asimov';
-- Modifier plusieurs colonnes
UPDATE LIVRES
SET note = 9, ann_publi = 1950
WHERE id = 3;
```
⚠️ **Attention** : Sans clause `WHERE`, tous les enregistrements seront modifiés !
### 4.3. Supprimer des données : DELETE
```sql
-- Supprimer les enregistrements correspondant à la condition
DELETE FROM LIVRES
WHERE ann_publi < 1950;
-- Supprimer tous les enregistrements (vider la table)
DELETE FROM LIVRES;
```
⚠️ **Attention** : Sans clause `WHERE`, tous les enregistrements seront supprimés !
---
## 5. Requêtes de sélection : SELECT
### 5.1. Syntaxe de base
```sql
SELECT colonnes
FROM table
WHERE conditions
ORDER BY colonne [ASC|DESC];
```
### 5.2. Sélectionner des colonnes
```sql
-- Toutes les colonnes
SELECT * FROM LIVRES;
-- Colonnes spécifiques
SELECT titre, auteur FROM LIVRES;
-- Avec alias
SELECT titre AS "Titre du livre", auteur AS "Écrivain"
FROM LIVRES;
```
### 5.3. Filtrer avec WHERE
**Opérateurs de comparaison :**
| Opérateur | Signification |
|-----------|---------------|
| `=` | Égal à |
| `<>` ou `!=` | Différent de |
| `<`, `<=` | Inférieur (ou égal) |
| `>`, `>=` | Supérieur (ou égal) |
| `BETWEEN a AND b` | Entre a et b (inclus) |
| `IN (v1, v2, ...)` | Dans la liste |
| `LIKE` | Correspondance de motif |
| `IS NULL` | Est nul |
**Exemples :**
```sql
-- Égalité
SELECT * FROM LIVRES WHERE auteur = 'Asimov';
-- Comparaison
SELECT * FROM LIVRES WHERE ann_publi > 1960;
-- Entre deux valeurs
SELECT * FROM LIVRES WHERE ann_publi BETWEEN 1950 AND 1970;
-- Dans une liste
SELECT * FROM LIVRES WHERE auteur IN ('Asimov', 'Bradbury', 'K.Dick');
-- Correspondance de motif (LIKE)
SELECT * FROM LIVRES WHERE titre LIKE '%Robot%';
-- % = n'importe quelle suite de caractères
-- _ = un seul caractère
-- Valeur nulle
SELECT * FROM LIVRES WHERE note IS NULL;
```
### 5.4. Combiner les conditions : AND, OR, NOT
```sql
-- ET logique
SELECT * FROM LIVRES
WHERE auteur = 'Asimov' AND ann_publi > 1950;
-- OU logique
SELECT * FROM LIVRES
WHERE auteur = 'Asimov' OR auteur = 'Bradbury';
-- Négation
SELECT * FROM LIVRES
WHERE NOT auteur = 'Asimov';
-- Combinaison avec parenthèses
SELECT * FROM LIVRES
WHERE (auteur = 'Asimov' OR auteur = 'Bradbury') AND note >= 8;
```
### 5.5. Trier les résultats : ORDER BY
```sql
-- Tri croissant (par défaut)
SELECT * FROM LIVRES ORDER BY ann_publi;
SELECT * FROM LIVRES ORDER BY ann_publi ASC;
-- Tri décroissant
SELECT * FROM LIVRES ORDER BY note DESC;
-- Tri multiple
SELECT * FROM LIVRES ORDER BY auteur ASC, ann_publi DESC;
```
### 5.6. Éliminer les doublons : DISTINCT
```sql
-- Liste des auteurs (sans doublons)
SELECT DISTINCT auteur FROM LIVRES;
```
### 5.7. Limiter les résultats : LIMIT
```sql
-- Les 5 premiers résultats
SELECT * FROM LIVRES LIMIT 5;
-- Pagination : 5 résultats à partir du 10e
SELECT * FROM LIVRES LIMIT 5 OFFSET 10;
```
---
## 6. Fonctions d'agrégation
Les fonctions d'agrégation effectuent des calculs sur un ensemble de valeurs.
### 6.1. Les fonctions principales
| Fonction | Description |
|----------|-------------|
| `COUNT(*)` | Nombre d'enregistrements |
| `COUNT(colonne)` | Nombre de valeurs non nulles |
| `SUM(colonne)` | Somme des valeurs |
| `AVG(colonne)` | Moyenne des valeurs |
| `MIN(colonne)` | Valeur minimale |
| `MAX(colonne)` | Valeur maximale |
### 6.2. Exemples
```sql
-- Nombre total de livres
SELECT COUNT(*) FROM LIVRES;
-- Nombre de livres par auteur
SELECT COUNT(*) FROM LIVRES WHERE auteur = 'Asimov';
-- Note moyenne
SELECT AVG(note) FROM LIVRES;
-- Note minimale et maximale
SELECT MIN(note), MAX(note) FROM LIVRES;
-- Somme des notes (peu utile ici, mais illustratif)
SELECT SUM(note) FROM LIVRES;
-- Année du livre le plus ancien
SELECT MIN(ann_publi) FROM LIVRES;
```
### 6.3. Regrouper les données : GROUP BY
`GROUP BY` permet de regrouper les enregistrements par valeur d'une colonne.
```sql
-- Nombre de livres par auteur
SELECT auteur, COUNT(*) AS nb_livres
FROM LIVRES
GROUP BY auteur;
-- Note moyenne par auteur
SELECT auteur, AVG(note) AS note_moyenne
FROM LIVRES
GROUP BY auteur;
-- Résultat :
-- +----------+--------------+
-- | auteur | note_moyenne |
-- +----------+--------------+
-- | Asimov | 8.67 |
-- | Bradbury | 7.50 |
-- | K.Dick | 8.33 |
-- +----------+--------------+
```
### 6.4. Filtrer les groupes : HAVING
`HAVING` filtre les résultats **après** le regroupement (contrairement à `WHERE` qui filtre **avant**).
```sql
-- Auteurs ayant écrit plus de 2 livres
SELECT auteur, COUNT(*) AS nb_livres
FROM LIVRES
GROUP BY auteur
HAVING COUNT(*) > 2;
-- Auteurs avec une note moyenne >= 8
SELECT auteur, AVG(note) AS note_moyenne
FROM LIVRES
GROUP BY auteur
HAVING AVG(note) >= 8;
```
**Différence WHERE / HAVING :**
```sql
-- WHERE filtre les lignes AVANT le regroupement
SELECT auteur, AVG(note)
FROM LIVRES
WHERE ann_publi > 1950 -- Filtre les livres publiés après 1950
GROUP BY auteur;
-- HAVING filtre les groupes APRÈS le regroupement
SELECT auteur, AVG(note)
FROM LIVRES
GROUP BY auteur
HAVING AVG(note) > 8; -- Garde les auteurs avec moyenne > 8
```
---
## 7. Jointures
Les jointures permettent de combiner des données provenant de plusieurs tables.
### 7.1. INNER JOIN (jointure interne)
Retourne uniquement les enregistrements qui ont une correspondance dans les deux tables.
```sql
SELECT LIVRES.titre, AUTEURS.nom, AUTEURS.prenom
FROM LIVRES
INNER JOIN AUTEURS ON LIVRES.id_auteur = AUTEURS.id;
```
**Schéma :**
```
LIVRES AUTEURS Résultat INNER JOIN
+----+------+ +----+------+ +------+------+
| id | id_a | | id | nom | | titre| nom |
+----+------+ +----+------+ +------+------+
| 1 | 1 | | 1 | Orwell| | 1984 | Orwell|
| 2 | 2 | | 2 | Asimov| | Fond.| Asimov|
| 3 | NULL | | 3 | Dick | +------+------+
+----+------+ +----+------+ (le livre 3 est exclu car id_a est NULL)
```
### 7.2. LEFT JOIN (jointure externe gauche)
Retourne tous les enregistrements de la table de gauche, même sans correspondance.
```sql
SELECT LIVRES.titre, AUTEURS.nom
FROM LIVRES
LEFT JOIN AUTEURS ON LIVRES.id_auteur = AUTEURS.id;
```
### 7.3. Alias de tables
Pour simplifier l'écriture :
```sql
SELECT L.titre, A.nom, A.prenom
FROM LIVRES L
INNER JOIN AUTEURS A ON L.id_auteur = A.id
WHERE A.langue_ecriture = 'français';
```
### 7.4. Jointures multiples
```sql
SELECT L.titre, A.nom, E.nom_editeur
FROM LIVRES L
INNER JOIN AUTEURS A ON L.id_auteur = A.id
INNER JOIN EDITEURS E ON L.id_editeur = E.id;
```
---
## 8. Ordre d'exécution des clauses
L'ordre d'écriture et l'ordre d'exécution sont différents :
**Ordre d'écriture :**
```sql
SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY ... LIMIT ...
```
**Ordre d'exécution :**
1. `FROM` et `JOIN` : Sélection des tables
2. `WHERE` : Filtrage des lignes
3. `GROUP BY` : Regroupement
4. `HAVING` : Filtrage des groupes
5. `SELECT` : Sélection des colonnes
6. `DISTINCT` : Élimination des doublons
7. `ORDER BY` : Tri
8. `LIMIT` : Limitation du nombre de résultats
---
## 9. Bonnes pratiques
### 9.1. Conventions d'écriture
- Mots-clés SQL en **MAJUSCULES** : `SELECT`, `FROM`, `WHERE`
- Noms de tables en **MAJUSCULES** : `LIVRES`, `AUTEURS`
- Noms de colonnes en **minuscules** : `titre`, `auteur`
- Indentation pour la lisibilité
```sql
SELECT L.titre,
A.nom,
A.prenom
FROM LIVRES L
INNER JOIN AUTEURS A ON L.id_auteur = A.id
WHERE A.langue_ecriture = 'français'
AND L.ann_publi > 1950
ORDER BY L.ann_publi DESC;
```
### 9.2. Sécurité : Injections SQL
⚠️ Ne jamais construire des requêtes SQL par concaténation de chaînes avec des entrées utilisateur !
```python
# DANGEREUX - Injection SQL possible
requete = "SELECT * FROM USERS WHERE nom = '" + nom_utilisateur + "'"
# SÉCURISÉ - Requête paramétrée
cursor.execute("SELECT * FROM USERS WHERE nom = ?", (nom_utilisateur,))
```
---
## 10. Synthèse
```
┌─────────────────────────────────────────────────────────────────┐
│ REQUÊTE SQL │
├─────────────────────────────────────────────────────────────────┤
│ SELECT [DISTINCT] colonnes, AGG(colonne) │
│ FROM table1 │
│ [INNER|LEFT] JOIN table2 ON condition │
│ WHERE conditions │
│ GROUP BY colonne │
│ HAVING condition_agregation │
│ ORDER BY colonne [ASC|DESC] │
│ LIMIT n [OFFSET m] │
└─────────────────────────────────────────────────────────────────┘
┌──────────────┬────────────────────────────────────────────────┐
│ Commande │ Action │
├──────────────┼────────────────────────────────────────────────┤
│ CREATE TABLE │ Créer une table │
│ DROP TABLE │ Supprimer une table │
│ INSERT INTO │ Insérer des données │
│ UPDATE │ Modifier des données │
│ DELETE │ Supprimer des données │
│ SELECT │ Interroger des données │
└──────────────┴────────────────────────────────────────────────┘
┌──────────────┬────────────────────────────────────────────────┐
│ Agrégation │ Description │
├──────────────┼────────────────────────────────────────────────┤
│ COUNT(*) │ Nombre d'enregistrements │
│ SUM(col) │ Somme │
│ AVG(col) │ Moyenne │
│ MIN(col) │ Minimum │
│ MAX(col) │ Maximum │
└──────────────┴────────────────────────────────────────────────┘
```
---
## Pour aller plus loin
- **SQL Murder Mystery** : Un jeu pour apprendre SQL en résolvant un meurtre
[https://mystery.knightlab.com](https://mystery.knightlab.com)
- **SQLBolt** : Tutoriel interactif
[https://sqlbolt.com](https://sqlbolt.com)
- **W3Schools SQL** : Référence complète
[https://www.w3schools.com/sql/](https://www.w3schools.com/sql/)
---
Auteur : Florian Mathieu
Licence CC BY NC
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.

279
SQL/EXERCICES.md Normal file
View File

@@ -0,0 +1,279 @@
# Exercices — SQL
---
## Contexte
Pour tous les exercices, on considère la base de données d'une médiathèque contenant les tables suivantes :
**Table AUTEURS :**
| id | nom | prenom | nationalite | ann_naissance |
|----|-----|--------|-------------|---------------|
| 1 | Orwell | George | britannique | 1903 |
| 2 | Herbert | Frank | américain | 1920 |
| 3 | Asimov | Isaac | américain | 1920 |
| 4 | Huxley | Aldous | britannique | 1894 |
| 5 | Bradbury | Ray | américain | 1920 |
| 6 | Dick | Philip K. | américain | 1928 |
| 7 | Barjavel | René | français | 1911 |
| 8 | Verne | Jules | français | 1828 |
**Table LIVRES :**
| id | titre | id_auteur | ann_publi | genre | nb_pages |
|----|-------|-----------|-----------|-------|----------|
| 1 | 1984 | 1 | 1949 | dystopie | 328 |
| 2 | Dune | 2 | 1965 | space opera | 896 |
| 3 | Fondation | 3 | 1951 | space opera | 256 |
| 4 | Le meilleur des mondes | 4 | 1931 | dystopie | 288 |
| 5 | Fahrenheit 451 | 5 | 1953 | dystopie | 192 |
| 6 | Ubik | 6 | 1969 | SF | 224 |
| 7 | Chroniques martiennes | 5 | 1950 | SF | 256 |
| 8 | La nuit des temps | 7 | 1968 | SF | 318 |
| 9 | Les Robots | 3 | 1950 | SF | 253 |
| 10 | Ravage | 7 | 1943 | post-apo | 316 |
| 11 | De la Terre à la Lune | 8 | 1865 | aventure | 192 |
| 12 | Vingt mille lieues sous les mers | 8 | 1870 | aventure | 480 |
**Table EMPRUNTS :**
| id | id_livre | id_adherent | date_emprunt | date_retour |
|----|----------|-------------|--------------|-------------|
| 1 | 1 | 101 | 2024-01-15 | 2024-01-29 |
| 2 | 3 | 102 | 2024-01-20 | NULL |
| 3 | 5 | 101 | 2024-02-01 | 2024-02-10 |
| 4 | 2 | 103 | 2024-02-05 | NULL |
| 5 | 1 | 104 | 2024-02-10 | 2024-02-20 |
---
## Exercice 1 : Requêtes SELECT simples
Écrire les requêtes SQL permettant d'obtenir :
1. Tous les livres de la table LIVRES.
2. Les titres et années de publication de tous les livres.
3. Les noms et prénoms de tous les auteurs.
4. Les titres des livres publiés après 1950.
5. Les titres des livres du genre "dystopie".
6. Les auteurs de nationalité française.
---
## Exercice 2 : Filtres et conditions
Écrire les requêtes SQL permettant d'obtenir :
1. Les livres publiés entre 1940 et 1960 (inclus).
2. Les livres ayant plus de 300 pages.
3. Les auteurs nés après 1900 et de nationalité américaine.
4. Les livres dont le titre contient le mot "Terre".
5. Les livres qui ne sont pas du genre "SF".
6. Les auteurs dont le prénom commence par la lettre "R".
---
## Exercice 3 : Tri et limitation
Écrire les requêtes SQL permettant d'obtenir :
1. Tous les livres triés par année de publication (du plus ancien au plus récent).
2. Les 5 livres les plus récents.
3. Les auteurs triés par année de naissance décroissante.
4. Les 3 livres ayant le plus de pages.
5. Les livres triés par genre, puis par titre alphabétique.
6. La liste des genres sans doublons.
---
## Exercice 4 : Fonctions d'agrégation
Écrire les requêtes SQL permettant d'obtenir :
1. Le nombre total de livres dans la base.
2. Le nombre de pages du livre le plus long.
3. L'année de publication du livre le plus ancien.
4. Le nombre moyen de pages des livres.
5. Le nombre de livres par genre.
6. Le nombre total de pages de tous les livres de Jules Verne (id_auteur = 8).
---
## Exercice 5 : GROUP BY et HAVING
Écrire les requêtes SQL permettant d'obtenir :
1. Le nombre de livres écrits par chaque auteur (afficher id_auteur et le compte).
2. Le nombre moyen de pages par genre.
3. Les genres ayant plus de 2 livres.
4. Les auteurs ayant écrit au moins 2 livres (afficher id_auteur et le nombre).
5. L'année de publication du premier livre de chaque auteur.
6. Les genres dont la moyenne de pages dépasse 250.
---
## Exercice 6 : Jointures
Écrire les requêtes SQL permettant d'obtenir :
1. Les titres des livres avec le nom et prénom de leur auteur.
2. Les titres des livres écrits par des auteurs français.
3. Les titres et auteurs des livres du genre "dystopie", triés par année.
4. Le nom des auteurs ayant écrit des livres de plus de 400 pages.
5. Les livres (titre) avec le nom de l'auteur, uniquement pour les auteurs américains nés après 1910.
6. Pour chaque nationalité, le nombre de livres écrits par des auteurs de cette nationalité.
---
## Exercice 7 : Requêtes complexes
Écrire les requêtes SQL permettant d'obtenir :
1. Le titre du livre le plus long de chaque auteur, avec le nom de l'auteur.
2. Les auteurs n'ayant écrit qu'un seul livre (afficher nom, prénom, titre du livre).
3. Le nombre moyen de pages des livres par nationalité d'auteur.
4. Les genres pour lesquels tous les livres ont été publiés après 1940.
5. Le nom complet (prénom + nom) des auteurs et le nombre total de pages qu'ils ont écrites.
---
## Exercice 8 : INSERT, UPDATE, DELETE
1. Écrire une requête pour ajouter l'auteur suivant :
- id : 9
- nom : "Le Guin"
- prénom : "Ursula K."
- nationalité : "américain"
- année de naissance : 1929
2. Écrire une requête pour ajouter le livre suivant :
- id : 13
- titre : "La Main gauche de la nuit"
- id_auteur : 9
- année de publication : 1969
- genre : "SF"
- nombre de pages : 304
3. Écrire une requête pour changer le genre de "Dune" en "SF".
4. Écrire une requête pour augmenter de 10% le nombre de pages de tous les livres de Jules Verne.
5. Écrire une requête pour supprimer tous les livres publiés avant 1900.
---
## Exercice 9 : Création de tables
1. Écrire la requête SQL pour créer la table EMPRUNTS avec :
- id (entier, clé primaire)
- id_livre (entier, clé étrangère vers LIVRES)
- id_adherent (entier)
- date_emprunt (date)
- date_retour (date, peut être NULL)
2. Écrire la requête SQL pour créer une table EDITEURS avec :
- id (entier, clé primaire)
- nom (texte)
- pays (texte)
- ann_creation (entier)
3. Comment modifier la table LIVRES pour ajouter une colonne id_editeur qui référence la table EDITEURS ?
---
## Exercice 10 : Analyse de requêtes
Pour chaque requête suivante, décrire en français ce qu'elle retourne :
1. ```sql
SELECT titre FROM LIVRES WHERE ann_publi = (SELECT MIN(ann_publi) FROM LIVRES);
```
2. ```sql
SELECT A.nom, COUNT(*)
FROM AUTEURS A
INNER JOIN LIVRES L ON A.id = L.id_auteur
GROUP BY A.id
HAVING COUNT(*) = (SELECT MAX(cnt) FROM (SELECT COUNT(*) as cnt FROM LIVRES GROUP BY id_auteur));
```
3. ```sql
SELECT DISTINCT genre FROM LIVRES WHERE genre NOT IN (SELECT genre FROM LIVRES WHERE ann_publi < 1950);
```
---
## Exercice 11 : QCM
1. Quelle clause permet de filtrer les résultats d'un GROUP BY ?
- [ ] a) WHERE
- [ ] b) FILTER
- [ ] c) HAVING
- [ ] d) GROUP FILTER
2. Quelle est la différence entre WHERE et HAVING ?
- [ ] a) Aucune, ils sont équivalents
- [ ] b) WHERE filtre avant GROUP BY, HAVING filtre après
- [ ] c) HAVING filtre avant GROUP BY, WHERE filtre après
- [ ] d) WHERE ne fonctionne qu'avec les nombres
3. Que retourne `SELECT COUNT(*) FROM LIVRES WHERE 1=0;` ?
- [ ] a) Une erreur
- [ ] b) NULL
- [ ] c) 0
- [ ] d) Rien
4. Quel type de jointure retourne tous les enregistrements de la table de gauche ?
- [ ] a) INNER JOIN
- [ ] b) LEFT JOIN
- [ ] c) RIGHT JOIN
- [ ] d) CROSS JOIN
5. Quelle fonction d'agrégation ignore les valeurs NULL ?
- [ ] a) COUNT(*)
- [ ] b) COUNT(colonne)
- [ ] c) Les deux
- [ ] d) Aucune
---
Auteur : Florian Mathieu
Licence CC BY NC
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.

View File

@@ -2,6 +2,8 @@
Ce TP vous guidera à travers la création et la manipulation d'une base de données en utilisant le logiciel **DB Browser for SQLite**. Vous apprendrez à créer des tables, insérer des données et exécuter des requêtes SQL pour interroger la base de données.
> **Prérequis** : Avoir lu le cours [Cours_SQL.md](Cours_SQL.md) avant de commencer ce TP.
## Prérequis
- Télécharger et installer [DB Browser for SQLite](https://sqlitebrowser.org/).
@@ -46,7 +48,7 @@ Ce TP vous guidera à travers la création et la manipulation d'une base de donn
);
```
- Cliquez sur le triangle vert (ou appuyez sur F5) pour exécuter la requête.
- Cliquez sur le bouton ▶ (ou appuyez sur F5) pour exécuter la requête.
- Un message "Requête exécutée avec succès" devrait apparaître.
![Table créée avec succès](assets/5.png)
@@ -77,7 +79,7 @@ Ce TP vous guidera à travers la création et la manipulation d'une base de donn
(16, 'De la Terre à la Lune', 'Verne', 1865, 10);
```
- Exécutez la requête en cliquant sur le triangle bleu ou en appuyant sur F5.
- Exécutez la requête en cliquant sur le bouton ▶ ou en appuyant sur F5.
- Un message de confirmation devrait apparaître.
![Données insérées avec succès](assets/6.png)
@@ -140,7 +142,7 @@ Ce TP vous guidera à travers la création et la manipulation d'une base de donn
WHERE auteur='Asimov' AND ann_publi>1953
```
Vérifiez que nous obtenons bien le livre écrit par Asimov publié après 1953.
Vérifiez que vous obtenez bien les livres écrits par Asimov publiés après 1953.
8. Écrivez une requête permettant d'obtenir les titres des livres publiés après 1945 qui ont une note supérieure ou égale à 9.
@@ -253,7 +255,7 @@ VALUES
14. À l'aide d'une requête SQL, ajoutez à la table LIVRES le livre suivant :
- id : 17
- titre : "2001 : L'Odyssée de l'espace"
- auteur : "Clarcke"
- auteur : "Clarke"
- année de publication : 1968
- note : 7
@@ -264,8 +266,15 @@ VALUES
--------
Source :
Source :
- [Pixees, site de David Roche](https://informatique-lycee.forge.apps.education.fr/terminale_nsi/cours-terminale/c3a/)
---
Auteur : Florian Mathieu
Licence CC BY NC
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.

359
SQL/TP_Streaming_Musical.md Normal file
View File

@@ -0,0 +1,359 @@
# TP : Base de données d'un service de streaming musical
## Contexte
Vous êtes data analyst chez **Musicfy**, un service de streaming musical concurrent de Spotify et Deezer. Votre mission est d'analyser les données d'écoute pour comprendre les habitudes des utilisateurs et proposer des améliorations.
Vous disposez d'une base de données contenant les informations sur les artistes, albums, titres, utilisateurs et leurs écoutes.
---
## Objectifs
- Pratiquer les requêtes SQL sur une base de données réaliste
- Maîtriser les jointures entre plusieurs tables
- Utiliser les fonctions d'agrégation pour l'analyse de données
- Comprendre l'organisation d'une base de données relationnelle
---
## Partie 1 : Découverte de la base de données
### 1.1. Création de la base
1. Ouvrez **DB Browser for SQLite** et créez une nouvelle base de données `musicfy.db`.
2. Exécutez les requêtes suivantes pour créer les tables :
```sql
-- Table des artistes
CREATE TABLE ARTISTES (
id INTEGER PRIMARY KEY,
nom TEXT NOT NULL,
pays TEXT,
genre_principal TEXT,
ann_debut INTEGER
);
-- Table des albums
CREATE TABLE ALBUMS (
id INTEGER PRIMARY KEY,
titre TEXT NOT NULL,
id_artiste INTEGER,
ann_sortie INTEGER,
nb_titres INTEGER,
FOREIGN KEY (id_artiste) REFERENCES ARTISTES(id)
);
-- Table des titres (chansons)
CREATE TABLE TITRES (
id INTEGER PRIMARY KEY,
nom TEXT NOT NULL,
id_album INTEGER,
duree_sec INTEGER,
explicite BOOLEAN DEFAULT FALSE,
FOREIGN KEY (id_album) REFERENCES ALBUMS(id)
);
-- Table des utilisateurs
CREATE TABLE UTILISATEURS (
id INTEGER PRIMARY KEY,
pseudo TEXT NOT NULL,
email TEXT UNIQUE,
pays TEXT,
type_abo TEXT DEFAULT 'gratuit',
date_inscription DATE
);
-- Table des écoutes
CREATE TABLE ECOUTES (
id INTEGER PRIMARY KEY,
id_utilisateur INTEGER,
id_titre INTEGER,
date_ecoute DATE,
duree_ecoute_sec INTEGER,
FOREIGN KEY (id_utilisateur) REFERENCES UTILISATEURS(id),
FOREIGN KEY (id_titre) REFERENCES TITRES(id)
);
```
### 1.2. Insertion des données
Exécutez les requêtes suivantes pour insérer les données :
```sql
-- Artistes
INSERT INTO ARTISTES (id, nom, pays, genre_principal, ann_debut) VALUES
(1, 'Daft Punk', 'France', 'Electro', 1993),
(2, 'Kendrick Lamar', 'USA', 'Hip-Hop', 2004),
(3, 'Adele', 'Royaume-Uni', 'Pop', 2006),
(4, 'BTS', 'Corée du Sud', 'K-Pop', 2013),
(5, 'Stromae', 'Belgique', 'Electro', 2009),
(6, 'Taylor Swift', 'USA', 'Pop', 2004),
(7, 'PNL', 'France', 'Hip-Hop', 2014),
(8, 'Beyoncé', 'USA', 'R&B', 1997),
(9, 'Ed Sheeran', 'Royaume-Uni', 'Pop', 2004),
(10, 'Aya Nakamura', 'France', 'Pop', 2014);
-- Albums
INSERT INTO ALBUMS (id, titre, id_artiste, ann_sortie, nb_titres) VALUES
(1, 'Random Access Memories', 1, 2013, 13),
(2, 'Discovery', 1, 2001, 14),
(3, 'DAMN.', 2, 2017, 14),
(4, 'To Pimp a Butterfly', 2, 2015, 16),
(5, '21', 3, 2011, 11),
(6, '30', 3, 2021, 12),
(7, 'Map of the Soul: 7', 4, 2020, 20),
(8, 'Racine carrée', 5, 2013, 13),
(9, 'Multitude', 5, 2022, 12),
(10, '1989', 6, 2014, 13),
(11, 'Midnights', 6, 2022, 13),
(12, 'Dans la légende', 7, 2016, 17),
(13, 'Deux frères', 7, 2019, 18),
(14, 'Lemonade', 8, 2016, 12),
(15, 'Renaissance', 8, 2022, 16),
(16, 'Divide', 9, 2017, 16),
(17, 'Nakamura', 10, 2018, 14),
(18, 'DNK', 10, 2023, 15);
-- Titres (sélection)
INSERT INTO TITRES (id, nom, id_album, duree_sec, explicite) VALUES
(1, 'Get Lucky', 1, 369, FALSE),
(2, 'Instant Crush', 1, 337, FALSE),
(3, 'Lose Yourself to Dance', 1, 353, FALSE),
(4, 'One More Time', 2, 320, FALSE),
(5, 'Digital Love', 2, 301, FALSE),
(6, 'Harder Better Faster Stronger', 2, 224, FALSE),
(7, 'HUMBLE.', 3, 177, TRUE),
(8, 'DNA.', 3, 185, TRUE),
(9, 'LOYALTY.', 3, 227, TRUE),
(10, 'Alright', 4, 219, TRUE),
(11, 'King Kunta', 4, 234, TRUE),
(12, 'Rolling in the Deep', 5, 228, FALSE),
(13, 'Someone Like You', 5, 285, FALSE),
(14, 'Set Fire to the Rain', 5, 241, FALSE),
(15, 'Easy On Me', 6, 224, FALSE),
(16, 'Oh My My', 6, 218, FALSE),
(17, 'Dynamite', 7, 199, FALSE),
(18, 'ON', 7, 242, FALSE),
(19, 'Papaoutai', 8, 234, FALSE),
(20, 'Formidable', 8, 192, FALSE),
(21, 'Alors on danse', 8, 205, FALSE),
(22, 'Santé', 9, 188, FALSE),
(23, 'L''enfer', 9, 224, FALSE),
(24, 'Shake It Off', 10, 219, FALSE),
(25, 'Blank Space', 10, 231, FALSE),
(26, 'Style', 10, 231, FALSE),
(27, 'Anti-Hero', 11, 201, FALSE),
(28, 'Lavender Haze', 11, 202, FALSE),
(29, 'Le monde ou rien', 12, 228, TRUE),
(30, 'DA', 12, 195, TRUE),
(31, 'Au DD', 13, 199, TRUE),
(32, 'A l''ammoniaque', 13, 234, TRUE),
(33, 'Formation', 14, 226, TRUE),
(34, 'Sorry', 14, 232, FALSE),
(35, 'BREAK MY SOUL', 15, 279, FALSE),
(36, 'CUFF IT', 15, 225, FALSE),
(37, 'Shape of You', 16, 233, FALSE),
(38, 'Castle on the Hill', 16, 261, FALSE),
(39, 'Perfect', 16, 263, FALSE),
(40, 'Djadja', 17, 176, FALSE),
(41, 'Pookie', 17, 185, TRUE),
(42, 'Copines', 18, 179, FALSE),
(43, 'SMS', 18, 164, FALSE);
-- Utilisateurs
INSERT INTO UTILISATEURS (id, pseudo, email, pays, type_abo, date_inscription) VALUES
(1, 'music_lover_33', 'music33@email.com', 'France', 'premium', '2020-03-15'),
(2, 'alex_beats', 'alex.b@email.com', 'France', 'gratuit', '2021-06-22'),
(3, 'sophie_melody', 'sophie.m@email.com', 'Belgique', 'premium', '2019-11-08'),
(4, 'dj_max', 'maxime.dj@email.com', 'France', 'famille', '2022-01-10'),
(5, 'luna_music', 'luna.music@email.com', 'Canada', 'etudiant', '2021-09-01'),
(6, 'kevin_hiphop', 'kevin.hh@email.com', 'USA', 'premium', '2020-07-19'),
(7, 'emma_pop', 'emma.pop@email.com', 'Royaume-Uni', 'gratuit', '2023-02-28'),
(8, 'thomas_electro', 'thomas.e@email.com', 'France', 'premium', '2018-05-12'),
(9, 'clara_world', 'clara.w@email.com', 'Espagne', 'etudiant', '2022-10-05'),
(10, 'jp_kpop', 'jp.kpop@email.com', 'Japon', 'premium', '2021-03-20');
-- Écoutes (échantillon)
INSERT INTO ECOUTES (id, id_utilisateur, id_titre, date_ecoute, duree_ecoute_sec) VALUES
(1, 1, 1, '2024-01-15', 369),
(2, 1, 4, '2024-01-15', 320),
(3, 1, 19, '2024-01-15', 234),
(4, 2, 7, '2024-01-15', 177),
(5, 2, 8, '2024-01-15', 150),
(6, 2, 29, '2024-01-15', 228),
(7, 3, 19, '2024-01-16', 234),
(8, 3, 20, '2024-01-16', 192),
(9, 3, 22, '2024-01-16', 188),
(10, 4, 1, '2024-01-16', 369),
(11, 4, 6, '2024-01-16', 224),
(12, 5, 17, '2024-01-16', 199),
(13, 5, 18, '2024-01-16', 242),
(14, 6, 7, '2024-01-17', 177),
(15, 6, 10, '2024-01-17', 219),
(16, 6, 11, '2024-01-17', 234),
(17, 7, 12, '2024-01-17', 228),
(18, 7, 24, '2024-01-17', 219),
(19, 7, 37, '2024-01-17', 233),
(20, 8, 1, '2024-01-17', 369),
(21, 8, 4, '2024-01-17', 320),
(22, 8, 5, '2024-01-17', 301),
(23, 8, 6, '2024-01-17', 224),
(24, 9, 40, '2024-01-18', 176),
(25, 9, 42, '2024-01-18', 179),
(26, 10, 17, '2024-01-18', 199),
(27, 10, 18, '2024-01-18', 242),
(28, 1, 19, '2024-01-18', 234),
(29, 1, 21, '2024-01-18', 205),
(30, 2, 31, '2024-01-18', 199),
(31, 3, 23, '2024-01-19', 224),
(32, 4, 35, '2024-01-19', 279),
(33, 5, 27, '2024-01-19', 201),
(34, 6, 8, '2024-01-19', 185),
(35, 7, 39, '2024-01-19', 263),
(36, 8, 2, '2024-01-19', 337),
(37, 9, 41, '2024-01-20', 185),
(38, 10, 17, '2024-01-20', 199),
(39, 1, 1, '2024-01-20', 369),
(40, 2, 7, '2024-01-20', 177);
```
### 1.3. Schéma de la base
Dessinez le schéma relationnel de la base de données en identifiant :
- Les clés primaires (PK)
- Les clés étrangères (FK)
- Les relations entre les tables
---
## Partie 2 : Requêtes de base
**Pour chaque question, écrivez la requête SQL et notez le résultat.**
1. Affichez tous les artistes de la base de données.
2. Affichez le nom et le pays des artistes français.
3. Affichez les titres de tous les albums sortis après 2015.
4. Affichez les pseudos des utilisateurs ayant un abonnement premium.
5. Affichez les titres (chansons) qui durent plus de 4 minutes (240 secondes).
6. Affichez les artistes dont le genre principal est "Hip-Hop" ou "Pop".
---
## Partie 3 : Requêtes avec jointures
7. Affichez le nom des titres avec le nom de leur album.
8. Affichez le nom des titres avec le nom de l'artiste qui les a créés.
*(Indice : vous devez passer par la table ALBUMS)*
9. Affichez les albums de Daft Punk avec leur année de sortie.
10. Affichez la liste des écoutes avec le pseudo de l'utilisateur et le nom du titre écouté.
11. Affichez tous les titres des artistes français.
12. Pour chaque écoute, affichez le pseudo de l'utilisateur, le nom du titre, le nom de l'album et le nom de l'artiste.
---
## Partie 4 : Agrégations
13. Combien y a-t-il de titres dans la base de données ?
14. Quelle est la durée moyenne des titres (en secondes) ?
15. Quel est le titre le plus long ? Le plus court ?
16. Combien d'albums a sorti chaque artiste ? Affichez le nom de l'artiste et le nombre d'albums.
17. Combien d'écoutes ont été effectuées par chaque utilisateur ? Affichez le pseudo et le nombre d'écoutes.
18. Quel est le nombre total de titres par genre musical ?
*(Indice : il faut passer par ARTISTES et ALBUMS)*
---
## Partie 5 : Requêtes avancées
19. Quels sont les artistes ayant sorti plus d'un album ?
20. Quels sont les 5 titres les plus écoutés ? Affichez le nom du titre et le nombre d'écoutes.
21. Quels utilisateurs ont écouté des titres de Stromae ?
22. Quelle est la durée totale d'écoute (en minutes) par utilisateur ?
23. Quels artistes n'ont aucun titre explicite dans leurs chansons ?
24. Pour chaque pays d'utilisateur, calculez le nombre d'écoutes et la durée totale d'écoute.
---
## Partie 6 : Analyse business
Le département marketing vous demande des analyses pour améliorer le service.
25. **Taux de conversion** : Combien d'utilisateurs ont un abonnement payant (premium, famille ou étudiant) vs gratuit ?
26. **Artistes populaires en France** : Quels artistes sont les plus écoutés par les utilisateurs français ?
27. **Recommandations** : Trouvez les utilisateurs qui ont écouté Daft Punk mais pas Stromae (ces utilisateurs pourraient aimer Stromae).
28. **Contenu explicite** : Quel pourcentage des titres est marqué comme explicite ?
29. **Rétention** : Quels utilisateurs inscrits avant 2021 ont écouté de la musique en janvier 2024 ?
30. **Rapport final** : Créez une vue synthétique montrant pour chaque artiste : son nom, son pays, son genre, le nombre d'albums, le nombre de titres et le nombre total d'écoutes.
---
## Partie 7 : Modifications de données (Bonus)
31. Ajoutez l'artiste "Angèle" (Belgique, Pop, 2014).
32. Ajoutez son album "Brol" sorti en 2018 avec 15 titres.
33. Un utilisateur change d'abonnement : mettez à jour `alex_beats` vers l'abonnement "etudiant".
34. Supprimez toutes les écoutes de l'utilisateur `jp_kpop`.
---
## Questions de réflexion
1. Pourquoi a-t-on séparé les artistes et les albums dans des tables différentes au lieu de tout mettre dans une seule table ?
2. Quel problème pourrait survenir si on supprimait un artiste qui a des albums dans la base ?
3. Comment pourrait-on améliorer la base pour gérer les collaborations entre artistes sur un même titre ?
4. Quelles informations supplémentaires pourrait-on ajouter pour un vrai service de streaming ?
---
## Barème indicatif
| Partie | Points |
|--------|--------|
| Partie 1 : Création et schéma | 2 |
| Partie 2 : Requêtes de base | 3 |
| Partie 3 : Jointures | 4 |
| Partie 4 : Agrégations | 4 |
| Partie 5 : Requêtes avancées | 4 |
| Partie 6 : Analyse business | 3 |
| **Total** | **20** |
---
Auteur : Florian Mathieu
Licence CC BY NC
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.

BIN
SQL/assets/sql_join.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB