我想将父实体的子实体的某个属性的总和约束到该父实体的某个属性。我想使用PostgreSQL并且不使用触发器。以下是一个例子;
假设我们有一个带有卷属性的包。我们想用更小的盒子填充它,它们有自己的体积属性。箱子里所有箱子的体积总和不能大于箱子的体积。
我想到的想法是这样的:
CREATE TABLE crates (
crate_id int NOT NULL,
crate_volume int NOT NULL,
crate_volume_used int NOT NULL DEFAULT 0,
CONSTRAINT crates_pkey PRIMARY KEY (crate_id),
CONSTRAINT ukey_for_fkey_ref_from_boxes
UNIQUE (crate_id, crate_volume, crate_volume_used),
CONSTRAINT crate_volume_used_cannot_be_greater_than_crate_volume
CHECK (crate_volume_used <= crate_volume),
CONSTRAINT crate_volume_must_be_positive CHECK (crate_volume >= 0)
);
CREATE TABLE boxes (
box_id int NOT NULL,
box_volume int NOT NULL,
crate_id int NOT NULL,
crate_volume int NOT NULL,
crate_volume_used int NOT NULL,
id_of_previous_box int,
previous_sum_of_volumes_of_boxes int,
current_sum_of_volumes_of_boxes int NOT NULL,
id_of_next_box int,
CONSTRAINT boxes_pkey PRIMARY KEY (box_id),
CONSTRAINT box_volume_must_be_positive CHECK (box_volume >= 0),
CONSTRAINT crate_fkey FOREIGN KEY (crate_id, crate_volume, crate_volume_used)
REFERENCES crates (crate_id, crate_volume, crate_volume_used) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED,
CONSTRAINT previous_box_self_ref_fkey FOREIGN KEY (id_of_previous_box, previous_sum_of_volumes_of_boxes)
REFERENCES boxes (box_id, current_sum_of_volumes_of_boxes) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED,
CONSTRAINT ukey_for_previous_box_self_ref_fkey UNIQUE (box_id, current_sum_of_volumes_of_boxes),
CONSTRAINT previous_box_self_ref_fkey_validity UNIQUE (crate_id, id_of_previous_box),
CONSTRAINT next_box_self_ref_fkey FOREIGN KEY (id_of_next_box)
REFERENCES boxes (box_id) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED,
CONSTRAINT next_box_self_ref_fkey_validity UNIQUE (crate_id, id_of_next_box),
CONSTRAINT self_ref_key_integrity CHECK (
(id_of_previous_box IS NULL AND previous_sum_of_volumes_of_boxes IS NULL) OR
(id_of_previous_box IS NOT NULL AND previous_sum_of_volumes_of_boxes IS NOT NULL)
),
CONSTRAINT sum_of_volumes_of_boxes_check1 CHECK (current_sum_of_volumes_of_boxes <= crate_volume),
CONSTRAINT sum_of_volumes_of_boxes_check2 CHECK (
(previous_sum_of_volumes_of_boxes IS NULL AND current_sum_of_volumes_of_boxes=box_volume) OR
(previous_sum_of_volumes_of_boxes IS NOT NULL AND current_sum_of_volumes_of_boxes=box_volume+previous_sum_of_volumes_of_boxes)
),
CONSTRAINT crate_volume_used_check CHECK (
(id_of_next_box IS NULL AND crate_volume_used=current_sum_of_volumes_of_boxes) OR
(id_of_next_box IS NOT NULL)
)
);
CREATE UNIQUE INDEX single_first_box ON boxes (crate_id) WHERE id_of_previous_box IS NULL;
CREATE UNIQUE INDEX single_last_box ON boxes (crate_id) WHERE id_of_next_box IS NULL;
我的问题是,如果这是一种方法,如果有更好的(更少混淆,更优化等)这样做的方式。或者我应该坚持使用触发器?
提前致谢。
答案 0 :(得分:3)
我的问题是,如果有更好的(更少混淆,更优化等)这样做的方式。
是的,总而言之:使用触发器......
不,不要介意你不想使用它。在这里使用触发器;没有ifs,没有buts。
扩展我和其他人之前发表的评论:
您正在做的事情相当于编写一个验证sum(boxes.volume) <= crate.volume
的约束触发器。它只是以一种非常非常的混合方式(通过检查约束和唯一键和外键伪装成聚合函数),并在您的应用程序中进行相关计算。
当两次并发更新试图影响同一个箱子时,您避免使用正版触发器的唯一成就将是错误。所有这一切,都是以维护不必要的唯一索引和外键为代价的。
当然,你最终会解决部分或全部这些问题,并通过使外键可以延期,添加锁,yada yada来进一步完善你的“实现”。但最终,你基本上做的就是写一个效率极低的聚合函数。
所以使用触发器。使用框上的after触发器在crates中维护current_volume列,并使用crates上的简单check()约束强制执行检查。或者在框上添加约束触发器,以直接强制执行检查。
如果您需要更有说服力,只需考虑您正在创建的开销。真。冷静地看一下它:不是使用触发器(如果是这样)在crates中维护一个卷列,而是维护不少于六个字段绝对没有超出你的约束的目的,以及许多无用的独特索引和与它们相关的外键约束,当我试图枚举它时,我真正失去了数。并检查对它们的约束。这些东西在存储和写入性能方面都有所增加。