Изменение данных: INSERT/UPDATE/DELETE, UPSERT, MERGE

Рейтинг: 57.8% · 13 голосов
Подробный курс по PostgreSQL: SQL по демобазе, типы данных и JSONB, индексы, транзакции и MVCC, VACUUM, EXPLAIN и планировщик, роли и привилегии, репликация, бэкап, настройка под нагрузку и под 1С. Актуально на 2026.
Ответить
Аватара пользователя
Egor_DBA
Сообщения: 47
Зарегистрирован: 11 май 2026, 05:31

Изменение данных: INSERT/UPDATE/DELETE, UPSERT, MERGE

Сообщение Egor_DBA »

Оглавление курса (47)
  1. Что такое PostgreSQL: история, философия, где применяется
  2. Урок 1. Что нового в PostgreSQL 15 и далее (15 -> 16 -> 17)
  3. Установка и первый запуск: Linux, Windows, Docker
  4. psql детально: метакоманды и работа в консоли
  5. Демобаза Авиаперевозки Postgres Pro: структура и развёртывание
  6. SELECT: проекция, фильтрация, сортировка
  7. Транзакции и ACID
  8. Уровни изоляции транзакций: Read Committed, Repeatable Read, Serializable
  9. Соединения таблиц (JOIN)
  10. Агрегация: GROUP BY, HAVING, GROUPING SETS
  11. Подзапросы и CTE (WITH), рекурсия
  12. Оконные функции
  13. Изменение данных: INSERT/UPDATE/DELETE, UPSERT, MERGE (вы здесь)
  14. Числовые и булевы типы, NULL и трёхзначная логика
  15. Строки, текст, дата и время, таймзоны
  16. Массивы, диапазоны, enum, составные типы
  17. JSON и JSONB: операторы и индексация
  18. DDL: CREATE TABLE, схемы, ALTER
  19. Ограничения целостности
  20. Представления и материализованные представления
  21. Последовательности, IDENTITY, генерируемые столбцы
  22. Индексы: B-tree и когда индекс не используется
  23. Типы индексов: Hash, GiST, SP-GiST, GIN, BRIN
  24. Продвинутые индексы: частичные, по выражению, покрывающие
  25. Обслуживание индексов и таблиц: bloat, REINDEX, CONCURRENTLY
  26. Полнотекстовый поиск
  27. Роли и пользователи
  28. Привилегии: GRANT/REVOKE
  29. Row-Level Security и безопасность данных
  30. Подключение приложения: pg_hba.conf, SSL, пулы соединений
  31. Серверное программирование: функции и процедуры
  32. Триггеры и события
  33. Архитектура PostgreSQL: процессы и память
  34. MVCC: версии строк и видимость
  35. VACUUM, autovacuum, freeze и wraparound
  36. WAL, контрольные точки и долговечность
  37. Блокировки и взаимоблокировки
  38. Планировщик запросов
  39. EXPLAIN и EXPLAIN ANALYZE: чтение планов
  40. Конфигурация сервера: ключевые параметры
  41. Настройка под нагрузку и под 1С
  42. Параллельные запросы и партиционирование
  43. Резервное копирование и восстановление
  44. Репликация: потоковая и hot standby
  45. Логическая репликация и кластерные решения
  46. Внешние данные, расширения и сертификация
  47. Разбор планов запросов на explain.tensor.ru: глубокое чтение EXPLAIN ANALYZE
Урок 12. Изменение данных: INSERT/UPDATE/DELETE, UPSERT, MERGE

До сих пор мы в основном читали данные. Теперь учимся их менять. Эта группа команд в postgresql называется DML - data manipulation language. Звучит сухо, но именно тут живут самые болезненные ошибки: затёртые строки, забытый WHERE в UPDATE, гонки при вставке. В уроке разберём четыре рабочих команды (INSERT, UPDATE, DELETE и MERGE), фишку RETURNING, паттерн UPSERT через ON CONFLICT и поговорим, как не превратить изменение данных в потерю данных. База - PostgreSQL 15, по дороге отмечу, что подвезли в 16 и 17.

Изображение

Как это работает

Любая команда изменения в PostgreSQL выполняется внутри транзакции. Если вы не открыли её явно через BEGIN, сервер сам обернёт одиночный запрос в неявную транзакцию: либо всё применилось, либо ничего. Это и есть та самая транзакционная безопасность, ради которой люди выбирают нормальную СУБД.

Важная особенность движка: PostgreSQL не переписывает строку на месте. При UPDATE он создаёт новую версию строки, а старую помечает как устаревшую. Эту модель называют MVCC. Отсюда два следствия. Первое - параллельные читатели не блокируют писателя и видят согласованный снимок данных. Второе - устаревшие версии (мёртвые кортежи) копятся, и их потом подчищает vacuum. То есть массовый UPDATE - это по сути массовая вставка плюс работа для автовакуума.

INSERT кладёт новые строки. UPDATE меняет существующие по условию WHERE. DELETE удаляет по WHERE. Команда без WHERE трогает всю таблицу - запомните это как мантру. UPSERT (INSERT ON CONFLICT) решает задачу вставь, а если такой ключ уже есть, то обнови. MERGE из PG15 - это декларативный способ за один проход развести строки на три сценария: совпало, не совпало в таблице, не совпало в источнике.

SQL и примеры

Работаем со схемой bookings демобазы Авиаперевозки. Начнём с простой вставки нового аэропорта. RETURNING возвращает то, что реально записалось - удобно для сгенерированных значений.

Код: Выделить всё

INSERT INTO airports_data (airport_code, airport_name, city, coordinates, timezone)
VALUES ('NSK', '{"en": "New Test"}', '{"en": "Test City"}',
        point(82.6, 55.0), 'Asia/Novosibirsk')
RETURNING airport_code, city;
Вставка из выборки. Скопируем в архивную таблицу все рейсы, которые уже прибыли.

Код: Выделить всё

INSERT INTO flights_archive
SELECT * FROM flights
WHERE status = 'Arrived' AND actual_arrival < now() - interval '1 year';
UPDATE с условием. Поднимем сумму бронирований, оформленных за конкретный день, на 5 процентов. WHERE здесь - граница между точечной правкой и катастрофой.

Код: Выделить всё

UPDATE bookings
SET total_amount = total_amount * 1.05
WHERE book_date::date = DATE '2017-07-01';
UPDATE с FROM - когда новое значение зависит от другой таблицы. Это аналог UPDATE ... JOIN из других СУБД. Перенесём статус из flights в связанную ticket_flights (пример учебный, показывает синтаксис).

Код: Выделить всё

UPDATE ticket_flights tf
SET amount = tf.amount
FROM flights f
WHERE tf.flight_id = f.flight_id
  AND f.status = 'Cancelled';
DELETE с USING - удаление с опорой на другую таблицу. Удалим посадочные талоны для отменённых рейсов.

Код: Выделить всё

DELETE FROM boarding_passes bp
USING ticket_flights tf, flights f
WHERE bp.ticket_no = tf.ticket_no
  AND bp.flight_id = tf.flight_id
  AND tf.flight_id = f.flight_id
  AND f.status = 'Cancelled'
RETURNING bp.ticket_no, bp.flight_id;
UPSERT. Допустим, ведём таблицу счётчиков посадок по рейсу. Хотим увеличить счётчик, а если строки ещё нет - создать её. ON CONFLICT ловит конфликт по первичному ключу. В блоке DO UPDATE доступна псевдотаблица EXCLUDED - это та строка, которую мы пытались вставить.

Код: Выделить всё

INSERT INTO flight_load (flight_id, seats_taken)
VALUES (1234, 1)
ON CONFLICT (flight_id)
DO UPDATE SET seats_taken = flight_load.seats_taken + EXCLUDED.seats_taken;
Вариант DO NOTHING просто молча пропускает дубликат - удобно при пакетной загрузке, когда часть строк уже есть.

Код: Выделить всё

INSERT INTO airports_data (airport_code, airport_name, city, coordinates, timezone)
VALUES ('SVO', '{"en": "x"}', '{"en": "x"}', point(0,0), 'UTC')
ON CONFLICT (airport_code) DO NOTHING;
MERGE (PG15+). Синхронизируем целевую таблицу со staging-источником за один проход: что совпало - обновим, чего нет - вставим.

Код: Выделить всё

MERGE INTO flight_load AS t
USING staging_load AS s
ON t.flight_id = s.flight_id
WHEN MATCHED THEN
    UPDATE SET seats_taken = s.seats_taken
WHEN NOT MATCHED THEN
    INSERT (flight_id, seats_taken) VALUES (s.flight_id, s.seats_taken);
В PG15 MERGE не умел RETURNING - это добавили в PG17, вместе с конструкцией WHEN NOT MATCHED BY SOURCE для зеркальной обработки строк, которых нет в источнике.

Частые грабли
  • UPDATE или DELETE без WHERE применяется ко всей таблице. Отрабатывает мгновенно, откатить можно только если вы внутри открытой транзакции и ещё не сделали COMMIT.
  • Между UPSERT и MERGE есть разница в гонках. ON CONFLICT атомарен и безопасен при параллельных вставках. MERGE в PG15/16 атомарность ключа сам не гарантирует - при конкурентной нагрузке может упасть с unique violation, нужен уникальный индекс и иногда повтор.
  • ON CONFLICT требует реального ограничения - первичного ключа или UNIQUE-индекса по указанным столбцам. Без него Postgres не знает, что считать конфликтом, и выдаст ошибку.
  • EXCLUDED путают с именем таблицы. В DO UPDATE через имя таблицы доступна старая строка, через EXCLUDED - предложенная к вставке. Перепутаете - получите не ту арифметику.
  • Массовый UPDATE раздувает таблицу мёртвыми версиями и нагружает vacuum. Большие правки лучше дробить на пакеты по диапазону ключа.
  • Порядок строк при UPDATE не определён, поэтому два встречных UPDATE одних и тех же строк в разных транзакциях легко дают взаимоблокировку (deadlock). Обновляйте в согласованном порядке.
  • RETURNING возвращает финальное состояние строки после срабатывания триггеров и DEFAULT - это и плюс, и сюрприз, если триггер переписал значение.
Мини-лаба
  • Создайте таблицу: CREATE TABLE flight_load (flight_id int PRIMARY KEY, seats_taken int NOT NULL DEFAULT 0).
  • Откройте транзакцию через BEGIN и выполните INSERT одной строки с RETURNING - посмотрите, что вернулось.
  • Выполните тот же INSERT ещё раз, но с ON CONFLICT (flight_id) DO UPDATE SET seats_taken = flight_load.seats_taken + 1. Проверьте SELECT, что счётчик вырос.
  • Сделайте UPDATE без WHERE, затем ROLLBACK и убедитесь через SELECT, что изменения откатились.
  • Создайте staging_load с парой строк (одна с существующим flight_id, одна с новым) и запустите MERGE из примера.
  • Удалите тестовую строку через DELETE ... RETURNING и зафиксируйте COMMIT.
  • Запустите VACUUM (VERBOSE) flight_load и найдите в выводе число вычищенных мёртвых версий.
Контрольные вопросы
  • Почему UPDATE в PostgreSQL порождает мёртвые версии строк и при чём тут vacuum?
  • Чем псевдотаблица EXCLUDED отличается от имени целевой таблицы внутри ON CONFLICT DO UPDATE?
  • В каких случаях UPSERT через ON CONFLICT безопаснее MERGE при параллельной нагрузке?
  • Что вернёт RETURNING, если на таблице висит BEFORE-триггер, меняющий вставляемое значение?
  • Зачем в DELETE используют USING, а в UPDATE - FROM, и что они дают?
  • Какие возможности MERGE появились в PG17 по сравнению с PG15?
👍3 ❤️5 🔥1 😄 🤔3
Аватара пользователя
llamageek
Сообщения: 1
Зарегистрирован: 14 май 2026, 12:05

Re: Изменение данных: INSERT/UPDATE/DELETE, UPSERT, MERGE

Сообщение llamageek »

А если я в UPDATE забыл WHERE, но команда шла без BEGIN - всё, приехали? Откатить уже никак?
👍 ❤️ 🔥1 😄 🤔
Аватара пользователя
wireguard69
Сообщения: 1
Зарегистрирован: 12 май 2026, 13:52

Re: Изменение данных: INSERT/UPDATE/DELETE, UPSERT, MERGE

Сообщение wireguard69 »

Проверил на 1с-овской базе: пакетная загрузка прайса через ON CONFLICT DO UPDATE летает, раньше городил SELECT а потом INSERT или UPDATE, тут одной командой и без гонок.
👍1 ❤️1 🔥1 😄 🤔
Ответить
← Предыдущая глава
Оконные функции
Следующая глава →
Числовые и булевы типы, NULL и трёхзначная логика

Все главы курса «PostgreSQL: от первого запроса до продакшена»

Поделиться темой: ✈ Telegram VK

Вернуться в «PostgreSQL: от первого запроса до продакшена»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость