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

The*_*uad 11 database-design eav

我正在构建一个库存数据库来存储企业硬件信息。数据库跟踪的设备范围包括工作站、笔记本电脑、交换机、路由器、移动电话等。我使用设备序列号作为主键。我遇到的问题是这些设备的其他属性各不相同,我不希望库存表中有与其他设备无关的字段。下面是指向部分数据库的 ERD 的链接(未显示某些 FK 关系)。例如,我正在尝试设置它,因此无法将具有工作站设备类型的设备放入电话表中。这似乎需要使用大量触发器来验证设备类型或类,并且随时会跟踪具有不同属性的不同设备的新表;

ERD1

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

小部件示例 ERD

如果属性都适用于我正在存储的项目,那么这种结构会很好用。例如,如果数据库只存储手机,则属性可能是触摸屏、触控板、键盘、4G、3G ……任何东西。在这种情况下,它们都适用于手机。我的数据库将具有主机名、电路类型、电话号码等属性,这些属性仅适用于特定类型的设备。

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

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

/sf/ask/653488391/

/sf/ask/87474271/

/sf/ask/389171121/

/sf/ask/462966171/

/sf/ask/35987801/

Eri*_*ikE 7

超类型/子类型

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

命名

当谈到命名时,在我看来你有两个选择(因为在我看来,只有“ID”的第三个选择是一个强烈的反模式)。要么像在父表中一样调用子类型键 ItemID,要么将其称为子类型名称,例如 DoohickeyID。经过一些思考和一些经验,我主张将其称为 DoohickeyID。这样做的原因是,即使子类型表可能会混淆实际上包含 Items(而不是 Doohickeys)的伪装,但与创建 Doohickey 表的 FK 相比,这是一个小的负面影响,并且列名没有比赛!

EAV 或不 EAV - 我对 EAV 数据库的经验

如果 EAV 是您真正必须做的,那么这就是您必须做的。但如果这不是你必须做的呢?

我构建了一个在企业中使用的 EAV 数据库。谢天谢地,数据集很小(虽然有几十种项目类型),所以性能还不错。但是如果数据库中的项目超过几千条就糟糕了!此外,这些表很难查询。这次经历让我真的希望将来尽可能避免使用 EAV 数据库。

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

尽管进行了所有这些修饰以使 EAV 数据库更易于使用,但我仍然不会在大多数正常查询中使用这些视图,因为它很慢。查询条件不是一直推回Value表的谓词,因此它必须在过滤之前构建该视图类型的所有项目的中间结果集。哎哟。所以我有很多很多查询,有很多很多连接,每个查询都会得到不同的值等等。他们的表现相对较好,但是哎哟!这是一个例子。创建这个(和它的更新触发器)的 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;
Run Code Online (Sandbox Code Playgroud)

这是由另一个存储过程从特殊元数据创建的另一种自动生成的视图,用于帮助查找项之间可以有多个路径的项之间的关系(具体来说:模块->服务器、模块->集群->服务器、模块->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
Run Code Online (Sandbox Code Playgroud)

混合方法

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

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

我要说明一下,我还没有像我在这里建议的那样制作这样一个混合超类型/子类型 EAV 元表装饰的数据库,还没有在现实世界中进行尝试。但是我在使用 EAV 时遇到的问题并不小,如果您的数据库将变得很大并且您希望在没有一些疯狂昂贵的巨大硬件的情况下获得良好的性能,那么做一些事情可能是绝对必要的

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

(注意:是的,这些视图确实是这样格式化的,而 PIVOT 视图确实有更新触发器。:) 如果有人真的对长而复杂的 UPDATE 触发器的可怕细节感兴趣,请告诉我,我会发布给你一个样本。)

还有一个想法

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


Joe*_*own 7

在您的情况下,最好的方法是实体-属性-值 (EAV) 模型的变体。有很多人回避 EAV,因为它在某些方面没有帮助并且经常被误用。但是,EAV 是一种非常适合您的特定要求的解决方案。

您希望针对您的情况包含的变体是将属性从您的实体(即您的库存项目)中抽象出一级。本质上,您希望定义具有属性列表的设备类型。然后定义设备实例,这些实例具有该类型设备应该具有的每个属性的值。

这是 ERD 草图:

ERD

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

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