182

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

  • 项目可以有大量的标签
  • 搜索使用给定标签集标记的所有项目必须快速(项目必须具有所有标签,因此它是 AND 搜索,而不是 OR 搜索)
  • 创建/写入项目可能会更慢以实现快速查找/读取

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

有任何想法吗?


感谢到目前为止的所有答案。

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

4

11 回答 11

82

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

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

连同性能测试:

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

请注意,那里的结论非常具体到 MySQL,它(至少在 2005 年编写时)具有非常差的全文索引特性。

于 2008-09-07T19:17:04.073 回答
24

关于ANDing:听起来你正在寻找“关系除法”操作。本文以简明易懂的方式介绍了关系划分。

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

于 2008-09-07T18:22:46.497 回答
15

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

ps——这里大家所说的方法在文章中被称为“Toxi”解决方案。

于 2008-11-05T00:40:15.943 回答
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 个,那么这仍然很重在数据库上。

于 2008-09-07T14:39:39.297 回答
7

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

这种具有适当缓存机制的解决方案可能会产生比本土解决方案更好的性能。

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

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

于 2008-09-07T14:52:22.587 回答
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]
于 2008-09-07T14:39:52.523 回答
1

我第二个@Zizzencs 建议你可能想要一些不完全以(R)DB 为中心的东西

不知何故,我相信使用普通的 nvarchar 字段来存储带有一些适当缓存/索引的标签可能会产生更快的结果。但这只是我。

我之前使用 3 个表来表示多对多关系(Item Tags ItemTags)实现了标记系统,但我想你会在很多地方处理标签,我可以告诉你,3 个表必须一直同时被操作/查询肯定会让你的代码更复杂。

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

于 2008-09-07T17:38:38.070 回答
0

您将无法避免加入,但仍会有些标准化。

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

 TagId (PK)| TagName (Indexed)

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

此 TagXREFID 列是第三个表的 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 Tags.TagName = @TagName1 AND Tags.TagName = @TagName2 等...并动态构建查询。

于 2008-09-07T14:43:30.113 回答
0

我喜欢做的是有一些代表原始数据的表,所以在这种情况下你会有

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

这在写入时间上工作得很快,并保持一切正常化,但您可能还注意到,对于每个标签,您需要为每个想要 AND 的其他标签连接表两次,因此读取速度很慢。

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

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

然后,您可以考虑 Tagged Item 表需要多久更新一次,如果每次插入都需要更新,则在游标插入事件中调用存储过程。如果是每小时任务,则设置每小时作业来运行它。

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

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

0000

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

1111

如果只是前两个...

1100

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

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

于 2008-09-07T16:25:50.710 回答
0

套用其他人所说的话:诀窍不在schema中,而在query中。

实体/标签/标签的幼稚模式是正确的方法。但正如您所见,目前还不清楚如何使用大量标签执行 AND 查询。

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

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

于 2008-09-07T17:12:54.233 回答
0

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

于 2011-01-14T05:17:45.037 回答