Skip to content

Chapter 5: Database Operations ​

5.3 QuerySet and Database API ​

QuerySet Concept ​

QuerySet is the core concept of Django ORM, representing a collection of database queries. QuerySet has the following characteristics:

  • Lazy Execution: QuerySet only executes database queries when results are needed
  • Chaining: Multiple methods can be called consecutively to build complex queries
  • Cacheable: The same QuerySet caches results to avoid repeated queries

Basic Query Operations ​

Getting All Objects ​

python
from myapp.models import Article

# Get all articles
articles = Article.objects.all()

# Convert to list (triggers query execution)
article_list = list(articles)

# Iterate through QuerySet (triggers query execution)
for article in articles:
    print(article.title)

Getting a Single Object ​

python
# Get by primary key
article = Article.objects.get(pk=1)

# Get by field
article = Article.objects.get(title="Django Tutorial")

# Handle non-existent objects
try:
    article = Article.objects.get(pk=999)
except Article.DoesNotExist:
    print("Article does not exist")

# Use get_or_create
article, created = Article.objects.get_or_create(
    title="New Article",
    defaults={'content': 'Default content'}
)

Filter Queries ​

python
# Basic filtering
published_articles = Article.objects.filter(is_published=True)

# Multi-condition filtering
recent_published = Article.objects.filter(
    is_published=True,
    created_at__gte=timezone.now() - timedelta(days=7)
)

# Exclude queries
unpublished_articles = Article.objects.exclude(is_published=True)

# Chained filtering
articles = Article.objects.filter(category='tech').exclude(is_published=False)

Field Lookups ​

Django provides rich field lookup methods:

python
# Exact match
articles = Article.objects.filter(title__exact="Django Tutorial")

# Contains query
articles = Article.objects.filter(title__contains="Django")

# Starts with
articles = Article.objects.filter(title__startswith="Django")

# Ends with
articles = Article.objects.filter(title__endswith="Tutorial")

# Null query
articles = Article.objects.filter(content__isnull=True)

# Range query
from datetime import datetime, timedelta
start_date = datetime(2025, 1, 1)
end_date = datetime(2025, 12, 31)
articles = Article.objects.filter(created_at__range=(start_date, end_date))

# IN query
categories = ['tech', 'science', 'art']
articles = Article.objects.filter(category__in=categories)

# Comparison queries
articles = Article.objects.filter(views__gt=100)   # Greater than
articles = Article.objects.filter(views__gte=50)   # Greater than or equal
articles = Article.objects.filter(views__lt=10)    # Less than
articles = Article.objects.filter(views__lte=5)    # Less than or equal

Sorting and Slicing ​

python
# Ascending sort
articles = Article.objects.order_by('created_at')

# Descending sort
articles = Article.objects.order_by('-created_at')

# Multi-field sorting
articles = Article.objects.order_by('category', '-created_at')

# Slicing operations (LIMIT and OFFSET)
first_5_articles = Article.objects.all()[:5]          # LIMIT 5
next_5_articles = Article.objects.all()[5:10]         # LIMIT 5 OFFSET 5
last_5_articles = Article.objects.all()[-5:]          # Last 5

# Random sorting
import random
articles = Article.objects.order_by('?')[:10]         # Random 10 articles

Aggregation and Annotations ​

Basic Aggregation ​

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

# Count
article_count = Article.objects.count()
category_count = Article.objects.aggregate(Count('category'))

# Average
avg_views = Article.objects.aggregate(Avg('views'))

# Maximum and minimum values
max_views = Article.objects.aggregate(Max('views'))
min_views = Article.objects.aggregate(Min('views'))

# Sum
total_views = Article.objects.aggregate(Sum('views'))

# Multiple aggregations
stats = Article.objects.aggregate(
    total=Count('id'),
    avg_views=Avg('views'),
    max_views=Max('views')
)

Group Aggregation ​

python
# Count articles by category
category_stats = Article.objects.values('category').annotate(
    article_count=Count('id'),
    avg_views=Avg('views')
).order_by('-article_count')

# Count by author
author_stats = Article.objects.values('author__username').annotate(
    article_count=Count('id'),
    total_views=Sum('views')
)

# Count by date
from django.db.models.functions import TruncDate
daily_stats = Article.objects.annotate(
    date=TruncDate('created_at')
).values('date').annotate(
    count=Count('id')
).order_by('date')

Annotations ​

python
from django.db.models import F, Value
from django.db.models.functions import Concat, Coalesce

# Use F expressions to reference field values
articles = Article.objects.annotate(
    popularity_score=F('views') * 0.7 + F('likes') * 0.3
)

# Field calculation
articles = Article.objects.annotate(
    reading_time=F('word_count') / 200  # Assuming reading speed of 200 words/minute
)

# String concatenation
articles = Article.objects.annotate(
    full_title=Concat('category', Value(': '), 'title')
)

# Handle null values
articles = Article.objects.annotate(
    safe_content=Coalesce('content', Value('No content'))
)

# Combine multiple functions
articles = Article.objects.annotate(
    display_title=Concat(
        Substr('title', 1, 20), 
        Value('...') if Length('title') > 20 else Value('')
    ),
    safe_summary=Coalesce('summary', Value('Click to view details'))
)

# Use F expressions for field operations
from django.db.models import F
articles = Article.objects.annotate(
    title_with_views=Concat('title', Value(' ('), F('view_count'), Value(' views)'))
)

Forward Queries ​

python
# One-to-one relationship
class AuthorProfile(models.Model):
    author = models.OneToOneField(Author, on_delete=models.CASCADE)
    bio = models.TextField()

# Get author's profile
author = Author.objects.get(pk=1)
profile = author.authorprofile

# One-to-many relationship
class Comment(models.Model):
    article = models.ForeignKey(Article, on_delete=models.CASCADE)
    content = models.TextField()

# Get all comments for an article
article = Article.objects.get(pk=1)
comments = article.comment_set.all()

# Many-to-many relationship
class Tag(models.Model):
    name = models.CharField(max_length=50)
    articles = models.ManyToManyField(Article)

# Get all tags for an article
article = Article.objects.get(pk=1)
tags = article.tag_set.all()

Reverse Queries ​

python
# Reverse query through foreign key
articles_with_comments = Article.objects.filter(comment__isnull=False)

# Reverse query through many-to-many
articles_with_popular_tags = Article.objects.filter(
    tag__name__in=['django', 'python']
)

# Optimize related queries
articles = Article.objects.select_related('author').prefetch_related('tags')

Query Optimization ​

When looping through related object attributes in templates, always use select_related or prefetch_related to preload data. This is a basic Django performance optimization skill; otherwise, it will cause N+1 query problems.

python
# select_related: For one-to-one and one-to-many relationships (SQL JOIN)
articles = Article.objects.select_related('author')  # Reduce query count

# prefetch_related: For many-to-many and reverse relationships (additional queries)
articles = Article.objects.prefetch_related('tags', 'comment_set')

# Combined usage, only 1 query
articles = Article.objects.select_related('author').prefetch_related(
    'tags', 'comment_set'
)

only and defer ​

python
# only: Load only specified fields
articles = Article.objects.only('title', 'created_at')

# defer: Exclude specified fields
articles = Article.objects.defer('content', 'metadata')

Bulk Operations ​

python
# Bulk create
articles_data = [
    Article(title=f'Article{i}', content=f'Content{i}')
    for i in range(100)
]
Article.objects.bulk_create(articles_data)

# Bulk update
Article.objects.filter(is_published=False).update(is_published=True)

# Bulk delete
Article.objects.filter(created_at__lt=timezone.now() - timedelta(days=365)).delete()

Raw SQL Queries ​

Executing Raw SQL ​

python
from django.db import connection

# Execute SQL directly
with connection.cursor() as cursor:
    cursor.execute("SELECT * FROM blog_article WHERE views > %s", [100])
    rows = cursor.fetchall()

# Use raw method
articles = Article.objects.raw('SELECT * FROM blog_article WHERE views > 100')

# Raw query with parameters
articles = Article.objects.raw(
    'SELECT * FROM blog_article WHERE category = %s AND views > %s',
    ['tech', 50]
)

Custom SQL Queries ​

python
# Custom manager method
class ArticleManager(models.Manager):
    def popular_articles(self, min_views=100):
        return self.raw('''
            SELECT * FROM blog_article 
            WHERE views > %s 
            ORDER BY views DESC
        ''', [min_views])

class Article(models.Model):
    # ... field definitions ...
    objects = ArticleManager()

# Use custom query
popular_articles = Article.objects.popular_articles(200)

Practical Example: Article Search Function ​

python
from django.db.models import Q

def search_articles(query, category=None, author=None):
    """
    Article search function
    """
    # Build query conditions
    conditions = Q()
    
    # Keyword search (title or content)
    if query:
        conditions |= Q(title__icontains=query)
        conditions |= Q(content__icontains=query)
    
    # Category filter
    if category:
        conditions &= Q(category=category)
    
    # Author filter
    if author:
        conditions &= Q(author__username=author)
    
    # Execute query and optimize
    articles = Article.objects.filter(conditions).select_related(
        'author'
    ).prefetch_related(
        'tags'
    ).order_by('-created_at')
    
    return articles

# Usage example
results = search_articles('Django', category='tech')

Advanced Query Techniques ​

Conditional Expressions ​

python
from django.db.models import Case, When, Value, IntegerField

# Conditional annotation
articles = Article.objects.annotate(
    popularity=Case(
        When(views__gt=1000, then=Value('Popular')),
        When(views__gt=100, then=Value('Average')),
        default=Value('Cold'),
        output_field=CharField()
    )
)

# Conditional update
Article.objects.update(
    status=Case(
        When(views__gt=1000, then=Value('popular')),
        default=Value('normal')
    )
)

Window Functions ​

Window Function Advantages

  • Database-side computation: Reduces application layer logic and improves performance
  • Complex analysis: Supports ranking, partitioning, moving averages, and other advanced analytics
  • Flexibility: Multiple window functions can be combined

Usage Notes

  • Requires database support for window functions (PostgreSQL, MySQL 8.0+, SQLite 3.25+)
  • Complex window functions may affect query performance
  • Be careful about N+1 query problems when using in templates
python
from django.db.models import Window, F, Avg, Sum
from django.db.models.functions import Rank, DenseRank, RowNumber, NthValue

# Rank by views descending, get top 10
# Result characteristics:
# Articles with the same views will get the same rank
# Ranks will have gaps (e.g.: 1, 2, 2, 4, 5...)
articles = Article.objects.annotate(
    rank=Window(
        expression=Rank(),  # Use Rank() ranking function
        order_by=F('views').desc()  # Order by views descending
    )
).filter(rank__lte=10)  # Only take top 10 ranked articles

# Dense ranking (no gaps)
articles = Article.objects.annotate(
    dense_rank=Window(
        expression=DenseRank(),
        order_by=F('views').desc()
    )
)

# Add row numbers for each article (ordered by creation time ascending)
articles = Article.objects.annotate(
    row_number=Window(
        expression=RowNumber(),  # Use RowNumber() row number function
        order_by=F('created_at').asc()  # Order by creation time ascending
    )
)

# Ranking within categories
articles = Article.objects.annotate(
    category_rank=Window(
        expression=Rank(),
        order_by=F('views').desc(),
        partition_by=F('category')  # Partition by category
    )
)

# Moving average (average views of the last 3 articles)
articles = Article.objects.annotate(
    moving_avg=Window(
        expression=Avg('views'),
        order_by=F('created_at').asc(),
        frame=Window.frame(start=-2, end=Window.CURRENT_ROW)
    )
)

Performance Monitoring and Debugging ​

Query Analysis ​

python
# Enable query logging
import logging
logger = logging.getLogger('django.db.backends')
logger.setLevel(logging.DEBUG)

# Use django-debug-toolbar
# Install: pip install django-debug-toolbar
# After configuration, you can view detailed query information

# Manually check queries
articles = Article.objects.all()
print(articles.query)  # View generated SQL

Query Optimization Recommendations ​

  1. Use indexes: Create database indexes for frequently queried fields
  2. Avoid N+1 queries: Use select_related and prefetch_related
  3. Bulk operations: Use bulk_create, bulk_update to reduce database round trips
  4. Limit result sets: Use slicing operations to limit returned data
  5. Cache queries: Use caching for data that doesn't change frequently

Summary ​

Django's QuerySet and database API provide powerful and flexible database operation capabilities:

  • ✅ Rich query methods: filter, exclude, get, annotate, etc.
  • ✅ Multiple field lookups: exact match, range queries, fuzzy search, etc.
  • ✅ Aggregation and grouping: Count, Sum, Avg, and other aggregation functions
  • ✅ Related query optimization: select_related, prefetch_related
  • ✅ Raw SQL support: raw queries and custom SQL
  • ✅ Performance optimization: bulk operations, query optimization techniques

Mastering these features can help you build efficient and maintainable database query logic.

Next ​

We will learn about using the Django view system.

6.1 Function Views →

Contents ​

Back to Course Outline

Released under the [BY-NC-ND License](https://creativecommons.org/licenses/by-nc-nd/4.0/deed.en).