# 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 Licence Creative Commons
Ce cours est mis à disposition selon les termes de la Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International.