database - 级联更新到相关对象

标签 database postgresql database-design triggers referential-integrity

我已经将我的数据库和应用程序设置为软删除行。每个表都有一个 is_active 列,其中的值应该是 TRUENULL。我现在遇到的问题是我的数据不同步,因为与 DELETE 语句不同,将值设置为 NULL 不会级联到单独表中的行另一个表中的“已删除”行是外键。

我已经采取措施通过从源表中查找非事件行并手动将其他表中的相关行也设置为非事件来更正数据。我认识到我可以在应用程序级别执行此操作(我在该项目中使用 Django/Python),但我觉得这应该是一个数据库进程。有没有一种方法可以利用类似 PostgreSQL 的 ON UPDATE 约束,以便当一行将 is_active 设置为 NULL 时,单独表中的所有行引用作为外键的更新行也自动将 is_active 设置为 NULL 吗?

这是一个例子:

一个评估有很多提交。如果评估被标记为无效,则与其相关的所有提交也应标记为无效。

最佳答案

在我看来,使用 NULL 表示 bool 值没有意义。 “is_active”的语义表明唯一合理的值是 True 和 False。此外,NULL 会干扰级联更新。

所以我没有使用 NULL。

首先,创建包含主键和主键唯一约束以及“is_active”的“父”表。

create table parent (
  p_id integer primary key,
  other_columns char(1) default 'x',
  is_active boolean not null default true,
  unique (p_id, is_deleted)
);

insert into parent (p_id) values
(1), (2), (3);

创建带有“is_active”列的子表。声明引用父表唯一约束中的列的外键约束(上面 CREATE TABLE 语句的最后一行)和级联更新。

create table child (
  p_id integer not null,
  is_active boolean not null default true,
  foreign key (p_id, is_active) references parent (p_id, is_active) 
    on update cascade,
  some_other_key_col char(1) not null default '!',
  primary key (p_id, some_other_key_col)
);

insert into child (p_id, some_other_key_col) values
(1, 'a'), (1, 'b'), (2, 'a'), (2, 'c'), (2, 'd'), (3, '!');

现在您可以将“parent”设置为 false,这将级联到所有引用表。

update parent 
set is_active = false 
where p_id = 1;

select *
from child
order by p_id;
p_id  is_active  some_other_key_col
--
1     f          a
1     f          b
2     t          a
2     t          c
2     t          d
3     t          !

Soft deletes are a lot simpler and have much better semantics if you implement them as valid-time state tables. FWIW, I think the terms soft delete, undelete, and undo are all misleading in this context, and I think you should avoid them.

PostgreSQL's range data types are particularly useful for this kind of work. I'm using date ranges, but timestamp ranges work the same way.

For this example, I'm treating only "parent" as a valid-time state table. That means that invalidating a particular row (soft deleting a particular row) also invalidates all the rows that reference it through foreign keys. It doesn't matter whether they reference it directly or indirectly.

I'm not implementing soft deletes on "child". I can do that, but I think that would make the essential technique unreasonably hard to understand.

create extension btree_gist; -- Necessary for the kind of exclusion
                             -- constraint below.

create table parent (
  p_id integer not null,
  other_columns char(1) not null default 'x',
  valid_from_to daterange not null,
  primary key (p_id, valid_from_to),
  -- No overlapping date ranges for a given value of p_id.
  exclude using gist (p_id with =, valid_from_to with &&)
);

create table child (
  p_id integer not null,
  valid_from_to daterange not null,
  foreign key (p_id, valid_from_to) references parent on update cascade,

  other_key_columns char(1) not null default 'x',
  primary key (p_id, valid_from_to, other_key_columns),

  other_columns char(1) not null default 'x'
);

插入一些示例数据。在 PostgreSQL 中,daterange 数据类型有一个特殊的值 'infinity'。在此上下文中,这意味着“parent”.“p_id”的值为 1 的行从“2015-01-01”一直有效。

insert into parent values 
(1, 'x', daterange('2015-01-01', 'infinity'));

insert into child values
(1, daterange('2015-01-01', 'infinity'), 'a', 'x'),
(1, daterange('2015-01-01', 'infinity'), 'b', 'y');

此查询将向您显示连接的行。

select *
from parent p 
left join child c 
       on p.p_id = c.p_id 
      and p.valid_from_to = c.valid_from_to;

要使行无效,请更新日期范围。此行(下方)从“2015-01-01”到“2015-01-31”有效。即在 2015-01-31 被软删除。

update parent
set valid_from_to = daterange('2015-01-01', '2015-01-31')
where p_id = 1 and valid_from_to = daterange('2015-01-01', 'infinity');

为 p_id 1 插入一个新的有效行,并取出在 1 月 31 日失效的子行。

insert into parent values (1, 'r', daterange(current_date, 'infinity'));

update child set valid_from_to = daterange(current_date, 'infinity')
where p_id = 1 and valid_from_to = daterange('2015-01-01', '2015-01-31');

Richard T Snodgrass 的开创性著作 用 SQL 开发面向时间的数据库应用程序 可从 his university web page 免费获得。 .

关于database - 级联更新到相关对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29570551/

相关文章:

c# - Npgsql:INSERT 命令不插入

sql - MS SQL Server 查询缓存

参数可能为 NULL 的 SQL 函数

ruby-on-rails - 存储日期和可选的月/日

MySQL ENUM 类型与连接表

sql-server - 将所有表的日期更改为sql server中的日期时间数据类型

database - 如何创建命名空间和设置

php - 使用 php 显示来自 postgresql 的图像(bytea)

postgresql - 估计 Postgres 索引的大小

regex - 高效的 for/each 循环来匹配短语?