Getting the SQL from a Django QuerySet
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Django's ORM generates SQL behind the scenes when you use QuerySets. Viewing the actual SQL is essential for debugging slow queries, understanding ORM behavior, verifying complex filters, and optimizing database performance. Django provides several ways to inspect the generated SQL — from the .query attribute to the Django Debug Toolbar.
Method 1: str(queryset.query)
The simplest way — access the .query attribute on any QuerySet:
Note: .query does not include parameter quoting — values appear inline without proper escaping. This SQL is for inspection only, not for direct execution.
Method 2: queryset.query.sql_with_params()
Get the SQL with parameterized placeholders:
This is closer to what Django actually sends to the database.
Method 3: django.db.connection.queries
Django logs all executed queries when DEBUG=True:
This only works when settings.DEBUG = True. In production, queries are not logged.
Method 4: EXPLAIN / EXPLAIN ANALYZE
Get the database's query execution plan:
EXPLAIN shows how the database plans to execute the query without running it. EXPLAIN ANALYZE runs the query and shows actual execution statistics.
Method 5: Django Debug Toolbar
The Django Debug Toolbar provides a visual SQL panel showing all queries per request:
The SQL panel shows each query, its execution time, duplicate queries, and the traceback showing where in your code each query was triggered.
Method 6: Logging SQL Queries
Configure Django's logging to print all SQL to the console:
Every SQL query will now appear in the console output during development.
Complex QuerySet Examples
Aggregation
Subqueries
JOINs with select_related
Counting Queries in Tests
Common Pitfalls
.queryshows unquoted parameters: The SQL fromstr(qs.query)does not properly escape string values. Do not copy-paste it into a database client — it may have syntax errors. Usesql_with_params()for parameterized SQL.- N+1 query problem: Accessing related objects in a loop generates one query per object. Use
select_related()(foreign keys) andprefetch_related()(reverse/many-to-many) to join them in a single query. connection.queriesonly works with DEBUG=True: In production (DEBUG=False), the query log is disabled for performance. Use thedjango.db.backendslogger or a monitoring tool like Sentry for production SQL inspection..queryon evaluated QuerySets:.queryis available even after the QuerySet is evaluated. It shows the SQL that was (or would be) executed, which is useful for debugging after the fact.- Raw SQL vs ORM: If
.queryshows unexpected SQL, check your filters and annotations. The ORM sometimes generates suboptimal queries — use.raw()orconnection.cursor()for hand-tuned SQL when performance is critical.
Summary
- Use
str(qs.query)for quick SQL inspection during development - Use
qs.query.sql_with_params()for parameterized SQL with proper placeholders - Use
connection.queriesto see all executed queries (requiresDEBUG=True) - Use
qs.explain(analyze=True)to get the database execution plan - Use Django Debug Toolbar for visual query analysis per request
- Configure
django.db.backendslogger for automatic SQL logging to console

