当子表中有关联数据时,如何在父表中禁用软删除

问题描述

我正在使用数据库表中的二进制检查is_deleted字段对数据进行软删除。我在 country (父级)和 city (子级)表中db。问题是,如果用户尝试软删除在城市表中具有关联数据的国家/地区,则不应允许用户将其删除

国家表中的

个字段:id,name,is_deleted

城市表中的字段:id,is,is_deleted,country_id(外键)

创建示例数据:

insert into country(id,is_deleted) values(1,'United Stated',false)

insert into city(id,country_id) values(1,'Washington',false,1)

尝试从具有关联数据的国家/地区删除

update country set is_deleted = true where id = 1

在软删除时,它应该给我错误或一些消息

解决方法

我能想到的唯一方法是有点尴尬:

/* add a column that mirrors "is_deleted" from "cuntry" */
ALTER TABLE city ADD country_is_deleted boolean DEFAULT FALSE NOT NULL;

/* this will be the target of the foreign key,no other purpose */
ALTER TABLE country ADD UNIQUE (id,is_deleted);

/* make sure "country_id" and "country_is_deleted" match the values
   in "country",and that every soft delete in "country" is replicated */
ALTER TABLE city ADD FOREIGN KEY (country_id,country_is_deleted)
                     REFERENCES country (id,is_deleted)
                     ON UPDATE CASCADE;

/* make sure no country can be deleted when the city is not deleted */
ALTER TABLE city ADD CHECK (country_is_deleted IS FALSE OR is_deleted IS TRUE);
,

@LaurenzAlbe提供了一个非常酷的解决方案,但可能有点尴尬。也许触发器会少一些(尽管我通常不喜欢触发器)。无论哪种方式,您都需要考虑以下几点:当我为已标记为已删除的国家/地区插入城市,而新的城市未标记为已删除时,会发生什么情况?参见fiddle

create or replace
function validate_all_cities_deleted()
  returns trigger 
  language plpgsql
as $$ 
declare nl constant varchar(1) := chr(10);
begin 
    if exists  
       ( select null  
           from city 
          where country_id = new.id
            and not is_deleted
       )
    then 
       raise exception 'Business Rule Violation:% Cannot "delete" % while it has at least 1 non-deleted city.',nl,new.name   
       using 
          hint = format ('Check all cities with with country_id = %s (%s),set their is_deleted = True,then retry',new.id,new.name)
       ;
end if; 

    return new; 
end; 
$$; 

create trigger remove_country_city_bur
  before update of is_deleted 
  on country 
  for each row 
  execute function validate_all_cities_deleted();