Skip to main content

đź”— ORM

Objects to database rows translator

The Translator Analogy

You speak English. Your database speaks SQL. Communication is hard:

You: "I want to get user 123 and update their name to Alice"

Database: "UPDATE users SET name = 'Alice' WHERE id = 123"

An ORM is a translator that converts between your programming language's objects and the database's tables.

You: user123.name = "Alice"; user123.save();

ORM: Translates to SQL automatically

Database: Stores the data

What Problem Does ORM Solve?

The Impedance Mismatch

Your Code (Objects):
  user = User()
  user.name = "Alice"
  user.posts = [Post(), Post()]

Database (Tables):
  users table: id, name, email
  posts table: id, user_id, content

These don't naturally fit together!
  - Objects have methods, tables don't
  - Objects can be nested, tables are flat
  - Objects use references, tables use foreign keys

ORM Bridges the Gap

Define once:
  class User:
    id: int
    name: string
    posts: list[Post]

ORM handles:
  - Creating the table structure
  - Translating queries to SQL
  - Converting results back to objects

How ORM Works

Define Models

A model describes your data structure:

class User:
  id = Integer, primary_key
  name = String(100)
  email = String(255), unique
  created_at = DateTime, default=now

ORM creates the corresponding table:
  CREATE TABLE users (
    id INT PRIMARY KEY,
    name VARCHAR(100),
    email VARCHAR(255) UNIQUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
  );

CRUD Without SQL

Create:
  user = User(name="Alice", email="alice@example.com")
  db.save(user)
  → INSERT INTO users (name, email) VALUES ('Alice', 'alice@...')

Read:
  user = User.find_by_id(123)
  → SELECT * FROM users WHERE id = 123

Update:
  user.name = "Alice Smith"
  db.save(user)
  → UPDATE users SET name = 'Alice Smith' WHERE id = 123

Delete:
  db.delete(user)
  → DELETE FROM users WHERE id = 123

Relationships

One-to-Many

One user has many posts:

class User:
  posts = relationship("Post")

class Post:
  user_id = foreign_key(User.id)

Usage:
  alice.posts → [Post1, Post2, Post3]
  post1.user → alice

ORM handles the JOIN automatically!

Many-to-Many

Users and groups (many-to-many):

users_groups table (join table):
  user_id, group_id

Usage:
  alice.groups → [Engineering, Marketing]
  engineering.users → [Alice, Bob, Carol]

One-to-One

User has one profile:

class User:
  profile = relationship("Profile")

class Profile:
  user_id = foreign_key(User.id), unique

Usage:
  alice.profile → Profile(bio="...")

Query Building

Chaining Methods

Instead of raw SQL:
  SELECT * FROM users
  WHERE status = 'active'
  AND age > 18
  ORDER BY created_at DESC
  LIMIT 10

ORM query builder:
  User
    .filter(status='active')
    .filter(age__gt=18)
    .order_by('-created_at')
    .limit(10)
    .all()

More readable, easier to build dynamically!

Lazy vs Eager Loading

Lazy loading:
  user = User.find(1)     # 1 query
  user.posts              # another query (N+1 problem!)

Eager loading:
  user = User.find(1).include('posts')  # 1 query with JOIN
  user.posts              # already loaded!

ORM Patterns

Active Record

Object knows how to save itself:

user = User.new(name="Alice")
user.save()                    # Object has save method
User.find(123)                 # Class method to query

Popular in: Rails (ActiveRecord), Laravel (Eloquent)

Data Mapper

Separate mapper handles persistence:

user = User(name="Alice")      # Just data
mapper.save(user)              # Mapper saves it
mapper.find(User, 123)         # Mapper queries

Popular in: SQLAlchemy, Doctrine

Data Mapper is more flexible but more complex.

LanguageORMPattern
PythonSQLAlchemyData Mapper
PythonDjango ORMActive Record
JavaScriptSequelizeActive Record
JavaScriptPrismaData Mapper
RubyActiveRecordActive Record
JavaHibernateData Mapper
PHPEloquentActive Record
PHPDoctrineData Mapper

Pros and Cons

Advantages

âś“ Write less SQL
✓ Database-agnostic (switch PostgreSQL → MySQL easier)
âś“ Type safety and validation
âś“ Easier to maintain
âś“ Relationships handled automatically
âś“ Migrations for schema changes

Disadvantages

âś— Performance overhead
âś— Complex queries can be harder
âś— "Magic" hides what's happening
âś— N+1 query problems are easy to introduce
âś— Learning curve for each ORM

When to Use Raw SQL

ORM Isn't the Only Answer

Use ORM for:
  âś“ Standard CRUD operations
  âś“ Simple queries
  âś“ Quick development

Use raw SQL for:
  âś— Complex reporting queries
  âś— Performance-critical operations
  âś— Bulk data operations
  âś— Database-specific features

Escape Hatch

Most ORMs let you use raw SQL when needed:

results = db.execute("SELECT * FROM users WHERE...")

The N+1 Problem

The Most Common ORM Mistake

Bad code:
  users = User.all()           # 1 query
  for user in users:
    print(user.posts)          # N queries (one per user!)

With 100 users = 101 queries!

Good code:
  users = User.all().include('posts')  # 1 query with JOIN
  for user in users:
    print(user.posts)          # Already loaded

With 100 users = 1 query!

Common Mistakes

1. Not Understanding Generated SQL

Use query logging to see what SQL your ORM generates.
What looks like one line might be 10 queries!

2. Over-fetching Data

Bad:  User.all()  (loads everything)
Good: User.select('id', 'name')  (only what you need)

3. Ignoring Database Indexes

ORM doesn't auto-create indexes.
Add them for frequently queried columns!

4. N+1 Queries

Often, you'll want to eager-load relationships you know you'll access.


FAQ

Q: ORM or raw SQL?

Start with ORM for convenience. Drop to raw SQL when performance matters.

Q: Can I use both?

Yes! Most ORMs support raw SQL for complex queries.

Q: Does ORM work with NoSQL?

Some do (e.g., Mongoose for MongoDB). Called ODM (Object Document Mapper).

Q: Are ORMs slow?

There's overhead, but for most applications it's negligible. Profile before optimizing.


Summary

ORMs translate between your code's objects and database tables, reducing SQL and speeding development.

Key Takeaways:

  • ORM maps objects to database tables
  • Handles CRUD without writing SQL
  • Relationships (1:N, N:M) managed automatically
  • Watch for N+1 query problem
  • Use raw SQL for complex/performance needs
  • Active Record vs Data Mapper patterns
  • Trade-off: convenience vs control

ORMs let you think in objects while the database thinks in tables!

Related Concepts

Leave a Comment

Comments (0)

Be the first to comment on this concept.

Comments are approved automatically.