Как обновить данные в 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.
Связанные темы
- INSERT UPDATE DELETE — шпаргалка
- Как удалить строку в SQL
- Как добавить столбец в SQL
- Транзакции ACID — шпаргалка
FAQ
UPDATE в транзакции обязательно?
На prod — да. Дает возможность откатить при ошибке.
Как ускорить массовый UPDATE?
Temp table + UPDATE с JOIN. Индекс на WHERE-колонке.
UPDATE или UPSERT?
UPDATE если знаете, что строка есть. UPSERT если не уверены.
После UPDATE в транзакции видят другие сессии?
Нет — до COMMIT видит только ваша транзакция.
Тренируйте SQL — откройте тренажёр с 1500+ вопросами для собесов.