2017-10-04 9 views
1

exemple le plus simple:Comment trier queryset attr annotée du champ ManyToMany

class User(models.Model): 
    name = ... 

class Group(models.Model): 
    members = models.ManyToManyField(User, through='GroupMembership') 

class GroupMembership(models.Model): 
    user = ... 
    group = ... 

Je veux obtenir la liste des groupes classés par domaine des membres annoté. J'utilise la recherche trigramme pour filtrer et annoter le jeu de requêtes utilisateur. Pour obtenir les utilisateurs annotés j'ai quelque chose comme ça:

User.objects.annotate(...).annotate(similarity=...) 

Et maintenant, je suis en train de trier des groupes queryset par la « similitude » des utilisateurs:

ann_users = User.objects.annotate(...).annotate(similarity=...) 
qs = Group.objects.prefetch_related(Prefetch('members', 
    queryset=ann_users)) 
qs.annotate(similarity=Max('members__similarity')).order_by('similarity') 

Mais cela ne fonctionne pas, parce que prefetch_related fait le 'joindre' en Python; donc j'ai l'erreur:

"FieldError: Cannot resolve keyword 'members' into field." 

Répondre

0

Je pense que vous avez une fonction de base de données similitude des noms par la recherche trigramme et son Django reliure ou vous créez des:

from django.db.models import Max, Func, Value, Prefetch 

class Similarity(Func): 
    function = 'SIMILARITY' 
    arity = 2 

SEARCHED_NAME = 'searched_name' 
ann_users = User.objects.annotate(similarity=Similarity('name', Value(SEARCHED_NAME))) 
qs = Group.objects.prefetch_related(Prefetch('members', queryset=ann_users)) 
qs = qs.annotate(
    similarity=Max(Similarity('members__name', Value(SEARCHED_NAME))) 
).order_by('similarity') 

La requête principale est compilé à

SELECT app_group.id, MAX(SIMILARITY(app_user.name, %s)) AS similarity 
FROM app_group 
LEFT OUTER JOIN app_groupmembership ON (app_group.id = app_groupmembership.group_id) 
LEFT OUTER JOIN app_user ON (app_groupmembership.user_id = app_user.id) 
GROUP BY app_group.id 
ORDER BY similarity ASC; 
-- params: ['searched_name'] 

Ce n'est pas exactement ce que vous voulez dans le titre, mais le résultat est le même. Remarques: L'efficacité de la fonction SIMILARITY évaluée dépend de l'optimiseur de requête de la base de données. Le plan de requête par commande EXPLAIN sera une réponse intéressante, si l'idée originale par requête brute dans un cas simplifié est meilleure.

+0

Merci pour votre réponse. J'ai ton idée. Malheureusement, je n'ai pas de fonction unique "Similarity"; ma recherche est plus compliquée - elle utilise la recherche Double Metaphone + Trigrams par plusieurs champs. –

+0

Et une autre chose: je suppose que Similarity ne devrait pas être utilisé deux fois, n'est-ce pas? –

+0

Si vous écrivez une expression de similarité, je peux la réécrire en une seule fonction. Une annotation ne peut pas être réutilisée par des champs connexes (ou je ne peux pas le faire). Les relations sont aux modèles pas aux requêtes ou aux queries. Par conséquent, il n'existe actuellement aucun moyen de réutiliser une annotation par un champ associé. Il est plus important de vérifier si le nombre d'évaluations de similarité est len ​​(User) + len (GroupMembership) ou 2 * len (User), pour votre backend. Deux fois plus lent ORM qu'un SQL manuscrit n'est pas mauvais. – hynekcer