Каскадное удаление за один запрос



Нет, речь не про on delete cascade



Во многих случаях мы не хотим использовать внешние ключи с каскадным удалением, как минимум потому что они существенно повышают цену ошибки - когда случайно удаляется одна сущность и каскадно пол базы



Но как тогда быть, если у нас достаточно глубокая иерархия сущностей, например



a <- b <- c



и хочется по a_id удалить все связанные b и c?



create table a (

id bigserial not null primary key

);



create table b (

id bigserial not null primary key,

a_id bigint not null references a (id)

);



create table c (

id bigserial not null primary key,

b_id bigint not null references b (id)

);




Наивное решение:



- поселектить из b по a_id

- удалить записи из c по полученным b_id

- удалить записи из b

- удалить записи из a



В итоге имеем 4 раудтрипа до базы и сложную логику, которая с ростом иерархии будет выглядить еще более громоздкой



Решение через CTE:



with a_deleted as (

delete from a where id = <a_id> returning *

), b_deleted as (

delete from b where a_id in (select id from a_deleted) returning *

) delete from c where b_id in (select id from b_deleted)




Почему это работает? CTE трактуется как единый стейтмент, соответственно все проверки констрейнтов будут осуществляться только после его полного выполнения



Это нам позволяет удалить все связанные с таблицей a сущности за один раундтрип до базы, и при необходимости легко масштабировать этот подход, просто добавляя новые строки в CTE, если иерархия вырастет



Ставьте 🔥 на этот пост, если нужен рассказ про еще один интересный вариант применения CTE в контексте внешних ключей