我有2个表(ItemType和Item)。 ItemType具有布尔值hasSize
,而Item表具有size
列。
我想强制执行size
仅在hasSize
布尔值= true时设置。
我已经看到了讨论使用触发器进行验证的解决方案。但是,这不能提供与约束相同的鲁棒性。
这是非理想模式设计的问题,还是对此有合适的解决方案?
答案 0 :(得分:3)
检查约束不能引用数据库中的其他表,并且触发器是围绕此限制的有效策略。我认为您的架构设计不是很糟糕。某些类型的项目相当大,并且这些信息属于item_type
表中。
这是一个不使用触发器的替代解决方案,在这里我使用WITH CHECK OPTION
修饰符对UPDATABLE VIEW施加了检查约束,以防止在基础表中修改数据。 请注意,这仅在通过视图插入或更新数据时才有效。您必须授予适当的权限,以便应用程序角色无法直接修改item
表,但可以通过UPDATABLE VIEW
这是一个自包含脚本,其中包含示例数据和示例插入失败以及插入成功
-- set up dummy `item_type` table
CREATE TABLE item_type (
id int primary key generated by default as identity,
has_size bool,
name text not null unique
);
INSERT INTO item_type
(has_size, name)
VALUES
(true, 'sizable type')
, (false, 'unisize');
-- set up dummy `item` table
CREATE TABLE item (
id int primary key generated by default as identity,
type_id int references item_type(id),
name text NOT NULL,
size text
);
INSERT INTO item
(type_id, name, size)
VALUES
(1, 'worlds best dad tee shirt', 'M')
, (2, 'usb key', NULL);
-- create updatable check view.
CREATE VIEW item_view AS
SELECT item.*
FROM item
WHERE EXISTS (
SELECT
FROM item_type
WHERE item.type_id = item_type.id
AND (item.size IS NOT NULL) = item_type.has_size
)
WITH CASCADED CHECK OPTION;
-- ^^^
-- i could have alternately used local
-- instead of cascaded
此插入将失败
INSERT INTO item_view
(type_id, name, size)
VALUES
(2, 'dad socks', 'M');
它会产生以下错误:
ERROR: new row violates check option for view "item_view"
DETAIL: Failing row contains (3, 2, dad socks, M).
此插入将成功
INSERT INTO item_view
(type_id, name, size)
VALUES
(2, 'dad socks', NULL);
SELECT * FROM item
按预期返回以下结果
id | type_id | name | size
----+---------+---------------------------+------
1 | 1 | worlds best dad tee shirt | M
2 | 2 | usb key |
4 | 2 | dad socks |
答案 1 :(得分:2)
是的,我将此称为设计问题。通常,可以通过 not 存储hassize
来避免此类问题,因为可以在查询数据库时派生它。
有时必须存储冗余数据以提高性能,但随后您必须付出可能担心的潜在不一致的代价。
如果您无法避免持续使用hassize
,那么使用触发器通常是最好的解决方案。尝试通过减少并发(例如锁或可序列化的隔离)来避免异常。