308 Chapitre 7. Production du site La différence entre les deux requêtes qui précèdent est subtile : COUNT(expr ) compte en fait le nombre de lignes telles que la valeur de expr n’est pas à NULL. Si on utilise ’*’, comme dans le premier cas, on est sûr de compter toutes les lignes puisqu’il y a toujours au moins un attribut qui n’est pas à NULL dans une ligne (par exemple l’attribut titre est déclaré à NOT NULL : voir chapitre 4). En revanche la deuxième requête ne comptera pas les lignes où id_realisateur est à NULL. Il n’est pas possible, sauf avec la clause GROUP BY qui est présentée plus loin, d’uti- liser simultanément des noms d’attributs et des fonctions d’agrégation. La requête suivante est donc incorrecte : SELECT titre , COUNT (id_realisateur) FROM Film WHERE genre = ’Western ’ AND annee > 1990 On demande en fait alors à MySQL deux choses contradictoires. D’une part il faut afficher tous les titres des westerns parus après 1990, d’autre part donner le nombre des réalisateurs de ces westerns. Il n’y a pas de représentation possible de cette information sous forme d’une table avec des lignes, des colonnes, et une seule valeur par cellule, et SQL, qui ne sait produire que des tables, rejette cette requête. La liste des fonctions d’agrégation est donnée dans la table 7.1. Tableau 7.1 — Les fonctions d’agrégation de MySQL Fonction Description COUNT (expression ) Compte le nombre de lignes. AVG(expression ) Calcule la moyenne de expression. MIN(expression ) Calcule la valeur minimale de expression. MAX(expression ) Calcule la valeur maximale de expression. SUM(expression ) Calcule la somme de expression. STD(expression ) Calcule l’écart-type de expression. Les requêtes dont nous avons besoin pour nos prédictions calculent des moyennes. La moyenne des notes pour l’internaute fogg@foo.fr est obtenue par : SELECT AVG( note ) FROM Notation WHERE email = ’fogg@foo . fr ’ Symétriquement, la moyenne des notes pour un film –par exemple, Vertigo–est obtenue par : SELECT AVG(note) FROM Notation WHERE titre = ’Vertigo ’ 7.4 Recommandations 309 La clause GROUP BY Dans les requêtes précédentes, on applique la fonction d’agrégation à l’ensemble du résultat d’une requête (donc potentiellement à l’ensemble de la table elle-même). Une fonctionnalité complémentaire consiste à partitionner ce résultat en groupes, pour appliquer la ou les fonction(s) d’agrégat à chaque groupe. On construit les groupes en associant les lignes partageant la même valeur pour un ou plusieurs attributs. La requête suivante affiche les internautes avec leur note moyenne : SELECT email , AVG(note) FROM Notation GROUP BY email On constitue ici un groupe pour chaque internaute (clause GROUP BY email). Puis on affiche ce groupe sous la forme d’une ligne, dans laquelle les attributs peuvent être de deux types : 1. ceux dont la valeur est constante pour l’ensemble du groupe, soit précisément les attributs du GROUP BY, ici l’attribut email ; 2. le résultat d’une fonction d’agrégation appliquée au groupe : ici AVG(note). Bien entendu, il est possible d’exprimer des ordres SQL comprenant une clause WHERE et d’appliquer un GROUP BY au résultat. D’autre part, il n’est pas nécessaire de faire figurer tous les attributs du GROUP BY dans la clause SELECT. Enfin, le AS permet de donner un nom aux attributs résultant d’une agrégation. Voici un exemple assez complet donnant la liste des films avec le nom et le prénom de leur metteur en scène, et la moyenne des notes obtenues, la note minimale et la note maximale. SELECT f . titre , nom, prenom, AVG(note) AS moyenne , MIN(note) AS noteMin , MAX(note) AS noteMax FROM Film AS f , Notation AS n, Artiste AS a WHERE f. titre = n. titre AND f.id_realisateur = a.id GROUP BY f . titre , nom, prenom L’interprétation est la suivante : (1) on exécute d’abord la requête SELECT FROM WHERE, puis (2) on prend le résultat et on le partitionne, enfin (3) on calcule le résultat des fonctions pour chaque groupe. 7.4.3 Recommandations de films Deux méthodes de recommandations sont proposées, toutes deux implantées dans le contrôleur Recomm. L’internaute peut choisir le nombre de films à afficher (10 par défaut) et appuyer sur un des boutons correspondant aux deux choix proposés (voir figure 7.5). L’action proposer associée au formulaire effectue quelques tests initiaux pour vérifier qu’un calcul de proposition est bien possible, et se contente d’appeler la fonction appropriée. L’affichage des films, standard, n’est pas montré dans le code ci-dessous. Il figure bien sûr dans les fichiers du site. 310 Chapitre 7. Production du site Figure 7.5 — Formulaire d’accès aux prédictions function proposer() { // D’abord on récupère les choix de l ’ utilisateur // Il faudrait vérifier qu’ils existent bien $nb_films = $_POST[ ’nb_films ’ ]; $liste_triee = isSet ($_POST[ ’films_tries ’]) ; if ($nb_films <= 0 or $nb_films > 30) { $this−>vue−>contenu = "Ce script attend une variable nb_films comprise entre 1 et 30\n" ; } else { // On vérifie que l ’internaute a noté suffisamment de films $nb_notes = Util :: NombreNotes($this−>session−>email , $this −>bd ) ; // Message d’avertissement s ’ il n’y a pas assez de films if ( $ n b_ not es < s e l f : : MIN_NOTES and ! $ l i s t e _ t r i e e ) { $this−>vue−>avertissement = "Vous n ’avez pas noté assez de films ($nb_notes) " . "pour que nous puissions établir une prédiction !\n" ; $liste_triee = true ; } else { $this−>vue−>avertissement = ""; } $this−>vue−>nb_notes = $nb_notes ; // Calcul de la liste des meilleurs films 7.4 Recommandations 311 if ($liste_triee) { $films = $this−>listeTriee ($nb_films) ; $this−>vue−>nb_films = $nb_films ; $this−>vue−>setFile ("contenu" , "recomm_liste_triee . tpl"); } else { // Calcul des prédictions $this−>vue−>ma_moyenne = Util :: moyenneInternaute($this−>session−>email , $this −>bd ) ; $films = $this−>prediction ($this−>session−>email , $nb_films , $this−>bd ) ; $this−>vue−>setFile("contenu", "recomm_liste_predite . tpl "); } // Ensuite on affiche la liste des films −− Voir le code } Les deux méthodes listeTriee() et prediction() correspondent respective- ment aux deux types de propositions possibles. Elles sont toutes deux implantées comme des méthodes privées du contrôleur Recomm et données plus bas. Liste des films les plus populaires La méthode listeTriee() s’appuie sur les fonctionnalités d’agrégation de SQL pour construire la liste des films avec leur note moyenne. Le résultat est placé dans un tableau associatif, la clé étant l’identifiant du film et la valeur la note moyenne pour ce film. Il ne reste plus qu’à trier sur la note, en ordre décroissant et à afficher les 10, 20 ou 30 premiers films de la liste triée. PHP fournit de nombreuses fonctions de tri de tableau (voir annexe C), dont asort() et arsort() pour trier sur les valeurs, et ksort() ou krsort() pour trier sur les clés un tableau associatif, respective- ment en ordre croissant et en ordre décroissant. C’est arsort() qui nous intéresse ici. Finalement, on affiche la liste des films (voir figure 7.6), en associant le titre à une ancre menant au contrôleur Film pour la présentation détaillée et le forum de discussion. private function listeTriee ($nbFilms) { $films = array () ; // Recherche des films et de leur note moyenne $ cl ass em ent = "SELECT i d_ film , AVG(note) AS note " . " FROM Notation GROUP BY id_film" ; $resultat = $this−>bd−>execRequete ($classement); 312 Chapitre 7. Production du site // On crée un tableau associatif des films , avec leur note // moyenne $i=1; while ($film = $this−>bd−>objetSuivant ($resultat)) { $films [$film−>id_film] = $film−>note ; if ($i++ >= $nbFilms) break ; } // Tri du tableau par ordre décroissant sur note moyenne arsort ($films); return $films ; } Figure 7.6 — Liste des films les plus appréciés Calcul des prédictions Le calcul des prédictions est nettement plus complexe que le tri des films sur leur note moyenne. La méthode prediction() fait elle-même appel à quelques autres fonctions implantées comme des méthodes statiques de la classe Util. 1. MoyenneInternaute(email ) calcule la note moyenne de l’internaute identifié par email. 2. ChercheNotation(email, id_film ), déjà rencontrée, recherche la notation d’un internaute sur un film. 3. CalculCorrelation(email1, email2 ), calcule le coefficient de corrélation entre deux internautes. 4. CalculPrediction(email, id_film, tableauCorrelation ), prédit la note d’un film pour un internaute, étant donné le tableau des corrélations entre cet internaute et tous les autres. Nous ne donnerons pas les deux premières fonctions ci-dessus qui se contentent d’exécuter une requête SQL (une agrégation pour MoyenneInternaute())et . possible de cette information sous forme d’une table avec des lignes, des colonnes, et une seule valeur par cellule, et SQL, qui ne sait produire que des tables, rejette cette requête. La liste des. permet de donner un nom aux attributs résultant d’une agrégation. Voici un exemple assez complet donnant la liste des films avec le nom et le prénom de leur metteur en scène, et la moyenne des. ; $this−>vue−>setFile("contenu", "recomm_liste_predite . tpl "); } // Ensuite on affiche la liste des films −− Voir le code } Les deux méthodes listeTriee() et prediction() correspondent