UPDATE: изменение данных

UPDATE: изменение данных

INSERT добавляет новые строки. UPDATE изменяет существующие. Это самый рискованный оператор DML для новичков: если забыть WHERE, изменятся все строки в таблице. В этом уроке разберём синтаксис, типичные случаи применения и главное правило безопасности.

Синтаксис UPDATE

UPDATE таблица
SET    col1 = value1,
       col2 = value2
WHERE  условие;

Пример — обновить email одного клиента:

UPDATE customers
SET    email = 'new_anna@example.com'
WHERE  id = 5;

СУБД найдёт строки, удовлетворяющие WHERE id = 5, и обновит колонку email. Если условие совпадает с несколькими строками — обновятся все.

Главное правило: всегда WHERE

-- ОПАСНО: обновит все строки в таблице!
UPDATE customers
SET    city = 'Москва';

-- Безопасно: обновит только конкретного клиента
UPDATE customers
SET    city = 'Москва'
WHERE  id = 42;

Первый запрос присвоит city = 'Москва' всем клиентам в базе. Это необратимое действие, если нет резервной копии. PostgreSQL не предупреждает и не спрашивает подтверждения.

Правило безопасности: перед UPDATE всегда выполните соответствующий SELECT с тем же WHERE и убедитесь, что он возвращает именно те строки, которые нужно изменить:

-- Сначала проверьте
SELECT * FROM customers WHERE id = 42;
-- Убедились что всё правильно — теперь обновляем
UPDATE customers SET city = 'Москва' WHERE id = 42;

Обновление нескольких колонок

SET принимает несколько присвоений через запятую:

UPDATE customers
SET    city  = 'Санкт-Петербург',
       email = 'updated@example.com',
       phone = '+7-921-123-4567'
WHERE  id = 5;

Все три колонки обновляются атомарно — либо все, либо ни одна (в рамках транзакции).

Вычисляемые значения в SET

Правая часть присвоения — выражение, которое может ссылаться на текущее значение той же колонки:

-- Повышение зарплаты на 10%
UPDATE employees
SET    salary = salary * 1.10
WHERE  department = 'IT';

-- Добавить бонус к текущей сумме
UPDATE orders
SET    total = total + 500
WHERE  status = 'completed' AND created_at >= '2024-01-01';

salary = salary * 1.10 — читает текущее значение salary, умножает на 1.1 и записывает обратно. Это атомарно: не нужно читать значение отдельным SELECT.

RETURNING в UPDATE

Как и INSERT, UPDATE поддерживает RETURNING:

UPDATE employees
SET    salary = salary * 1.10
WHERE  department = 'IT'
RETURNING id, name, salary;

Возвращает новые значения после обновления. Это полезно, когда нужно немедленно использовать обновлённые данные в приложении.

Проверь себя: вернёт ли RETURNING старые или новые значения колонки salary?

UPDATE с подзапросом

Значение в SET может быть подзапросом:

-- Установить зарплату каждому сотруднику = средняя по его отделу
UPDATE employees e
SET    salary = (
    SELECT AVG(salary)
    FROM   employees e2
    WHERE  e2.department = e.department
);

Коррелированный подзапрос вычисляет среднюю зарплату по отделу для каждого сотрудника. Это пример, который скорее для демонстрации — в реальности так уравнивать зарплаты не стоит.

UPDATE ... FROM: обновление с данными из другой таблицы

PostgreSQL позволяет UPDATE с FROM — обновлять строки на основе данных из другой таблицы:

UPDATE orders o
SET    status = 'shipped'
FROM   shipments s
WHERE  s.order_id = o.id
  AND  s.shipped_at IS NOT NULL
  AND  o.status    = 'paid';

FROM shipments добавляет данные из другой таблицы в UPDATE-запрос. Это эффективнее, чем обновлять через подзапрос для каждой строки. Результат: заказы, для которых есть запись об отправке, переходят в статус shipped.

NULL в UPDATE

Присвоить NULL явно:

UPDATE customers
SET    phone = NULL
WHERE  id = 7;

Это работает — phone получает NULL. Нельзя сделать только если колонка объявлена NOT NULL:

UPDATE customers SET email = NULL WHERE id = 7;
-- ERROR: null value in column "email" violates not-null constraint (если email NOT NULL)

Сколько строк обновил UPDATE

PostgreSQL возвращает сообщение UPDATE N — количество обновлённых строк. Если WHERE не нашёл ни одной строки — UPDATE 0, не ошибка. Это важно: UPDATE 0 означает, что условие не совпало с данными, а не что произошла ошибка.

Приложения часто проверяют это число: если ожидали обновить 1 строку, а получили 0 — значит объект не найден.

UPDATE в транзакции: безопасное обновление

Если не уверены в правильности UPDATE, выполните его внутри транзакции:

BEGIN;

-- Смотрим что изменится
SELECT * FROM customers WHERE city = 'Казань';

-- Обновляем
UPDATE customers SET city = 'Казань (обл.)' WHERE city = 'Казань';

-- Проверяем результат
SELECT * FROM customers WHERE city = 'Казань (обл.)';

-- Если всё верно — подтверждаем
COMMIT;
-- Если что-то не так — отменяем
-- ROLLBACK;

BEGIN начинает транзакцию. COMMIT фиксирует изменения. ROLLBACK отменяет всё, что было после BEGIN. До COMMIT изменения видны только в текущем соединении — другие пользователи ещё видят старые данные. Подробнее о транзакциях — в модуле 10.

Этот паттерн — стандартная практика при любых рискованных UPDATE или DELETE.

Типичные ошибки

1. UPDATE без WHERE:

UPDATE products SET price = 0;  -- обнулит цену у всех товаров!

2. Неправильное условие WHERE:

UPDATE users SET role = 'admin' WHERE name = 'Иван';
-- Может обновить нескольких пользователей с одинаковым именем
-- Лучше: WHERE id = 123

3. Обновление связанных данных (нарушение FK):

UPDATE customers SET id = 999 WHERE id = 1;
-- ERROR: если orders.customer_id = 1 и FK настроен без CASCADE

Практический пример: смена статуса заказов

-- Все заказы старше 30 дней в статусе 'paid' → 'abandoned'
UPDATE orders
SET    status     = 'abandoned',
       updated_at = NOW()
WHERE  status = 'paid'
  AND  created_at < NOW() - INTERVAL '30 days'
RETURNING id, customer_id, amount;

INTERVAL '30 days' — PostgreSQL-синтаксис для вычитания интервала из даты. Возвращённые строки можно использовать для отправки уведомлений клиентам.

Массовое обновление по условию

UPDATE с WHERE на диапазон значений — стандартный инструмент для пакетных операций:

-- Применить скидку 15% ко всем товарам категории 'Одежда'
UPDATE products
SET    price         = price * 0.85,
       discounted_at = NOW()
WHERE  category = 'Одежда'
  AND  price > 1000;

-- Деактивировать пользователей, не заходивших более года
UPDATE users
SET    status       = 'inactive',
       deactivated_at = NOW()
WHERE  status       = 'active'
  AND  last_login_at < NOW() - INTERVAL '1 year';

Оба примера обновляют сразу множество строк. PostgreSQL обрабатывает их эффективно — один обход таблицы. Проверьте RETURNING id чтобы узнать, сколько строк изменилось.

Краткий итог

  • UPDATE таблица SET col = value WHERE условие — обновляет строки по условию
  • Без WHERE обновятся все строки — всегда проверяйте SELECT перед UPDATE
  • SET принимает несколько присвоений через запятую; значения могут ссылаться на текущие
  • RETURNING возвращает новые значения обновлённых строк
  • UPDATE ... FROM позволяет обновлять с данными из другой таблицы
  • UPDATE 0 строк — не ошибка, просто условие не совпало

UPDATE и конкурентный доступ

В многопользовательской системе два запроса могут пытаться обновить одну строку одновременно. PostgreSQL использует MVCC (Multi-Version Concurrency Control) для изоляции — каждая транзакция видит согласованный снимок данных. Последний COMMIT побеждает, и изменения не теряются в неожиданном порядке.

Для случаев, когда нужна гарантия эксклюзивного обновления (например, «взять задачу из очереди»), используют SELECT ... FOR UPDATE — блокировку строки на время транзакции. Это продвинутая тема, но важно знать, что такой механизм существует.

Что дальше

Вы умеете изменять данные. Следующий урок — DELETE: удаление строк. Та же логика безопасности с WHERE — плюс особенности удаления связанных данных.

Попробуйте интерактивную версию

Практические задачи, квизы и AI-наставник — бесплатный старт без карты

Перейти к практике