标记的数据库设计


171

您如何设计数据库以支持以下标记功能:

  • 项目可以具有大量标签
  • 搜索带有给定标签集的所有项目必须快速(这些项目必须具有ALL标记,因此这是AND搜索,而不是OR搜索)
  • 创建/写入项目可能较慢,无法快速查找/阅读

理想情况下,应使用单个SQL语句对(至少)带有一组n个给定标签的所有标签进行查找。由于要搜索的标签数量以及任何项目上的标签数量都是未知的并且可能很高,因此使用JOIN是不切实际的。

有任何想法吗?


到目前为止,感谢您提供所有答案。

但是,如果我没记错的话,给出的答案将显示如何对标签进行“或”搜索。(选择所有具有n个标签中的一个或多个标签的项目)。我正在寻找有效的AND搜索。(选择所有带有n个标签的项目-可能还有更多。)

Answers:


22

关于ANDing:听起来您正在寻找“关系除法”操作。本文以简洁但可理解的方式介绍了关系划分。

关于性能:基于位图的方法听起来似乎很适合这种情况。但是,我不认为“手动”实现位图索引是个好主意,就像digiguru所建议的:每当添加新标签时,听起来情况就很复杂(?),但是某些DBMS(包括Oracle)提供了位图索引,这可能会以某种方式之所以有用,是因为内置的索引系统消除了索引维护的潜在复杂性;此外,提供位图索引的DBMS在执行查询计划时应该能够适当考虑它们。


4
我不得不说答案有点短视,因为使用数据库的位域类型会将您限制为特定位数。这并不意味着每个项目都限于一定数量的标签,而是整个系统中只能有一定数量的唯一标签(通常最多32个或64个)。
Mark Renouf

1
假设3nf实现(Question,Tag,Question_has_Tag)和Question_has_Tag中Tag_id上的位图索引,则每次添加或删除问题时都必须重建位图索引。像这样的查询select * from question q inner join question_has_tag qt where tag_id in (select tag_id from tags where (what we want) minus select tag_id from tags where (what we don't)应该很好,并且可以在中间表上存在正确的b树索引的情况下进行扩展
Adam Musch 2010年

“本文”链接已死。我想读一下:(
mpen 2010年

3
马克:这个看起来不错:simple-talk.com/sql/t-sql-programming / ...这可能是我所提到的那个的重新发布的版本。
Troels Arvin

该文章的网址不再有效
Sebastien H.

77

这是一篇有关标记数据库模式的好文章:

http://howto.philippkeller.com/2005/04/24/Tags-Database-schemas/

以及性能测试:

http://howto.philippkeller.com/2005/06/19/Tagsystems-performance-tests/

请注意,那里的结论是非常特定于MySQL的,MySQL的结论(至少在撰写本文时是在2005年)具有非常差的全文索引特性。


1
我还希望对您如何使用SO实施标记系统有更详细的技术见解。我认为您在播客上说过,您将所有问题的所有标签都放在一栏中,然后即时对其进行序列化/反序列化?我很想了解更多信息,也许还会看到一些代码片段。我一直在四处寻找并找到任何详细信息,在我问关于META的问题之前,您是否已完成此操作的链接?
Marston A.

5
有关Meta的问题具有关于SO模式的一些信息:meta.stackexchange.com/questions/1863/so-database-schema
Barrett

原始链接已死,但我想我找到了它们的新位置。您可能要验证这些是您所指的文章。
布拉德·拉尔森

12
尽管由@Jeff编写,但这本质上仍然是仅链接的答案。
curiousdannii 2015年

13

我不认为直接解决方案会出现问题:项目表,标签表,“标签”交叉表

交叉表上的指标应该足够优化。选择适当的项目将是

SELECT * FROM items WHERE id IN  
    (SELECT DISTINCT item_id FROM item_tag WHERE  
    tag_id = tag1 OR tag_id = tag2 OR ...)  

与标记将是

SELECT * FROM items WHERE  
    EXISTS (SELECT 1 FROM item_tag WHERE id = item_id AND tag_id = tag1)  
    AND EXISTS (SELECT 1 FROM item_tag WHERE id = item_id AND tag_id = tag2)  
    AND ...

诚然,对于大量的比较标签而言,效率不高。如果要在内存中维护标签计数,则可以使查询以不经常使用的标签开始,因此AND序列的计算速度更快。根据要匹配的标签的预期数量以及与它们中的任何一个匹配的期望,这可能是一个好的解决方案,如果要匹配20个标签,并且期望某个随机项将匹配其中的15个,那么这仍然很繁琐在数据库上。


13

我只是想强调一下@Jeff Atwood链接到的文章(http://howto.philippkeller.com/2005/04/24/Tags-Database-schemas/)非常详尽(它讨论了3种不同模式的优点方法),并为AND查询提供了一个很好的解决方案,该查询通常会比到目前为止所提到的要好(即,它没有为每个术语使用相关的子查询)。在评论中也有很多好东西。

ps-每个人都在这里谈论的方法在本文中称为“ Toxi”解决方案。


3
我记得读过这篇很棒的文章,但是不幸的是,现在链接已经消失了。:(是否有人知道它的镜像吗?
localhost

5
链接已死:<
Aaron

6

您可能要尝试使用非严格的数据库解决方案(例如Java Content Repository实现(例如Apache Jackrabbit)),并使用基于Apache Lucene的基础之上构建的搜索引擎。

与本地解决方案相比,具有适当缓存机制的此解决方案可能会产生更好的性能。

但是,我真的不认为在中小型应用程序中,您将需要比以前的文章中提到的规范化数据库更复杂的实现。

编辑:经过澄清,将类似JCR的解决方案与搜索引擎一起使用似乎更具吸引力。从长远来看,这将大大简化您的程序。


5

最简单的方法是创建标签表。
Target_Type-如果要标记多个表
Target-要标记的记录的键-标签
Tag的文本

查询数据将类似于:

Select distinct target from tags   
where tag in ([your list of tags to search for here])  
and target_type = [the table you're searching]

更新
根据您对AND条件的要求,上面的查询将变成这样

select target
from (
  select target, count(*) cnt 
  from tags   
  where tag in ([your list of tags to search for here])
    and target_type = [the table you're searching]
)
where cnt = [number of tags being searched]

1

我第二个@Zizzencs建议您可能想要的不是完全以(R)DB为中心的内容

我以某种方式相信使用简单的nvarchar字段通过适当的缓存/索引存储该标签可能会产生更快的结果。但这就是我。

我已经实现了使用3个表表示之前的多对多关系(项目标签ItemTags)的标记系统,但是我想您将在很多地方处理标签,我可以告诉您,使用3个表必须始终被同时操纵/查询肯定会使您的代码更复杂。

您可能要考虑增加的复杂性是否值得。


0

您将无法避免加入并且仍会被标准化。

我的方法是拥有一个标签表。

 TagId (PK)| TagName (Indexed)

然后,您的项目表中有一个TagXREFID列。

这个TagXREFID列是第3个表的FK,我将其称为TagXREF:

 TagXrefID | ItemID | TagId

因此,获取某项的所有标签将类似于:

SELECT Tags.TagId,Tags.TagName 
     FROM Tags,TagXref 
     WHERE TagXref.TagId = Tags.TagId 
         AND TagXref.ItemID = @ItemID

为了获得标签的所有项目,我将使用以下代码:

SELECT * FROM Items, TagXref
     WHERE TagXref.TagId IN 
          ( SELECT Tags.TagId FROM Tags
                WHERE Tags.TagName = @TagName; )
     AND Items.ItemId = TagXref.ItemId;

要将AND标记串在一起,您可以对上述语句进行一些修改,以添加AND Tags.TagName = @ TagName1 AND Tags.TagName = @ TagName2等...并动态构建查询。


0

我想做的是有许多表代表原始数据,因此在这种情况下,

Items (ID pk, Name, <properties>)
Tags (ID pk, Name)
TagItems (TagID fk, ItemID fk)

这样可以在写入时间上快速运行,并使所有内容正常化,但是您可能还需要注意,对于每个标签,您需要为要与的每个其他标签进行两次表连接,因此读取速度很慢。

一种改善读取的解决方案是通过设置一个存储过程来在命令中创建一个缓存表,该存储过程实质上是创建一个新表来表示扁平化格式的数据...

CachedTagItems(ID, Name, <properties>, tag1, tag2, ... tagN)

然后,您可以考虑“标记项”表需要保持多长时间更新一次(如果它在每次插入中),然后在游标插入事件中调用存储过程。如果这是每小时的任务,则设置一个每小时的任务来运行它。

现在,要变得真正聪明的数据检索,您将需要创建一个存储过程以从标记中获取数据。而不是在大量的case语句中使用嵌套查询,而是要传递一个包含要从数据库中选择的标签列表的单个参数,并返回记录集的Items。使用按位运算符,最好是二进制格式。

以二进制格式,很容易解释。假设有四个标签分配给一个项目,用二进制可以表示

0000

如果将所有四个标签都分配给一个对象,则该对象将如下所示:

1111

如果只是前两个...

1100

这只是在所需列中查找带有1和0的二进制值的情况。使用SQL Server的按位运算符,您可以使用非常简单的查询来检查第一列中是否有1。

检查此链接以了解更多信息


0

用其他人的话来解释一下:技巧不在模式中,而在查询中

实体/标签/标签的幼稚模式是正确的方法。但是,正如您所看到的,目前尚不清楚如何执行带有许多标签的AND查询。

优化该查询的最佳方法将取决于平台,因此,我建议您使用RDBS重新标记您的问题,并将标题更改为“在标记数据库上执行AND查询的最佳方法”之类的内容。

我对MS SQL有一些建议,但如果您使用的不是平台,请尽量避免。


6
您可能不应该避免透露某些技术,因为其他尝试在此问题领域工作的人可能实际上正在使用该技术并且会从中受益。
布赖恩·里贝因

0

上述答案的一种变体是采用标签ID,对其进行排序,组合为^分隔的字符串并对其进行哈希处理。然后,只需将哈希与该项目相关联即可。标签的每种组合都会产生一个新密钥。要执行AND搜索,只需使用给定的标签ID重新创建哈希并进行搜索。更改项目上的标签将导致重新创建哈希。具有相同标签集的项目共享相同的哈希键。


4
使用这种方法,您只能搜索带有完全相同的标签集的条目-这总是很简单的。在我最初的问题中,我想查找包含所有查询标签的条目,甚至可能更多。
Christian Berg

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.