想一想如何在 OO 设计中对操作进行建模:操作将是公共超类 Operation 的子类。每个子类都具有该操作所需的相应设备的强制对象成员。
用 SQL 建模的方法是Class Table Inheritance。创建一个通用的超级表:
CREATE TABLE Operation (
operation_id SERIAL PRIMARY KEY,
operation_type CHAR(1) NOT NULL,
UNIQUE KEY (operation_id, operation_type),
FOREIGN KEY (operation_type) REFERENCES OperationTypes(operation_type)
);
然后为每个操作类型定义一个子表,其中包含每个所需设备类型的列。例如,OperationFoo 对 equipA 和 equipB 各有一个列。由于它们都是必需的,因此这些列是NOT NULL。通过为设备创建类表继承超表,将它们约束为正确的类型。
CREATE TABLE OperationFoo (
operation_id INT PRIMARY KEY,
operation_type CHAR(1) NOT NULL CHECK (operation_type = 'F'),
equipA INT NOT NULL,
equipB INT NOT NULL,
FOREIGN KEY (operation_id, operation_type)
REFERENCES Operations(operation_d, operation_type),
FOREIGN KEY (equipA) REFERENCES EquipmentA(equip_id),
FOREIGN KEY (equipB) REFERENCES EquipmentB(equip_id)
);
表OperationBar不需要装备,所以它没有装备栏:
CREATE TABLE OperationBar (
operation_id INT PRIMARY KEY,
operation_type CHAR(1) NOT NULL CHECK (operation_type = 'B'),
FOREIGN KEY (operation_id, operation_type)
REFERENCES Operations(operation_d, operation_type)
);
Table OperationBaz 有一个必需的设备equipA,那么equipB 和equipC 中的至少一个必须是NOT NULL。为此使用CHECK 约束:
CREATE TABLE OperationBaz (
operation_id INT PRIMARY KEY,
operation_type CHAR(1) NOT NULL CHECK (operation_type = 'Z'),
equipA INT NOT NULL,
equipB INT,
equipC INT,
FOREIGN KEY (operation_id, operation_type)
REFERENCES Operations(operation_d, operation_type)
FOREIGN KEY (equipA) REFERENCES EquipmentA(equip_id),
FOREIGN KEY (equipB) REFERENCES EquipmentB(equip_id),
FOREIGN KEY (equipC) REFERENCES EquipmentC(equip_id),
CHECK (COALESCE(equipB, equipC) IS NOT NULL)
);
同样在表OperationQuux 中,您可以使用CHECK 约束来确保每对中至少一个设备资源为非空:
CREATE TABLE OperationQuux (
operation_id INT PRIMARY KEY,
operation_type CHAR(1) NOT NULL CHECK (operation_type = 'Q'),
equipA INT,
equipB INT,
equipC INT,
equipD INT,
FOREIGN KEY (operation_id, operation_type)
REFERENCES Operations(operation_d, operation_type),
FOREIGN KEY (equipA) REFERENCES EquipmentA(equip_id),
FOREIGN KEY (equipB) REFERENCES EquipmentB(equip_id),
FOREIGN KEY (equipC) REFERENCES EquipmentC(equip_id),
FOREIGN KEY (equipD) REFERENCES EquipmentD(equip_id),
CHECK (COALESCE(equipA, equipB) IS NOT NULL AND COALESCE(equipC, equipD) IS NOT NULL)
);
这似乎需要做很多工作。但是你问如何在 SQL 中做到这一点。在 SQL 中执行此操作的最佳方法是使用声明性约束来建模您的业务规则。很显然,这要求你每次创建新的操作类型时都要创建一个新的子表。当操作和业务规则从不(或几乎从不)改变时,这是最好的。但这可能不符合您的项目要求。大多数人说,“但我需要一个不需要改变架构的解决方案。”
大多数开发人员可能不进行类表继承。更常见的是,他们只是像其他人提到的那样使用一对多的表结构,并仅在应用程序代码中实现业务规则。也就是说,您的应用程序包含仅插入适合每种操作类型的设备的代码。
依赖应用程序逻辑的问题在于它可能包含错误并可能插入不满足业务规则的数据。类表继承的优势在于,通过精心设计的约束,RDBMS 始终如一地强制执行数据完整性。您可以确保数据库确实无法存储不正确的数据。
但这也可能会受到限制,例如,如果您的业务规则发生变化并且您需要调整数据。在这种情况下,常见的解决方案是编写一个脚本来转储所有数据,更改您的架构,然后以现在允许的形式 (Extract, Transform, and Load = ETL) 重新加载数据。
所以你必须决定:你想在应用程序层还是在数据库架构层编写代码?使用这两种策略都有合理的理由,但无论哪种方式都会很复杂。
关于您的评论:您似乎在谈论将表达式存储为数据字段中的字符串。我建议反对这样做。数据库用于存储数据,而不是代码。您可以在约束或触发器中执行一些有限的逻辑,但代码属于您的应用程序。
如果您有太多操作要在单独的表中建模,请在应用程序代码中建模。将表达式存储在数据列中并期望 SQL 使用它们来评估查询就像围绕大量使用 eval() 设计应用程序一样。