设计数据库来保存不同的元数据信息

Posted

技术标签:

【中文标题】设计数据库来保存不同的元数据信息【英文标题】:designing database to hold different metadata information 【发布时间】:2011-03-15 12:54:36 【问题描述】:

所以我正在尝试设计一个数据库,让我可以将一个产品与多个类别联系起来。这部分我想通了。但我无法解决的是持有不同类型的产品详细信息的问题。

例如,产品可以是一本书(在这种情况下,我需要引用该书的元数据,例如 isbn、作者等),或者它可以是一个商业列表(具有不同的元数据)..

我应该如何解决这个问题?

【问题讨论】:

注意:如果数据库存储有关书籍的信息,那么特定书籍的详细属性将是“数据”而不是“元数据”。元数据将是关于存储机制本身的数据,例如 Book.Title 是一个不可为空的 nvarchar(255)。然而,如果数据存储在一本书中(如年历),那么关于书本身的信息(如 ISBN 等)将是元数据。 :-) 【参考方案1】:

应该输入产品。例如在产品表中包含 type_id,它指向您将支持的产品类别,并让您知道要查询哪些其他表以获取适当的相关属性。

【讨论】:

【参考方案2】:

您可以采用无模式方法:

将元数据作为 JSON 对象保存在 TEXT 列中(或其他序列化,但 JSON 更好,原因很快就会解释)。

这种技术的优点:

    查询更少:一次查询即可获取所有信息,无需“定向”查询(获取元元数据)和连接。

    您可以随时添加/删除您想要的任何属性,无需更改表(这在某些数据库中存在问题,例如mysql锁定表,并且对于大表需要很长时间)

    由于它是 JSON,因此您不需要在后端进行额外处理。您的网页(我假设它是一个 Web 应用程序)只是从您的 Web 服务中读取 JSON,仅此而已,您可以将 JSON 对象与 javascript 一起使用。

问题:

    可能会浪费空间,如果您有 100 本书的同一作者,那么所有书籍都只有 author_id 的作者表更节省空间。

    需要实现索引。由于您的元数据是一个 JSON 对象,因此您没有立即拥有索引。但是为您需要的特定元数据实现特定索引相当容易。比如你想按作者索引,所以你用author_id和item_id创建一个author_idx表,当有人搜索作者时,你可以查找这个表和项目本身。

根据规模,这可能有点过头了。在较小规模的连接上就可以了。

【讨论】:

【参考方案3】:

这称为观察模式。

三个对象,例如

Book
Title = 'Gone with the Wind' 
Author = 'Margaret Mitchell'
ISBN   = '978-1416548898'

Cat
Name = 'Phoebe'
Color = 'Gray'
TailLength = 9 'inch'

Beer Bottle
Volume = 500 'ml'
Color = 'Green'

这就是表格的样子:

Entity
EntityID    Name            Description
   1        'Book'            'To read'
   2        'Cat'             'Fury cat' 
   3        'Beer Bottle'     'To ship beer in'

.

PropertyType
PropertyTypeID   Name        IsTrait         Description
   1            'Height'     'NO'       'For anything that has height' 
   2            'Width'      'NO'       'For anything that has width' 
   3            'Volume'     'NO'       'For things that can have volume'
   4            'Title'      'YES'      'Some stuff has title' 
   5            'Author'     'YES'      'Things can be authored' 
   6            'Color'      'YES'      'Color of things' 
   7            'ISBN'       'YES'      'Books would need this'
   8            'TailLength' 'NO'       'For stuff that has long tails'
   9            'Name'       'YES'      'Name of things'

.

Property
PropertyID   EntityID  PropertyTypeID      
    1           1              4     -- book, title
    2           1              5     -- book, author
    3           1              7     -- book, isbn
    4           2              9     -- cat, name
    5           2              6     -- cat, color
    6           2              8     -- cat, tail length
    7           3              3     -- beer bottle, volume
    8           3              6     -- beer bottle, color

.

Measurement
PropertyID     Unit       Value 
    6          'inch'       9          -- cat, tail length
    7          'ml'        500         -- beer bottle, volume

.

Trait
PropertyID         Value 
    1         'Gone with the Wind'     -- book, title
    2         'Margaret Mitchell'      -- book, author
    3         '978-1416548898'         -- book, isbn
    4         'Phoebe'                 -- cat, name
    5         'Gray'                   -- cat, color
    8         'Green'                  -- beer bottle, color

编辑:

Jeffrey 提出了一个有效的观点(见评论),所以我将扩展答案。

该模型允许动态(即时)创建任意数量的实体 具有任何类型的属性而无需架构更改。然而,这种灵活性是有代价的——存储和搜索比通常的表设计更慢且更复杂。

是时候举个例子了,但首先,为了让事情更简单,我会将模型展平为视图。

create view vModel as 
select 
      e.EntityId
    , x.Name  as PropertyName
    , m.Value as MeasurementValue
    , m.Unit
    , t.Value as TraitValue
from Entity           as e
join Property         as p on p.EntityID       = p.EntityID
join PropertyType     as x on x.PropertyTypeId = p.PropertyTypeId
left join Measurement as m on m.PropertyId     = p.PropertyId
left join Trait       as t on t.PropertyId     = p.PropertyId
;

从评论中使用 Jefferey 的例子

with 
q_00 as ( -- all books
    select EntityID
    from vModel
    where PropertyName = 'object type'
      and TraitValue   = 'book' 
),
q_01 as ( -- all US books
    select EntityID
    from vModel as a
    join q_00   as b on b.EntityID = a.EntityID
    where PropertyName = 'publisher country'
      and TraitValue   = 'US' 
),
q_02 as ( -- all US books published in 2008
    select EntityID
    from vModel as a
    join q_01   as b on b.EntityID = a.EntityID
    where PropertyName     = 'year published'
      and MeasurementValue = 2008 
),
q_03 as ( -- all US books published in 2008 not discontinued
    select EntityID
    from vModel as a
    join q_02   as b on b.EntityID = a.EntityID
    where PropertyName = 'is discontinued'
      and TraitValue   = 'no' 
),
q_04 as ( -- all US books published in 2008 not discontinued that cost less than $50
    select EntityID
    from vModel as a
    join q_03   as b on b.EntityID = a.EntityID
    where PropertyName     = 'price'
      and MeasurementValue < 50 
      and MeasurementUnit  = 'USD'
)
select
      EntityID
    , max(case PropertyName when 'title' than TraitValue else null end) as Title
    , max(case PropertyName when 'ISBN'  than TraitValue else null end) as ISBN
from vModel as a
join q_04   as b on b.EntityID = a.EntityID
group by EntityID ;

这看起来写起来很复杂,但仔细观察你可能会注意到 CTE 中的一个模式。

现在假设我们有一个标准的固定模式设计,其中每个对象属性都有自己的列。 查询类似于:

select EntityID, Title, ISBN
from vModel
WHERE ObjectType       = 'book'
  and PublisherCountry = 'US'
  and YearPublished    = 2008
  and IsDiscontinued   = 'no'
  and Price            < 50
  and Currency         = 'USD'
;

【讨论】:

谢谢,这是一个很好的答案和线索。让我们在这个讨论中添加更多内容。在这种设计中,如何解释快速搜索?我想这需要很多连接?? 这是一个非常糟糕的主意,它会导致进一步的问题。请不要这样做。 您的意思是“fury cat”还是“furry cat”。好吧,也许你有一只猫,就像我前女友曾经养过的那只猫,可以恰当地称为狂暴猫。 我仍然不喜欢这种方法,但您的回答对这次讨论很有帮助。我已将我的反对票改为赞成票。 这是一个 EAV。根据 Bill Karwin amazon.co.uk/SQL-Antipatterns-Programming-Pragmatic-Programmers/… 的 SQL 反模式【参考方案4】:

我不打算回答,但现在接受的答案有一个非常糟糕的主意。永远不应该使用关系数据库来存储简单的属性值对。这会导致很多问题。

解决这个问题的最佳方法是为每种类型创建一个单独的表。

Product
-------
ProductId
Description
Price
(other attributes common to all products)

Book
----
ProductId (foreign key to Product.ProductId)
ISBN
Author
(other attributes related to books)

Electronics
-----------
ProductId (foreign key to Product.ProductId)
BatteriesRequired
etc.

每个表格的每一行都应该代表一个关于现实世界的命题,表格的结构及其约束应该反映所代表的现实。你越接近这个理想,数据就越清晰,报告和以其他方式扩展系统就越容易。它的运行效率也会更高。

【讨论】:

确实,我也喜欢超级类型-子类型——问题是当子类型表的数量达到数千时会发生什么?如何处理动态添加新类型的情况?在这种情况下会推荐什么?原生 XML 存储或 ... 我很想知道您对上述达米尔问题的看法。 @bukzor , @Damir Sudarevic - 事实上,我应该回答这个问题,但我很遗憾没有回答。答案是数据库的设计应该反映已知的真实情况。如果要添加新的“类型”,则只能对这些类型中一致的事物进行关系建模。可能需要某种属性/价值系统,但仅限于那些“软”的东西。基本上,如果某些东西可以由系统用户而不是程序员修改,那么它必须作为数据而不是结构来存储。 我有一个类似的问题,我正在创建一个仓库管理系统,不同的产品有不同的属性。使用您描述的结构,允许网站的管理员用户添加新产品类型是否可以接受? 这是个好主意...除非您有 100 个属性。这就是您摒弃关系数据库概念并进行非规范化的地方。标准做法。即使是最具学术性的数据库书籍也会告诉您非规范化的时间和地点。这是其中之一。【参考方案5】:

在这类问题中,你有三个选择:

    创建一个包含“通用”列的表。例如,如果您同时销售书籍和烤面包机,那么您的烤面包机可能没有 ISBN 和标题,但它们仍然有某种产品标识符和描述。因此,请给字段通用名称,如“product_id”和“description”,对于书籍,product_id 是 ISBN,对于烤面包机,它是制造商的部件号,等等。

当现实世界中的所有实体都以相同的方式处理时(至少在大多数情况下),这很有效,因此即使不是“相同”的数据,至少也必须有类似的数据。当存在真正的功能差异时,这就会崩溃。就像我们计算烤面包机的瓦特 = 伏特 * 安培一样,书籍很可能没有相应的计算。当您开始创建包含书籍页数和烤面包机电压的 pages_volts 字段时,事情已经失控了。

    使用 Damir 建议的属性/值方案。请参阅我对他的帖子的评论,了解其中的利弊。

    我通常建议的是类型/子类型方案。为“产品”创建一个包含类型代码和通用字段的表。然后为每个真正的类型——书籍、烤面包机、猫等等——创建一个连接到产品表的单独表。然后当你需要做书特定的处理时,处理书表。当需要进行泛型处理时,处理产品表。

【讨论】:

这不是个好主意..您不能对此类“混合数据类型”强制执行任何操作...(当烤面包机具有字母数字 ID 时,您不能为 ISBN 使用 INTEGER)...等等 @Mr.P 我绝对同意。我想说这是可能的,但通常是个坏主意。【参考方案6】:

我知道这可能不是您正在寻找的答案,但不幸的是,关系数据库 (SQL) 是建立在结构化预定义模式的概念之上的。您正在尝试将非结构化无模式数据存储在不是为其构建的模型中。是的,你可以捏造它,这样你就可以在技术上存储无限量的元数据,但这很快就会导致很多问题并很快失控。只需看看 Wordpress 以及他们使用这种方法遇到的问题数量,您就会很容易明白为什么这不是一个好主意。

幸运的是,这一直是关系数据库长期存在的问题,这就是为什么开发了使用文档方法的 NoSQL 无模式数据库并且在过去十年中如此受欢迎的原因。这是所有财富 500 强科技公司用来存储不断变化的用户数据的方法,因为它允许单个记录在保留在同一个集合(表)中的同时拥有尽可能多或尽可能少的字段(列)。

因此,我建议研究 MongoDB 等 NoSQL 数据库,并尝试转换为它们,或者将它们与您的关系数据库结合使用。您知道需要具有相同数量的列来表示它们的任何类型的数据都应该存储在 SQL 中,并且您知道记录之间存在差异的任何类型的数据都应该存储在 NoSQL 数据库中。

【讨论】:

以上是关于设计数据库来保存不同的元数据信息的主要内容,如果未能解决你的问题,请参考以下文章

创建将在不同环境中提取有关数据集和表大小的元数据信息的视图

编辑本地媒体文件元数据信息

2021年大数据Hadoop:HDFS的元数据辅助管理

未保存注销仪表板时通过 cmd 运行的元数据库

小知识系列:查询数据库数据的元信息

需要编写一个 sql 脚本来查找 oracle 中任何数据库的元数据详细信息