如何设计数据库模式以支持使用类别标记?

时间:2008-12-16 23:18:41

标签: sql schema tags foreign-keys entity-attribute-value

我正在尝试Database Design for Tagging这样的内容,除了我的每个代码都被归为类别。

例如,假设我有一个关于车辆的数据库。假设我们实际上并不太了解车辆,因此我们无法指定所有车辆将具有的列。因此,我们将用信息“标记”车辆。

1. manufacture: Mercedes
   model: SLK32 AMG
   convertible: hardtop

2. manufacture: Ford
   model: GT90
   production phase: prototype

3. manufacture: Mazda
   model: MX-5
   convertible: softtop

现在您可以看到所有汽车都标有其制造商和型号,但其他类别并不完全匹配。请注意,汽车只能拥有每个类别中的一个。 IE浏览器。一辆汽车只能有一个制造商。

我想设计一个数据库,以支持搜索所有梅赛德斯,或者能够列出所有制造商。

我目前的设计是这样的:

vehicles
  int vid
  String vin

vehicleTags
  int vid
  int tid

tags
  int tid
  String tag
  int cid

categories
  int cid
  String category

我有所有正确的主键和外键,除了我无法处理每辆车只能有一个制造商的情况。或者我可以吗?

我可以在vehicleTags中为复合主键添加外键约束吗? IE浏览器。我是否可以添加一个约束,使得复合主键(vid,tid)只能在vehicleTags中没有行时才添加到vehicleTags,这样对于同一个vid,还没有一个tid在同样的cid?

我的猜测是否定的。我认为这个问题的解决方案是向vehicleTags添加一个cid列,并创建新的复合主键(vid,cid)。它看起来像是:

vehicleTags
  int vid
  int cid
  int tid

这可以防止汽车有两个制造商,但现在我已经复制了tid在cid中的信息。

我的架构应该是什么?

Tom在上一个问题How do you do many to many table outer joins?

中的数据库架构中发现了这个问题

修改
我知道在示例中制造应该真的是车辆表中的一列,但是假设你不能这样做。这个例子只是一个例子。

6 个答案:

答案 0 :(得分:13)

这是Entity-Attribute-Value设计的另一个变体。

更易识别的EAV表如下所示:

CREATE TABLE vehicleEAV (
  vid        INTEGER,
  attr_name  VARCHAR(20),
  attr_value VARCHAR(100),
  PRIMARY KEY (vid, attr_name),
  FOREIGN KEY (vid) REFERENCES vehicles (vid)
);

有些人强制attr_name引用预定义属性名称的查找表,以限制混乱。

您所做的只是将EAV表分布在三个表上,但不会改善元数据的顺序:

CREATE TABLE vehicleTag (
  vid         INTEGER,
  cid         INTEGER,
  tid         INTEGER,
  PRIMARY KEY (vid, cid),
  FOREIGN KEY (vid) REFERENCES vehicles(vid),
  FOREIGN KEY (cid) REFERENCES categories(cid),
  FOREIGN KEY (tid) REFERENCES tags(tid)
);

CREATE TABLE categories (
  cid        INTEGER PRIMARY KEY,
  category   VARCHAR(20) -- "attr_name"
);

CREATE TABLE tags (
  tid        INTEGER PRIMARY KEY,
  tag        VARCHAR(100) -- "attr_value"
);

如果您要使用EAV设计,则只需要vehicleTagscategories表。

CREATE TABLE vehicleTag (
  vid         INTEGER,
  cid         INTEGER,     -- reference to "attr_name" lookup table
  tag         VARCHAR(100, -- "attr_value"
  PRIMARY KEY (vid, cid),
  FOREIGN KEY (vid) REFERENCES vehicles(vid),
  FOREIGN KEY (cid) REFERENCES categories(cid)
);

但请记住,您正在将数据与元数据混合。您将无法将某些约束应用于数据模型。

  • 如何使其中一个类别成为必需类(传统列使用NOT NULL约束)?
  • 如何使用SQL数据类型验证某些标记值?你不能,因为你为每个标签值使用一个长字符串。对于您将来需要的每个标签,这个字符串是否足够长?你无法分辨。
  • 如何将某些标记限制为一组允许值(传统表使用外键查找表)?这是你的“软顶”与“软顶”的例子。但是您无法对tag列进行约束,因为该约束将适用于其他类别的所有其他标记值。您可以有效地将发动机尺寸和油漆颜色限制为“软顶”。

SQL数据库与此模型不兼容。要做到正确是非常困难的,并且查询它变得非常复杂。如果继续使用SQL,则最好按常规方式对表进行建模,每个属性只有一列。如果您需要“子类型”,则为每个子类型(Class-Table Inheritance)定义一个从属表,否则使用Single-Table Inheritance。如果每个实体的属性具有无限的变化,请使用Serialized LOB

为这些流动的非关系数据模型设计的另一种技术是语义数据库,将数据存储在RDF中并使用SPARQL进行查询。一个免费的解决方案是Sesame

答案 1 :(得分:3)

我需要解决这个确切的问题(相同的一般领域和一切 - 汽车零件)。我发现问题的最佳解决方案是使用Lucene / Xapian / Ferret / Sphinx或您喜欢的全文索引器。比SQL提供的性能要好得多。

答案 2 :(得分:1)

您描述的不是标签,标签只是值,它们没有关联的键。 标签通常以字符串列的形式实现,值是一个分隔的值列表。

例如#1,标签字段将包含一个值,例如:

“制造_梅赛德斯,型号_SLK32 AMG,敞篷车_硬顶”

然后,用户通常能够通过一个或多个标签的存在轻松过滤条目。从数据库的角度来看,它本质上是无模式数据。标签有缺点,但它们也避免了使用 EAV 模型带来的极端复杂性。如果您确实需要一个 EAV 模型,那么也可能值得考虑一个包含 JSON 数据的属性字段。查询比较痛苦,但还是没有跨多表查询EAV那么可怕。

答案 3 :(得分:0)

我认为您的解决方案是简单地将制造商列添加到您的车辆表中。这是一个你知道所有车辆都会拥有的属性(即汽车不会自动出现),并且通过在车辆表中将其作为一列,您可以解决每辆车只有一个制造商的问题。此方法适用于您知道将由所有车辆共享的任何属性。然后,您可以为非通用的其他属性实施标记系统。

因此,从您的示例中可以看出车辆表格如下:

vehicle
  vid
  vin
  make
  model

答案 4 :(得分:0)

一种方法是稍微重新考虑您的架构,将标记键从值中标准化:

vehicles
  int vid
  string vin

tags
  int tid
  int cid
  string key

categories
  int cid
  string category

vehicleTags
  int vid
  int tid
  string value

现在您只需要vehicleTags(vid, tid)上的唯一约束。

或者,有一些方法可以创建超出简单外键的约束:根据您的数据库,您是否可以编写自定义约束或插入/更新触发器来强制执行车辆标记唯一性?

答案 5 :(得分:0)

我需要解决这个确切的问题(相同的一般领域和一切 - 汽车零件)。我发现问题的最佳解决方案是使用Lucene / Xapian / Ferret / Sphinx或您喜欢的全文索引器。比SQL提供的性能要好得多。

现在,我几乎从未构建过一个不涉及全文索引器的数据库支持的Web应用程序。这个问题和搜索的一般问题经常出现,无法从工具箱中省略索引器。