2009-10-13 4 views
1

Objectif: Proposer des objets en fonction des choix de l'utilisateurMySQL: objets Suggérant (optimisation d'une requête multi-jointure)

données: Tableau contenant les informations sur la façon dont les utilisateurs commanderaient un sous-ensemble d'objets du pire au meilleur; Exemple:

  1 2 3 4 5 6 
    John: A B G J S O 
    Mary: A C G L 
    Joan: B C L J K 
    Stan: G J C L 

Il y a environ 20 fois plus d'utilisateurs que d'objets, la gamme de chaque utilisateur contient 50 à 200 objets.

Le tableau:

CREATE TABLE IF NOT EXISTS `pref` (
    `usr` int(10) unsigned NOT NULL, 
    `obj` int(10) unsigned NOT NULL, 
    `ord` int(10) unsigned NOT NULL, 
    UNIQUE KEY `u_o` (`usr`,`obj`), 
    KEY `u` (`usr`) 
) ENGINE=MyISAM DEFAULT CHARSET=utf8; 

idée de base: Itérer à l'intérieur des objets de l'utilisateur à partir de la deuxième pire, des paires de construction (A> B); Recherchez-les dans les files d'attente des autres utilisateurs et répertoriez les éléments mieux que A selon ces utilisateurs.

Query:

SELECT e.obj, COUNT(e.obj) AS rate 
FROM pref a, pref b, pref c, pref d, pref e 

WHERE a.usr = '222' # step 1: select a pair of objects A, B, where A is better than B according to user X 
AND a.obj = '111' 
AND b.usr = a.usr 
AND b.ord < a.ord 

AND c.obj = a.obj # step 2: find users thinking that object A is better than B 
AND d.obj = b.obj 
AND d.ord < c.ord 
AND d.usr = c.usr 

AND e.ord > c.ord # step 3: find objects better than A according to these users 
AND e.usr = c.usr 

GROUP BY e.obj 
ORDER BY rate DESC; 

Alias:
a objet A ('111'), l'utilisateur courant ('222')
b objet B, pire qu'un après l'utilisateur courant (a plus faible valeur de « ord » de A)
c objet A dans la gamme d'un autre utilisateur
d objet B dans la gamme d'un autre utilisateur
e objet mieux que A dans la gamme d'autres utilisateurs

Le plan d'exécution (Ouo et uo étant les indices comme suggéré par Quassnoi):

+----+-------------+-------+------+---------------+------+---------+---------------------+------+----------------------------------------------+ 
| id | select_type | table | type | possible_keys | key | key_len | ref     | rows | Extra          | 
+----+-------------+-------+------+---------------+------+---------+---------------------+------+----------------------------------------------+ 
| 1 | SIMPLE  | a  | ref | ouo,uo  | ouo | 8  | const,const   | 1 | Using index; Using temporary; Using filesort | 
| 1 | SIMPLE  | b  | ref | ouo,uo  | uo | 4  | const    | 86 | Using where         | 
| 1 | SIMPLE  | d  | ref | ouo,uo  | ouo | 4  | db.b.obj   | 587 | Using index         | 
| 1 | SIMPLE  | c  | ref | ouo,uo  | ouo | 8  | const,db.d.usr  | 1 | Using where; Using index      | 
| 1 | SIMPLE  | e  | ref | uo   | uo | 4  | db.d.usr   | 80 | Using where         | 
+----+-------------+-------+------+---------------+------+---------+---------------------+------+----------------------------------------------+ 

La requête semble bien fonctionner aussi longtemps que la l'ensemble de données n'est pas trop grand; des idées sur la façon de le rationaliser pour soutenir de plus grands ensembles de données?

+0

Combien d'objets par utilisateur y aura-t-il en moyenne? – Quassnoi

+0

Environ 50-200 objets par utilisateur. – Mike

+0

Avez-vous vraiment besoin de ce classement exactement comme il est maintenant? La requête pourrait être facilement améliorée si ce n'est pour ce classement.Aussi, pourriez-vous s'il vous plaît poster le plan d'exécution de la requête? – Quassnoi

Répondre

3

La requête est très bien, il suffit de créer les indices suivants:

pref (obj, usr, ord) 
pref (usr, ord) 

Mise à jour:

Essayez cette syntaxe.

Le système de notation est plus simple mais assez similaire: il donne presque la même note sur les résultats aléatoires de test que j'ai créés.

SELECT oa.obj, SUM(weight) AS rate 
FROM (
     SELECT usr, ord, 
       (
       SELECT COUNT(*) 
       FROM pref a 
       JOIN pref ob 
       ON  ob.obj = a.obj 
       WHERE ob.usr = o.usr 
         AND a.usr = 50 
         AND a.ord < 
         (
         SELECT ord 
         FROM pref ai 
         WHERE ai.usr = 50 
           AND ai.obj = 75 
         ) 
         AND ob.ord < o.ord 
       ) AS weight 
     FROM pref o 
     WHERE o.obj = 75 
     HAVING weight >= 0 
     ) ow 
JOIN pref oa 
ON  oa.usr = ow.usr 
     AND oa.ord > ow.ord 
GROUP BY 
     oa.obj 
ORDER BY 
     rate DESC 

Cette requête donne le poids à chaque élément une cote supérieure à A par tous les utilisateurs qui ont évalué A.

Le poids est égal au nombre d'articles cotés A par les deux utilisateurs.

+0

Merci! C'est dommage, cependant. Pour un utilisateur avec 150 objets dans une table avec 250k lignes, cela prend environ 8-9 secondes (renvoyant 376 lignes). :/ J'ai essayé de changer le type de table de MyISAM à la mémoire, mais pour une raison quelconque, il ne veut pas utiliser les index multi-colonnes alors. Bizarre. – Mike