INSERT, UPDATE, DELETE в SQL: шпаргалка
Зачем аналитику DML
На собесе аналитика спрашивают редко, но на позициях «DA + DE» — часто. Плюс нужно уметь не угробить прод.
INSERT
Базовый
INSERT INTO users (name, email) VALUES ('Иван', 'ivan@example.com');
-- Несколько строк
INSERT INTO users (name, email) VALUES
('Петр', 'petr@example.com'),
('Аня', 'anya@example.com');INSERT SELECT
INSERT INTO archive_users
SELECT * FROM users WHERE last_active < CURRENT_DATE - INTERVAL '2 year';INSERT ... ON CONFLICT (UPSERT)
PostgreSQL:
INSERT INTO users (id, email)
VALUES (1, 'new@example.com')
ON CONFLICT (id) DO UPDATE SET email = EXCLUDED.email;EXCLUDED — ссылка на «пытавшуюся вставиться» строку.
MySQL:
INSERT INTO users (id, email) VALUES (1, 'new@example.com')
ON DUPLICATE KEY UPDATE email = 'new@example.com';RETURNING — возврат данных
INSERT INTO orders (user_id, amount)
VALUES (1, 100)
RETURNING order_id, created_at;Типичный кейс: получить автогенерированный id сразу.
UPDATE
Базовый
UPDATE users SET status = 'active' WHERE id = 1;
-- Обновить несколько колонок
UPDATE users SET status = 'active', updated_at = NOW() WHERE id = 1;UPDATE с подзапросом
UPDATE users
SET orders_count = (
SELECT COUNT(*) FROM orders WHERE orders.user_id = users.id
);UPDATE ... FROM (PostgreSQL)
UPDATE users u
SET city = c.name
FROM cities c
WHERE c.id = u.city_id;Удобнее, чем коррелированный подзапрос.
RETURNING
UPDATE orders SET status = 'cancelled'
WHERE status = 'pending' AND created_at < CURRENT_DATE - INTERVAL '7 day'
RETURNING order_id, user_id;Возвращает обновлённые строки — полезно для audit.
Если хочется сразу закрепить тему на практике — открой тренажёр в Telegram. 10 минут в день — и синтаксис в пальцах.
DELETE
Базовый
DELETE FROM users WHERE status = 'deleted';DELETE с JOIN (PostgreSQL через USING)
DELETE FROM orders o
USING users u
WHERE o.user_id = u.id AND u.is_spam = TRUE;DELETE всех строк
DELETE FROM temp_table;
-- vs
TRUNCATE temp_table;TRUNCATE быстрее (не логирует каждую строку), но не откатывается в транзакции (в некоторых СУБД) и сбрасывает auto-increment.
RETURNING
DELETE FROM sessions
WHERE expired_at < NOW()
RETURNING session_id, user_id;MERGE (SQL Server, Oracle, новый в PostgreSQL 15+)
Комбо INSERT + UPDATE + DELETE в одной операции:
MERGE INTO users u
USING new_data n ON u.id = n.id
WHEN MATCHED THEN UPDATE SET email = n.email
WHEN NOT MATCHED THEN INSERT (id, email) VALUES (n.id, n.email);На собесе про MERGE спрашивают редко, но знать полезно.
Безопасность — как не убить прод
1. Всегда начинайте с SELECT
-- Сначала проверьте, что удалится
SELECT * FROM users WHERE last_active < CURRENT_DATE - INTERVAL '2 year';
-- Потом только DELETE
DELETE FROM users WHERE last_active < CURRENT_DATE - INTERVAL '2 year';2. Используйте транзакции
BEGIN;
DELETE FROM orders WHERE status = 'draft';
-- Проверяем через SELECT
SELECT COUNT(*) FROM orders WHERE status = 'draft';
-- Если норм — COMMIT
COMMIT;
-- Если нет — ROLLBACK3. Бэкапы перед массовыми операциями
Копия таблицы перед изменениями:
CREATE TABLE users_backup_2026_04_15 AS SELECT * FROM users;
-- Теперь можно работать4. Ограничение через LIMIT (в UPDATE/DELETE)
-- PostgreSQL
DELETE FROM temp WHERE id IN (
SELECT id FROM temp LIMIT 1000
);
-- MySQL
DELETE FROM temp LIMIT 1000;При массовых операциях чанкуйте — не блокируйте всю таблицу на 10 минут.
5. WHERE — обязательно
Классическая авария:
-- 💀 Удалит ВСЕ
DELETE FROM users;
-- ✅ Правильно
DELETE FROM users WHERE id = 1;Хорошая привычка: писать WHERE 1=2 сначала, потом править.
ACID и DML
INSERT/UPDATE/DELETE — транзакционные операции. В транзакции они либо все применяются, либо все откатываются. См. шпаргалку по транзакциям.
Чтобы не только читать теорию, но и решать реальные задачи — загляните в бот Карьерника. Там по каждой теме подборка вопросов с разборами.
10 задач с собесов
1. Вставить нового пользователя с возвратом id
INSERT INTO users (name, email) VALUES ('Иван', 'ivan@x.com')
RETURNING id;2. UPSERT — либо создать, либо обновить
INSERT INTO settings (user_id, theme) VALUES (1, 'dark')
ON CONFLICT (user_id) DO UPDATE SET theme = EXCLUDED.theme;3. Удалить старые сессии
DELETE FROM sessions WHERE expired_at < NOW();4. Обновить цены всех товаров одной категории +10%
UPDATE products SET price = price * 1.1 WHERE category = 'electronics';5. Пометить неактивных пользователей
UPDATE users SET status = 'inactive'
WHERE last_login < CURRENT_DATE - INTERVAL '90 day';6. Массовая вставка из CSV (через COPY)
COPY users FROM '/tmp/users.csv' WITH CSV HEADER;7. Бэкап перед миграцией
CREATE TABLE users_before_migration AS SELECT * FROM users;8. Удаление дубликатов
DELETE FROM users a USING users b
WHERE a.id < b.id AND a.email = b.email;9. DELETE с LIMIT (чанками)
DELETE FROM logs WHERE id IN (
SELECT id FROM logs WHERE created_at < '2025-01-01' LIMIT 10000
);Повторять в цикле до конца.
10. UPDATE по JOIN
UPDATE orders o
SET total = oi.sum_total
FROM (SELECT order_id, SUM(price * quantity) AS sum_total FROM order_items GROUP BY order_id) oi
WHERE o.id = oi.order_id;Как тренироваться
DML — теоретически просто, практически рискованно. Тренируйтесь на dev-базе, потом только выносите на staging/prod.
Совет: в продакшне всегда используйте транзакции для DML. Даже DELETE WHERE id = 1 лучше выполнить в BEGIN/COMMIT — мало ли что.
Читайте также
FAQ
UPSERT — что это?
Insert OR Update: если строки с таким ключом нет — вставить, если есть — обновить. В PostgreSQL через ON CONFLICT. В MySQL — ON DUPLICATE KEY UPDATE.
Почему DELETE медленный?
Логирует каждую строку в WAL, обновляет индексы, проверяет триггеры. TRUNCATE для массового удаления быстрее.
Можно ли UPDATE с JOIN?
В PostgreSQL через FROM-клаузу. В MySQL — UPDATE t1 JOIN t2 SET ... . В стандартном SQL — через коррелированный подзапрос.
Как откатить DELETE?
Только через ROLLBACK (в рамках транзакции). После COMMIT — только из бэкапа. Поэтому в продакшне сначала BEGIN, потом DELETE, потом проверка, потом COMMIT.