元数据数据库设计

时间:2014-03-19 13:47:45

标签: sql-server database-design

我正在尝试将有关文档的元数据存储到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

我可以这么说:

  • 给我所有姓名='John'
  • 的文件
  • 给我所有的文件名称='John'和CustomerId = 5
  • 向我提供InserDate ='2014-01-01'和City ='London'
  • 的所有文件

这将是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年的数据....因此,这些表格将是大而且好的索引是性能的必要条件。

最诚挚的问候 马格努斯

2 个答案:

答案 0 :(得分:1)

SQL Server 2008+提供了三种相关功能来处理此类情况:

  • Sparse Columns,即使一次只使用一个子集,也可以定义数百列
  • Column Sets允许您对这些列进行分组并将其视为一个组
  • Filtered indexes只能为实际包含值的行编制索引。

这些功能允许您使用或多或少的普通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
 )

可以实现具有更强大密钥的更紧密模型,以确保在数据库级别无法将属性分配给具有“不适当”类型的文档。

查询必须使用连接,但(可能)只有DocumentsDocumentAttributes表格会很大。 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解决方案可能就是你想要的。

(请注意,与我原来的帖子一样,我没有测试过这段代码,所以这里可能存在拼写错误或微妙 - 或者不那么微妙的错误。)