ORMs vs SQL Learn SQL: The Hard Truth Nobody Wants to Admit

I spent three years building data pipelines at a fintech in Bangalore. We used Django ORM for everything. And I mean everything — including a real-time ris...

orms learn hard truth nobody wants admit
By Nishaant Dixit
ORMs vs SQL Learn SQL: The Hard Truth Nobody Wants to Admit

ORMs vs SQL Learn SQL: The Hard Truth Nobody Wants to Admit

ORMs vs SQL Learn SQL: The Hard Truth Nobody Wants to Admit

I spent three years building data pipelines at a fintech in Bangalore. We used Django ORM for everything. And I mean everything — including a real-time risk scoring system that needed to evaluate 40,000 transactions per second.

The ORM crashed. Not once. Three times in production. Each time, we blamed the database. Each time, it was our fault for not understanding what SQL the ORM was actually generating.

That was 2021. By 2023, I'd rewritten those critical paths in raw SQL. Throughput went from 40K TPS to 180K TPS. Same hardware. Same database.

Here's the thing: I'm not anti-ORM. I'm anti-blind-faith-in-abstractions. This guide is about ORMs vs SQL learn SQL — not which one wins, but when each one should win.

By the end, you'll know exactly when to reach for SQL, when to reach for an ORM, and why most developers get this wrong.


Why Your ORM Is Lying to You

Let's start with a concrete example. Say you're building an e-commerce platform. You need to find all users who placed an order in the last 30 days, along with their total spend.

Here's what the ORM looks like in something like SQLAlchemy:

python
users = db.session.query(User).outerjoin(Order).filter(
    Order.created_at >= datetime.now() - timedelta(days=30)
).all()

Looks clean. Feels clean. But here's what actually hits Postgres:

sql
SELECT * FROM users LEFT OUTER JOIN orders ON users.id = orders.user_id 
WHERE orders.created_at >= '2026-06-05 14:30:00'

Problem? That SELECT * is pulling every column from both tables. If your users table has 25 columns and orders has 40, you're fetching 65 columns per row. For 50,000 users with average 10 orders each? That's 500,000 rows transferred over the wire.

The ORM doesn't tell you this. It can't tell you this. Because it's designed for developer convenience, not query optimization.

Raw SQL or ORMs? Why ORMs are a preferred choice makes the case that ORMs reduce boilerplate and prevent injection attacks. Both true. But that article doesn't show you the 500MB network transfer that happens every time you run that query in production.


The ORMs vs SQL Learn SQL Decision Matrix

Here's the framework I use at SIVARO. It's not complicated.

Use ORMs when:

  • You're building CRUD APIs with standard operations
  • You're prototyping and iterating fast
  • Your team changes frequently and needs guardrails
  • You have fewer than 5 tables with straightforward relationships

Use raw SQL when:

  • You need to move data at scale (100K+ rows per query)
  • You're doing aggregations, window functions, or recursive CTEs
  • You're building reports or dashboards
  • You're doing ETL/ELT work
  • Performance is a hard requirement, not a nice-to-have

I know people will scream "but you can optimize ORM queries!" Yes, you can. By writing raw SQL inside the ORM. At that point, you're paying the complexity cost of the ORM without getting the benefit.

ORM's are the Cigarettes of the Data Engineering World calls ORMs the "cigarettes of the data engineering world." Harsh. But not wrong. They're addictive because they're comfortable. But every query you don't understand is a fire you're lighting in production.


The N+1 Problem Isn't the Real Problem

Everyone talks about N+1 queries. You know the drill: fetching a list of records, then hitting the database once per parent record to get children. Classic ORM pitfall.

But that's not the real problem. The real problem is invisible cardinality.

I saw this at a food delivery startup in 2024. Their Rails app was using ActiveRecord to generate a daily sales report. The query looked innocent:

ruby
restaurants = Restaurant.where(active: true)
restaurants.each do |restaurant|
  puts restaurant.orders.where(created_at: Date.today).count
end

That's one query for active restaurants, then one count query per restaurant. With 12,000 active restaurants, that's 12,001 queries. The report took 47 minutes.

Rewriting to raw SQL:

sql
SELECT r.name, COUNT(o.id) 
FROM restaurants r 
LEFT JOIN orders o ON r.id = o.restaurant_id 
  AND o.created_at::date = CURRENT_DATE
WHERE r.active = true
GROUP BY r.name;

One query. 0.8 seconds. The difference isn't SQL vs ORM — it's understanding what your tool is doing under the hood.

When you learn SQL properly, you start thinking in sets. You stop thinking in loops. That shift alone will make you a better engineer, regardless of what abstraction layer you put on top.


When ORMs Actually Save Your Ass

Let me be fair. ORMs aren't useless. I've been burned by both sides.

In 2022, I was building the backend for a healthcare compliance platform. We had 14 legal entities, each with different database schemas. Yes, separate schemas per tenant. The horror, I know.

Using SQLAlchemy with its declarative base and polymorphic models saved us months. Without an ORM, we'd have handwritten the same boilerplate CRUD operations 14 times. Each with its own SQL injection vector probability.

ORMs Are Awesome makes this exact point: ORMs handle the 80% case. They prevent SQL injection. They handle connection pooling. They give you migrations that actually work.

The key insight? ORMs are great for writes; dangerous for reads at scale.

Single record inserts, updates, deletes — let the ORM handle them. Complex analytical queries — write SQL.


How to Actually Learn SQL (Not Just Memorize Syntax)

Most "learn SQL" tutorials are garbage. They teach you SELECT, FROM, WHERE, and call it done.

Real SQL isn't about syntax. It's about understanding how databases execute queries.

Here's what I tell every engineer who joins SIVARO:

Step 1: Learn window functions. Anyone can write GROUP BY. Window functions let you rank, lag, lead, running sum — without losing row-level detail.

sql
SELECT 
  user_id,
  order_date,
  amount,
  SUM(amount) OVER (
    PARTITION BY user_id 
    ORDER BY order_date
    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
  ) as running_total
FROM orders;

Step 2: Understand execution plans. Run EXPLAIN ANALYZE on every query you write. Until you see what Postgres/MySQL is actually doing, you're guessing.

Step 3: Master CTEs. Common Table Expressions make complex queries readable. They're the closest SQL gets to functional programming.

Step 4: Learn indexing strategy. B-tree, Hash, GiST, GIN — each has a use case. Most developers only know B-tree.

Step 5: Accept that ORMs vs SQL learn SQL is a false binary. You need both. The question is where the boundary lives for your system.


The Production Reality Nobody Talks About

The Production Reality Nobody Talks About

Let me walk you through a real debugging session from last month.

A client was using an ORM-based Python service to process video metadata. They had a pipeline that extracted audio tracks from videos using FFmpeg AAC encoder, then stored the metadata in Postgres.

The pipeline was slow. Like, 14 hours slow for 200,000 videos.

Looking at the code, I found this pattern:

python
for video in videos:
    metadata = extract_metadata(video.path)
    db.session.add(Metadata(video_id=video.id, **metadata))
    if count % 100 == 0:
        db.session.commit()

That's 200,000 individual INSERTs. Each one creates a transaction log entry. Each one requires a round trip. And each iteration calls extract_metadata() synchronously, which invokes FFmpeg.

We rewrote it:

  1. Used Python retry library circuit breaker pattern for the FFmpeg calls — because AAC encoding can fail transiently, and retrying blindly makes things worse
  2. Batched the SQL inserts in chunks of 1000
  3. Used COPY FROM instead of INSERT when possible

Result: 47 minutes. Same hardware. Same data.

The ORM wasn't the problem. The pattern of using the ORM was. When you learn SQL, you learn batches, not loops. You learn set operations, not sequential processing.


The SIVARO Stack: What We Actually Use in Production

People ask me what stack we run at SIVARO. Here's the truth:

For data infrastructure, we use raw SQL almost exclusively. Our core pipeline processes 200K events/sec. Every microsecond matters. We can't afford ORM overhead.

But for internal tools, admin panels, and configuration management? SQLAlchemy. Because speed of development matters more than query performance when you're dealing with 100 rows.

The boundary isn't technology. It's criticality. If a slow query takes down the system, write SQL. If a debugging session takes down your sprint velocity, use an ORM.


Why Most Teams Get This Wrong

Three reasons:

1. ORM-first culture. Most bootcamps and tutorials start with ORMs. Students never see SQL. They don't understand what their tool is doing. When production breaks, they blame the database.

2. Premature optimization aversion. "Don't optimize before you measure" is good advice. But it's not an excuse to ignore fundamentals. Understanding SQL isn't optimization — it's literacy.

3. The seniority gap. Senior engineers know SQL. Junior engineers know ORMs. When the senior leaves, the knowledge goes with them. ORMs are overrated. When to use them, and when to lose them. points out that "ORMs do a tremendous disservice to junior developers by shielding them from the database."

I've seen startups burn months of engineering time because nobody knew how to write a recursive CTE. They kept trying to model hierarchies in application code. A single SQL query would have solved it.


When to Use Python Retry Library Circuit Breaker Patterns

This is a tangent. But it matters.

When you're doing anything with external systems — including databases — you need resilience patterns. The Python retry library circuit breaker combination is my go-to.

Here's the pattern:

python
from retry import retry
from circuitbreaker import circuit

@retry(tries=3, delay=1, backoff=2)
@circuit(failure_threshold=5, recovery_timeout=30)
def execute_query(sql, params):
    with get_connection() as conn:
        return conn.execute(sql, params).fetchall()

The retry handles transient failures. The circuit breaker prevents cascading failures when the database is down. Together, they saved us during a 2025 AWS us-east-1 outage that took down half of our competitors.

If you're using an ORM, these patterns still apply. But you need to know where your ORM's retry logic ends and your own begins. Most ORMs have naive retry that makes things worse during real outages.


FAQ: ORMs vs SQL Learn SQL

Q: I'm a junior developer. Should I learn SQL first or an ORM?
Learn SQL first. Build a project with raw SQL. Then learn the ORM. You'll understand what the ORM is doing for you and when it's hurting you.

Q: Can I use an ORM for a high-traffic API?
Yes, if you profile every query. Use SQL for the hot paths. Let the ORM handle the CRUD stuff. Most production systems I've seen are hybrid.

Q: What about NoSQL databases?
Same principle. Learn the query language. Understand how indexes work. Don't let an abstraction layer hide the cost of your operations.

Q: How do I convince my team to use more raw SQL?
Show, don't tell. Next time there's a slow query, write the raw SQL equivalent. Show the performance difference. Let the data speak.

Q: Is it worth learning SQL if I mostly use BigQuery or Snowflake?
Absolutely. Cloud data warehouses use SQL. The dialect differs, but the set-based thinking is identical. You'll write cheaper, faster queries.

Q: What about stored procedures? Should I use them?
Use them for complex transactional logic. Avoid them for simple CRUD — that's what application code is for. ORMs and stored procedures are not friends.

Q: How does FFmpeg AAC encoder relate to database performance?
Indirectly but importantly. If your pipeline mixes media processing with database writes, you need to understand both. The FFmpeg AAC encoder can be CPU-bound. If you run it in the same process as your database connection pool, you'll starve both. Separate your concerns.


The Hard Truth

The Hard Truth

Here it is.

ORMs vs SQL learn SQL isn't a debate. It's a curriculum. Learn SQL first. Master it. Then decide which pieces to delegate to an ORM.

Most people think ORMs make you faster. They do — for the first two weeks. Then you spend six months debugging the abstraction.

I started SIVARO in 2018 because I saw this pattern everywhere. Teams building beautiful application code on terrible data foundations. ORMs hiding the rot until it was too late.

Don't be that team.

Learn SQL. Understand your data. Then decide what to abstract.


Nishaant Dixit — Founder of SIVARO. Building data infrastructure and production AI systems since 2018. Built systems processing 200K events/sec.

Free · No Commitment · 48-Hour Delivery

Get a free infrastructure audit

2-hour remote session. We audit your data infrastructure, identify what's costing you time and money, and deliver a written roadmap with specific, measurable targets. No pitch.

Book Your Free Audit
N
Nishaant Dixit
Founder & Lead Engineer at SIVARO

Building data-intensive systems since 2018. 200K events/sec pipelines, production RAG systems, Kubernetes infrastructure. LinkedIn →

Start a Project
Need help with your infrastructure?

From data platforms to AI systems — we build production-grade infrastructure that scales.

Explore Our Services