C.W.K.
Stream
Lesson 02 of 10 · published

Raw SQL vs ORM

~14 min · apps, orm

Level 0스키마 새싹
0 XP0/86 lessons0/10 achievements
0/120 XP to next level120 XP to go0% complete

스펙트럼의 양 끝

한쪽 끝, raw SQL: 최대 제어, 최대 verbosity, 모든 쿼리 수작업. 다른 쪽, 풀 ORM (Django ORM, ActiveRecord, Hibernate): 행을 객체에 매핑, 메서드 호출에서 SQL 생성, DB 추상. 대부분 건강한 프로젝트가 둘 다 사용 — hot/복잡 path 엔 raw SQL, 지루한 CRUD 엔 ORM 또는 query builder.

ORM 이 잘 하는 거

  • 지루한 CRUD — 단일 행 INSERT/UPDATE/DELETE.
  • 스키마 마이그레이션 — 모델 변경에서 DDL 생성.
  • 정적 타입 언어의 타입 안전.
  • 어느 DB 쓰는지 추상 (실제로 거의 안 쓸모).

ORM 이 못 하는 거

  • 복잡한 join, window function, CTE — ORM 과 싸우는 게 그냥 SQL 쓰는 것보다 느림.
  • N+1 쿼리 문제 — Operations 트랙 참조.
  • Bulk 연산 — 모든 ORM 이 COPY 숨김.
  • Postgres 특화 기능 — JSONB 연산자, FTS, pgvector 다 raw SQL 또는 드라이버 레벨 지원 필요.

실용적 답

ORM 은 잘 하는 거에; 다른 모든 거엔 raw SQL (같은 드라이버/connection 통해) 로 내려와. 한 프로젝트에 둘 다 공존 가능 — 거의 항상 그래야.

Code

CRUD 위한 ORM (SQLAlchemy)·python
from sqlalchemy.orm import Session
from .models import User

with Session(engine) as session:
    user = User(name="Alice", email="alice@example.com")
    session.add(user)
    session.commit()
    print(user.id)
같은 엔진의 raw SQL + 복잡 쿼리·python
# 같은 connection 풀, 같은 트랜잭션, 그러나 표현력 있는 Postgres
result = session.execute(
    text("""
        SELECT u.id, u.name,
               COUNT(o.id) AS order_count,
               SUM(o.total) AS spent
        FROM users u
        LEFT JOIN orders o ON o.user_id = u.id
        WHERE u.created_at >= now() - interval '30 days'
        GROUP BY u.id, u.name
        HAVING COUNT(o.id) >= :min_orders
        ORDER BY spent DESC
        LIMIT :n
    """),
    {"min_orders": 3, "n": 50},
)
for row in result.mappings():
    print(row["name"], row["spent"])

External links

Exercise

ORM 에서 어색한 쿼리 하나 찾기 (복잡 aggregation, JSONB op, FTS). 같은 세션 통해 raw SQL 로 재작성. 가독성 비교 + 각 쪽이 가치 있는 이유 설명.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.