UPDATE — это оператор SQL, который меняет значения столбцов в уже существующих строках таблицы. Новые строки добавляет INSERT, удаляет — DELETE, а UPDATE только переписывает значения в строках, отобранных условием WHERE. Без WHERE он изменит все строки таблицы.
Содержание
Ниже — синтаксис, обновление по другой таблице (здесь диалекты расходятся сильнее всего), RETURNING и защита от случайного обновления всей таблицы. Примеры запускал 23.09.2026 в SQLite 3.51, PostgreSQL 18.6 и MySQL 8.4; T-SQL-варианты проверял на Azure SQL Edge (движок SQL Server 2019), не на SQL Server 2022.
Минимальный рабочий пример
Учебная база — книжный магазин: таблица books (остатки и цены) и sales (продажи за день). Скрипт запускается в SQLite как есть, например sqlite3 -header -column shop.db < shop.sql.
CREATE TABLE books (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL,
stock INTEGER NOT NULL CHECK (stock >= 0),
price INTEGER NOT NULL CHECK (price > 0)
);
CREATE TABLE sales (
book_id INTEGER NOT NULL REFERENCES books(id),
qty INTEGER NOT NULL CHECK (qty > 0)
);
INSERT INTO books VALUES
(1, 'Мастер и Маргарита', 12, 650),
(2, 'Анна Каренина', 3, 540),
(3, 'Мертвые души', 2, 480),
(4, 'Илиада', 7, 720);
INSERT INTO sales VALUES (2, 2), (3, 1), (2, 1);
UPDATE books
SET price = 590
WHERE id = 2;
SELECT id, title, price FROM books;
Изменилась одна строка — цена «Анны Карениной»:
id title price
-- ------------------ -----
1 Мастер и Маргарита 650
2 Анна Каренина 590
3 Мертвые души 480
4 Илиада 720
Цены — целые рубли: в SQLite DECIMAL не дает точной дробной арифметики. CHECK пригодится ниже.
Каждый следующий пример запускается на исходных данных: пересоздайте базу скриптом выше без последнего UPDATE, иначе числа в статье не совпадут с вашими.
Синтаксис: SET и WHERE
UPDATE таблица
SET столбец1 = выражение1,
столбец2 = выражение2
WHERE условие;
SET задает, какие столбцы и на что менять (константа, выражение от текущих значений, CASE, подзапрос), WHERE — какие строки. Условие то же, что в SELECT.
Скидка 15% и пометка для книг с остатком меньше 5 штук:
UPDATE books
SET price = price * 85 / 100,
title = title || ' (акция)'
WHERE stock < 5;
Обновятся две строки: у «Анны Карениной» цена станет 459, у «Мертвых душ» — 408 (здесь оба результата целые; при дробном результате SQLite, PostgreSQL и SQL Server отбросят дробную часть целочисленного деления, а в MySQL / дает дробное число, и при записи в INTEGER оно округлится: из цены 7 получится 6, а не 5). Конкатенация || работает в SQLite и PostgreSQL; в MySQL по умолчанию || — это логическое ИЛИ, там нужен CONCAT(title, ' (акция)').
Порядок присваиваний в SET зависит от СУБД
Запрос UPDATE books SET stock = stock + 1, price = stock WHERE id = 4; на одной и той же строке (stock = 7) дает разный результат:
| СУБД | stock | price | Почему |
|---|---|---|---|
| PostgreSQL 18, SQLite 3.51 | 8 | 7 | Все правые части считаются по старым значениям строки |
| MySQL 8.4 | 8 | 8 | Присваивания в одной таблице выполняются слева направо, price видит уже новый stock |
Для переносимого кода не ссылайтесь в SET на столбец, меняемый тем же запросом.
Главный риск: UPDATE без WHERE
Неверно — забыли условие:
UPDATE books SET price = 500;
Результат: цена 500 у всех четырех книг, без подтверждения. Исправление — транзакция и проверка числа строк до фиксации:
BEGIN;
UPDATE books SET price = 500;
SELECT changes() AS changed; -- SQLite; в PostgreSQL число строк пишет сам psql: UPDATE 4
ROLLBACK; -- ожидали 1 строку, получили 4 - откатываем
После ROLLBACK цены вернутся к 650, 540, 480 и 720. Рабочая привычка для ручных правок: BEGIN -> SELECT с тем же WHERE -> UPDATE -> сверка числа строк -> COMMIT или ROLLBACK. Проверочный SELECT стоит внутри транзакции, а не перед ней.
Граница changes(): функция SQLite считает только строки, которые изменил непосредственно последний оператор. Изменения от триггеров, каскадов внешних ключей и REPLACE в это число не входят — их проверяйте отдельно.
Если данные меняют одновременно
Транзакция из примера выше защищает от собственной ошибки: запрос можно откатить. Но она не гарантирует, что проверенные SELECT-ом строки не изменит кто-то другой, пока вы решаете, делать ли UPDATE. В PostgreSQL на уровне изоляции по умолчанию (Read Committed) даже два SELECT внутри одной транзакции могут увидеть разные данные, если между ними другая транзакция зафиксировала изменения. Поэтому уровней защиты три:
- Учебный откат —
BEGIN, проверка,UPDATE, сверка числа строк,ROLLBACKпри расхождении. Достаточно для ручных правок, когда с таблицей никто больше не работает. - Блокировка выбранных строк —
SELECT ... FOR UPDATEвнутри транзакции. Другие транзакции, которые попытаются изменить или так же заблокировать эти строки, будут ждатьCOMMITилиROLLBACK:
BEGIN;
SELECT id, stock FROM books WHERE id = 3 FOR UPDATE; -- строка 3 заблокирована до конца транзакции
UPDATE books SET stock = stock - 1 WHERE id = 3;
COMMIT;
Так работают PostgreSQL и MySQL (InnoDB). В SQL Server тот же эффект дает табличная подсказка WITH (UPDLOCK) в SELECT. В SQLite FOR UPDATE нет: там запись блокирует всю базу, и транзакцию для правок открывают как BEGIN IMMEDIATE.
- Условное обновление — проверку кладут прямо в
WHEREтого жеUPDATE, и отдельныйSELECTне нужен:
UPDATE books SET stock = stock - 1
WHERE id = 3 AND stock >= 1; -- спишем, только если есть что списать
Если остаток к этому моменту уже 0, запрос обновит 0 строк — это и есть сигнал, что данные изменились. Приложение сверяет число обновленных строк с ожидаемым (1) и при расхождении перечитывает данные. Тот же прием с отдельным столбцом-версией (WHERE id = :id AND version = :v, в SET — version = version + 1) называют оптимистичной блокировкой.
Граница: в PostgreSQL, MySQL и SQLite клиент по умолчанию работает в режиме автокоммита — без явного BEGIN каждый UPDATE фиксируется сразу, и откатить его уже нельзя.
В MySQL есть дополнительный предохранитель — режим безопасных обновлений:
SET SESSION sql_safe_updates = 1;
UPDATE books SET price = 500;
ERROR 1175 (HY000): You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column.
С WHERE id = 1 тот же запрос проходит. Клиент mysql включает его флагом --safe-updates. В PostgreSQL и SQLite встроенного аналога нет — только транзакции и дисциплина.
Второй предохранитель — ограничения схемы. Попытка продать больше, чем есть на складе:
UPDATE books SET stock = stock - 5 WHERE id = 3;
SQLite отклонит запрос с сообщением CHECK constraint failed: stock >= 0, остаток «Мертвых душ» останется 2. Без CHECK в таблице просто появился бы отрицательный остаток.
Обновление по данным другой таблицы
Задача: списать со склада продажи из sales.
Вариант 1: коррелированный подзапрос (переносимый)
Неверно:
UPDATE books
SET stock = stock - (SELECT SUM(qty) FROM sales
WHERE sales.book_id = books.id);
Результат: NOT NULL constraint failed: books.stock. Для книг без продаж подзапрос возвращает NULL, а stock - NULL тоже NULL. Без NOT NULL остатки двух книг молча стали бы NULL. Исправление — ограничить строки:
UPDATE books
SET stock = stock - (SELECT SUM(qty) FROM sales
WHERE sales.book_id = books.id)
WHERE id IN (SELECT book_id FROM sales);
Остатки: 12, 0, 1, 7 — верно (у «Анны Карениной» было две продажи: 2 + 1). Такой запрос работает во всех четырех СУБД.
Вариант 2: UPDATE с JOIN — синтаксис по диалектам
Единого синтаксиса «UPDATE с JOIN» в SQL нет, у каждой СУБД свой:
| СУБД | Форма |
|---|---|
| PostgreSQL | UPDATE t SET ... FROM другая WHERE условие связи |
| SQLite (с версии 3.33.0) | Как в PostgreSQL: UPDATE t SET ... FROM ... WHERE ... |
| MySQL, MariaDB | UPDATE t JOIN другая ON ... SET ... |
| SQL Server | UPDATE t SET ... FROM t JOIN другая ON ... |
Частая ошибка (PostgreSQL):
UPDATE books
SET stock = books.stock - s.qty
FROM sales AS s
WHERE s.book_id = books.id;
Результат: у книги 2 остаток 1 в PostgreSQL 18 и 2 в SQLite 3.51 (MySQL 8.4 на аналогичном JOIN дал 1), а правильно 0. Если строке books соответствуют несколько строк sales, она обновляется один раз по одной из пар, какой — не определено. Ошибки нет, данные тихо испорчены.
Исправление — сначала агрегировать, чтобы на одну книгу приходилась одна строка. PostgreSQL и SQLite:
UPDATE books AS b
SET stock = b.stock - s.sold
FROM (SELECT book_id, SUM(qty) AS sold
FROM sales GROUP BY book_id) AS s
WHERE s.book_id = b.id;
MySQL:
UPDATE books AS b
JOIN (SELECT book_id, SUM(qty) AS sold
FROM sales GROUP BY book_id) AS s ON s.book_id = b.id
SET b.stock = b.stock - s.sold;
SQL Server (T-SQL):
UPDATE b
SET b.stock = b.stock - s.sold
FROM books AS b
JOIN (SELECT book_id, SUM(qty) AS sold
FROM sales GROUP BY book_id) AS s ON s.book_id = b.id;
Во всех случаях остатки 12, 0, 1, 7. В MySQL UPDATE ... JOIN можно менять столбцы сразу нескольких таблиц, но ORDER BY и LIMIT в многотабличной форме запрещены — они есть только у однотабличного UPDATE в MySQL.
RETURNING: увидеть, что изменилось
RETURNING возвращает измененные строки тем же запросом. Пример для PostgreSQL 18:
UPDATE books AS b
SET stock = b.stock - s.sold
FROM (SELECT book_id, SUM(qty) AS sold
FROM sales GROUP BY book_id) AS s
WHERE s.book_id = b.id
RETURNING b.id, old.stock AS was, new.stock AS now;
id | was | now
----+-----+-----
2 | 3 | 0
3 | 2 | 1
(2 rows)
Обращение к old и new в RETURNING появилось в PostgreSQL 18; в более ранних версиях RETURNING отдает только новые значения. Поддержка по СУБД:
| СУБД | Как получить измененные строки |
|---|---|
| PostgreSQL | RETURNING (с версии 18 — также old.*/new.*) |
| SQLite | RETURNING с версии 3.35.0, только новые значения |
| SQL Server | OUTPUT inserted.stock, deleted.stock |
| MySQL 8.4 | Нет, нужен отдельный SELECT в той же транзакции |
Если не получилось
- Обновлено 0 строк.
WHEREничего не нашел: проверьте его отдельнымSELECT. Частая причина — сравнение сNULL:col = NULLне истинно никогда, нужноcol IS NULL. В MySQL 0 бывает и тогда, когда строка найдена, но новое значение совпало со старым: клиентmysqlпишет0 rows affected(Rows matched: 1 Changed: 0). Драйвер, подключенный с флагомCLIENT_FOUND_ROWS, вернет число найденных строк, а не измененных. - Обновились все строки. Нет
WHEREили условие всегда истинно. Транзакция еще открыта —ROLLBACK. ЕслиUPDATEуже зафиксирован, откатить его нельзя: остановите дальнейшие записи и используйте штатное восстановление СУБД — резервную копию вместе с журналом транзакций до нужного момента (point-in-time recovery). Восстановление из одной полной копии откатит и все корректные изменения после нее, поэтому сначала отработайте процедуру на копии базы. - Ошибка синтаксиса у
FROMилиJOIN. Синтаксис другого диалекта:UPDATE ... JOINне поймут PostgreSQL и SQLite,UPDATE ... FROM— MySQL. В SQLite старше 3.33.0FROMне поддерживается — используйте подзапрос. - Число получилось не то. Несколько строк-источников на одну целевую — агрегируйте до
UPDATE. - Запрос завис. Строку держит другая незавершенная транзакция — завершите ее.
Выводы
UPDATEменяет значения в существующих строках; какие строки — решаетWHERE, без него меняется вся таблица.- Безопасный порядок для ручной правки:
BEGIN,SELECTс тем же условием,UPDATE, сверка числа строк,COMMITилиROLLBACK. Если данные меняют параллельно — блокировка строк (SELECT ... FOR UPDATE) или условныйUPDATEс проверкой числа обновленных строк. - UPDATE с JOIN пишется по-разному в каждом диалекте; переносимый вариант — коррелированный подзапрос с ограничивающим
WHERE. - При нескольких строках-источниках на одну целевую строку
UPDATEс соединением тихо берет одну из них — агрегируйте заранее. RETURNINGесть в PostgreSQL и SQLite 3.35+, в SQL Server его роль играетOUTPUT, в MySQL аналога нет.
Где применяется / связь с практикой
UPDATE нужен везде, где данные живут: смена статусов заказов, списание остатков, массовые исправления после миграции. На массовых исправлениях данные и ломают чаще всего, поэтому привычка к транзакциям ценится не меньше синтаксиса.
Освойте тему на практике
Системно разобрать SQL — от SELECT и соединений до транзакций, индексов и различий диалектов — можно на курсе Otus по SQL. Бесплатно познакомиться с преподавателями и форматом можно на открытых уроках Otus.
FAQ
Можно ли обновить строки в определенном порядке или только первые N?
В MySQL однотабличный UPDATE поддерживает ORDER BY и LIMIT. В SQL Server есть UPDATE TOP (n), но какие n строк обновятся, не гарантировано, поэтому порядок задают так же, как в PostgreSQL. В PostgreSQL ORDER BY/LIMIT в UPDATE нет — отбирают ключи подзапросом с ORDER BY ... LIMIT и обновляют по ним; в SQLite ORDER BY/LIMIT в UPDATE работают только в сборке с опцией SQLITE_ENABLE_UPDATE_DELETE_LIMIT (системный sqlite3 3.51 в macOS ее включает, другие сборки — не всегда).
Можно ли установить значение по умолчанию?
Да: SET столбец = DEFAULT подставит значение из определения столбца. Это работает в PostgreSQL, MySQL и SQL Server; SQLite ключевое слово DEFAULT в SET не поддерживает.
Чем UPDATE отличается от UPSERT?
UPDATE не создает строк. UPSERT вставляет строку или обновляет ее при конфликте ключа: ON CONFLICT DO UPDATE в PostgreSQL и SQLite, ON DUPLICATE KEY UPDATE в MySQL, MERGE в SQL Server.



