DELETE: удаление строк
DELETE: удаление строк
DELETE — третий оператор DML. Он удаляет строки из таблицы по условию. Как и UPDATE, DELETE без WHERE удалит все строки — это необратимое действие. В этом уроке разберём синтаксис, правила безопасности и особенности удаления при наличии внешних ключей.
Базовый синтаксис DELETE
DELETE FROM таблица
WHERE условие;
Пример — удалить конкретного клиента:
DELETE FROM customers
WHERE id = 5;
СУБД найдёт строки, совпадающие с условием id = 5, и удалит их. Если условие выберет несколько строк — удалятся все.
Главное правило: всегда WHERE
-- ОПАСНО: удалит все строки из таблицы customers!
DELETE FROM customers;
-- Безопасно
DELETE FROM customers WHERE id = 5;
DELETE FROM customers без WHERE эквивалентен «очистить таблицу». PostgreSQL выполнит это без предупреждения. Правило то же, что для UPDATE:
- Напишите соответствующий
SELECTс тем жеWHERE - Убедитесь, что он возвращает именно те строки, которые нужно удалить
- Только потом выполните
DELETE
DELETE с RETURNING
Как INSERT и UPDATE, DELETE поддерживает RETURNING:
DELETE FROM sessions
WHERE expires_at < NOW()
RETURNING id, user_id;
Возвращает данные удалённых строк. Полезно для логирования: «какие именно сессии были удалены». Или для отправки уведомлений: «каких пользователей затронула очистка».
Удаление и внешние ключи
Если в другой таблице есть строки, ссылающиеся на удаляемую строку через внешний ключ — PostgreSQL по умолчанию откажет в удалении:
-- customers: id=1 (Анна)
-- orders: customer_id=1 (есть заказы Анны)
DELETE FROM customers WHERE id = 1;
-- ERROR: update or delete on table "customers" violates foreign key constraint
-- on table "orders": key (id)=(1) is still referenced from table "orders"
PostgreSQL защищает целостность данных: нельзя удалить клиента, пока у него есть заказы.
Варианты решения:
- Сначала удалить зависимые строки:
DELETE FROM orders WHERE customer_id = 1;
DELETE FROM customers WHERE id = 1;
-
Каскадное удаление (
ON DELETE CASCADE) — настраивается при создании внешнего ключа. При удалении строки все связанные строки удаляются автоматически. Удобно, но опасно: легко случайно удалить данные, которые хотелось сохранить. -
ON DELETE SET NULL— связанный внешний ключ устанавливается вNULLвместо удаления строки.
Подробнее об ограничениях и ON DELETE — в модуле 9.
Проверь себя: что произойдёт при DELETE FROM customers WHERE id = 1, если ограничение FK настроено с ON DELETE CASCADE?
TRUNCATE: быстрая очистка таблицы
Если нужно удалить все строки из таблицы, есть специальная команда:
TRUNCATE TABLE customers;
TRUNCATE значительно быстрее DELETE FROM customers без WHERE, потому что не проходит по строкам поочерёдно, а освобождает хранилище «одним движением». Но у него есть важное отличие: TRUNCATE сбрасывает счётчик SERIAL/sequence, а DELETE — нет.
DELETE FROM customers; -- удалит строки, id-счётчик продолжится с текущего
TRUNCATE TABLE customers; -- удалит строки, id-счётчик сбросится в 1
TRUNCATE также не поддерживает WHERE — это всегда полная очистка. Для частичного удаления — только DELETE.
DELETE с подзапросом
WHERE в DELETE поддерживает подзапросы:
-- Удалить все заказы клиентов из Казани
DELETE FROM orders
WHERE customer_id IN (
SELECT id FROM customers WHERE city = 'Казань'
);
-- Удалить дубликаты (оставить только строку с минимальным id)
DELETE FROM emails e1
WHERE EXISTS (
SELECT 1
FROM emails e2
WHERE e2.address = e1.address
AND e2.id < e1.id
);
Второй пример — классический паттерн удаления дублирующихся строк.
DELETE vs TRUNCATE vs DROP TABLE
Три способа «убрать данные» — разные по смыслу:
| Команда | Что делает | Обратимо |
|---|---|---|
DELETE WHERE | Удаляет строки по условию | Да (в транзакции) |
DELETE без WHERE | Удаляет все строки, таблица остаётся | Да (в транзакции) |
TRUNCATE | Удаляет все строки быстро, сбрасывает счётчик | В транзакции |
DROP TABLE | Удаляет таблицу целиком | Нет |
DROP TABLE — DDL-команда (модуль 9), она уничтожает не только данные, но и схему таблицы. TRUNCATE и DELETE без WHERE оставляют таблицу — только очищают её.
DELETE ... USING: удаление с данными из другой таблицы
PostgreSQL поддерживает DELETE ... USING — аналог UPDATE ... FROM:
-- Удалить заказы клиентов, помеченных как мошеннические
DELETE FROM orders o
USING customers c
WHERE o.customer_id = c.id
AND c.is_fraudulent = true;
USING позволяет фильтровать удаляемые строки по условию из другой таблицы без подзапроса.
Производительность DELETE
DELETE на больших таблицах работает медленнее, чем кажется. Причины:
- Каждая строка физически не удаляется сразу — помечается как «мёртвая» (dead tuple) и убирается при следующей операции
VACUUM - Индексы тоже обновляются для каждой удалённой строки
- Если есть триггеры
BEFORE DELETEилиAFTER DELETE— они выполняются для каждой строки
На таблицах с миллионами строк и масштабным удалением — рассмотрите TRUNCATE (если нужно удалить всё) или удаление по частям через LIMIT в подзапросе.
Мягкое удаление (soft delete)
В production-системах физическое удаление строк — редкость. Чаще используют «мягкое удаление»: вместо DELETE добавляют флаг is_deleted = true или deleted_at = NOW():
-- Мягкое удаление
UPDATE customers
SET deleted_at = NOW()
WHERE id = 5;
-- Все SELECT исключают удалённых
SELECT * FROM customers
WHERE deleted_at IS NULL;
Преимущества: данные не теряются, можно восстановить, есть история удалений. Недостаток: нужно помнить добавлять WHERE deleted_at IS NULL во все запросы.
Практический пример: очистка устаревших данных
Периодическая очистка — типичная задача обслуживания БД:
-- Удалить истёкшие сессии старше 7 дней
DELETE FROM user_sessions
WHERE expires_at < NOW() - INTERVAL '7 days'
RETURNING user_id, session_id, expires_at;
-- Удалить неподтверждённые регистрации старше 24 часов
DELETE FROM pending_registrations
WHERE created_at < NOW() - INTERVAL '24 hours'
AND confirmed_at IS NULL;
Такие запросы запускаются по расписанию для поддержания чистоты данных.
Опасные комбинации: DELETE без тестирования
На production-базах перед DELETE критически важно тестирование. Потерянные данные без резервной копии — катастрофа. Несколько практических правил:
- Транзакция + проверка: как и для
UPDATE, заверните рискованныйDELETEв транзакцию с промежуточнымSELECT:
BEGIN;
SELECT COUNT(*) FROM logs WHERE created_at < '2023-01-01'; -- сколько строк будет удалено?
DELETE FROM logs WHERE created_at < '2023-01-01' RETURNING id;
-- Если количество правильное — COMMIT, иначе ROLLBACK
ROLLBACK; -- или COMMIT
LIMITвDELETE: PostgreSQL поддерживаетDELETEс подзапросом иLIMITдля удаления по частям:
-- Удалять по 1000 строк за раз
DELETE FROM logs
WHERE id IN (
SELECT id FROM logs WHERE created_at < '2023-01-01' LIMIT 1000
);
Это полезно для больших таблиц — не блокировать всю таблицу одной длинной транзакцией.
- Резервная копия перед масштабным удалением:
-- Сначала скопировать
CREATE TABLE logs_backup_2023 AS SELECT * FROM logs WHERE created_at < '2023-01-01';
-- Потом удалить
DELETE FROM logs WHERE created_at < '2023-01-01';
Краткий итог
DELETE FROM таблица WHERE условие— удаляет строки по условию- Без
WHEREудалятся все строки — всегда проверяйтеSELECTпередDELETE RETURNINGвозвращает данные удалённых строк- При наличии FK — СУБД защищает от удаления родительской строки, если есть дочерние
TRUNCATE— быстрая полная очистка таблицы, сбрасывает счётчик- Мягкое удаление (
deleted_at) — предпочтительный подход в production
Что дальше
Вы освоили DELETE. Последний урок модуля — INSERT INTO ... SELECT и UPSERT: вставка из другой таблицы и элегантная обработка конфликтов при вставке.