ORM vs SQL 차이점

heeyoung_1023·2026년 3월 12일

ORM vs SQL 차이점


핵심 한 줄

ORM = 파이썬으로 DB 조작 (편리하고 안전)
SQL = DB에게 직접 명령 (강력하고 세밀한 제어 가능)

둘 중 하나만 쓰는 게 아니라, ORM 기본 + 복잡한 쿼리는 SQL 조합이 실무 정석이에요! 🎯

ORM이 하는 일

파이썬 클래스   ←→   DB 테이블
파이썬 객체    ←→   DB 행 (Row)
클래스 속성    ←→   DB 컬럼 (Column)

User 클래스    ←→   users 테이블
user = User() ←→   users 테이블의 한 행
user.name     ←→   name 컬럼의 값

## 언제 뭘 쓸까?

ORM 쓸 때
├── 일반적인 CRUD 작업
├── 빠른 개발이 필요할 때
├── DB를 바꿀 가능성이 있을 때
└── 보안이 중요할 때

SQL 직접 쓸 때
├── 복잡한 통계/집계 쿼리
├── 성능이 극도로 중요할 때
└── ORM으로 표현이 너무 복잡할 때


|        | SQL          | ORM               |
| ------ | ------------ | ----------------- |
| 언어     | SQL 문법       | 클래스/메서드로 작성       |
| 작성 방식  | 문자열로 직접 작성   | 클래스/메서드로 작성       |
| DB 종속성 | DB 바꾸면 문법 바뀜 | 코드 그대로 유지         |
| 러닝커브   | SQL 별도 학습 필요 | 파이썬만 알면 됨         |
| 복잡한 쿼리 | 자유롭고 강력함     | 한계가 있음            |
| 성능 제어  | 직접 최적화 가능    | ORM이 알아서 (N+1 위험) |
| 보안     | SQL 인젝션 위험   | 자동 방어             |



---

## 같은 작업, 다른 문법

### 조회

```python
# SQL
SELECT * FROM question WHERE id = 1;

# ORM
Question.objects.get(id=1)

ORM 장단점

장점단점
생산성SQL 몰라도 DB 사용 가능복잡한 쿼리는 오히려 어려움
안전성|인젝션 자동 방어잘못 쓰면 N+1 성능 문제
유지보수코드가 직관적내부 SQL 파악이 어려울 수 있음
이식성DB 바꿔도 코드 그대로ORM마다 문법이 달라

조건 조회

# SQL
SELECT * FROM question WHERE subject LIKE '%파이썬%' ORDER BY create_date DESC;

# ORM
Question.objects.filter(subject__contains='파이썬').order_by('-create_date')

생성

# SQL
INSERT INTO question (subject, content) VALUES ('제목', '내용');

# ORM
Question.objects.create(subject='제목', content='내용')

JOIN

# SQL
SELECT q.subject, a.content
FROM question q
JOIN answer a ON q.id = a.question_id
WHERE q.id = 1;

# ORM
question = Question.objects.prefetch_related('answer_set').get(id=1)
question.answer_set.all()

결정적 차이 3가지

1️⃣ DB를 바꿔도 코드가 그대로

# SQL — MySQL에서 PostgreSQL로 바꾸면
# MySQL
SELECT * FROM question LIMIT 10;
# PostgreSQL
SELECT * FROM question LIMIT 10 OFFSET 0;  # 문법이 달라짐 😰

# ORM — DB 바꿔도 코드 동일
Question.objects.all()[:10]  # 어떤 DB든 그대로 ✅

2️⃣ 보안 (SQL 인젝션)

# SQL — 위험! 😰
user_input = "1 OR 1=1"  # 악의적인 입력
query = f"SELECT * FROM users WHERE id = {user_input}"
# → SELECT * FROM users WHERE id = 1 OR 1=1
# → 전체 데이터가 털림! 💀

# ORM — 자동으로 막아줌 ✅
User.objects.get(id=user_input)
# 내부적으로 자동 이스케이프 처리

3️⃣ 복잡한 쿼리는 SQL이 유리

# 복잡한 통계 쿼리 — SQL이 훨씬 직관적
# SQL
SELECT DATE(create_date), COUNT(*) as cnt
FROM question
GROUP BY DATE(create_date)
HAVING cnt >= 10
ORDER BY cnt DESC;

# ORM — 복잡하고 가독성 떨어짐 😰
from django.db.models import Count
from django.db.models.functions import TruncDate
Question.objects \
    .annotate(date=TruncDate('create_date')) \
    .values('date') \
    .annotate(cnt=Count('id')) \
    .filter(cnt__gte=10) \
    .order_by('-cnt')

언제 뭘 쓸까?

ORM 쓸 때
├── 일반적인 CRUD 작업
├── 빠른 개발이 필요할 때
├── DB를 바꿀 가능성이 있을 때
└── 보안이 중요할 때

SQL 직접 쓸 때
├── 복잡한 통계/집계 쿼리
├── 성능이 극도로 중요할 때
└── ORM으로 표현이 너무 복잡할 때

실제로는 섞어서 써요

# Django에서 복잡한 건 Raw SQL 직접 사용
from django.db import connection

with connection.cursor() as cursor:
    cursor.execute("""
        SELECT DATE(create_date), COUNT(*)
        FROM question
        GROUP BY DATE(create_date)
    """)
    result = cursor.fetchall()

핵심 한 줄

ORM = 파이썬으로 DB 조작 (편리하고 안전) SQL = DB에게 직접 명령 (강력하고 세밀한 제어 가능)

둘 중 하나만 쓰는 게 아니라, ORM 기본 + 복잡한 쿼리는 SQL 조합이 실무 정석이에요! 🎯

ORM 완전 정복


핵심 개념부터

ORM이 없으면 파이썬에서 DB 쓸 때 이렇게 해야 해요:

# ORM 없이 (Raw SQL)
import sqlite3
conn = sqlite3.connect('db.sqlite3')
cursor = conn.cursor()
cursor.execute("SELECT * FROM users WHERE age >= 25")
rows = cursor.fetchall()
for row in rows:
    print(row[0], row[1])  # 인덱스로 접근 😰

ORM 쓰면:

# ORM 있으면 (Django ORM)
users = User.objects.filter(age__gte=25)
for user in users:
    print(user.id, user.name)  # 속성으로 접근 😊

SQL을 직접 안 써도 파이썬 코드로 DB를 조작할 수 있게 해주는 것!


ORM이 하는 일

파이썬 클래스   ←→   DB 테이블
파이썬 객체    ←→   DB 행 (Row)
클래스 속성    ←→   DB 컬럼 (Column)

User 클래스    ←→   users 테이블
user = User() ←→   users 테이블의 한 행
user.name     ←→   name 컬럼의 값

1. 모델 정의 (테이블 만들기)

# Django ORM
from django.db import models

class User(models.Model):
    name  = models.CharField(max_length=100)   # VARCHAR(100)
    age   = models.IntegerField()              # INT
    email = models.EmailField(unique=True)     # VARCHAR UNIQUE
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.name

# 이게 내부적으로 이 SQL로 변환됨 ↓
# CREATE TABLE users (
#     id         INT PRIMARY KEY AUTO_INCREMENT,
#     name       VARCHAR(100),
#     age        INT,
#     email      VARCHAR(200) UNIQUE,
#     created_at DATETIME
# );

2. CRUD 비교 — SQL vs ORM

✅ CREATE (생성)

# SQL
INSERT INTO users (name, age, email) VALUES ('김철수', 25, 'chul@gmail.com');

# Django ORM
user = User(name='김철수', age=25, email='chul@gmail.com')
user.save()

# 더 짧게
User.objects.create(name='김철수', age=25, email='chul@gmail.com')

✅ READ (조회)

# SQL
SELECT * FROM users;
SELECT * FROM users WHERE id = 1;
SELECT * FROM users WHERE age >= 25 ORDER BY name;

# Django ORM
User.objects.all()                          # 전체 조회
User.objects.get(id=1)                      # 단건 조회
User.objects.filter(age__gte=25).order_by('name')  # 조건 + 정렬

✅ UPDATE (수정)

# SQL
UPDATE users SET age = 26 WHERE id = 1;

# Django ORM
user = User.objects.get(id=1)
user.age = 26
user.save()

# 한 줄로
User.objects.filter(id=1).update(age=26)

✅ DELETE (삭제)

# SQL
DELETE FROM users WHERE id = 1;

# Django ORM
User.objects.filter(id=1).delete()

3. 조건 필터 — ORM 문법

# SQL WHERE 조건들을 ORM으로
User.objects.filter(age=25)           # age = 25
User.objects.filter(age__gte=25)      # age >= 25
User.objects.filter(age__lte=25)      # age <= 25
User.objects.filter(age__gt=25)       # age > 25
User.objects.filter(age__lt=25)       # age < 25
User.objects.filter(age__in=[20,25])  # age IN (20, 25)
User.objects.filter(name__contains='철')     # LIKE '%철%'
User.objects.filter(name__startswith='김')   # LIKE '김%'
User.objects.filter(name__isnull=True)       # IS NULL

# 범위
User.objects.filter(age__range=(20, 30))  # BETWEEN 20 AND 30

# AND 조건
User.objects.filter(age__gte=20, name__startswith='김')

# OR 조건
from django.db.models import Q
User.objects.filter(Q(age=25) | Q(name='이영희'))

4. 관계 설정 — JOIN을 ORM으로

# 테이블 관계 정의
class User(models.Model):
    name = models.CharField(max_length=100)

class Post(models.Model):
    user    = models.ForeignKey(User, on_delete=models.CASCADE)  # 1:N
    title   = models.CharField(max_length=200)
    content = models.TextField()

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)  # 1:1
    bio  = models.TextField()

class Tag(models.Model):
    posts = models.ManyToManyField(Post)  # N:M
    name  = models.CharField(max_length=50)
# JOIN 쿼리
# SQL
SELECT * FROM posts JOIN users ON posts.user_id = users.id WHERE users.name = '김철수';

# ORM (훨씬 직관적!)
posts = Post.objects.filter(user__name='김철수'<)

# 역방향 접근 (유저의 모든 게시글)
user = User.objects.get(id=1)
user.post_set.all(<)   # 자동으로 생성되는 역참조

5. 성능 최적화 — N+1 문제

ORM의 대표적인 함정이에요:

# ❌ 나쁜 예 (N+1 문제)
# 유저 10명 조회 → 쿼리 1번
# 각 유저의 게시글 조회 → 쿼리 10번
# 총 11번 DB 조회 발생!

users = User.objects.all()          # 쿼리 1번
for user in users:
    print(user.post_set.all())      # 유저마다 쿼리 1번씩 추가 💀


# ✅ 좋은 예 (select_related / prefetch_related)
# 한 번에 JOIN해서 가져옴 → 쿼리 1번으로 끝!

users = User.objects.prefetch_related('post_set').all()
for user in users:
    print(user.post_set.all())      # 추가 쿼리 없음 ✅

6. SQLAlchemy (FastAPI에서 쓰는 ORM)

from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class User(Base):
    __tablename__ = 'users'

    id    = Column(Integer, primary_key=True)
    name  = Column(String(100))
    age   = Column(Integer)
    email = Column(String(200), unique=True)

# 조회
session.query(User).filter(User.age >= 25).all()
session.query(User).filter(User.name == '김철수').first()

# 생성
user = User(name='김철수', age=25)
session.add(user)
session.commit()

ORM 장단점

장점|단점
생산성SQL 몰라도 DB 사용 가능복잡한 쿼리는 오히려 어려움
안전성|인젝션 자동 방어잘못 쓰면 N+1 성능 문제
유지보수코드가 직관적내부 SQL 파악이 어려울 수 있음
이식성DB 바꿔도 코드 그대로ORM마다 문법이 달라

핵심 한 줄 요약

SQL      =  DB에게 직접 말 걸기
ORM      =  파이썬이 통역사 역할을 해서 대신 SQL로 변환해주는 것

User.objects.filter(age__gte=25)
        ↓ ORM이 자동 변환
SELECT * FROM users WHERE age >= 25;

파이썬 코드 → ORM → SQL → DB 순서로 동작하고, 개발자는 SQL 대신 파이썬 문법만으로 DB를 다룰 수 있어요! 🎯

profile
개발언어를 배우는 과정 기록

0개의 댓글