$ sudo teach|
    $ sudo teach|
    IT school
  • Telegram
  • Партнёрам
  • Все курсы
$ sudo teach IT
OOO "SALEPROFIT"Контакты и реквизитыIT-Park Logo

Школа

  • Блог
  • Проверить сертификат

Сотрудничество

  • Стать учителем
  • Партнёрская программа
  • О проекте

Право

  • Оферта
  • Политика конфиденциальности

© 2023–2026 $ sudo teach IT™. All Rights Reserved. Public user contributions licensed under CC BY-SA 4.0 license with attribution required
TelegramGitHubYouTube
ГлавнаяБлогПереход с raw SQL на SQLModel: где выигрываете, а где придётся думать

Переход с raw SQL на SQLModel: где выигрываете, а где придётся думать

$ sudo teach IT
·25 июля 2026 г.·11 мин·63
Переход с raw SQL на SQLModel: где выигрываете, а где придётся думать

Сравним подходы на практике: модели, связи, ограничения, миграции и работа с сложными запросами. Дадим правила, когда SQLModel — лучший выбор, а когда лучше писать SQL осознанно.

Содержание
Что именно вы меняете: raw SQL vs моделиraw SQL: сила — в выражении и контролеSQLModel: схему вы описываете ближе к доменуМодели и типы: где ORM действительно выигрываетТипизация и валидация “до запроса”Нюанс: “уникальность” — не только индексы, но и политика обработкиСвязи: where SQLModel помогает, но не магияОдин-ко-многим и многие-ко-многим: меньше ручного трудаНо нужно думать: загрузка отношений и N+1Ограничения и индексы: проще объявить, но сложнее держать в согласииКак переносится логика ограниченийИндексы и сложные ограниченияМиграции: главный “взрослый” вопрос при переходеГде переход ломается чаще всегоРекомендации по процессуРепозитории и запросы: как выглядит «обычный день» с SQLModelCRUD и простые выборкиНо вопрос “сколько SQL генерируется” остаётсяСложные запросы: где SQLModel выигрывает, а где raw SQL неизбеженКогда ORM справляется хорошоКогда без “осознанного SQL” всё равно придётся думать“Компромиссная” стратегия: гибридный подходПереход шаг за шагом: так обычно делают правильноШаг 1. Выделите “границы” функциональностиШаг 2. Сначала модели и миграции, потом запросыШаг 3. Контроль N+1 и количества запросовШаг 4. Для сложных запросов сделайте осознанный выборТипичные ошибки при переходе на SQLModel1) Путаница между Pydantic-валидацией и DB-constraints2) Неучёт стратегии загрузки отношений3) Миграции не соответствуют моделям4) Непонимание того, какой SQL реально выполняетсяПравила выбора: когда SQLModel — лучший, а когда raw SQL осознаннееSQLModel часто лучший выбор, если…raw SQL (или SQLAlchemy Core) часто разумнее, если…Практический вывод: как думать о переходе, а не просто “заменить библиотеку”

Многие проекты на Python начинались с raw SQL по простой причине: так быстрее получить работающий результат, особенно когда схема уже есть и запросы «знают, как надо». Со временем же вы сталкиваетесь с типичными болями: дублирование логики в строках SQL, дрейф схемы и моделей, сложность с валидацией входных данных, неочевидные связи и слабая поддержка переносимости.

SQLModel (надстройка над SQLAlchemy) обещает решить значительную часть этих проблем: модели становятся “единственным источником правды” для схемы, типы данных — ближе к Python, а типизация и валидация — к входным данным. Но на практике переход — не «заменили строку на модель и всё стало лучше». В реальном коде выигрыши проявляются в определённых зонах, а местами приходится думать ещё внимательнее, чем с raw SQL.

Ниже — разбор подходов на практике: как меняются модели, связи, ограничения, миграции и работа со сложными запросами. Дам правила выбора: когда SQLModel — лучший инструмент, а когда лучше оставлять осознанно написанный SQL.


Что именно вы меняете: raw SQL vs модели

raw SQL: сила — в выражении и контроле

Raw SQL — это:

  • максимальный контроль над тем, что реально выполняется в БД;
  • простая проверка «как будет» через EXPLAIN/ANALYZE;
  • возможность использовать специфические фичи конкретного СУБД (например, PostgreSQL JSONB-операторы, CTE-цепочки, оконные функции).

Но цена — в том, что предметная область живёт в нескольких местах:

  • схемы — в миграциях/DDL;
  • структуры — где-то в DTO/Pydantic;
  • запросы — в репозиториях строками SQL;
  • связи и ограничения — частично в БД, частично «в голове».

Со временем это даёт расхождения и «магические» зависимости.

SQLModel: схему вы описываете ближе к домену

SQLModel упрощает:

  • объявление таблиц и полей (как правило — через Python-объекты);
  • валидацию входных данных (частично на уровне Pydantic);
  • управление связями и ограничениями на уровне ORM;
  • единообразие: меньше кода, который описывает одно и то же разными способами.

Но важно понимать: SQLModel не отменяет SQL. Он лишь переводит часть логики на уровень запросов ORM. Если вам нужен конкретный план выполнения или сложная оптимизация, ORM может стать «ещё одним слоем», который придётся уметь обходить.


Модели и типы: где ORM действительно выигрывает

Типизация и валидация “до запроса”

С raw SQL валидация обычно живёт в отдельных слоях (Pydantic DTO, ручные проверки). В SQLModel типы и ограничения часто становятся общими:

  • поля модели задают тип данных;
  • Pydantic-часть помогает поймать некорректные входные значения до записи в БД;
  • ограничения валидации могут частично подтягиваться из схемы.

Пример упрощённой модели пользователя:

code
from typing import Optional
from sqlmodel import SQLModel, Field


class User(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    email: str = Field(index=True, unique=True)
    age: Optional[int] = Field(default=None, ge=0, le=150)
    is_active: bool = Field(default=True)

В чём плюс по сравнению с raw SQL:

  • меньше разрозненных мест, где вы проверяете age или ограничиваете email;
  • форма входных данных часто совпадает с моделью домена/таблицы.

Однако подводный камень: ограничения, заданные на уровне Python/валидации, — это не то же самое, что ограничения на уровне БД. DB-уровень остаётся обязательным для консистентности. Если вы полагаетесь только на валидацию ORM, вы рискуете получить рассинхрон при параллельных изменениях или альтернативных путях записи данных.

Нюанс: “уникальность” — не только индексы, но и политика обработки

В модели выше unique=True создаёт уникальный constraint (в зависимости от миграций/генерации). Но как вы обрабатываете нарушение уникальности?

С raw SQL часто вы ловили конкретную ошибку СУБД и отвечали на неё. В ORM нужно всё равно:

  • отлавливать исключения на уровне транзакции;
  • маппить их на понятные ошибки домена.

То есть ORM уменьшает количество кода, но не снимает проблему проектирования ошибок.


Связи: where SQLModel помогает, но не магия

Один-ко-многим и многие-ко-многим: меньше ручного труда

С raw SQL связи обычно реализуются через JOIN’ы, где:

  • вы вручную пишете условия соединения;
  • вы заботитесь о фильтрах, пагинации, агрегациях;
  • вы иногда копируете паттерны из запроса в запрос.

В SQLModel типичные связи декларативнее. Пример: у пользователя есть посты (один-ко-многим).

code
from typing import List, Optional
from sqlmodel import SQLModel, Field, Relationship


class PostBase(SQLModel):
    title: str
    content: str


class User(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    email: str = Field(index=True, unique=True)
    posts: List["Post"] = Relationship(back_populates="author")


class Post(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    title: str
    content: str

    user_id: int = Field(foreign_key="user.id")
    author: Optional[User] = Relationship(back_populates="posts")

В этом месте SQLModel часто даёт выигрыш:

  • код становится ближе к предметной модели;
  • меньше ручных JOIN’ов при работе с объектами (в зависимости от того, как вы строите запросы и грузите отношения).

Но нужно думать: загрузка отношений и N+1

Самая частая проблема при переходе на ORM — «скрытый N+1». Raw SQL почти всегда заставляет вас думать о том, какой JOIN сделать. ORM иногда позволяет случайно уйти в режим:

  • загрузили список пользователей;
  • по каждому пользователю лениво достали посты;
  • и получили десятки/сотни запросов.

SQLModel как часть экосистемы SQLAlchemy не отменяет эту реальность. Поэтому правило для перехода звучит так:

Если отношения нужны в ответе API — проектируйте загрузку заранее (joined/ selectin), а не полагайтесь на дефолты.

В противном случае вы можете даже ухудшить производительность относительно raw SQL.


Ограничения и индексы: проще объявить, но сложнее держать в согласии

Как переносится логика ограничений

В raw SQL ограничения закреплены в миграциях: CHECK, FOREIGN KEY, UNIQUE, NOT NULL, partial indexes.

В SQLModel вы объявляете их в моделях (полями и параметрами Field). Однако вопрос миграций остаётся тем же: кто и как применяет DDL к БД.

Важно уточнить практику команды:

  • вы используете Alembic для миграций?
  • миграции генерируются из моделей автоматически или вручную?
  • как вы контролируете изменения в схемах в ревью?

Если миграции не дисциплинированы, то “модель в коде” и “схема в БД” расходятся — и вы получаете те же проблемы, что были с raw SQL, только в другом месте.

Индексы и сложные ограничения

Лёгкие индексы index=True в модели — обычно не проблема. А вот:

  • составные индексы;
  • partial indexes;
  • выражения для функциональных индексов;
  • специфичные для PostgreSQL constraint’ы;

— часто потребуют более глубокого уровня: либо прямых DDL в миграциях, либо расширенной конфигурации метаданных.

Вывод практический: SQLModel хорошо описывает базовые constraints, но для «тяжёлой» индексной политики всё равно будет место для осознанного SQL в миграциях (или в фабрикации индексов).


Миграции: главный “взрослый” вопрос при переходе

Где переход ломается чаще всего

Переход с raw SQL на ORM обычно начинается с репозиториев и моделей. Но схема в БД — источник истины. Поэтому вам придётся синхронизировать:

  • изменения моделей;
  • изменения миграций;
  • ожидания рантайма (какие constraints реально уже существуют).

Типичный сценарий провала:

  1. вы поменяли модель, добавили поле и unique=True;
  2. в коде ORM уже думает, что constraint есть;
  3. миграции не применились (или применились не везде);
  4. на проде получили неожиданные дубликаты или ошибки.

С raw SQL это тоже случается, но там обычно весь путь DDL/запросов контролируется более прямо.

Рекомендации по процессу

  • Договоритесь, что схема — это миграции. Модели — описание этой схемы, но они не заменяют процесс DDL.
  • В ревью миграций убедитесь, что изменения действительно соответствуют изменённым моделям.
  • На время миграции используйте фазовый подход:
    • сначала добавить nullable поля и индексы/constraints без строгих проверок;
    • затем backfill;
    • затем сделать constraint строгим (например, NOT NULL).

Это универсальная стратегия, она одинаково полезна и при raw SQL, и при SQLModel.


Репозитории и запросы: как выглядит «обычный день» с SQLModel

CRUD и простые выборки

CRUD обычно становится короче. Пример: создание пользователя.

code
from sqlmodel import Session, select
from typing import Optional

# engine создаётся отдельно
# from sqlalchemy.engine import Engine

def create_user(session: Session, email: str) -> Optional[int]:
    user = User(email=email)
    session.add(user)
    session.commit()
    session.refresh(user)
    return user.id

Для чтения:

code
def get_user_by_email(session: Session, email: str) -> Optional[User]:
    statement = select(User).where(User.email == email)
    return session.exec(statement).first()

С raw SQL вы бы написали курсор/строку, маппинг в структуру, обработку результата. ORM снимает часть рутины, а код становится ближе к домену.

Но вопрос “сколько SQL генерируется” остаётся

ORM генерирует SQL, и важно понимать:

  • какая часть будет “in-database”;
  • какие вычисления окажутся в Python;
  • как будет выглядеть конкретный SQL.

Практический подход: в сложных местах включайте логирование и смотрите реальный SQL, который исполняется. Это особенно важно при переходе, когда ожидания разработчика могут не совпасть с тем, что сделал ORM.


Сложные запросы: где SQLModel выигрывает, а где raw SQL неизбежен

Когда ORM справляется хорошо

SQLModel/SQLAlchemy отлично подходит для:

  • выборок по фильтрам и сортировкам;
  • пагинации;
  • простых агрегаций;
  • выборок с предсказуемыми JOIN’ами;
  • комбинаций условий and_ / or_ / in_, если логика не превращается в монстра.

Пример запроса: список постов пользователя с условием по активности.

(Упрощённо, без демонстрации загрузки отношений.)

code
from sqlmodel import select

def posts_for_active_users(session):
    statement = (
        select(Post)
        .join(User, Post.user_id == User.id)
        .where(User.is_active == True)
        .order_by(Post.id.desc())
    )
    return session.exec(statement).all()

Когда без “осознанного SQL” всё равно придётся думать

Есть несколько классов ситуаций, где raw SQL остаётся оправданным:

  1. Сильно специфичный SQL конкретной СУБД
    Например, сложная работа с JSONB, рекурсивные CTE, window functions с нестандартными выражениями, materialized views.

  2. Запросы, где критичен план выполнения
    Вы часто делаете “тонкую” оптимизацию: хинты, особые индексы, точные условия в нужном порядке. ORM может дать SQL, который “выглядит похоже”, но по плану будет хуже.

  3. Очень сложные отчётные выборки
    Когда запрос превращается в многоступенчатую аналитическую конструкцию, ORM может увеличить когнитивную нагрузку. Иногда проще и честнее оставить SQL, чем пытаться выразить его через построитель запросов.

  4. Сложные маппинги результатов
    Например, запрос возвращает набор полей, который не ложится прямо на модель таблицы. Тогда raw SQL может быть проще, а ORM — потребует доп. слоёв (alias, column mapping, DTO).

“Компромиссная” стратегия: гибридный подход

Хорошая практика для перехода — не “всё ORM”, а “всё там, где это даёт пользу”.

  • Используйте SQLModel для схемы, валидации, базовых CRUD и большинства транзакционных операций.
  • Для действительно тяжёлых запросов оставляйте raw SQL (или SQLAlchemy Core), но:
    • контролируйте параметры безопасно (bind parameters);
    • документируйте, почему именно этот запрос не перевели в ORM;
    • сохраняйте тесты и/или план выполнения (на уровне подхода, не магии).

Если вы решаете оставить raw SQL в одном месте, это не провал перехода — это инженерное решение.


Переход шаг за шагом: так обычно делают правильно

Шаг 1. Выделите “границы” функциональности

Начните с модулей, где:

  • много одинаковой структуры объектов (таблицы/DTO);
  • запросы типовые;
  • доменная логика не завязана на специфичный SQL.

Где плохо начинать: отчётность со сложными CTE и хитрой аналитикой — лучше позже.

Шаг 2. Сначала модели и миграции, потом запросы

Если вы начнёте переписывать запросы раньше, чем привести схему и миграции в порядок, получите двойную нестабильность:

  • запросы могут быть неактуальны относительно БД;
  • миграции ещё не поддерживают новые constraints.

Начните с “вертикали схемы”: модели → миграции → запуск на staging → проверки.

Шаг 3. Контроль N+1 и количества запросов

Соберите список API-эндпоинтов/сценариев, которые читают отношения. Для каждого:

  • решите, какие поля реально нужны;
  • выберите стратегию загрузки (и проверьте запросы в логах).

Не пытайтесь “починить потом”, N+1 обычно сначала незаметен, а потом становится дорогим.

Шаг 4. Для сложных запросов сделайте осознанный выбор

Не превращайте “переход” в соревнование: кто перепишет быстрее. Если конкретный запрос — кандидат на raw SQL, то:

  • оставьте его raw;
  • используйте SQLModel там, где это приносит регулярную пользу.

Типичные ошибки при переходе на SQLModel

1) Путаница между Pydantic-валидацией и DB-constraints

Да, SQLModel опирается на Pydantic и может валидировать поля. Но:

  • это не заменяет constraint’ы в БД;
  • это не гарантирует целостность при конкурентных изменениях;
  • это не заменяет транзакционные правила.

Ошибка: считать, что “раз модель валидирует — значит данные в БД валидны”. На практике нет.

2) Неучёт стратегии загрузки отношений

Ошибка: “отношения сами подтянутся”. Итог — N+1.

Правило: когда отношения нужны — проектируйте загрузку.

3) Миграции не соответствуют моделям

Ошибка: модель меняется, а миграции — “потом”. Потом часто превращается в инциденты.

Правило: схема — миграции, модели — описание.

4) Непонимание того, какой SQL реально выполняется

ORM — это генератор. Он полезен, но может:

  • добавлять лишние JOIN’ы;
  • влиять на порядок условий;
  • менять форму SQL для агрегатов.

Без проверки вы можете получить ухудшение производительности, даже не заметив.


Правила выбора: когда SQLModel — лучший, а когда raw SQL осознаннее

SQLModel часто лучший выбор, если…

  • вам важны схемы “в одном месте”: модели + валидация + ограничения;
  • вы хотите уменьшить объём кода в репозиториях;
  • большая часть запросов — транзакционные и типовые (CRUD, выборки, простые JOIN);
  • у команды есть дисциплина по миграциям и ревью SQL/запросов.

raw SQL (или SQLAlchemy Core) часто разумнее, если…

  • запрос завязан на конкретные фичи СУБД или сложные аналитические конструкции;
  • вы тщательно оптимизируете план выполнения;
  • результат не ложится в ORM-модели (или маппинг становится сложнее самого SQL);
  • вы уже достигли стабильной производительности и миграция в ORM принесёт больше риска, чем выгоды.

Практический вывод: как думать о переходе, а не просто “заменить библиотеку”

Переход с raw SQL на SQLModel — это не про библиотеку ради библиотеки. Это про распределение ответственности:

  • SQLModel помогает собрать доменную модель и схему ближе друг к другу.
  • Миграции и стратегия запросов всё равно требуют инженерной дисциплины.
  • ORM ускоряет разработку для типичных задач, но сложные запросы не исчезают — просто их иногда проще оставлять SQL’ом, а не пытаться “приручить” их построителем ORM.

Если хотите разлож

Войдите, чтобы поставить лайк и оставить комментарий.

Автор

$ sudo teach IT

SQLModel

Курс по теме

SQLModel

С нуля до уверенной работы с базами данных на Python. Подходит для начинающих и тех, кто уже пишет на Python. Разберитесь с SQLModel раз и навсегда — и перестаньте бояться SQL и таблиц.

Открыть курс

Продолжите обучение

Все курсы
Python – для начинающих!

Python – для начинающих!

С нуля до профессионального уровня. Подходит для всех. Учитесь каждый день и овладейте самым популярным языком программирования.

Перейти к курсу
Простыми словами о защищенном соединении

Простыми словами о защищенном соединении

Базовый курс для обычных людей: что такое защищённое соединение, как оно устроено, что оно умеет и чего не умеет, как выбрать сервис и настроить его на своих устройствах.

Перейти к курсу
Ботостроение Telegram

Ботостроение Telegram

Лёгкий, быстрый и доступный способ познакомиться с миром ботостроения в Telegram. Видео, конспекты, практика и помощь – всё у нас на курсе.

Перейти к курсу
Сервис, который не падает - как построить безопасную зашифрованную сеть

Сервис, который не падает - как построить безопасную зашифрованную сеть

Практический курс по безопасности сети: как собрать собственный сервис защищённого доступа - от пустого сервера до подписки с автовыбором - и, что важнее, как потом не уронить его своими руками.

Перейти к курсу

Другие статьи

SQLModel vs SQLAlchemy: что выбрать для Python-проекта с FastAPI
sqlmodel

SQLModel vs SQLAlchemy: что выбрать для Python-проекта с FastAPI

Сравниваем два подхода к работе с базами данных в Python-экосистеме: где SQLModel упрощает жизнь, а где мощность SQLAlchemy незаменима. С примерами моделей, запросов и миграций.

18 июля 2026 г.
780
SQLModel в реальном проекте: миграции, индексы и как не потерять контроль над схемой
sqlmodel

SQLModel в реальном проекте: миграции, индексы и как не потерять контроль над схемой

Соберем рабочую стратегию для SQLModel: от согласованного описания ограничений и индексов до миграций без сюрпризов и проверки, что модель действительно соответствует БД. Рассмотрим, как тестами защитить схему от регрессий.

30 июля 2026 г.
590
Создание Telegram бота в 2026 легко и просто! Полный курсы!
создание

Создание Telegram бота в 2026 легко и просто! Полный курсы!

19 июня 2026 г.
1431
Как выбрать, куда идти в IT: веб, боты, данные — без хаоса
выбрать

Как выбрать, куда идти в IT: веб, боты, данные — без хаоса

Разберём самые понятные направления для новичка (веб, боты, аналитика/данные) и чем они отличаются по шагам обучения. В конце вы получите простой план, как проверить, что вам подходит, и какой стартовый путь выбрать.

5 октября 2026 г.
580
Путь от README до демо: как собрать портфолио разработчика, чтобы его хотели смотреть
путь

Путь от README до демо: как собрать портфолио разработчика, чтобы его хотели смотреть

Разберём структуру репозитория, как упаковать проект в понятную историю (проблема → решение → запуск → метрики качества) и какие артефакты повышают доверие: скринкасты, архитектурная схема, чек-лист продакшн-готовности.

17 сентября 2026 г.
1090
Как понять, какой язык программирования учить первым: простой тест и выбор
понять

Как понять, какой язык программирования учить первым: простой тест и выбор

Помогу новичку выбрать первый язык без стресса: объясню варианты по целям (веб, боты, анализ данных), что учить в первую очередь и как не потратить месяцы впустую. В конце — краткий план старта на ближайшие недели.

2 октября 2026 г.
630

Комментарии

Станьте первым в обсуждении

Поделитесь мнением о статье или задайте вопрос автору

Содержание

Что именно вы меняете: raw SQL vs моделиraw SQL: сила — в выражении и контролеSQLModel: схему вы описываете ближе к доменуМодели и типы: где ORM действительно выигрываетТипизация и валидация “до запроса”Нюанс: “уникальность” — не только индексы, но и политика обработкиСвязи: where SQLModel помогает, но не магияОдин-ко-многим и многие-ко-многим: меньше ручного трудаНо нужно думать: загрузка отношений и N+1Ограничения и индексы: проще объявить, но сложнее держать в согласииКак переносится логика ограниченийИндексы и сложные ограниченияМиграции: главный “взрослый” вопрос при переходеГде переход ломается чаще всегоРекомендации по процессуРепозитории и запросы: как выглядит «обычный день» с SQLModelCRUD и простые выборкиНо вопрос “сколько SQL генерируется” остаётсяСложные запросы: где SQLModel выигрывает, а где raw SQL неизбеженКогда ORM справляется хорошоКогда без “осознанного SQL” всё равно придётся думать“Компромиссная” стратегия: гибридный подходПереход шаг за шагом: так обычно делают правильноШаг 1. Выделите “границы” функциональностиШаг 2. Сначала модели и миграции, потом запросыШаг 3. Контроль N+1 и количества запросовШаг 4. Для сложных запросов сделайте осознанный выборТипичные ошибки при переходе на SQLModel1) Путаница между Pydantic-валидацией и DB-constraints2) Неучёт стратегии загрузки отношений3) Миграции не соответствуют моделям4) Непонимание того, какой SQL реально выполняетсяПравила выбора: когда SQLModel — лучший, а когда raw SQL осознаннееSQLModel часто лучший выбор, если…raw SQL (или SQLAlchemy Core) часто разумнее, если…Практический вывод: как думать о переходе, а не просто “заменить библиотеку”