Django
Querysets
Recherche…
Introduction
Un Queryset
est fondamentalement une liste d'objets dérivés d'un Model
, par une compilation de requêtes de base de données.
Requêtes simples sur un modèle autonome
Voici un modèle simple que nous utiliserons pour exécuter quelques requêtes de test:
class MyModel(models.Model):
name = models.CharField(max_length=10)
model_num = models.IntegerField()
flag = models.NullBooleanField(default=False)
Obtenez un objet de modèle unique où l'id / pk est 4:
(S'il n'y a pas d'éléments avec l'id de 4 ou s'il y en a plus d'un, cela lancera une exception.)
MyModel.objects.get(pk=4)
Tous les objets du modèle:
MyModel.objects.all()
Les objets de modèle dont l' flag
défini sur True
:
MyModel.objects.filter(flag=True)
Modéliser des objets avec un model_num
supérieur à 25:
MyModel.objects.filter(model_num__gt=25)
Les objets de modèle portant le name
"Élément bon marché" et le flag
défini sur False
:
MyModel.objects.filter(name="Cheap Item", flag=False)
Modélise un name
recherche simple pour une chaîne spécifique (sensible à la casse):
MyModel.objects.filter(name__contains="ch")
Modèles Recherche simple name
de chaîne spécifique (insensible à la casse):
MyModel.objects.filter(name__icontains="ch")
Requêtes avancées avec des objets Q
Vu le modèle:
class MyModel(models.Model):
name = models.CharField(max_length=10)
model_num = models.IntegerField()
flag = models.NullBooleanField(default=False)
Nous pouvons utiliser des objets Q
pour créer des conditions AND
, OR
dans votre requête de recherche. Par exemple, supposons que tous les objets ayant flag=True
OR model_num>15
.
from django.db.models import Q
MyModel.objects.filter(Q(flag=True) | Q(model_num__gt=15))
Ce qui précède se traduit par WHERE flag=True OR model_num > 15
même pour un ET que vous feriez.
MyModel.objects.filter(Q(flag=True) & Q(model_num__gt=15))
Q
objets Q
nous permettent également de faire des requêtes NOT avec l'utilisation de ~
. Disons que nous voulions obtenir tous les objets qui ont flag=False
AND model_num!=15
, nous ferions:
MyModel.objects.filter(Q(flag=True) & ~Q(model_num=15))
Si vous utilisez des objets Q et des paramètres "normaux" dans filter()
, les objets Q
doivent être placés en premier . La requête suivante recherche les modèles avec ( flag
défini sur True
ou un numéro de modèle supérieur à 15
) et un nom commençant par "H".
from django.db.models import Q
MyModel.objects.filter(Q(flag=True) | Q(model_num__gt=15), name__startswith="H")
Remarque: les objets Q
peuvent être utilisés avec toute fonction de recherche prenant en compte les arguments de mots clés tels que filter
, exclude
, get
. Assurez-vous que lorsque vous utilisez avec get
, vous ne retournerez qu'un objet ou que l'exception MultipleObjectsReturned
sera déclenchée.
Réduire le nombre de requêtes sur ManyToManyField (problème n + 1)
Problème
# models.py:
class Library(models.Model):
name = models.CharField(max_length=100)
books = models.ManyToManyField(Book)
class Book(models.Model):
title = models.CharField(max_length=100)
# views.py
def myview(request):
# Query the database.
libraries = Library.objects.all()
# Query the database on each iteration (len(author) times)
# if there is 100 librairies, there will have 100 queries plus the initial query
for library in libraries:
books = library.books.all()
books[0].title
# ...
# total : 101 queries
Solution
Utilisez prefetch_related
sur ManyToManyField
si vous savez que vous devrez accéder ultérieurement à un champ qui est un champ ManyToManyField
.
# views.py
def myview(request):
# Query the database.
libraries = Library.objects.prefetch_related('books').all()
# Does not query the database again, since `books` is pre-populated
for library in libraries:
books = library.books.all()
books[0].title
# ...
# total : 2 queries - 1 for libraries, 1 for books
prefetch_related
peut également être utilisé sur les champs de recherche:
# models.py:
class User(models.Model):
name = models.CharField(max_length=100)
class Library(models.Model):
name = models.CharField(max_length=100)
books = models.ManyToManyField(Book)
class Book(models.Model):
title = models.CharField(max_length=100)
readers = models.ManyToManyField(User)
# views.py
def myview(request):
# Query the database.
libraries = Library.objects.prefetch_related('books', 'books__readers').all()
# Does not query the database again, since `books` and `readers` is pre-populated
for library in libraries:
for book in library.books.all():
for user in book.readers.all():
user.name
# ...
# total : 3 queries - 1 for libraries, 1 for books, 1 for readers
Cependant, une fois que le jeu de requête a été exécuté, les données extraites ne peuvent pas être modifiées sans toucher à nouveau la base de données. Les éléments suivants exécuteraient des requêtes supplémentaires, par exemple:
# views.py
def myview(request):
# Query the database.
libraries = Library.objects.prefetch_related('books').all()
for library in libraries:
for book in library.books.filter(title__contains="Django"):
print(book.name)
Les éléments suivants peuvent être optimisés en utilisant un objet Prefetch
, introduit dans Django 1.7:
from django.db.models import Prefetch
# views.py
def myview(request):
# Query the database.
libraries = Library.objects.prefetch_related(
Prefetch('books', queryset=Book.objects.filter(title__contains="Django")
).all()
for library in libraries:
for book in library.books.all():
print(book.name) # Will print only books containing Django for each library
Réduire le nombre de requêtes sur le champ ForeignKey (problème n + 1)
Problème
Les groupes de requêtes Django sont évalués paresseusement. Par exemple:
# models.py:
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
author = models.ForeignKey(Author, related_name='books')
title = models.CharField(max_length=100)
# views.py
def myview(request):
# Query the database
books = Book.objects.all()
for book in books:
# Query the database on each iteration to get author (len(books) times)
# if there is 100 books, there will have 100 queries plus the initial query
book.author
# ...
# total : 101 queries
Le code ci-dessus fait que django interroge la base de données pour trouver l'auteur de chaque livre. Ceci est inefficace et il est préférable de ne disposer que d'une seule requête.
Solution
Utilisez select_related
sur ForeignKey
si vous savez que vous devrez ultérieurement accéder à un champ ForeignKey
.
# views.py
def myview(request):
# Query the database.
books = Books.objects.select_related('author').all()
for book in books:
# Does not query the database again, since `author` is pre-populated
book.author
# ...
# total : 1 query
select_related
peut également être utilisé dans les champs de recherche:
# models.py:
class AuthorProfile(models.Model):
city = models.CharField(max_length=100)
class Author(models.Model):
name = models.CharField(max_length=100)
profile = models.OneToOneField(AuthorProfile)
class Book(models.Model):
author = models.ForeignKey(Author, related_name='books')
title = models.CharField(max_length=100)
# views.py
def myview(request):
books = Book.objects.select_related('author')\
.select_related('author__profile').all()
for book in books:
# Does not query database
book.author.name
# or
book.author.profile.city
# ...
# total : 1 query
Obtenir le jeu de requête SQL pour Django
L'attribut query
sur queryset vous donne une syntaxe équivalente SQL pour votre requête.
>>> queryset = MyModel.objects.all()
>>> print(queryset.query)
SELECT "myapp_mymodel"."id", ... FROM "myapp_mymodel"
Attention:
Cette sortie ne doit être utilisée qu'à des fins de débogage. La requête générée n'est pas spécifique au backend. En tant que tels, les paramètres ne sont pas correctement cités, ce qui les rend vulnérables à l'injection SQL, et la requête peut même ne pas être exécutable sur votre backend de base de données.
Obtenir le premier et le dernier enregistrement de QuerySet
Pour obtenir le premier objet:
MyModel.objects.first()
Pour obtenir les derniers objets:
MyModel.objects.last()
Utilisation du filtre Premier objet:
MyModel.objects.filter(name='simple').first()
Utilisation du filtre Dernier objet:
MyModel.objects.filter(name='simple').last()
Requêtes avancées avec des objets F
Un objet F () représente la valeur d'un champ de modèle ou d'une colonne annotée. Il permet de faire référence aux valeurs de champs de modèle et d'effectuer des opérations de base de données en les utilisant sans avoir à les extraire de la base de données en mémoire Python. - Expressions F ()
Il est approprié d'utiliser des objets F()
chaque fois que vous devez référencer la valeur d'un autre champ dans votre requête. En soi, les objets F()
ne veulent rien dire et ils ne peuvent et ne doivent pas être appelés en dehors d'un jeu de requête. Ils sont utilisés pour référencer la valeur d'un champ sur le même jeu de requête.
Par exemple, donné un modèle ...
SomeModel(models.Model):
...
some_field = models.IntegerField()
... un utilisateur peut interroger des objets dont la valeur some_field
correspond à deux fois son id
en référençant la valeur du champ id
lors du filtrage à l'aide de F()
comme ceci:
SomeModel.objects.filter(some_field=F('id') * 2)
F('id')
référence simplement la valeur de l' id
pour cette même instance. Django l'utilise pour créer l'instruction SQL correspondante. Dans ce cas, quelque chose qui ressemble beaucoup à ceci:
SELECT * FROM some_app_some_model
WHERE some_field = ((id * 2))
Sans les expressions F()
, cela se ferait avec du SQL brut ou du filtrage en Python (ce qui réduit les performances, surtout quand il y a beaucoup d'objets).
Les références:
De la définition de la classe F()
:
Un objet capable de résoudre les références aux objets de requête existants. - source F
Remarque: Cet exemple publié provient de la réponse répertoriée ci-dessus avec le consentement de TinyInstance.