Django ORM : maîtriser les requêtes optimisées

🏷️ Back-end 📅 16/01/2026 09:00:00 👤 Mezgani said
Django Orm Python Database Optimization
Django ORM : maîtriser les requêtes optimisées

Exploitez la puissance du Django ORM : relations, N+1 queries, select_related(), prefetch_related(), indexation et bonnes pratiques de performance.

QuerySet : les bases

Les QuerySets sont les couches d'abstraction de Django ORM. Elles sont paresseuses (lazy) : elles n'exécutent la requête que lorsque nécessaire :

# Pas d'exécution - QuerySet seulement
users = User.objects.filter(age__gte=18)

# Exécution - itération sur les résultats
for user in users:
    print(user.name)

# Ou conversion en liste
users_list = list(users)

Les filtres QuerySet les plus courants :

FiltreSyntaxeSQL équivalent
Égalfilter(age=25)WHERE age = 25
Supérieurfilter(age__gte=18)WHERE age >= 18
Contientfilter(name__icontains='john')LIKE
Infilter(status__in=['active', 'pending'])IN
Nullfilter(deleted_at__isnull=True)IS NULL

Relations entre modèles

Django ORM supporte les relations One-to-Many, Many-to-Many et One-to-One :

# models.py
from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)
    email = models.EmailField(unique=True)

class Book(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')
    publication_date = models.DateField()
    tags = models.ManyToManyField('Tag', related_name='books')

class Tag(models.Model):
    name = models.CharField(max_length=50, unique=True)

Accédez aux relations inversées via le related_name :

# Accès direct
author = Author.objects.get(id=1)
books = author.books.all()  # Utilise related_name

# Ou sans related_name
books = Book.objects.filter(author__id=1)
À retenir : Utilisez toujours on_delete=models.CASCADE ou SET_NULL pour éviter les données orphelines.

Éviter le problème N+1

Le problème N+1 génère une requête pour chaque enregistrement. Django fournit select_related() et prefetch_related() :

# ❌ MAUVAIS - 1 + N requêtes (N+1 problem)
books = Book.objects.all()
for book in books:
    print(book.author.name)  # Nouvelle requête pour chaque livre!

# ✅ BON - Une seule requête (JOIN)
books = Book.objects.select_related('author')
for book in books:
    print(book.author.name)  # Aucune nouvelle requête

# ✅ POUR les relations Many-to-Many
books = Book.objects.prefetch_related('tags')
for book in books:
    for tag in book.tags.all():
        print(tag.name)  # Cache utilisé, pas de nouvelle requête

Chaînez les optimisations :

books = Book.objects.select_related('author') \
    .prefetch_related('tags') \
    .filter(publication_date__year=2024) \
    .values('title', 'author__name')
Note : select_related() utilise LEFT JOIN (pour ForeignKey), prefetch_related() fait des requêtes séparées (pour ManyToMany).

Agrégation et statistiques

Utilisez agrégations pour calculer directement en base de données :

from django.db.models import Count, Sum, Avg, Max, Min

# Nombre total de livres par auteur
authors_books = Author.objects.annotate(
    total_books=Count('books')
).values('name', 'total_books')

# Prix moyen des livres
avg_price = Book.objects.aggregate(avg_price=Avg('price'))
# Résultat: {'avg_price': 29.99}

# Statistiques multiples
stats = Book.objects.aggregate(
    total=Count('id'),
    avg_price=Avg('price'),
    max_price=Max('price'),
    min_price=Min('price')
)
# {'total': 150, 'avg_price': 29.99, 'max_price': 99.99, 'min_price': 9.99}

Indexation et performance

Optimisez les performances en créant des index :

class Book(models.Model):
    title = models.CharField(max_length=200, db_index=True)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    publication_date = models.DateField(db_index=True)
    is_published = models.BooleanField(default=False, db_index=True)

    class Meta:
        indexes = [
            models.Index(fields=['publication_date', '-created_at']),
            models.Index(fields=['author', 'is_published']),
        ]

Pour analyser les performances, utilisez django.db.connection.queries :

from django.db import connection
from django.test.utils import override_settings

@override_settings(DEBUG=True)
def check_queries():
    books = Book.objects.select_related('author').all()
    list(books)

    print(f"Nombre de requêtes: {len(connection.queries)}")
    for query in connection.queries:
        print(f"Temps: {query['time']}, SQL: {query['sql'][:100]}")
À retenir : L'indexation réduit les temps de requête, mais ralentit les insertions. Choisissez stratégiquement les champs indexés.