我有一张非常标准的人物表,例如:
CREATE TABLE [Contact](
[ContactID] [bigint] IDENTITY(1,1) NOT NULL,
[ContactType] [nvarchar](50) NULL,
[Forename] [nvarchar](60) NULL,
[Surname] [nvarchar](60) NULL,
[Company] [nvarchar](60) NULL
}
Example Data :
01, "Student", "Bob", "Smith", Blank
02, "Staff", "Robert", "Smithe", "Roberts And Sons"
Etc
此表包含所有联系人共有的所有字段。但是,我有一些“类型”的联系,可能有也可能没有一个只针对那种联系类型的字段。例如,如果记录具有“ContactType ='student'”,我想存储一个名为“studentid”的额外字段。有许多不同类型的接触,每个接触的场地要求略有不同。为了在将来的某个点添加到这种情况,每个联系人类型可能会添加额外的字段。
如果我将每个字段添加到联系人表格中,我最终会得到99%的记录不需要的大量字段。所以我打算创建一个这样的第二个表:
CREATE TABLE [ContactMetaData](
[ContactID] [bigint] NOT NULL,
[PropName] [nvarchar](200) NOT NULL,
[PropData] [nvarchar](200) NULL
)
Example Data:
01, "StudentID", "0123456"
01, "CourseName", "IT"
01, "Average", "10"
02, "Ranking", "22"
02, "ProductTypes", "IT Equipment"
ETC
对于每个额外的字段,我只需在该表中添加一条记录,其中包含该字段的名称和值。我可以使用代码来提取这些信息等。
我的问题是
这是最好的方法吗,因为除了每个字段的大桌子之外,我还有另一种方法。鉴于这种方法,可以在许多属性字段中执行复杂的查询,如果是这样,怎么办?例如我如何列出“IT”课程的所有学生,其中“平均”为10个“姓”以“D”开头?
答案 0 :(得分:0)
是的,如果您确实有许多未使用的字段,请采用此方法(称为EAV data structure)。您可以使用普通模型对此结构执行所有查询,只需使用适当的连接来旋转它们。
答案 1 :(得分:0)
您建议的基于属性的方法是合理的,特别是如果需要在运行时创建新的联系人类型和属性而不更改架构。
如果在运行时修复了联系人类型和属性集,则可以考虑为每种类型创建子表。也就是说,创建像 StudentInfo , StaffInfo 等表格。如果你这样做了,你就不再需要 ContactType 字段了。隐含在相应子表中存在的行中。该模型还可以处理有人分为两类的尴尬局面,例如:同时也是员工的学生。
然而,忽略所有这些,让我们看看关于查询属性表的第二个问题。您可以执行此类查询。此查询将回答您的示例:
SELECT *
FROM Contact AS c
INNER JOIN ContactMetaData AS crs
ON crs.ContactId = c.ContactId
AND crs.PropName = 'CourseName'
AND crs.PropData = 'IT'
INNER JOIN ContactMetaData AS av
ON av.ContactId = c.ContactId
AND av.PropName = 'Average'
AND av.PropData = 10
WHERE c.Forename LIKE 'D%'
诀窍是多次加入 ContactMetaData 表,对于您想要测试的每个自定义属性一次。
这种查询方式的一个问题是它几乎肯定必须在运行时生成 - 毕竟,这组属性在运行时是动态的。您可以通过采用不同的方法来表达查询来避免这种情况:
DECLARE @propertyCriteria TABLE (
PropName NVARCHAR(200) NOT NULL,
PropData NVARCHAR(200) NULL
)
INSERT INTO @propertyCriteria VALUES ('CourseName', 'IT')
INSERT INTO @propertyCriteria VALUES ('Average', '10')
SELECT *
FROM Contact AS c
WHERE c.Forename LIKE 'D%'
AND NOT EXISTS (
SELECT *
FROM @propertyCriteria AS crit
LEFT JOIN ContactMetaData AS meta
ON meta.ContactId = c.ContactId
AND meta.PropName = crit.PropName
AND meta.PropData = crit.PropData
WHERE meta.ContactId IS NULL
)
这样做的好处是查询现在在运行时被修复,因为动态属性条件是由插入临时表 @propertyCriteria 的内容确定的。缺点是现在需要临时表,并且查询的执行效果不如内部联接(尽管如果您的数据库中只有50,000条记录,您可能不会注意到差异)。另请注意,第二种方法仅适用于AND在一起的标准。如果你想要OR,解决方案变得更加复杂。
顺便提一下,如果要在属性值和条件中允许NULL,请不要忘记考虑到涉及NULL的比较运算符总是返回false的事实(即NULL = NULL为false,NULL<> ; NULL为false,等等。)