Представьте, что на кровати лежит книга, и нужно вернуть ее в шкаф. Вместо того, чтобы отнести книгу, вы толкаете шкаф к кровати, кладете книгу на полку, а потом толкаете шкаф на место. Подобная сцена уместна в ситкоме, но в целом это абсурд.

Однако именно так большинство работает с базой данных: без конца гоняет данные туда-сюда, хотя можно обойтись командой.

Предположим, нужно обновить статус пользователа по коду. На ORM это выглядит так:

user = models.User.get_by_id(42)
user.status = 'active'
user.save()

Разумеется, никому не интересно, какие запросы будут выполнены. Их никто не смотрит и даже не знает, как включить (см. прошлый совет). А запросы будут такими:

select * from users
where id = 42

update users set status='active'
where id = 42

Внимание: зачем нужен первый запрос? Мы выбрали все поля пользователя. Зачем? Второй UPDATE делает именно то, что нужно: обновляет статус по номеру. Ради этого не стоило читать пользователя! Нужно оставить только второй запрос.

Говорят: перед обновлением я должен убедиться, что пользователь существует. Это легко сделать и после: оператор UPDATE возвращает число обновленных записей. Если оно равно нулю, пользователя не было. Не говоря уж о том, что существование записи проверяется SELECT-ом без полей или условием EXISTS – незачем выгребать все поля.

Вообще, следите, чтобы данные извлекались из базы как можно реже. База прекрасно умеет обновлять и перекладывать данные без участия клиента. Например, кто-то блокирует запись при чтении, чтобы обновить ее:

begin;
select * from users where id = 42 for update;
update users set ... where id = 42;
commit;

Зачем читать и блокировать запись? Просто обнови ее.

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

create or replace pg_temp.get_new_rating(user user)
returns integer
immutable strict parallel safe language sql
return ...

и обновляют сразу много пользователей:

update users
set rating = get_new_rating(users)
FROM subquery
where id = subquery.user_id

Поздапрос subquery выбирает пользователей, которых нужно обновить, а функция get_new_rating возвращает новый рейтинг.

Уже слышу истерику: омг, хранимки, мои глаза! Ну, тут уж на ваше усмотрение. Хотите пользоваться базой нормально? Понадобится сырой скуль. Для этого нужно отложить ORM и почитать книжки. Но оно того стоит.