Introduction
Django’s ORM (Object-Relational Mapper) is one of its most powerful features. It allows you to interact with your database using Python code instead of writing raw SQL.
At the heart of the ORM is the QuerySet — a powerful abstraction that lets you retrieve, filter, and manipulate data efficiently.
In this tutorial, you will go beyond the basics and learn how to:
- filter and query data
- chain multiple queries
- optimize performance
- use advanced ORM features
- write clean and efficient database logic
This knowledge is essential because every Django application depends heavily on database queries.
What You Will Learn
By the end of this tutorial, you will understand:
- what QuerySets are
- how Django ORM works internally
- filtering, excluding, and ordering data
- chaining QuerySets
- using lookups (
__icontains,__gt, etc.) - aggregations and annotations
- performance optimization (
select_related,prefetch_related) - lazy evaluation
- best practices for writing efficient queries
Prerequisites
You should already know:
- Django models
- basic CRUD operations
- Class-Based Views (especially
ListViewandDetailView)
1. What is a QuerySet?
A QuerySet is a collection of database queries.
When you write:
Post.objects.all()Django does NOT immediately fetch data from the database. Instead, it creates a QuerySet.
👉 A QuerySet is:
- lazy (executed only when needed)
- chainable
- iterable
- powerful and flexible
2. Basic QuerySet Operations
Get All Objects
Post.objects.all()Get One Object
Post.objects.get(id=1)⚠️ If no object or multiple objects exist, get() raises an error.
Filter Data
Post.objects.filter(title="Django")Returns all posts with the exact title.
Exclude Data
Post.objects.exclude(title="Django")Returns all posts except those with that title.
Order Data
Post.objects.order_by('created_at')Ascending order
Post.objects.order_by('-created_at')Descending order
3. QuerySet Chaining
One of the most powerful features is chaining.
Post.objects.filter(title__icontains='django').order_by('-created_at')This means:
- filter posts containing “django”
- sort them from newest to oldest
Each method returns a new QuerySet, so you can chain as many as you want.
4. Field Lookups (Very Important)
Django provides many lookup expressions.
Common Lookups
Case-insensitive search
Post.objects.filter(title__icontains='django')Greater than
Post.objects.filter(id__gt=5)Less than
Post.objects.filter(id__lt=10)Range
Post.objects.filter(id__range=(1, 10))Starts with
Post.objects.filter(title__startswith='Django')Ends with
Post.objects.filter(title__endswith='Guide')Date filtering
Post.objects.filter(created_at__year=2025)5. Combining Filters
You can combine conditions using Q objects.
Example
from django.db.models import Q
Post.objects.filter(
Q(title__icontains='django') | Q(content__icontains='python')
)This means:
- posts containing “django” OR “python”
AND condition
Post.objects.filter(
Q(title__icontains='django') & Q(content__icontains='python')
)6. Limiting QuerySets
First object
Post.objects.first()Last object
Post.objects.last()Limit results
Post.objects.all()[:5]Skip results
Post.objects.all()[5:10]7. Checking Existence
Instead of:
if Post.objects.filter(title='Django'):Use:
Post.objects.filter(title='Django').exists()👉 This is faster and more efficient.
8. Counting Objects
Post.objects.count()Counts rows directly in the database.
9. Updating Objects
Update multiple records
Post.objects.filter(title='Old').update(title='New')Update a single object
post = Post.objects.get(id=1)
post.title = "Updated title"
post.save()10. Deleting Objects
Delete one object
post = Post.objects.get(id=1)
post.delete()
Delete multiple objects
Post.objects.filter(title='Test').delete()11. Aggregation
Aggregation is used to compute values like:
- count
- sum
- average
- min
- max
Example
from django.db.models import Count
Post.objects.aggregate(total_posts=Count('id'))More Examples
from django.db.models import Avg, Max
Post.objects.aggregate(
avg_id=Avg('id'),
max_id=Max('id')
)12. Annotation
Annotation adds calculated fields to each object.
Example
from django.db.models import Count
from .models import Category
Category.objects.annotate(post_count=Count('post'))Each category will now have a post_count attribute.
13. Lazy Evaluation (Very Important)
QuerySets are lazy.
This means Django does NOT hit the database until necessary.
Example
qs = Post.objects.all()No query yet.
But when you do:
for post in qs:
print(post.title)👉 Now the query runs.
When QuerySets Execute
- iteration (
for) - converting to list
- slicing
- calling
len() - printing
14. Performance Optimization
Problem: N+1 Queries
Example:
for post in Post.objects.all():
print(post.author.name)This may cause many database queries.
Solution: select_related
Post.objects.select_related('author')Used for ForeignKey relationships.
Solution: prefetch_related
Post.objects.prefetch_related('tags')Used for:
- ManyToMany
- reverse ForeignKey
Summary
| Method | Use Case |
|---|---|
select_related | ForeignKey |
prefetch_related | ManyToMany |
15. Using ORM in Class-Based Views
Example
class PostListView(ListView):
model = Post
def get_queryset(self):
return Post.objects.filter(title__icontains='django').order_by('-created_at')This lets you customize what data is shown.
16. Raw SQL (When Needed)
Sometimes you need raw SQL.
Post.objects.raw("SELECT * FROM blog_post WHERE id > %s", [5])⚠️ Use this only when necessary.
17. Common Mistakes
Using get() instead of filter()
Post.objects.get(title='Django')⚠️ Can crash if multiple results exist.
Not using exists()
if Post.objects.filter(title='Django'):❌ Inefficient
✔️ Better:
Post.objects.filter(title='Django').exists()Forgetting query optimization
Not using select_related or prefetch_related can slow your app.
18. Best Practices
- use
filter()instead ofget()when unsure - use
exists()for checks - avoid unnecessary queries
- use
select_relatedandprefetch_related - chain QuerySets for clean code
- keep logic in models or managers when possible
19. Mini Project: Search System
Goal
Create a search feature.
View
class PostSearchView(ListView):
model = Post
template_name = 'blog/search.html'
context_object_name = 'posts'
def get_queryset(self):
query = self.request.GET.get('q')
if query:
return Post.objects.filter(
title__icontains=query
)
return Post.objects.none()Template
<form method="get">
<input type="text" name="q" placeholder="Search...">
<button type="submit">Search</button>
</form>
{% for post in posts %}
<h2>{{ post.title }}</h2>
{% empty %}
<p>No results found.</p>
{% endfor %}20. Summary
In this tutorial, you learned:
- QuerySets are the core of Django ORM
- they are lazy and chainable
- filtering, ordering, and lookups are essential
- aggregation and annotation add power
- performance optimization is critical
- QuerySets integrate perfectly with Class-Based Views
Mastering QuerySets is essential for building efficient Django applications.
21. Mini Quiz
1. What is a QuerySet?
A. A database table
B. A collection of queries
C. A Python list
D. A model
2. Which method filters data?
A. get()
B. filter()
C. all()
D. order_by()
3. Which method checks existence efficiently?
A. count()
B. exists()
C. len()
D. first()
4. Which method is used for ForeignKey optimization?
A. prefetch_related()
B. select_related()
C. annotate()
D. aggregate()
5. What is QuerySet behavior?
A. Immediate
B. Lazy
C. Static
D. Fixed
22. What Comes Next?
Next tutorial:
Tutorial: Relationships in Django Models
(ForeignKey, OneToOne, ManyToMany — deep understanding of data relationships)
💬 Comments
No comments yet. Be the first to comment!
Login to comment.