The ORM: Queries
Django's ORM (Object-Relational Mapper) lets you query your database using Python instead of raw SQL — you call methods on a model's manager to create, retrieve, filter, update, and delete rows, and Django translates those calls into SQL for you.
Learn The ORM: Queries in our free Django course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.
Part of the free Django course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
In this lesson you'll learn how to create and fetch objects, filter and order them with field lookups, and update, delete, count, and aggregate rows — all without writing a single line of SQL.
Every model has a manager , available as Model.objects . The manager is your gateway to the database. To add a new row you can either build an instance and call save() , or do both at once with create() .
all() returns a QuerySet (a lazy collection of rows). get() returns a single instance and expects exactly one match.
filter() keeps the rows that match a condition; exclude() drops them. Conditions are written as keyword arguments using field lookups — a field name, two underscores, and a lookup type.
You can change a single object by setting an attribute and calling save() , or update many rows at once with the QuerySet's update() . Removing rows is done with delete() . To summarise data, use count() , exists() , and aggregate() .
update() and bulk delete() run in a single SQL statement and do not call save() on each object, which makes them fast for changing many rows at once.
Fill in the blank with the correct field lookup so the query keeps only posts with at least 100 views. (Hint: the lookup for "greater than or equal" is __gte .)
❌ DoesNotExist / MultipleObjectsReturned from get()
get() demands exactly one row — it raises DoesNotExist when nothing matches and MultipleObjectsReturned when several do.
✅ Fix: use filter() when many results are possible, or look up by a unique field like the primary key.
❌ Expecting a QuerySet to have already hit the database
QuerySets are lazy — building one runs no SQL, so changes made afterward can surprise you.
✅ Fix: force evaluation when you need the data now with list(qs) , a loop, or len(qs) .
❌ Using = instead of a lookup inside filter()
Writing filter(title="x") is fine for exact matches, but you cannot write Python comparison operators like filter(views > 100) .
✅ Fix: use a lookup — filter(views__gt=100) — or __exact to be explicit about equality.
Combine filtering, ordering, counting, and averaging into a single report — the everyday work of querying with the ORM.
Lesson 8 complete — you can query the database in Python!
You now know how to create and fetch objects, filter and exclude rows with field lookups, order and slice QuerySets, and update, delete, count, and aggregate data — all through the ORM instead of raw SQL.
🚀 Up next: Relationships (FK, M2M) — connect models together with ForeignKey and ManyToManyField, then query across them.
Practice quiz
Which method returns exactly one object and raises DoesNotExist when nothing matches?
- get()
- filter()
- all()
- exclude()
Answer: get(). get() returns a single instance and raises DoesNotExist if there is no match.
What does Model.objects.create(...) do?
- Builds an instance without saving
- Builds and saves a new row in one call
- Deletes a matching row
- Returns a QuerySet of all rows
Answer: Builds and saves a new row in one call. create() builds the instance and runs the INSERT in a single call.
Which call keeps only rows where views is at least 100?
- filter(views__lt=100)
- exclude(views__gte=100)
- filter(views__gte=100)
- filter(views=100)
Answer: filter(views__gte=100). The __gte lookup means greater than or equal, so views__gte=100 keeps views of 100 or more.
What does exclude() do?
- Keeps rows matching the condition
- Counts matching rows
- Orders the rows
- Drops rows matching the condition
Answer: Drops rows matching the condition. exclude() removes the rows that match its condition, the opposite of filter().
Why are Django QuerySets described as lazy?
- They cache forever
- They run no SQL until the results are used
- They always hit the database twice
- They only work on SQLite
Answer: They run no SQL until the results are used. A QuerySet runs no SQL when built; Django waits until you iterate, slice, or call len().
Which lookup matches titles that contain the substring 'Django'?
- title__contains='Django'
- title__exact='Django'
- title__startswith='Django'
- title__in='Django'
Answer: title__contains='Django'. __contains tests whether the field value contains the given substring.
What does order_by('-views') do?
- Sorts by views ascending
- Sorts by views descending
- Filters out negative views
- Groups rows by views
Answer: Sorts by views descending. A leading minus sign means descending order, so the highest views come first.
What does slicing a QuerySet like [:3] add to the SQL?
- A WHERE clause
- An ORDER BY clause
- A LIMIT clause
- A GROUP BY clause
Answer: A LIMIT clause. Slicing adds SQL LIMIT so Django fetches only those rows instead of the whole table.
Which method changes many rows in a single SQL statement without calling save() on each?
- save()
- create()
- get()
- update()
Answer: update(). QuerySet.update() runs one bulk SQL UPDATE and does not call save() per object.
What does aggregate(Avg('views')) return?
- A QuerySet of rows
- A single object
- A dictionary of computed values
- A list of titles
Answer: A dictionary of computed values. aggregate() returns a dictionary such as {'views__avg': 215.0}.