2010-05-07 3 views
1

Je dois optimiser une requête pour un classement qui prend une éternité (la requête elle-même fonctionne, mais je sais que c'est horrible et je viens de l'essayer avec un bon nombre d'enregistrements et ça donne un timeout) .Optimiser la requête de classement lent

Je vais brièvement expliquer le modèle. J'ai 3 tables: joueur, équipe et player_team. J'ai des joueurs, qui peuvent appartenir à une équipe. Aussi évident que cela puisse paraître, les joueurs sont stockés dans la table des joueurs et les équipes en équipe. Dans mon application, chaque joueur peut changer d'équipe à tout moment, et un journal doit être conservé. Cependant, un joueur est considéré comme appartenant à une seule équipe à un moment donné. L'équipe actuelle d'un joueur est la dernière qu'il a rejoint.

La structure du joueur et de l'équipe n'est pas pertinente, je pense. J'ai une colonne d'identification PK dans chaque. Dans player_team j'ai:

id   (PK) 
player_id (FK -> player.id) 
team_id  (FK -> team.id) 

Maintenant, chaque équipe est assignée un point pour chaque joueur qui s'est joint. Donc, maintenant, je veux obtenir un classement des N premières équipes avec le plus grand nombre de joueurs.

Ma première idée était d'obtenir en premier les joueurs actuels de player_team (c'est-à-dire un record pour chaque joueur, cet enregistrement doit être l'équipe actuelle du joueur). Je n'ai pas réussi à trouver un moyen simple de le faire (essayé GROUP BY player_team.player_id AYANT player_team.id = MAX (player_team.id), mais cela ne l'a pas coupé

J'ai essayé un certain nombre de requêtes que didn ' travail t, mais a réussi à obtenir ce travail.

SELECT 
    COUNT(*) AS total, 
    pt.team_id, 
    p.facebook_uid AS owner_uid, 
    t.color 
FROM 
    player_team pt 
JOIN player p ON (p.id = pt.player_id) 
JOIN team t ON (t.id = pt.team_id) 
WHERE 
    pt.id IN (
     SELECT max(J.id) 
     FROM player_team J 
     GROUP BY J.player_id 
    ) 

GROUP BY 
    pt.team_id 
ORDER BY 
    total DESC 
LIMIT 50    

Comme je l'ai dit, cela fonctionne, mais semble très mauvais et exécute le pire, donc je suis sûr qu'il doit y avoir une meilleure façon d'aller. tout le monde a des idées pour cette optimisation?

J'utilise MySQL, en passant.

Merci à l'avance

Ajout de l'explication. (Désolé, ne savez pas comment formater correctement)

id select_type  table type possible_keys key  key_len  ref  rows Extra 
1 PRIMARY  t ALL  PRIMARY  NULL NULL NULL 5000 Using temporary; Using filesort 
1 PRIMARY  pt ref  FKplayer_pt77082,FKplayer_pt265938,new_index FKplayer_pt77082 4 t.id 30 Using where 
1 PRIMARY  p eq_ref PRIMARY  PRIMARY  4 pt.player_id 1 
2 DEPENDENT SUBQUERY J index NULL new_index 8 NULL 150000 Using index 
+2

Est-ce que vous quittez définitivement toutes les combinaisons d'équipes de joueurs qui se sont déjà produites dans player_team? N'êtes-vous pas en train de le marquer d'une façon ou d'une autre (une colonne qui a 0 pour une relation historique, 1 pour une relation actuelle ferait bien)? – marr75

+0

Oui, je quitte la combinaison puisque je dois tenir un journal. Je pensais avoir un drapeau actuel, et j'irais probablement de l'avant s'il n'y a pas de meilleure alternative. Mais je pense qu'il y a peut-être un meilleur moyen. (Je suis un noob sql!) Merci pour votre suggestion, cependant. –

+0

S'il vous plaît poster votre expliquer. –

Répondre

2

Essayez ceci:

SELECT t.*, cnt 
FROM (
     SELECT team_id, COUNT(*) AS cnt 
     FROM (
       SELECT player_id, MAX(id) AS mid 
       FROM player_team 
       GROUP BY 
         player_id 
       ) q 
     JOIN player_team pt 
     ON  pt.id = q.mid 
     GROUP BY 
       team_id 
     ) q2 
JOIN team t 
ON  t.id = q2.team_id 
ORDER BY 
     cnt DESC 
LIMIT 50 

Créer un index sur player_team (player_id, id) (dans cet ordre) pour que cela fonctionne rapidement.

+0

Merci Quassnoi. Je pense que vous vouliez dire pt.id = q.mid dans la condition ON; changer cela et a travaillé. J'ai essayé cela et les résultats sont arrivés très vite. N'a pas vérifié si les résultats sont encore corrects, mais le fera le plus tôt possible. Merci encore! –

+1

+1 pour mémoriser les index –

+0

Désolé, je voulais dire cette deuxième condition ON, qui devrait lire "t.id = q2.team_id" au lieu de "t.team_id = q2.team_id" –

1

Je trouve parfois que des requêtes plus complexes dans MySQL doivent être brisé en deux morceaux.

La première pièce tirera les données requises dans une table temporaire et la deuxième pièce sera la requête qui tente de manipuler l'ensemble de données créé. Cela entraîne certainement un gain de performance significatif.

+0

Merci. C'est l'une des premières idées qui me sont venues à l'esprit (mais avec une table réelle). L'autre option que je considère est d'avoir un drapeau pour marquer une relation player_team en tant que current/active. –

2

C'est la sous-requête qui le tue - si vous ajoutez un champ current sur la table player_team, où vous lui donnez la valeur = 1 s'il est actuel, et 0 s'il est ancien, vous pouvez simplifier cet alot en faisant simplement:

SELECT 
    COUNT(*) AS total, 
    pt.team_id, 
    p.facebook_uid AS owner_uid, 
    t.color 
FROM 
    player_team pt 
JOIN player p ON (p.id = pt.player_id) 
JOIN team t ON (t.id = pt.team_id) 
WHERE 
    player_team.current = 1 
GROUP BY 
    pt.team_id 
ORDER BY 
    total DESC 
LIMIT 50 

Avoir plusieurs entrées dans la table player_team pour la même relation où la seule façon de faire la distinction que l'on est le dossier « courant » est en comparant deux (ou plus) lignes, je pense est une mauvaise pratique. J'ai été dans cette situation avant et les solutions de contournement que vous devez faire pour que cela fonctionne vraiment tuer la performance. Il est de loin préférable de pouvoir voir quelle ligne est courante en faisant une recherche simple (dans ce cas, where current=1) - ou en déplaçant des données historiques dans un tableau complètement différent (selon votre situation, cela pourrait être exagéré).

+0

Merci. J'envisage d'ajouter cette colonne. Je veux juste voir s'il y a d'autres options. –

+0

Avec le drapeau actuel, vous pouvez ajouter deux autres colonnes, activate_datetime et inactivate_datetime de cette façon, vous le saurez, lorsque la transition réelle s'est produite. –

+0

@Nitin Midha. Merci pour la suggestion. J'ai en fait une colonne "créée" pour stocker l'horodatage de l'insertion de la ligne (qui est le moment où le joueur a rejoint l'équipe).J'ai juste essayé de laisser des choses moins importantes hors du post juste pour ne pas ajouter trop de fouillis. –

0

Cela se les équipes actuelles avec des couleurs commandées par taille:

SELECT team_id, COUNT(player_id) c AS total, t.color 
    FROM player_team pt JOIN teams t ON t.team_id=pt.team_id 
    GROUP BY pt.team_id WHERE current=1 
    ORDER BY pt.c DESC 
    LIMIT 50; 

Mais vous n'avez pas donné une condition pour quel joueur doit être considéré comme propriétaire de l'équipe. Votre requête actuelle montre arbitrairement qu'un joueur est owner_id à cause du groupement, pas parce que ce joueur est le propriétaire actuel. Si votre table player_team contient une colonne 'owner', vous pouvez joindre la requête ci-dessus à une requête des propriétaires. Quelque chose comme:

SELECT o.facebook_uid, a.team_id, a.color, a.c 
FROM player_teams pt1 
    JOIN players o ON (pt1.player_id=o.player_id AND o.owner=1) 
    JOIN (...above query...) a 
    ON a.team_id=pt1.team_id; 
0

Vous pouvez ajouter une colonne « last_playteam_id » à la table des joueurs, et le mettre à jour chaque fois qu'un joueur change son équipe avec le pk de table player_team.

Ensuite, vous pouvez le faire:

SELECT 
    COUNT(*) AS total, 
    pt.team_id, 
    p.facebook_uid AS owner_uid, 
    t.color 
FROM 
    player_team pt 
JOIN player p ON (p.id = pt.player_id) and p.last_playteam_id = pt.id 
JOIN team t ON (t.id = pt.team_id) 
GROUP BY 
    pt.team_id 
ORDER BY 
    total DESC 
LIMIT 50 

Cela pourrait être le plus rapide parce que vous n'avez pas à mettre à jour les anciennes lignes de player_team à courant = 0. Vous pouvez également ajouter à la place une colonne "last_team_id" et conserver son équipe actuelle, vous obtenez le résultat le plus rapide pour la requête ci-dessus, mais cela peut être moins utile avec d'autres requêtes.