Как обновить данные в SQL

Карьерник — квиз-тренажёр в Telegram с 1500+ вопросами для собесов аналитика. SQL, Python, A/B, метрики. Бесплатно.

Зачем это нужно

UPDATE — обычная задача: обогатить строки данными из другой таблицы, присвоить сегмент (gold / silver / bronze), пометить записи как устаревшие, пересчитать денормализованные поля. Для аналитика UPDATE чаще возникает в ETL-джобах или в ad-hoc скриптах для исправления данных.

Опасность та же, что у DELETE — забытый WHERE обновляет всю таблицу. Плюс есть нюансы производительности: «тупой» UPDATE с подзапросом на миллионе строк может идти часами. Правильный паттерн — UPDATE с JOIN, temp table для массовых апдейтов, транзакция для возможности отката.

В статье — 12 рабочих шаблонов:

  • UPDATE по условию и с CASE для разных значений
  • UPDATE с JOIN в Postgres vs MySQL (разный синтаксис)
  • UPSERT через ON CONFLICT и ON DUPLICATE KEY
  • RETURNING для получения обновлённых строк
  • Массовые UPDATE через временную таблицу
  • Безопасный UPDATE в транзакции

Базовый синтаксис

UPDATE users
SET name = 'Alice'
WHERE id = 1;

Критично: без WHERE обновит ВСЕ строки.

1. Обновить одну колонку

UPDATE users
SET email = 'new@example.com'
WHERE id = 42;

2. Обновить несколько колонок

UPDATE users
SET
    email = 'new@example.com',
    name  = 'New Name',
    updated_at = NOW()
WHERE id = 42;

3. Обновить по условию (разным значениям)

Через CASE

UPDATE users
SET tier = CASE
    WHEN total_spent > 100000 THEN 'gold'
    WHEN total_spent > 10000  THEN 'silver'
    ELSE 'bronze'
END;

Одна команда — разные значения для разных строк.

4. UPDATE с JOIN

Postgres — UPDATE ... FROM

UPDATE orders o
SET customer_name = u.name
FROM users u
WHERE o.user_id = u.id;

MySQL — UPDATE с JOIN

UPDATE orders o
INNER JOIN users u ON u.id = o.user_id
SET o.customer_name = u.name;

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

UPDATE users
SET last_order_at = (
    SELECT MAX(created_at) FROM orders WHERE user_id = users.id
);

6. Обновить на основе агрегата

-- обновить total_spent в users на основе orders
UPDATE users u
SET total_spent = stats.total
FROM (
    SELECT user_id, SUM(total) AS total
    FROM orders
    WHERE status = 'paid'
    GROUP BY user_id
) stats
WHERE u.id = stats.user_id;

7. Увеличить значение

-- увеличить balance на 100
UPDATE accounts
SET balance = balance + 100
WHERE id = 42;

-- счётчик кликов
UPDATE articles
SET click_count = click_count + 1
WHERE id = 10;

8. Обновить с RETURNING

Postgres позволяет получить обновлённые строки:

UPDATE users
SET tier = 'gold'
WHERE total_spent > 100000
RETURNING id, name, tier;

9. Массовые обновления

По IN-списку

UPDATE products
SET is_featured = TRUE
WHERE id IN (100, 101, 102, 103);

Через временную таблицу

Для тысяч обновлений — лучше через temp table:

CREATE TEMP TABLE updates (id INT, new_price NUMERIC);
INSERT INTO updates VALUES (1, 100), (2, 200), ...;

UPDATE products p
SET price = u.new_price
FROM updates u
WHERE p.id = u.id;

Быстрее, чем цикл из 1000 UPDATE.

10. UPDATE с LIMIT

MySQL:

UPDATE orders SET status = 'processed'
WHERE status = 'pending'
ORDER BY created_at
LIMIT 1000;

Для постепенной миграции — повторять batch-ами.

11. UPSERT — INSERT or UPDATE

Postgres — ON CONFLICT

INSERT INTO users (id, email, name)
VALUES (42, 'alice@example.com', 'Alice')
ON CONFLICT (id) DO UPDATE
SET email = EXCLUDED.email,
    name = EXCLUDED.name;

MySQL — ON DUPLICATE KEY

INSERT INTO users (id, email, name)
VALUES (42, 'alice@example.com', 'Alice')
ON DUPLICATE KEY UPDATE
    email = VALUES(email),
    name = VALUES(name);

12. Безопасный UPDATE (transaction)

BEGIN;

-- посмотреть, сколько обновим
SELECT COUNT(*) FROM users WHERE tier = 'bronze' AND total_spent > 10000;

-- выполнить
UPDATE users SET tier = 'silver'
WHERE tier = 'bronze' AND total_spent > 10000;

-- проверить
SELECT COUNT(*) FROM users WHERE tier = 'silver';

COMMIT;
-- или ROLLBACK, если что-то не так

Частые ошибки

1. UPDATE без WHERE

Обновит ВСЕ строки. Прямая catастрофа на production.

2. UPDATE вместо DELETE + INSERT

Обычно UPDATE эффективнее. Меньше risk (строки не теряются), меньше IO.

3. Подзапрос вычисляется для каждой строки

-- медленно: подзапрос на каждую строку
UPDATE users
SET last_order = (SELECT MAX(created_at) FROM orders WHERE user_id = users.id);

Быстрее — UPDATE с JOIN:

UPDATE users u
SET last_order = stats.max_date
FROM (SELECT user_id, MAX(created_at) AS max_date FROM orders GROUP BY 1) stats
WHERE u.id = stats.user_id;

4. Забыть updated_at

Если есть audit-колонки — обновите их тоже:

UPDATE users SET name = 'x', updated_at = NOW() WHERE id = 1;

Или триггер на автоматическое обновление.

5. Блокировки

UPDATE на большой таблице без индекса → полная блокировка. Для миграций — batched.

Связанные темы

FAQ

UPDATE в транзакции обязательно?

На prod — да. Дает возможность откатить при ошибке.

Как ускорить массовый UPDATE?

Temp table + UPDATE с JOIN. Индекс на WHERE-колонке.

UPDATE или UPSERT?

UPDATE если знаете, что строка есть. UPSERT если не уверены.

После UPDATE в транзакции видят другие сессии?

Нет — до COMMIT видит только ваша транзакция.


Тренируйте SQL — откройте тренажёр с 1500+ вопросами для собесов.