我正在尝试将有关文档的元数据存储到SQL Server中。该文档存储在文档存档中,并返回一个标识符,以便我可以通过要求存档按标识符获取文档来获取该文档。
我们的用户希望能够根据不同的元数据搜索此文档。元数据可以是1个属性或5个,具体取决于文档类型,用户应该能够从管理站点创建新的文档类型。
我可以在这看到两个解决方案。一个是每个文档类型获取它自己的元数据表,其中所有元数据属性都是预定义的,如果应该添加一个,则需要创建一个新列。如果创建了新的文档类型,则需要创建新的元数据表。我们的DBA会因为这样的解决方案而烦恼,我也看到索引存在问题。因为如果文档类型具有5个不同的元数据属性,则需要在搜索中指定1或4个元数据属性进行搜索。然后我需要为所有可能搜索的不同组合编写索引。
这是一个例子(fictiv)
|documentId | Name | InsertDate | CustomerId | City
| 1 | John | 2014-01-01 | 2 | London
| 2 | John | 2014-01-20 | 5 | New York
| 3 | Able | 2014-01-01 | 10 | Paris
我可以这么说:
这将是3个不同的索引,然后我没有涵盖所有可能的组合。这是不切实际的。
所以我看着邪恶的'EAV'(反)模式。
因此,不是将元数据作为列,而是将行作为行。
|documentId | MetaAttribute | MetaValue
| 1 | Name | John
| 1 | InsertDate | 2014-01-01
| 1 | CustomerId | 2
| 1 | City | London
| 2 | Name | John
| 2 | InsertDate | 2014-01-20
| 2 | CustomerId | 5
| 2 | City | New York
| 3 | Name | Able
| 3 | InserDate | 2014-01-01
| 3 | CustomerId | 10
| 3 | City | Paris
这里创建一个MetaAttribute och metaValue索引很简单,并且它已被覆盖。如果创建了新的文档类型,则可以使用该文档类型将新元数据创建到MetaAttributeTable(包含不同文档类型的所有MetaAttribute)。因此,如果添加了新的文档类型或者将新属性添加到文档类型,则无需创建新表或库。相反,所有MetaValues都是字符串:(并且用于查找文档ID的SQL查询有点复杂。
这就是我想到的。 (在此示例中,MetaAttribute是一个字符串,但它将是MetaAttribute表的ID)
SELECT * FROM [Document]
WHERE ID IN (SELECT documentId FROM [MetaData]
WHERE ((MetaAttribute = 'Name' AND MetaValue = 'John')
OR (MetaAttribute = 'CustomerId' and MetaValue = '5'))
GROUP BY [documentId]
HAVING Count(1) = 2)
在这里,我需要询问Name ='John'和CustomerId = 5.我是通过查找所有具有Name ='John'和CustomerId ='5'的记录以及在documentId上的Group itd和count number来做到的。组中的项目。如果我得到2,则对于此搜索,Name ='John'和CustomerId ='5'都为真。返回documentId并使用它来检索有关文档的信息,例如文档存档存储ID。
应该有一个更好的SQL语句,不是吗?
所以我的问题是。有没有比这些更好的方法2. EAV模式是如此糟糕,我应该坚持第一个approche并有一个吓坏了DBA和“千万个索引”
我们正在讨论一个系统,每月将有大约10到2千万条新记录,并且包含至少3年的数据....因此,这些表格将是大而且好的索引是性能的必要条件。
最诚挚的问候 马格努斯
答案 0 :(得分:1)
SQL Server 2008+提供了三种相关功能来处理此类情况:
这些功能允许您使用或多或少的普通SQL语句来处理所有元数据列。
这些功能专门用于解决EAV /元数据方案。
修改强>
如果您拥有一组始终填充的有限属性,则无需使用稀疏列或EAV反模式。
您可以像平常一样创建表,并添加索引以优化您遇到的实际工作负载。某些类型的查询的发生频率远高于其他类型,SQL Server的Index调优顾问可以根据使用SQL Server的Profiler捕获的跟踪建议使用的索引和统计信息。
很可能只有一部分列会加速搜索,其余部分可以作为include
列添加到索引中。
全文搜索
更强大的选择是使用SQL Server的Full Text Search。这将允许您使用任意属性执行查询。这是另一种使用文档/内容管理系统,ERP和CRM来处理任意属性的技术。
使用FTS,您只需指定要包含在一个FTS索引中的列,而不必为每个属性创建单独的索引。
您可以在SELECT查询中使用FTS谓词,如下所示:
SELECT Name, ListPrice
FROM Production.Product
WHERE ListPrice = 80.99
AND CONTAINS(Name, 'Mountain')
这可以导致更简单的查询(您只需编写修改后的选择)和管理(无需担心索引中的列顺序,只需要管理一个FTS索引)
答案 1 :(得分:1)
如果您拥有无界属性,EAV模型很有吸引力 - 也就是说,任何人都可以将任何内容设置为属性。但是,从您的描述中可以看出情况并非如此 - 可能的文档属性来自已知且相当有限的集合。如果是这种情况,则例行规范化建议如下:
-- One per document
CREATE TABLE Document
(
DocumentId -- primary key
,DocumentType
,<etc>
)
-- One per "type" of document
CREATE TABLE DocumentType
(
DocumentTypeId -- pirmary key
,Name
)
-- One per possible document attribute.
-- Note that multiple document types can reference the same attribute
CREATE TABLE DocumentAttributes
(
AttributeId -- primary key
,Name
)
-- This lists which attributes are used by a given type
CREATE TABLE DocumentTypeAttributes
(
DocumentTypeId
,AttributeId
-- compound primary key on both columns
-- foeign keys on both columns
)
-- This contains the final association of document and attributes
CREATE TABLE DocumentAttributeValues
(
DocumentId
,AttributeId
,Value
-- compound primary key on DocumentId, AttributeId
-- foeign keys on both columns ot their respective parent tables
)
可以实现具有更强大密钥的更紧密模型,以确保在数据库级别无法将属性分配给具有“不适当”类型的文档。
查询必须使用连接,但(可能)只有Documents
和DocumentAttributes
表格会很大。 on(AttributeId + Value)上的索引便于按属性类型进行查找,并且根据基数,(Value + AttributeId)上的索引可以使搜索特定属性非常有效。
(编辑)
哦,聪明,我创建了两个同名的表。我已将最后一个重命名为DocumentAttributeValues。 (免费建议显然值得你付出的代价!)
这显示了这些系统在SQL中的难度,因为您必须单独“查找”这两个属性。从好的方面来说,你不必担心“这种类型是否适用于此文档”,因为这些规则在加载数据时已经应用(更好)。两个例子:
这个在连接中拼出一切,因此我认为它可能比下一个更糟糕:
-- Top-down
SELECT do.DocumentId
from Documents do
inner join DocumentAttributes da1
on da.Name = 'Name'
inner join DocumentAttributeValues dav1
on dav1.AttributeId = da1.AttributeId
and dav1.Value = 'John'
inner join DocumentAttributes da2
on da2.Name = 'CustomerId'
inner join DocumentAttributeValues dav2
on dav2.AttributeId = da2.AttributeId
and dav2.Value = '5'
这个选择属性,然后查找哪些文档包含所有属性。它可能表现更好,因为只需要处理的表少一个:
-- Bottom-up
SELECT xx.DocumentId
from (-- All documents with name "John"
select dav.DocumentId
from DocumentAttributes da
inner join DocumentAttributeValues dav
on dav.AttributeId = da.AttributeId
where da.Name = 'Name'
and dav.Value = 'John'
-- This combines the two sets, with "all" keeping any duplicate entries
union all
-- All documents with CustomerId = "5"
select dav.DocumentId
from DocumentAttributes da
inner join DocumentAttributeValues dav
on dav.AttributeId = da.AttributeId
where da.Name = 'CustomerId'
and dav.Value = '5') xx -- Have to give the subquery an alias
group by xx.DocumentId
having count(*) = 2
虽然可能进一步改进,但您要过滤的属性越多,查询就越丑陋。五个属性max在SQL中可以正常工作,但是如果你有很多属性,NoSQL解决方案可能就是你想要的。
(请注意,与我原来的帖子一样,我没有测试过这段代码,所以这里可能存在拼写错误或微妙 - 或者不那么微妙的错误。)