SQLLab
Все статьи

DML в SQL: INSERT, UPDATE, DELETE — полное руководство

Как вставлять, изменять и удалять данные в SQL: INSERT с подзапросами, UPDATE с JOIN, DELETE RETURNING, массовые операции и безопасные паттерны.

16 марта 2026 г.·5 мин чтения·

DML (Data Manipulation Language) — команды для изменения данных: INSERT, UPDATE, DELETE. В отличие от SELECT, они меняют состояние базы. Разберём каждую команду с практическими примерами.

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

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

INSERT INTO products (name, category, price, stock)
VALUES ('Ноутбук', 'Электроника', 85000, 15);

Порядок столбцов в VALUES должен совпадать с порядком столбцов в скобках.

Вставка нескольких строк

INSERT INTO products (name, category, price)
VALUES
    ('Мышь',       'Электроника', 1500),
    ('Стол',       'Мебель',      12000),
    ('Наушники',   'Электроника', 8000);

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

INSERT с SELECT

-- Скопировать товары из архива в основную таблицу
INSERT INTO products (name, category, price)
SELECT name, category, price
FROM products_archive
WHERE archived_at > NOW() - INTERVAL '1 year';

RETURNING — получить вставленные данные

INSERT INTO users (email, name)
VALUES ('alice@example.com', 'Алиса')
RETURNING id, created_at;
-- Вернёт: id и created_at новой строки без отдельного SELECT

Это очень удобно для получения автоинкрементного ID:

WITH new_user AS (
    INSERT INTO users (email) VALUES ('bob@example.com')
    RETURNING id
)
INSERT INTO profiles (user_id, bio)
SELECT id, 'Новый пользователь'
FROM new_user;

ON CONFLICT — upsert

-- Если email уже существует — обновить имя
INSERT INTO users (email, name)
VALUES ('alice@example.com', 'Алиса Updated')
ON CONFLICT (email)
DO UPDATE SET
    name = EXCLUDED.name,
    updated_at = NOW();

-- Если конфликт — ничего не делать
INSERT INTO user_settings (user_id, setting, value)
VALUES (42, 'theme', 'dark')
ON CONFLICT (user_id, setting)
DO NOTHING;

EXCLUDED — ссылка на строку, которую пытались вставить.


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

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

UPDATE products
SET price = 90000, updated_at = NOW()
WHERE id = 1;

⚠️ UPDATE без WHERE обновит все строки! Всегда проверяйте условие.

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

UPDATE users
SET
    name = 'Иван Иванов',
    email = 'ivan@example.com',
    updated_at = NOW()
WHERE id = 42;

Обновление с вычислением

-- Поднять цену на 10% для всей электроники
UPDATE products
SET price = price * 1.10
WHERE category = 'Электроника';

-- Увеличить счётчик
UPDATE posts SET views = views + 1 WHERE id = 100;

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

-- Обновить статус пользователей, у которых истекла подписка
UPDATE users
SET status = 'inactive'
WHERE id IN (
    SELECT user_id
    FROM subscriptions
    WHERE end_date < NOW() AND is_active = true
);

UPDATE с JOIN (через FROM)

PostgreSQL поддерживает UPDATE ... FROM:

-- Присвоить пользователям категорию на основе таблицы сегментов
UPDATE users u
SET segment = s.segment_name
FROM user_segments s
WHERE s.user_id = u.id;
-- Обновить цену товара на основе прайс-листа поставщика
UPDATE products p
SET price = pl.new_price, updated_at = NOW()
FROM supplier_price_list pl
WHERE pl.product_sku = p.sku
  AND pl.effective_date = CURRENT_DATE;

RETURNING в UPDATE

UPDATE orders
SET status = 'shipped', shipped_at = NOW()
WHERE id = 555
RETURNING id, status, shipped_at;

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

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

DELETE FROM products WHERE id = 5;

⚠️ DELETE без WHERE удалит все строки! Медленнее TRUNCATE, но безопаснее — работает в транзакции, срабатывают триггеры.

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

-- Удалить товары, которые ни разу не продавались и старше года
DELETE FROM products
WHERE created_at < NOW() - INTERVAL '1 year'
  AND id NOT IN (SELECT DISTINCT product_id FROM order_items);

-- Безопаснее через NOT EXISTS (избегаем NULL-проблемы)
DELETE FROM products
WHERE created_at < NOW() - INTERVAL '1 year'
  AND NOT EXISTS (
    SELECT 1 FROM order_items WHERE product_id = products.id
  );

DELETE с JOIN (через USING)

-- Удалить заказы пользователей, помеченных как мошенники
DELETE FROM orders o
USING users u
WHERE o.user_id = u.id
  AND u.is_fraudster = true;

RETURNING в DELETE

-- Удалить и вернуть удалённые строки
DELETE FROM sessions
WHERE expires_at < NOW()
RETURNING user_id, created_at, expires_at;

Мягкое удаление (soft delete)

Часто вместо DELETE используют пометку:

-- Вместо удаления — пометка
UPDATE users
SET deleted_at = NOW(), is_active = false
WHERE id = 42;

-- Фильтровать в запросах:
SELECT * FROM users WHERE deleted_at IS NULL;

Преимущества: данные не теряются, можно восстановить, есть аудит.


TRUNCATE vs DELETE

-- Удалить все строки (медленнее)
DELETE FROM logs;

-- Очистить таблицу мгновенно (нельзя откатить без транзакции)
TRUNCATE TABLE logs;

-- TRUNCATE с перезапуском счётчиков
TRUNCATE TABLE logs RESTART IDENTITY;

-- TRUNCATE нескольких таблиц
TRUNCATE TABLE sessions, temp_data, cache;
DELETETRUNCATE
WHERE
Триггеры❌ (по умолчанию)
Откат✅ (в транзакции)
СкоростьМедленнееМгновенно
Сбросить IDRESTART IDENTITY

Безопасные паттерны

Сначала SELECT, потом DELETE/UPDATE

-- 1. Проверить, что попадёт под изменение
SELECT * FROM orders WHERE status = 'pending' AND created_at < '2026-01-01';

-- 2. Убедившись, что всё верно — выполнить
DELETE FROM orders WHERE status = 'pending' AND created_at < '2026-01-01';

Использовать транзакцию

BEGIN;

UPDATE accounts SET balance = balance - 1000 WHERE id = 1;
UPDATE accounts SET balance = balance + 1000 WHERE id = 2;

-- Проверить результат
SELECT id, balance FROM accounts WHERE id IN (1, 2);

-- Если всё ок
COMMIT;
-- Если что-то не так
ROLLBACK;

Лимит на UPDATE/DELETE

-- Удалять батчами по 1000 строк (безопаснее для больших таблиц)
DELETE FROM logs
WHERE id IN (
    SELECT id FROM logs
    WHERE created_at < NOW() - INTERVAL '90 days'
    LIMIT 1000
);
-- Повторять в цикле

Итог

КомандаНазначениеКлючевые особенности
INSERTДобавить строкиRETURNING, ON CONFLICT
UPDATEИзменить строкиFROM для JOIN, RETURNING
DELETEУдалить строкиUSING для JOIN, RETURNING
TRUNCATEОчистить таблицуБыстро, без WHERE

Всегда используйте WHERE в UPDATE и DELETE. Тестируйте через SELECT перед запуском. Заворачивайте опасные операции в транзакцию.

Похожие статьи

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

Тренажёр с реальными задачами — бесплатно и без регистрации

Открыть тренажёр →