超类型/子类型
如何看待超型/亚型模式?公用列放在父表中。每个不同的类型都有自己的表,其父级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的视图确实具有更新触发器。给您的样品。)
还有一个主意
将所有数据放在一个表中。为列指定通用名称,然后出于多种目的重复使用/滥用它们。在这些视图上创建视图以为其赋予明智的名称。当合适的数据类型未使用的列不可用时添加列,并更新您的视图。尽管我对子类型/超类型的讨论不多,但这可能是最好的方法。