小编典典

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

sql

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

  • 操作Foo需要设备AB
  • 操作Bar无需任何设备
  • 操作Baz需要设备BC或者D
  • 操作Quux需要设备(AB)和(CD

用SQL表示这一点的最佳方法是什么?

我敢肯定人们以前已经做过,但是我不知道从哪里开始。

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

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

更新2: 连词和析取词的层次不应该太深。该Quux示例可能是最复杂的,尽管似乎有一个A | (D & E & F)案例。


阅读 146

收藏
2021-04-17

共1个答案

小编典典

考虑一下如何在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)
);

然后针对每种操作类型,为每个所需的设备类型定义一个带有一列的子表。例如,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)
);

表OperationBaz具有一台必需的设备equipA,然后至少有一个,equipB并且equipC必须是NOT NULLCHECK为此使用约束:

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和Load
= ETL
)重新加载数据。

因此,您必须决定:是否要在应用程序层或数据库架构层中对此进行编码?使用这两种策略都是有正当理由的,但这两种方法都会很复杂。


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

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

2021-04-17