设计问题:可过滤的属性,SQL

a p*_*erd 4 mysql sql orm entity-relationship

我的数据库中有两个表,OperationEquipment.操作需要零个或多个属性.但是,属性的​​归属有一些逻辑:

  • 操作Foo需要设备AB
  • 操作Bar不需要任何设备
  • 操作Baz需要设备B和任何一个CD
  • 操作Quux需要设备(AB)和(CD)

在SQL中表示这个的最佳方法是什么?

我相信人们之前已经这样做了,但我不知道从哪里开始.

(FWIW,我的应用程序是用Python和Django构建的.)

更新1:将有大约一千Operation行和大约三十Equipment行.信息以CSV格式提供,类似于上面的描述:Quux, (A & B) | (C & D)

更新2:连词和分离的级别不应太深.这个Quux例子可能是最复杂的,尽管似乎有一个A | (D & E & F)案例.

Bil*_*win 11

想想你如何在OO设计中建模操作:操作将是一个共同超类的子类Operation.每个子类都具有该操作所需的相应设备的强制对象成员.

使用SQL对此进行建模的方法是类表继承.创建一个通用的超级表:

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)
);
Run Code Online (Sandbox Code Playgroud)

然后,对于每种操作类型,定义一个子表,其中包含每个所需设备类型的列.例如,OperationFoo每个equipA和的列都有一个equipB.因为它们都是必需的,所以列是NOT NULL.通过为设备创建Class Table Inheritance超级表,将它们约束为正确的类型.

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)
);
Run Code Online (Sandbox Code Playgroud)

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)
);
Run Code Online (Sandbox Code Playgroud)

表OperationBaz已要求一个设备equipA,然后中的至少一个equipBequipC必须是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)
);
Run Code Online (Sandbox Code Playgroud)

同样在表中,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)
);
Run Code Online (Sandbox Code Playgroud)

这可能看起来很多工作.但是你问过如何在SQL中做到这一点.在SQL中执行此操作的最佳方法是使用声明性约束来为业务规则建模.显然,这要求您在每次创建新操作类型时都创建一个新的子表.当操作和业务规则从未(或几乎从未)改变时,这是最好的.但这可能不符合您的项目要求.大多数人说,"但我需要一个不需要架构改变的解决方案."

大多数开发人员可能不会执行类表继承.更常见的是,它们只是像其他人提到的那样使用一对多表结构,并且仅在应用程序代码中实现业务规则.也就是说,您的应用程序包含的代码只能插入适用于每种操作类型的设备.

依赖于应用程序逻辑的问题在于它可能包含错误并且可能插入不满足业务规则的数据.类表继承的优点在于,通过精心设计的约束,RDBMS可以一致地强制执行数据完整性.您可以确保数据库确实无法存储不正确的数据.

但这也可能是限制性的,例如,如果您的业务规则发生变化并且您需要调整数据.这种情况下的常见解决方案是编写脚本以转储所有数据,更改模式,然后以现在允许的形式重新加载数据(Extract,Transform和Load = ETL).

所以你必须决定:你想在应用层或数据库架构层中编码吗?使用这两种策略都有合理的理由,但无论如何都会变得复杂.


重新评论:您似乎在谈论将表达式存储为数据字段中的字符串.我建议要这样做.数据库用于存储数据,而不是代码.您可以在约束或触发器中执行一些有限的逻辑,但代码属于您的应用程序.

如果您有太多操作要在单独的表中建模,请在应用程序代码中对其进行建模.在数据列中存储表达式并期望SQL使用它们来评估查询就像设计一个大量使用的应用程序eval().