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

时间:2010-02-08 05:12:45

标签: sql mysql 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)个案例。

8 个答案:

答案 0 :(得分:10)

考虑如何在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的每个equipAequipB都有一列。由于它们都是必需的,因此列为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)
);

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,然后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)
);

同样在表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()设计应用程序一样。

答案 1 :(得分:2)

我认为您应该在OperationEquipment之间建立一对多关系或多对多关系,具体取决于每个条目是否有一个Equipment条目设备或每种设备类型。

我建议不要将业务逻辑放入数据库模式,因为业务逻辑可能会发生变化,而您不必更改模式作为响应。

答案 2 :(得分:1)

看起来你需要能够将某些设备组合在一起作为连接或分离,并将这些组合在一起......

OperationEquipmentGroup
   id int
   operation_id int 
   is_conjuction bit 

OperationEquipment
   id int
   operation_equipment_group_id int
   equipment_id

如果排序列很重要,您可以添加排序列,也可以添加组表的另一列,以指定组合的方式(仅在排序时才有意义)。但是,通过您的示例,它看起来只是组合在一起。

答案 3 :(得分:0)

由于操作可以有一个或多个设备,因此您应该使用链接表。您的架构将是这样的:

操作

  • ID
  • othercolumn

设备

  • ID
  • othercolumn

Operation_Equipment_Link

  • OperationID
  • EquipmentID

第三个表中的两个字段可以设置为复合主键,因此您不需要第三个字段,并且可以更轻松地将重复项保留在表中。

答案 4 :(得分:0)

除了Nicholai的建议,我解决了类似的问题:

表操作有一个附加字段“OperationType”

表设备有一个附加字段“EquipmentType”

我有一个额外的表“DefaultOperationEquipmentType”,指定每个OperationType需要包含哪个EquipmentType,例如

OperationType  EquipmentType
==============.=============.
Foo_Type       A_Type
Foo_Type       B_Type
Baz_Type       B_Type
Baz_Type       C_Type

我的应用程序不需要像(A或B)那样的复杂条件,因为在我的业务逻辑中,两种替代设备属于同一类型的设备,例如在PC环境中,我可以使用设备鼠标(A)或轨迹球(B),但它们都属于EquipmentType“PointingDevice_Type”

希望有所帮助

答案 5 :(得分:0)

根据我的理解,您希望将设备与操作相关联,以便稍后将业务逻辑应用于此,在这种情况下,您将需要3个表:

操作:

  • ID
  • 名称

设备:

  • ID
  • 名称

Operations_Equipment:

  • equipment_id
  • operation_id
  • 符号

符号是A,B,C等......

如果您的条件如(A & B) | (C & D),您可以轻松了解哪种设备。

答案 6 :(得分:0)

要意识到我没有在野外测试过。话虽这么说,我能看到做出映射的最佳方法是使用非规范化表进行分组。

*(除了比尔的方式,这很难设置,但在正确完成时很有道理)

Operations:
--------------------
Op_ID int not null pk
Op_Name varchar 500

Equipment: 
--------------------
Eq_ID int not null pk
Eq_Name varchar 500
Total_Available int

Group:
--------------------
Group_ID int not null pk
-- Here you have a choice. You can either:
-- Not recommended   
Equip varchar(500) --Stores a list of EQ_ID's {1, 3, 15}
-- Recommended
Eq_ID_1 bit
Eq_1_Total_Required
Eq_ID_2 bit
Eq_2_Total_Required
Eq_ID_3 bit
Eq_3_Total_Required
-- ... etc.

Operations_to_Group_Mapping:
--------------------
Group_ID int not null frk
Op_ID int not null frk

因此,如果X: A | (D & E & F)

Operations:
--------------------
Op_ID    Op_Name
1        X

Equipment: 
--------------------
Eq_ID    Eq_Name    Total_Available
1        A          5
-- ... snip ...
22       D          15
23       E          0
24       F          2

Group:
--------------------
Group_ID    Eq_ID_1    Eq_1_Total_Required -- ... etc. ...
1           TRUE       3
-- ... snip ...
2           FALSE      0

Operations_to_Group_Mapping:
--------------------
Group_ID    Op_ID
1           1
2           1 

答案 7 :(得分:0)

由于厌恶在SQL中放置递归(树)结构,听起来这真的是你正在寻找的。我会使用这样建模的东西:

Operation
----------------
OperationID            PK
RootEquipmentGroupID   FK -> EquipmentGroup.EquipmentGroupID
...

Equipment
----------------
EquipmentID            PK
...

EquipmentGroup
----------------
EquipmentGroupID       PK
LogicalOperator

EquipmentGroupEquipment
----------------
EquipmentGroupID |     (also FK -> EquipmentGroup.EquipmentGroupID)
EntityType       |     PK (all 3 columns)
EntityID         |     (not FK, but references either Equipment.EquipmentID
                        or EquipmentGroup.EquipmentGroupID)

现在我提出了一个可以说是丑陋的架构,请允许我解释一下......

每个设备组可以是and组或or组(由LogicalOperator列指定)。每个组的成员都在EquipmentGroupEquipment表中定义,EntityID引用Equipment.EquipmentID或其他EquipmentGroup.EquipmentGroupID,目标由EntityType中的值决定。这将允许您组成由设备或其他组组成的组。

这将允许您表示像“需要设备A”这样简单的东西,如下所示:

EquipmentGroupID   LogicalOperator
--------------------------------------------
1                  'AND'

EquipmentGroupID   EntityType   EntityID
--------------------------------------------
1                  1            'A'

...一直到你的“A |(D& E& F)”,看起来像这样:

EquipmentGroupID   LogicalOperator
--------------------------------------------
1                  'OR'
2                  'AND'

EquipmentGroupID   EntityType   EntityID
--------------------------------------------
1                  1            'A'
1                  2            2 -- group ID 2
2                  1            'D'
2                  1            'E'
2                  1            'F'

(我意识到我在EntityID列中混合了数据类型;这只是为了让它更清晰。显然你不会在实际实现中这样做)

这也可以让您表示任意复杂性的结构。虽然我意识到你(正确地)不希望overarchitect解决方案,但我认为如果不打破1NF(通过将多个设备组合成一个列),你真的可以用更少的东西逃脱。