INSERT, UPDATE, DELETE — изменение данных в SQL

Коротко

INSERT, UPDATE и DELETE — DML-команды (Data Manipulation Language) для изменения данных в таблицах. INSERT добавляет строки, UPDATE изменяет существующие, DELETE удаляет. Аналитику эти команды нужны для подготовки тестовых данных, заполнения витрин, исправления ошибок в данных. На собеседованиях спрашивают разницу между DELETE и TRUNCATE, безопасность UPDATE без WHERE и RETURNING.

INSERT — вставка данных

Одна строка

INSERT INTO users (username, email, created_at)
VALUES ('ivan', 'ivan@example.com', NOW());

Столбцы, не указанные в списке, получат DEFAULT или NULL. Если столбец NOT NULL и без DEFAULT — ошибка.

Несколько строк

INSERT INTO products (name, price, category)
VALUES
    ('Ноутбук', 75000, 'electronics'),
    ('Наушники', 5000, 'electronics'),
    ('Книга SQL', 1200, 'books');

Одна команда INSERT с несколькими VALUES работает быстрее, чем три отдельных INSERT.

INSERT ... SELECT

INSERT INTO monthly_revenue (month, revenue, orders)
SELECT
    DATE_TRUNC('month', order_date) AS month,
    SUM(amount) AS revenue,
    COUNT(*) AS orders
FROM orders
WHERE order_date >= '2025-01-01'
GROUP BY DATE_TRUNC('month', order_date);

Вставляет результат запроса. Удобно для наполнения витрин данных и агрегированных таблиц.

INSERT ... RETURNING (PostgreSQL)

INSERT INTO users (username, email)
VALUES ('maria', 'maria@example.com')
RETURNING user_id, created_at;

RETURNING возвращает вставленные данные — не нужен отдельный SELECT для получения сгенерированного ID.

INSERT ... ON CONFLICT (UPSERT)

INSERT INTO daily_metrics (metric_date, dau, revenue)
VALUES ('2025-03-15', 1500, 250000)
ON CONFLICT (metric_date)
DO UPDATE SET
    dau = EXCLUDED.dau,
    revenue = EXCLUDED.revenue;

Если строка с таким metric_date уже есть — обновить. Если нет — вставить. EXCLUDED ссылается на значения, которые пытались вставить. В MySQL аналог — INSERT ... ON DUPLICATE KEY UPDATE.

UPDATE — обновление данных

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

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

Никогда не запускайте UPDATE без WHERE (если не хотите обновить все строки в таблице).

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

UPDATE products
SET
    price = price * 1.1,
    updated_at = NOW()
WHERE category = 'electronics';

Увеличивает цену на 10% для всей электроники. Можно использовать текущее значение столбца в выражении.

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

UPDATE users
SET segment = 'premium'
WHERE user_id IN (
    SELECT user_id
    FROM orders
    GROUP BY user_id
    HAVING SUM(amount) > 100000
);

Помечает как premium пользователей с суммой заказов больше 100 000.

UPDATE с JOIN (PostgreSQL)

UPDATE orders o
SET status = 'vip_order'
FROM users u
WHERE o.user_id = u.user_id
  AND u.segment = 'premium';

В PostgreSQL UPDATE ... FROM позволяет обновлять на основе данных из другой таблицы. В MySQL — UPDATE orders o JOIN users u ON ... SET ....

UPDATE ... RETURNING

UPDATE products
SET is_active = FALSE
WHERE stock = 0
RETURNING product_id, name;

Возвращает обновлённые строки — удобно для логирования изменений.

DELETE — удаление данных

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

DELETE FROM orders
WHERE order_date < '2020-01-01';

Удалит все заказы до 2020 года. Без WHERE удалит все строки.

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

DELETE FROM user_sessions
WHERE user_id NOT IN (
    SELECT user_id FROM users
);

Удалит «осиротевшие» сессии, у которых нет соответствующего пользователя.

DELETE с USING (PostgreSQL)

DELETE FROM order_items oi
USING orders o
WHERE oi.order_id = o.order_id
  AND o.status = 'cancelled';

Удаляет позиции отменённых заказов. Аналог DELETE с JOIN.

DELETE ... RETURNING

DELETE FROM expired_tokens
WHERE expires_at < NOW()
RETURNING token_id, user_id;

Безопасность: транзакции

Перед массовыми UPDATE или DELETE — оберните в транзакцию:

BEGIN;

-- Сначала проверить, что затронет
SELECT COUNT(*) FROM users WHERE last_login < '2023-01-01';

-- Если всё ок — выполнить
DELETE FROM users WHERE last_login < '2023-01-01';

-- Проверить результат и подтвердить
-- COMMIT;   -- подтвердить
-- ROLLBACK; -- отменить

Пока не выполнен COMMIT, изменения не применены. ROLLBACK откатывает всё до BEGIN. Это спасёт от случайного UPDATE users SET email = 'oops' без WHERE.

DELETE vs TRUNCATE

Критерий DELETE TRUNCATE
Фильтрация WHERE Да Нет (все строки)
Транзакция Да Зависит от СУБД
Триггеры Срабатывают Не срабатывают
Скорость Медленнее (построчно) Быстрее (сброс таблицы)
RETURNING Да (PostgreSQL) Нет
Автоинкремент Не сбрасывает Сбрасывает

Подробнее — в сравнении DELETE vs TRUNCATE.

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

UPDATE/DELETE без WHERE. Самая опасная ошибка. UPDATE users SET role = 'admin' сделает всех админами. Всегда добавляйте WHERE или оборачивайте в транзакцию.

INSERT с неправильным порядком столбцов. INSERT INTO users VALUES (...) без указания столбцов зависит от порядка столбцов в таблице. Всегда указывайте список столбцов явно.

Нарушение FOREIGN KEY. INSERT строки с user_id, которого нет в таблице users — ошибка внешнего ключа. DELETE пользователя, у которого есть заказы — тоже ошибка (если нет ON DELETE CASCADE).

Забыли RETURNING. После INSERT с SERIAL приходится делать отдельный SELECT для получения ID. RETURNING решает эту проблему в одном запросе (PostgreSQL, Oracle).

Вопросы с собеседований

-- Чем отличается DELETE от TRUNCATE? -- DELETE удаляет строки с возможностью WHERE, поддерживает транзакции и триггеры. TRUNCATE очищает всю таблицу быстрее, но без фильтрации. DELETE не сбрасывает автоинкремент, TRUNCATE — сбрасывает.

-- Что произойдёт при UPDATE без WHERE? -- Обновятся все строки в таблице. Это легальная операция, но почти всегда ошибка. Используйте транзакции для защиты.

-- Что такое UPSERT? -- INSERT с обработкой конфликта: если строка существует — обновить, если нет — вставить. В PostgreSQL: INSERT ... ON CONFLICT ... DO UPDATE. В MySQL: INSERT ... ON DUPLICATE KEY UPDATE.

-- Как вставить данные из одной таблицы в другую? -- INSERT INTO target_table (col1, col2) SELECT col1, col2 FROM source_table WHERE .... Типы и количество столбцов должны совпадать.

-- Зачем нужен RETURNING? -- Возвращает изменённые строки без дополнительного SELECT. Полезно для получения сгенерированных ID после INSERT, подтверждения обновлений и логирования удалений. Работает в PostgreSQL.


Потренируйтесь решать задачи — откройте тренажёр с 1500+ вопросами для подготовки к собеседованиям аналитиков.

FAQ

Можно ли откатить DELETE?

Да, если DELETE выполнен внутри транзакции (BEGIN ... ROLLBACK). Без транзакции — данные удалены безвозвратно. TRUNCATE в PostgreSQL тоже можно откатить внутри транзакции, но в MySQL — нельзя.

INSERT ... SELECT vs CREATE TABLE ... AS SELECT?

INSERT ... SELECT добавляет данные в существующую таблицу. CREATE TABLE ... AS SELECT создаёт новую таблицу и заполняет данными. Второй вариант быстрее для одноразовых витрин.

Как обновить миллионы строк без блокировки?

Обновляйте батчами: UPDATE ... WHERE id BETWEEN 1 AND 10000, затем следующий диапазон. Это снижает нагрузку на БД и не блокирует другие запросы надолго.

Как тренироваться

DML-команды — часть базового SQL для аналитика. В тренажёре Карьерник есть задачи на INSERT, UPDATE, DELETE и проектирование запросов. Больше вопросов — в разделе с примерами.