408 lines
11 KiB
Markdown
408 lines
11 KiB
Markdown
|
|
# 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>.
|