Aujourd'hui je vais aborder un problème que j'ai rencontré lorsque j'ai souhaité ajouter un système de vote à la digg pour des contenus sur un CMS.
Explication du système de vote digg like
Un système de vote à la digg est un des systèmes de votes les plus simples pour savoir si vos visiteurs apprécient ou non vos contenus. Il consiste à poser une question oui-non à vos visiteurs du genre "Avez-vous aimez ce contenu ?" et de leurs proposer deux choix :
- "Oui" assimilable à l'action digg
- "Non" assimilable à l'action bury
Ce système de vote permet de limiter les choix du visiteurs et obtient généralement un taux de transformation plus performant que les systèmes de notations classiques (par étoile ...) car l'action est simple pour l'utilisateur.
Structure de la table de contenu
Présentation faite du système de vote voici maintenant une table de contenu type avec les champs pour stockés les votes dig et bury des utilisateurs :
CREATE TABLE IF NOT EXISTS CONTENTS (
CON_ID mediumint(8) unsigned NOT NULL auto_increment,
CON_TITLE varchar(150) NOT NULL,
CON_TEXT text NOT NULL,
CON_DIG smallint(6) NOT NULL,
CON_BURY smallint(6) NOT NULL,
PRIMARY KEY (CON_ID),
);
Avec :
- CON_ID : identifiant du contenu
- CON_TITLE : titre du contenu
- CON_TEXT : corps de l'article
- CON_DIG : nombre de votes positifs
- CON_BURY : nombre de votes négatifs
Les champs CON_DIG et CON_BURY sont volontairement des entiers signés, vous comprendrez par la suite.
Requête pour un digg
Voici la requête qui correspond à un vote positif de l'utilisateur pour le contenu 1 :
UPDATE CONTENTS SET CON_DIG=CON_DIG+1 WHERE CON_ID = 1
Requête pour un bury
Et maintenant la requête qui correspond à un vote négatif pour le contenu 1 :
UPDATE CONTENTS SET CON_BURY=CON_BURY+1 WHERE CON_ID = 1
Requête de tri des contenus les plus pertinents
Après avoir peuplé ma table de lorem ipsum je lance alors ma première requête sensée refléter les votes des utilisateurs :
/*Sélection des contenus en les triant selon les votes des utilisateurs*/
SELECT CON_ID FROM CONTENTS ORDER BY CON_DIG DESC, CON_BURY ASC
Je lance un explain pour voir si la requete est optimisée et là horreur : using filesort.
Bon personnellement je m'en doutais un peu sans index il ne faut pas rêver donc je rajoute l'index suivant à la table CONTENTS :
ALTER TABLE CONTENTS ADD INDEX DIG (CON_DIG,CON_BURY);
Je relance la même requête :
SELECT CON_ID FROM CONTENTS ORDER BY CON_DIG DESC, CON_BURY ASC
Et la toujours la même chose : using filesort.
Optimisation de la requête de tri
Aprés quelques instants de réflexion je me dis que mysql n'utilise pas l'index DIG à cause de l'order by ASC et DESC et qu'il n'arrive pas à optimiser la requête car il ne peut pas utiliser deux indexes avec des sens de tris différents (un croissant et l'autre décroissant).
Je décide alors de tester la requête suivante :
EXPLAIN SELECT CON_ID FROM CONTENTS ORDER BY CON_DIG DESC, CON_BURY DESC
Résultat : using index. Hip ! Hip ! Hip ! HouraaaAAAAaa !
C'est bon j'ai trouvé la solution il faut que je stocke les vote bury en négatif. Ma requête de bury se transforme donc en :
UPDATE CONTENTS SET CON_BURY=CON_BURY-1 WHERE CON_ID = 1
Vous comprenez maintenant pourquoi mes colonnes CON_DIG et CON_BURY sont des entiers signés et je peux utiliser la version optimisée le requête de tri :
SELECT CON_ID FROM CONTENTS ORDER BY CON_DIG DESC, CON_BURY DESC
Et enfin pour afficher à mes utilisateurs un nombre positif pour les bury :
SELECT CON_ID,CON_DIG AS DIG,ABS(CON_BURY) AS BURY FROM CONTENTS ORDER BY CON_DIG DESC, CON_BURY DESC
Voilà c'est fini j'espère que cela n'a pas été trop fastidieux à comprendre le cas échéant bon courage ;)
Image : jahovil