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.

Tip: paste the formatted SQL into your database's EXPLAIN or EXPLAIN ANALYZE command. The combination of human-readable SQL and the query plan shows both what the query does and how efficiently the database executes it.