Django peut faire des requêtes imbriquées et exclusions
-
23-09-2019 - |
Question
J'ai besoin d'aide mettre sur pied cette requête dans Django. J'ai simplifié l'exemple ici juste coupe droite au point.
MyModel(models.Model):
created = models.DateTimeField()
user = models.ForeignKey(User)
data = models.BooleanField()
La requête que je voudrais créer en anglais sonnerait comme:
Donne-moi chaque enregistrement qui a été créé hier pour lesquels des données est faux où le fait que les données mêmes de gamme ne semble jamais aussi vrai pour l'utilisateur donné
Voici un exemple d'entrée / sortie en cas qui n'a pas été clair.
Valeurs Tableau
ID Created User Data
1 1/1/2010 admin False
2 1/1/2010 joe True
3 1/1/2010 admin False
4 1/1/2010 joe False
5 1/2/2010 joe False
Sortie queryset
1 1/1/2010 admin False
3 1/1/2010 admin False
Qu'est-ce que je cherche à faire est d'exclure enregistrement n ° 4. La raison en est que dans la plage donnée « hier », les données apparaît comme vrai une fois pour l'utilisateur dans le dossier n ° 2, donc qui exclurait notice # 4.
Dans un sens, il semble presque comme il y a 2 requêtes en cours. L'un pour déterminer les enregistrements dans la plage donnée, et un à exclure les dossiers qui se croisent avec les enregistrements « True ».
Comment puis-je faire cette requête avec le Django ORM?
La solution
Vous n'avez pas besoin d'une requête imbriquée. Vous pouvez générer une liste des mauvais utilisateurs de PKs et d'exclure les enregistrements contenant les clés primaires dans la requête suivante.
bad = list(set(MyModel.obejcts.filter(data=True).values_list('user', flat=True)))
# list(set(list_object)) will remove duplicates
# not needed but might save the DB some work
rs = MyModel.objects.filter(datequery).exclude(user__pk__in=bad)
# might not need the pk in user__pk__in - try it
Vous pouvez condenser qui décompose en une ligne, mais je pense que ce aussi propre que vous obtiendrez. 2 requêtes ne sont pas si mal.
Edit: Vous pouvez wan lire la documentation à ce sujet:
http://docs.djangoproject.com/en/dev / ref / modèles / QuerySets / #
Il fait sonner comme il nids automatiques de la requête (donc seulement un des incendies de requête dans la base de données) si elle est comme ceci:
bad = MyModel.objects.filter(data=True).values('pk')
rs = MyModel.objects.filter(datequery).exclude(user__pk__in=bad)
et MySQL n'optimise pas si bien que mon code ci-dessus (2 requêtes complètes) peut réellement finir par courir beaucoup plus vite.
Essayez les deux et faire la course!
Autres conseils
ressemble, vous pouvez utiliser:
de django.db.models importer F
MyModel.objects.filter(datequery).filter(data=False).filter(data = F('data'))
objet F
disponible à partir de la version 1.0
S'il vous plaît, testez-le, je ne suis pas sûr.
Merci à l'évaluation paresseuse, vous pouvez casser votre requête en un petit nombre de variables différentes pour le rendre plus facile à lire. Voici un peu de temps de jeu de ./manage.py shell
dans le style Oli déjà présenté.
> from django.db import connection
> connection.queries = []
> target_day_qs = MyModel.objects.filter(created='2010-1-1')
> bad_users = target_day_qs.filter(data=True).values('user')
> result = target_day_qs.exclude(user__in=bad_users)
> [r.id for r in result]
[1, 3]
> len(connection.queries)
1
On pourrait dire aussi result.select_related()
si vous vouliez tirer dans les objets utilisateur dans la même requête.