How to Clean Up Generated SQL From an ORM
ORMs generate SQL that works but rarely SQL that's readable. When a query is slow or returning wrong results, you need to read the SQL — and reading minified, alias-heavy ORM output line by line is genuinely difficult. Formatting it first makes the problem visible.
What ORM-generated SQL looks like
Here's typical output from a Django ORM query for a user with their orders:
SELECT "users"."id", "users"."name", "users"."email", "orders"."id", "orders"."created_at", "orders"."total" FROM "users" LEFT OUTER JOIN "orders" ON ("users"."id" = "orders"."user_id") WHERE "users"."active" = True ORDER BY "users"."name" ASC LIMIT 100
This is valid SQL. It's also exhausting to read. Formatted:
SELECT
"users"."id",
"users"."name",
"users"."email",
"orders"."id",
"orders"."created_at",
"orders"."total"
FROM "users"
LEFT OUTER JOIN "orders"
ON ("users"."id" = "orders"."user_id")
WHERE "users"."active" = True
ORDER BY "users"."name" ASC
LIMIT 100
The JOIN condition is now visible. The SELECT list shows exactly which columns are being fetched. If there's an N+1 problem or a missing index, this formatted version makes it findable.
How to get the ORM-generated SQL
Django: str(queryset.query) or enable django.db.backends logging to see queries in the console.
SQLAlchemy: str(query.statement.compile(dialect=postgresql.dialect(), compile_kwargs={"literal_binds": True}))
ActiveRecord (Rails): queries are logged to the Rails log. Use to_sql on a relation object.
Hibernate/JPA: set hibernate.show_sql=true and hibernate.format_sql=true in your configuration.
Prisma: set DEBUG="prisma:query" environment variable.
What to look for after formatting
N+1 patterns: if you see the same query structure repeated many times in your logs with different ID values, you have an N+1 — the ORM is fetching a parent record and then fetching each child separately. The fix is usually eager loading (include in ActiveRecord, select_related/prefetch_related in Django).
Full table scans: if the WHERE clause doesn't use an indexed column, the database scans every row. A formatted query makes it easy to see which columns are in the WHERE and then check if those columns have indexes.
Overfetching: SELECT * or a long SELECT list that includes columns you don't use. The formatted version makes it easy to count columns and question whether they're all needed.