要以完全标准化的方式执行此操作,您需要以下内容:
MEANING_ITEM 具有以下索引:
- {SITE_ID, MEANING_NO, ITEM_NO} - 自动为主键创建,并且可以有效地搜索具有给定标签的项目。
- {ITEM_NO, SITE_ID, MEANING_NO} - 可以有效地查询相反的内容:“获取给定项目的标签”。
注意:如果您的 DBMS 支持它,请考虑将此表聚类。聚簇表中的二级索引可能很昂贵(因为它们需要包含整个 PK 的副本并且可能导致双重查找),但在这种情况下,两个索引都包含相同的字段(因此所有“额外”字段都已经在二级index) 并且索引之外没有字段,因此不需要双重查找。通过集群,您只是消除了(无用的)表堆,并且只剩下两个 B 树。
该模型具有以下属性:
- 标签和项目都以特定于站点的方式标识,默认情况下查询特定于站点的标签。如果您想查询标签名称而不考虑站点,只需
SITE_ID = ...
从下面查询中的 WHERE 子句中省略即可。由于 TAG_NAME 处于 TAG PK 的前沿,因此无需额外索引即可有效满足无站点查询。
- 项目不能使用来自“错误”站点的标签进行标记。我们正在使用识别关系,它将 SITE_ID 传播到“菱形”依赖关系的两个边缘,合并到“菱形”的底部(在 MEANING_ITEM 中),这就是为我们提供这种保证的原因。
- 标签同义词被有效地表示(在同一站点内具有相同含义的标签被视为同义词)。如果我们尝试在标签上实现 M:N 自关系,则不会发生各种异常。1
- 由于标签的含义是特定于站点的,因此同义词也是特定于站点的。
- MEANING 表是存储有关标签的附加信息(例如描述)的自然位置,这些信息将由所有同义词共享。
1我们如何处理同义词及物性?如果 A、B 和 C 是同义词,我们是只存储 AB 和 BC 还是同时存储 AC?我们如何执行它?如果我们不强制执行它,我们将需要某种递归查询来选择所有依赖项。每个连接都需要一行,浪费空间和性能。
要获取具有任何给定标签的项目,您需要执行类似于此的查询...
SELECT *
FROM ITEM
WHERE EXISTS (
SELECT *
FROM TAG JOIN MEANING_ITEM ON
TAG.SITE_ID = MEANING_ITEM.SITE_ID
AND TAG.MEANING_NO = MEANING_ITEM.MEANING_NO
WHERE
TAG.SITE_ID = <site id>
AND TAG.NAME IN ( <list of tags> )
AND ITEM.SITE_ID = MEANING_ITEM.SITE_ID
AND ITEM.ITEM_NO = MEANING_ITEM.ITEM_NO
)
注意:我们可以从上面的查询中完全省略 JOIN 到 MEANING - JOIN 所需的所有字段都已经在 TAG 中。
对于具有所有给定标签的项目,您需要一些 COUNTing,类似于以下内容:
SELECT *
FROM ITEM
WHERE <number of tags> = (
SELECT COUNT(DISTINCT TAG_NAME)
FROM TAG JOIN MEANING_ITEM ON
TAG.SITE_ID = MEANING_ITEM.SITE_ID
AND TAG.MEANING_NO = MEANING_ITEM.MEANING_NO
WHERE
TAG.SITE_ID = <site id>
AND TAG.NAME IN ( <list of tags> )
AND ITEM.SITE_ID = MEANING_ITEM.SITE_ID
AND ITEM.ITEM_NO = MEANING_ITEM.ITEM_NO
)
现在这看起来像很多 JOIN-ing,但这个模型非常适合集群(又名索引组织)表和使用索引覆盖查询。
在考虑出于性能原因对这种设计进行非规范化之前,您可能需要接近 StackExchange 的真实数据量(例如,通过删除联结表并限制每个项目的标签数量)。
在任何情况下,在进行任何特定设计之前,都要测量实际的数据量。