库存项目具有不同属性时的库存数据库结构


10

我正在建立一个库存数据库来存储企业硬件信息。数据库跟踪的设备范围从工作站,便携式计算机,交换机,路由器,移动电话等开始。我使用设备序列号作为主键。我遇到的问题是这些设备的其他属性各不相同,并且我不希望清单表中的字段与其他设备无关。下面是数据库部分ERD的链接(未显示某些FK关系)。例如,我正在尝试进行设置,因此无法将具有工作站设备类型的设备放入手机表中。这似乎需要使用许多触发器来验证设备类型或类,并且只要有不同属性的不同设备被跟踪,就可以创建新表。

ERD1

我研究了设置可以映射到序列号的属性表,但是这将允许将不适用于设备类型的属性分配给设备,例如,有人可以根据需要将电话号码属性分配给工作站。我在此站点上找到了一个解释,该解释给出了以下结构:

小部件样本ERD

如果属性都适用于我要存储的项目,则此结构将非常有用。例如,如果数据库仅存储手机,则属性可以是诸如触摸屏,触控板,键盘,4G,3G ...之类的东西。在这种情况下,它们都适用于电话。我的数据库将具有诸如主机名,circuitType,phoneNumber之类的属性,这些属性仅适用于特定类型的设备。

我要进行设置,以便仅将适用于给定设备类型的属性分配给该类型的设备。关于如何设置此数据库的任何建议?我不确定这是否是一对一关系的正确使用,还是有更好的方法来做到这一点。预先感谢您抽出宝贵的时间对此进行研究。

这是我阅读的其他一些主题。他们给了我一些很好的见解,但我认为它们并不适用:

/programming/9335548/how-to-structure-database-for-inventory-of-unlike-items

/programming/1249632/database-structure-for-items-with-varying-attributes

/programming/5559587/product-inventory-with-multiple-attributes

/programming/6613802/question-about-setting-up-inventory-database

/programming/514111/how-to-best-represent-items-with-variable-of-attributes-in-a-database

Answers:


6

超类型/子类型

如何看待超型/亚型模式?公用列放在父表中。每个不同的类型都有自己的表,其父级ID作为其自己的PK,并且包含并非所有子类型都共有的唯一列。您可以在父表和子表中都包含一个类型列,以确保每个设备不能超过一个子类型。在子项和父项之间建立FK(项ID,项类型ID)。您可以将FK用于父类型表或子类型表,以在其他位置保持所需的完整性。例如,如果允许使用任何类型的ItemID,则为父表创建FK。如果只能引用SubItemType1,则为该表创建FK。我会将TypeID保留在引用表之外。

命名

在命名方面,正如我所见,您有两种选择(因为在我看来,“ ID”的第三种选择是很强的反模式)。可以像在父表中一样调用子类型项ItemID,也可以将其称为子类型名称,例如DoohickeyID。经过一番思考和一些经验之后,我主张将其命名为DoohickeyID。这样做的原因是,即使实际上可能因包含项目(而不是Doohickeys)的伪装而对子类型表产生混淆,但是与为Doohickey表创建FK且列名不包含时相比,这是一个很小的负面结果比赛!

接受EAV还是不接受EAV-我对EAV数据库的经验

如果EAV是您真正要做的事情,那么这就是您要做的事情。但是,如果不是您必须执行的操作怎么办?

我建立了一个在企业中使用的EAV数据库。谢天谢地,数据集很小(尽管有数十种项目类型),所以性能还不错。但是,如果数据库中包含数千个项目,那就太糟糕了!此外,这些表很难查询。这种经验使我非常希望将来尽可能避免使用EAV数据库。

现在,在数据库中,我创建了一个存储过程,该过程为存在的每个子类型自动构建PIVOTed视图。我可以从AutoDoohickey查询。我关于子类型的元数据有一个“ ShortName”列,其中包含适用于视图名称的对象安全名称。我什至使视图可以更新!不幸的是,您不能在联接上更新它们,但是可以向它们插入一个已经存在的行,该行将转换为UPDATE。不幸的是,您不能仅更新几列,因为无法向VIEW指示您要使用INSERT-to-UPDATE转换过程更新哪些列:NULL值看起来就像“将该列更新为NULL”,即使您想要指示“完全不更新此列”。

尽管进行了所有这样的修饰,以使EAV数据库更易于使用,但我仍然不会在大多数普通查询中使用这些视图,因为它很慢。查询条件不是断言完全推回到Value表中,因此它必须在过滤之前构建该视图类型的所有项目的中间结果集。哎哟。所以我有很多查询,有很多连接,每个查询都获得不同的值,依此类推。他们的表现相对不错,但是哎呀!这是一个例子。创建该SP(及其更新触发器)的SP是一头巨大的野兽,我为此而感到自豪,但是您不想尝试维护它。

CREATE VIEW [dbo].[AutoModule]
AS
--This view is automatically generated by the stored procedure AutoViewCreate
SELECT
   ElementID,
   ElementTypeID,
   Convert(nvarchar(160), [3]) [FullName],
   Convert(nvarchar(1024), [435]) [Descr],
   Convert(nvarchar(255), [439]) [Comment],
   Convert(bit, [438]) [MissionCritical],
   Convert(int, [464]) [SupportGroup],
   Convert(int, [461]) [SupportHours],
   Convert(nvarchar(40), [4]) [Ver],
   Convert(bit, [28744]) [UsesJava],
   Convert(nvarchar(256), [28745]) [JavaVersions],
   Convert(bit, [28746]) [UsesIE],
   Convert(nvarchar(256), [28747]) [IEVersions],
   Convert(bit, [28748]) [UsesAcrobat],
   Convert(nvarchar(256), [28749]) [AcrobatVersions],
   Convert(bit, [28794]) [UsesDotNet],
   Convert(nvarchar(256), [28795]) [DotNetVersions],
   Convert(bit, [512]) [WebApplication],
   Convert(nvarchar(10), [433]) [IFAbbrev],
   Convert(int, [437]) [DataID],
   Convert(nvarchar(1000), [463]) [Notes],
   Convert(nvarchar(512), [523]) [DataDescription],
   Convert(nvarchar(256), [27991]) [SpecialNote],
   Convert(bit, [28932]) [Inactive],
   Convert(int, [29992]) [PatchTestedBy]
FROM (
   SELECT
      E.ElementID + 0 ElementID,
      E.ElementTypeID,
      V.AttrID,
      V.Value
   FROM
      dbo.Element E
      LEFT JOIN dbo.Value V ON E.ElementID = V.ElementID
   WHERE
      EXISTS (
         SELECT *
         FROM dbo.LayoutUsage L
         WHERE
            E.ElementTypeID = L.ElementTypeID
            AND L.AttrLayoutID = 7
      )
) X
PIVOT (
   Max(Value)
   FOR AttrID IN ([3], [435], [439], [438], [464], [461], [4], [28744], [28745], [28746], [28747], [28748], [28749], [28794], [28795], [512], [433], [437], [463], [523], [27991], [28932], [29992])
) P;

这是由另一种存储过程根据特殊元数据创建的另一种自动生成的视图,以帮助查找可以在它们之间具有多个路径的项目之间的关系(具体而言:模块->服务器,模块->群集->服务器,模块-> DBMS- >服务器,模块-> DBMS->集群->服务器):

CREATE VIEW [dbo].[Link_Module_Server]
AS
-- This view is automatically generated by the stored procedure LinkViewCreate
SELECT
   ModuleID = A.ElementID,
   ServerID = B.ElementID
FROM
   Element A
   INNER JOIN Element B
      ON EXISTS (
         SELECT *
         FROM
            dbo.Element R1
         WHERE
            A.ElementID = R1.ElementID1
            AND B.ElementID = R1.ElementID2
            AND R1.ElementTypeID = 38
      ) OR EXISTS (
         SELECT *
         FROM
            dbo.Element R1
            INNER JOIN dbo.Element R2 ON R1.ElementID2 = R2.ElementID1
         WHERE
            A.ElementID = R1.ElementID1
            AND R1.ElementTypeID = 40
            AND B.ElementID = R2.ElementID2
            AND R2.ElementTypeID = 38
      ) OR EXISTS (
         SELECT *
         FROM
            dbo.Element R1
            INNER JOIN dbo.Element R2 ON R1.ElementID2 = R2.ElementID1
         WHERE
            A.ElementID = R1.ElementID1
            AND R1.ElementTypeID = 38
            AND B.ElementID = R2.ElementID2
            AND R2.ElementTypeID = 3122
      ) OR EXISTS (
         SELECT *
         FROM
            dbo.Element R1
            INNER JOIN dbo.Element R2 ON R1.ElementID2 = R2.ElementID1
            INNER JOIN dbo.Element C2 ON R2.ElementID2 = C2.ElementID
            INNER JOIN dbo.Element R3 ON R2.ElementID2 = R3.ElementID1
         WHERE
            A.ElementID = R1.ElementID1
            AND R1.ElementTypeID = 40
            AND C2.ElementTypeID = 3080
            AND R2.ElementTypeID = 38
            AND B.ElementID = R3.ElementID2
            AND R3.ElementTypeID = 3122
      )
WHERE
   A.ElementTypeID = 9
   AND B.ElementTypeID = 17

混合方法

如果必须具有EAV数据库的某些动态方面,则可以考虑像创建这样的数据库一样创建元数据,而实际上使用超类型/子类型设计模式。是的,您将必须创建新表,并添加,删除和修改列。但是,通过适当的预处理(就像我对EAV数据库的“自动”视图所做的那样),您可以使用真正的类似表的对象。只是,它们不会像我的那么陈旧,查询优化器可以断言下推到基表(请阅读:对它们执行得很好)。在超类型表和子类型表之间只有一个联接。您的应用程序可以设置为读取元数据以发现它应该做什么(或者在某些情况下可以使用自动生成的视图)。

或者,如果您有一组多级子类型,则只需几个联接。多层次的意思是当某些子类型共享公共列而不是全部时,您可能有一个子类型表,这些子表本身就是其他一些表的超类型。例如,如果要存储有关服务器,路由器和打印机的信息,则“ IP设备”的中间子类型可能很有意义。

我要警告的是,我还没有像这样建议的,尚未在现实​​世界中尝试过的混合超类型/亚型EAV修饰修饰数据库。但是我在EAV上遇到的问题并不小,如果数据库很大并且您想要良好的性能而又不需要一些疯狂的昂贵巨型硬件,那么做某件事可能绝对是必须的

我认为,花时间对真正的子类型表进行自动使用/创建/修改将是最好的。专注于数据驱动的灵活性会使EAV听起来如此吸引人(并且相信我,我喜欢当有人向我询问元素类型的新属性时,如何在大约18秒内添加它,他们便可以立即开始在网站上输入数据)。但是灵活性可以通过多种方式实现!预处理是另一种方法。这种功能强大的方法很少有人使用,它具有完全由数据驱动的优点,但具有硬编码的性能。

(注意:是的,这些视图的格式实际上是这样的,而PIVOT的视图确实具有更新触发器。给您的样品。)

还有一个主意

将所有数据放在一个表中。为列指定通用名称,然后出于多种目的重复使用/滥用它们。在这些视图上创建视图以为其赋予明智的名称。当合适的数据类型未使用的列不可用时添加列,并更新您的视图。尽管我对子类型/超类型的讨论不多,但这可能是最好的方法。


我想到了这种设计,其中每个子类型表都有来自父级和罕见字段的PK。我以为可以将类型字段放在父表和每个子类型表中,然后在它们上放一个CHECK约束。我决定避免这种设计,因为无论何时需要跟踪新型设备以及许多一对一关系,它都需要一个新表。显得凌乱和僵化。不过,感谢您的投入。
TheSecretSquad 2012年

我建立了一个在企业中使用的EAV数据库。谢天谢地,数据集很小(尽管有数十种项目类型),所以性能还不错。但是,如果数据库中包含数千个项目,则可能是这样。这种经验使我真的希望将来尽可能避免使用EAV数据库,因为它们很难查询。
ErikE 2012年

而且,在我看来,花时间自动化真正的子类型表的使用/创建/修改将最终是最佳的。
ErikE 2012年

在检查了EAV模式之后,我意识到必须将属性的值共享一种数据类型(在这种情况下为所有字符串)。此外,查询EAV设置也很麻烦。父类型/子类型看起来更好。现在我的问题是,某些表仅允许特定的设备类型。我是否通过在每个表中放置设备类ID(电话,计算机,路由器)并在该字段上放置检查约束来进行验证,还是将其从子类型表中排除并在每个表上使用触发器?请参阅ERD3以供参考。
TheSecretSquad 2012年

1
对于查询EAV数据,通常要为要查询的数据建立关系表的数据集市,然后使用一些脚本填充它们。这些查询将运行得更快,但仅针对您放入数据集市的数据,并且该设置需要进行一些规划。
FrustratedWithFormsDesigner 2012年

6

在您的情况下,最好的方法是对实体属性值(EAV)模型进行更改。有很多人回避EAV,因为它在某些方面无济于事,并且经常被滥用。但是,EAV是一种可以满足您的特定要求的解决方案。

您要针对您的情况包括的变体是将属性从您的实体(即,库存物料)上移一个级别。本质上,您想定义具有属性列表的设备类型。然后,定义设备实例,该实例具有该类型的设备应该具有的每个属性的值。

这是ERD草图:

ERD

DEVICE_ATTRIBUTE包含每种通用属性类型的值。 DEVICE_TYPE定义适用于给定类型设备的通用属性列表(这些是TYPICAL_DEVICE_ATTRIBUTEs

这使您可以控制需要为设备填写哪些属性,同时让不同类型的设备具有不同的属性列表。通过将设备的属性与另一个设备对齐,还可以使您轻松比较设备。


这看起来与ssmusoke建议的内容相似。我根据他的建议更改了ERD,看起来与您的建议相符。请随时访问http://www.dividegraphics.com/ERD2.jpg查看新的RD,并提供任何反馈。
TheSecretSquad 2012年

@reallythecrash-您是正确的,我建议使用与ssmusoke相同的基本方法,我只是在回答问题上采用了不同的方法,希望能够更轻松地理解模型的结构以及使用EAV的原理,许多人(不公平地)谴责这是一种反模式。
乔尔·布朗

经过一些研究,我了解了为什么人们会认为EAV是一种反模式。我认为使用EAV来存储数据很简单,但是对于查询和维护数据类型尤其复杂。我认为这是一种目的狭窄的模式,应该由经验丰富的开发人员使用,他们可以正确地实现它,即不是我。我可能会选择超类型/子类型范例。
TheSecretSquad 2012年

@JoelBrown-您使用什么软件来绘制该图?看起来很酷。
维达尔

@Vidar-我将Visio与ERD smartshapes一起使用,该形状是我使用James Martin视觉惯例创建的,并使用粗略的自定义线条图案绘制。我发现这是用于快速/起草数据模型的好工具。当图表过于正式时,它可能会使某些人认为它已经完成,因此,进行一些粗略的设计有助于防止人们就数据模型的牢固性/完成性得出结论。
乔尔·布朗

1
  1. 总体方法如下:

a)实体-属性-值模型方法,用于将不同设备的属性处理为设备类型。每种设备类型都有一个属性列表,您可以跟踪其值

b)对于每种设备类型,您通过与单个设备相对应的序列号跟踪库存详细信息。

  1. 因此,您将得到以下表格:

a)属性-定义所有设备(此表中的所有内容)列的属性:id,名称,描述

b)项目属性-定义特定设备的允许属性-itemid,attributeid

c)项目定义-定义一个项目,例如Black Berry Torch 4500,Iphone 4S,Iphone 3S等-ID,名称,描述,categoryid(如果要添加类别,例如手机,开关等)

d)设备-单个设备-id,itemid,inventorydate,deactivatedate,serialnumber ...(基本上是设备的所有其他属性)

如果要跟踪有关设备事务处理的任何其他信息,则可以根据需要添加更多链接到设备的表。


谢谢您的意见。这与我要寻找的内容一致,但我只是不知道该怎么做。我更改了ERD以反映您的规格。为每种设备类型输入所有允许的属性似乎需要更多的工作,但是看起来它提供了最大的灵活性。我将制作一个小型原型,以查看它是否按照我认为的方式工作。再次感谢。如果您想看一下,我上传了带有更改的ERD,并让我知道我的工作是否正确。http://www.dividegraphics.com/ERD2.jpg
TheSecretSquad 2012年

是的,您的方向正确。
Stephen Senkomago Musoke 2012年

EAV将提供很大的灵活性,但您还可以使用更多的元数据来保持其正常运行。
FrustratedWithFormsDesigner 2012年

@FrustratedWithFormsDesigner似乎是不可避免的,当系统存储产品种类繁多,手机,交换机,PC,笔记本电脑等...优于更多的表,我会说更多的元数据
斯蒂芬Senkomago穆索凯

1
@ssmusoke:同意,但是我想强调这一点,因为我已经看到人们未能意识到元数据的重要性,然后他们的EAV实现成为一场噩梦。
FrustratedWithFormsDesigner 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.