2010-03-07 6 views
1

J'essaie de déterminer s'il y a moyen de faire une agrégation quelque peu complexe dans Django en utilisant son ORM, ou si je dois utiliser extra() pour coller quelque chose de brut SQLAgrégation entre les colonnes de Django

Voici mes modèles d'objet (dépouillées pour montrer juste l'essentiel):

class Submission(Models.model) 
    favorite_of = models.ManyToManyField(User, related_name="favorite_submissions") 

class Response(Models.model) 
    submission = models.ForeignKey(Submission) 
    voted_up_by = models.ManyToManyField(User, related_name="voted_up_responses") 

Ce que je veux faire est la somme de toutes les voix pour une communication donnée: c'est, tous les votes pour l'une des ses réponses, puis en incluant également le nombre de personnes qui ont marqué la soumission en tant que favori.

J'ai la première partie de travail en utilisant le code suivant; cela renvoie le total des voix toutes les réponses de chaque soumission:

submission_list = Response.objects\ 
    .values('submission')\ 
    .annotate(votes=Count('voted_up_by'))\ 
    .filter(votes__gt=0)\ 
    .order_by('-votes')[:TOP_NUM] 

(. Donc, après avoir obtenu le total des votes, je trie par ordre décroissant et retourner les meilleures soumissions de TOP_NUM, pour obtenir un « best of » annonce)

Cette partie fonctionne. Est-il possible de suggérer d'inclure le nombre de personnes qui ont favorisé chaque soumission dans ses votes? (Je préfèrerais éviter extra() pour la portabilité, mais je pense que cela peut être nécessaire, et je suis prêt à l'utiliser.)

EDIT: J'ai réalisé après avoir lu les suggestions ci-dessous que je devrais avoir été plus clair dans ma description du problème. La solution idéale serait celle qui m'a permis de trier par le nombre total de votes (la somme de voted_up_by et favorited), puis de choisir seulement le nombre le plus élevé, tous dans la base de données. Si ce n'est pas possible, je suis prêt à charger quelques-uns des champs de chaque réponse et à faire le traitement en Python; mais comme je traiterai plus de 100 000 enregistrements, ce serait bien d'éviter cette surcharge. (En outre, à Adam et Dmitry: Je suis désolé pour le retard dans la réponse!)

Répondre

1

Une possibilité serait de réorganiser votre requête actuelle légèrement. Que faire si vous avez essayé quelque chose comme ce qui suit:

submission_list = Response.objects\ 
    .annotate(votes=Count('voted_up_by'))\ 
    .filter(votes__gt=0)\ 
    .order_by('-votes')[:TOP_NUM] 
submission_list.query.group_by = ['submission_id'] 

Cela renverra un queryset d'objets de réponse (objets avec la même soumission seront regroupés). Pour accéder à la présentation connexe et/ou la liste des favorite_of/compter, vous avez deux options:

num_votes = submission_list[0].votes 
submission = submission_list[0].submission 
num_favorite = submission.favorite_of.count() 

ou ...

submissions = [] 
for response in submission_list: 
    submission = response.submission 
    submission.votes = response.votes 
    submissions.append(submission) 
num_votes = submissions[0].votes 
submission = submissions[0] 
num_favorite = submission.favorite_of.count() 

Fondamentalement, la première option a l'avantage d'être encore un QuerySet, mais vous devez être sûr d'accéder à l'objet de soumission afin d'obtenir des informations sur la soumission (puisque chaque objet dans le jeu de requête est techniquement une réponse). La deuxième option a l'avantage d'être une liste des soumissions avec la liste favorite_of ainsi que les votes, mais ce n'est plus un jeu de requête (assurez-vous donc de ne plus avoir à modifier la requête par la suite).

0

Vous pouvez compter favoris dans une autre requête comme

favorite_list = Submission.objects.annotate(favorites=Count(favorite_of)) 

Après que vous ajoutez les valeurs de deux listes:

total_votes = {} 
for item in submission_list: 
    total_votes[item.submission.id] = item.voted_by 
for item in favorite_list: 
    has_votes = total_votes.get(item.id, 0) 
    total_votes[item.id] = has_votes + item.favorites 

J'utilise ids dans le dictionnaire parce que les objets de soumission ne seront pas identiques . Si vous avez besoin des soumissions elles-mêmes, vous pouvez utiliser un dictionnaire ou un tuple de magasin (soumission, votes) au lieu de simplement voter.

Ajouté: cette solution est meilleure que la précédente car vous n'avez que deux requêtes DB.

Questions connexes